/*
 * Copyright (c) 2000-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * OpenFWTK project legacy API functions 
 *
 * (C) Copyright 2000-2002 ArkanoiD <ark@eltex.ru>
 * (C) Copyright 2000 Eberhard Mattes <em-gw@windhager.de>
 *
 */

#include <ctype.h>
#include <time.h>
#include <termios.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <inttypes.h>
#include <errno.h>
#include "firewall.h"
#include "fwstats.h"
#include "fwfunc.h"
#include "auth.h"
#include "magic.h"

magic_t	proxy_magic;

extern uint32_t arc4random(void);
extern void addlist(char*,char***);
extern void freelist(char***);
extern size_t strlcpy(char*,const char*,size_t);
extern int proxy_conf_int(Cfg*,const char*,int,int,int);
extern char* proxy_conf_string(Cfg*,const char*);
static sig_atomic_t hup_flag;
extern void setproctitle(const char*,...);
extern int proxy_nodns;

#ifndef TCSASOFT
#define TCSASOFT 0
#endif

#ifndef INADDR_NONE
#define INADDR_NONE (in_addr_t) -1
#endif

static char* moduleId ATTR_UNUSED = "$Id: misc.c,v 1.28 2007/09/10 02:49:13 arkenoi Exp $";

struct	fwstats	proxy_stats = {0,0,0,0,0,"","","unauth","",0,""};

char    proxy_name[MAXPATHLEN];
char    proxy_chroot[MAXPATHLEN];
int     proxy_uid = -1;
int     proxy_gid = -1;
Cfg     *proxy_confp;
int     proxy_timeout = PROXY_TIMEOUT;
int	proxy_nodns = 0;

int     namatch(const char *const,const char *const);


void*
xmalloc(size_t size)
{
	void	*p;
	if ((p = malloc(size)))
		return(p);
	syslog(LLEV,"fwtksyserr: malloc()");
	exit(1);
}

void*
xrealloc(void *ptr,size_t size)
{
	void	*p;
	if ((p = realloc(ptr,size)))
		return(p);
	syslog(LLEV,"fwtksyserr: malloc()");
	exit(1);
}

char*
xstrndup (const char *s, size_t len)
{
  char *p = xmalloc (len + 1);
  memcpy (p, s, len);
  p[len] = 0;
  return p;
}

char*
xstrdup(const char *str)
{
	char	*p;
	if ((p = strdup(str)))
		return(p);
	syslog(LLEV,"fwtksyserr: malloc()");
	exit(1);
}

	
int
isalldigits(char *s)
{
	if (*s == '\0')
		return(0);
	while (isdigit(*s))
		s++;
	return(*s == '\0');
}

long
randomnumber()
{

/*	We assume the library will do all dirty work for us	*/

	return((long)arc4random()); 
}

char *
getpassword(prompt)
	const char *prompt;
{
	struct termios term;
	register int ch;
	register char *p;
	FILE *fp, *outfp;
	int echo;
	static char buf[AUTH_PWSIZ + 1];
	sigset_t oset, nset;

	/*
	 * read and write to /dev/tty if possible; else read from
	 * stdin and write to stderr.
	 */

	/*
	Comented out for legacy login-sh compatibility
	*/

	outfp = stderr;
	fp = stdin;

	/*
	 * note - blocking signals isn't necessarily the
	 * right thing, but we leave it for now.
	 */
	sigemptyset(&nset);
	sigaddset(&nset, SIGINT);
	sigaddset(&nset, SIGTSTP);
	(void)sigprocmask(SIG_BLOCK, &nset, &oset);

	(void)tcgetattr(fileno(fp), &term);
	if ((echo = (term.c_lflag & ECHO))) {
		term.c_lflag &= ~ECHO;
		(void)tcsetattr(fileno(fp), TCSAFLUSH|TCSASOFT, &term);
	}
	(void)fputs(prompt, outfp);
	rewind(outfp);			/* implied flush */
	for (p = buf; (ch = getc(fp)) != EOF && ch != '\n';)
		if (p < buf + AUTH_PWSIZ)
			*p++ = ch;
	*p = '\0';
	(void)write(fileno(outfp), "\n", 1);
	if (echo) {
		term.c_lflag |= ECHO;
		(void)tcsetattr(fileno(fp), TCSAFLUSH|TCSASOFT, &term);
	}
	(void)sigprocmask(SIG_SETMASK, &oset, NULL);
	if (fp != stdin)
		(void)fclose(fp);
	return(buf);
}

int
matchip(char *pattern,char *string)
{
	char		patbuf[MAX_HOSTNAME];
	char 		*mask;
	uint32_t	patip,patmask,hostip;

	if (strchr(pattern,'*'))
		return(namatch(pattern,string));

	if (strlen(pattern) >= sizeof(patbuf)) {
		syslog(LLEV,"fwtkcfgerr: matchip(): pattern %.100s too long",pattern);
		return(0);
	}

	strlcpy(patbuf,pattern,sizeof(patbuf));

	if ((mask = strpbrk (patbuf, "/:"))) {
		*mask = '\0';
		if ( (patmask = inet_addr(++mask)) == INADDR_NONE) {
			syslog(LLEV,"fwtkcfgerr: matchip(): malformed netmask: %.100s",mask);
			return(0);
		}
	} else
		patmask = 0xffffffff;


	if ( (patip = inet_addr(patbuf)) == INADDR_NONE) {
		syslog(LLEV,"fwtkcfgerr: matchip(): malformed pattern: %.100s",patbuf);
		return(0);
	}

	if ( (hostip = inet_addr(string)) == INADDR_NONE) {
		syslog(LLEV,"fwtkcfgerr: matchip(): malformed ip: %.100s",patbuf);
		return(0);
	}
	return ((hostip & patmask) == patip);
}


int
maskmatch(char *pattern,char *string)
{
	return(matchip(pattern,string));
}

int
checkreverse(const struct in_addr haddr, const char *const rname)
{
 	struct		hostent		*hp;
			int		isok;
	unsigned	char		*c;
			char		**tp;
			char		*name;

	name = xstrdup(rname);

/*
 *	we could rely on (isascii && isalpha) || isdigit, but..
 */
	isok = 0;

	for (c= (unsigned char*) name ; *c ; c++) {
		if ( ((*c < 'a') || (*c > 'z')) &&
		     ((*c < 'A') || (*c > 'Z')) &&
		     ((*c < '0') || (*c > '9')) &&
		      (*c!= '.') && (*c!= '-')  )  {
			syslog(LLEV,"securityalert: %.512s/%.20s invalid host name",name,inet_ntoa(haddr));
			free(name);
			return(0);
		}
		isok |= !isdigit(*c) && (*c!= '.');
	}

/*
 *	Will be detected later anyways, but let's be paranoid
 */ 
	if (!isok) {
		syslog(LLEV,"securityalert: possible spoof %.512s/%.20s can appear as fake IP",name,inet_ntoa(haddr));
		free(name);
		return(0);
	}

	if (!(hp = gethostbyname(name))) {
		syslog(LLEV,"%.512s/%.20s host name lookup failed",name,inet_ntoa(haddr));
		free(name);
		return(0);
	}

	if(hp->h_length > sizeof(haddr.s_addr)) {
		syslog(LLEV,"securityalert: invalid host address length (%d) hostname %.512s",hp->h_length, name);
		free(name);
		return(0);
	}

	tp = hp->h_addr_list;
	for ( ; *(tp) ; tp++ ) {
		if (!memcmp(*(tp),(char *)&haddr,hp->h_length)) {
			char *x;
			for(x = name; *x != '\0'; x++)
				*x = tolower(*x);
			free(name);
			return(1);
		} 
	}

	syslog(LLEV,"securityalert: possible spoof %.512s/%.20s != %.512s name lookup mismatch",name,inet_ntoa(haddr),inet_ntoa(*(struct in_addr *)*(hp->h_addr_list)));
	free(name);
	return(0);

}


char*
ip2name(struct in_addr haddr)
{
 	static		char		name[MAX_HOSTNAME];
	struct		hostent		*hp;

	bzero(name,sizeof(name));
	
	if (proxy_nodns) {
		strlcpy(name,"unknown",sizeof(name));
		return(name);
	}

	hp = gethostbyaddr((char *)&haddr,sizeof(haddr),AF_INET);

	if (!hp) {
		syslog(LLEV,"%.512s host name lookup failed",inet_ntoa(haddr));
		strlcpy(name,"unknown",sizeof(name));
		return(name);
	}

	if (strlen(hp->h_name) >= sizeof(name)) {
		syslog(LLEV,"securityalert: %.512s/%.20s host name buffer overrun",hp->h_name,inet_ntoa(haddr));
		strlcpy(name,"unknown",sizeof(name));
		return(name);
	}
	if (!checkreverse(haddr,hp->h_name)) {
		strlcpy(name,"unknown",sizeof(name));
		return(name);
	}
	strlcpy(name,hp->h_name,sizeof(name));
	return(name);
}

int
matchpatbyreverse(char *pattern, struct in_addr haddr)
{
	struct		hostent		*hp;
			char		**aliases = NULL;
	register	char		**ap;

	if (proxy_nodns || !(hp = gethostbyaddr((char *)&haddr,sizeof(haddr),AF_INET)))
		return(0);
	for (ap = hp->h_aliases; ap && *ap && **ap; ap++) 
		addlist(*ap,&aliases);
	if (checkreverse(haddr,xstrdup(hp->h_name)) && namatch(pattern,hp->h_name)) {
		return(1);
	} else {
		for (ap = aliases; ap && *ap && **ap; ap++) {
			if (checkreverse(haddr,*ap) &&  
				namatch(pattern,*ap)) {
					freelist(&aliases);
					return(1);
			}
		}
	}
	freelist(&aliases);
	return(0);
}

int
matchnamebyip(char *pattern,char *name,int nsize)
{
	struct		hostent		*hp;

	hp = gethostbyname(name);

	if (!hp)
		return(0);

	if(hp->h_length > sizeof(uint32_t)) {
		syslog(LLEV,"securityalert: invalid host address length (%d) hostname %.512s",hp->h_length, name);
		strlcpy(name,"unknown",MAX_HOSTNAME);
		return(0);
	}
	for ( ; *(hp->h_addr_list) ; hp->h_addr_list++ ) 
		if (matchip(pattern,inet_ntoa(*((struct in_addr *)*(hp->h_addr_list)))))
			return(1);
	return(0);
}
	

int
hostmatch(char *pattern,char *name)
{
			char		patbuf[MAX_HOSTNAME];
			char		namebuf[MAX_HOSTNAME];
	unsigned	char		*c;
			int 		nname,npat;

	if (strlen(pattern) >= sizeof(patbuf)) {
		syslog(LLEV,"fwtkcfgerr: hostmatch(): pattern %.100s too long",pattern);
		return(0);
	}
	if (strlen(name) >= sizeof(namebuf)) {
		syslog(LLEV,"fwtksyserr: hostmatch(): name %.100s too long",name);
		return(0);
	}

	strlcpy(patbuf,pattern,sizeof(patbuf));
	strlcpy(namebuf,name,sizeof(namebuf));

	npat = 0;
	for (c = (unsigned char*) patbuf; *c; c++) {
		*c = tolower(*c);
		npat |= (!isdigit(*c) && (*c != '.')
			  && (*c != '*') && (*c != ':')
			  && (*c != '/'));
	}

	nname = 0;

	for (c = (unsigned char*) namebuf; *c; c++) {
		*c = tolower(*c);
		nname |= (!isdigit(*c) && (*c != '.'));
	}
	
	if (npat) {
		if (!nname) {
			struct in_addr haddr;
			if (( haddr.s_addr = inet_addr(name)) == INADDR_NONE) {
				syslog(LLEV,"hostmatch: malformed address: %.100s",name);
				return(0);
			}
			return(matchpatbyreverse(patbuf,haddr));
		}
		return(namatch(patbuf,namebuf));
	} else {
		if (nname)
			return(matchnamebyip(patbuf,namebuf,sizeof(namebuf)));
		return(matchip(patbuf,namebuf));
	}
}

int
set_oob_notification(int fd)
{
	if(fcntl(fd,F_SETOWN,getpid()) == -1) {
		syslog(LLEV,"fwtksyserr: F_SETOWN: %s", strerror(errno));
		return(1);
	}
	return(0);
}

int
daemonize()
{
	signal(SIGALRM,SIG_IGN);
	setsid();

	switch(fork()) {
		case -1:
			syslog(LLEV,"fwtksyserr: fork failed");
			exit(1);
		case 0:
			setsid();
			syslog(LLEV,"daemon running");
			return(0);
		default:
			exit(0);
	}
}

void
waitwaitwait(int unused)
{
	int	status;
	
	while (waitpid(-1,&status,WNOHANG) > 0)
		proxy_stats.child_count--;
	signal(SIGCHLD, waitwaitwait);
}

void
hup_exit(int sig)
{
	syslog(LLEV,"caught signal %d",sig);
	exit(1);
}

void
hup_catch(int sig)
{
  	if (sig == SIGHUP)
    		hup_flag = 1;
}

int do_daemon(int port)
{
		int		s;
		int		csock;
	struct	sockaddr_in	sa;
		int		optval;

	daemonize();

	signal(SIGTERM, hup_exit);
	signal(SIGINT, hup_exit);
	signal(SIGHUP, hup_catch);

	setproctitle("MASTER DAEMON port=%d", port);
	if ((s = socket(AF_INET,SOCK_STREAM,0)) <0) {
		syslog(LLEV,"fwtksyserr: socket: %s", strerror(errno));
		exit(1);
	}
	optval = 1;
	setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
	
	memset(&sa,0,sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);

	while (bind(s,(struct sockaddr *)&sa,sizeof(sa))) {
		syslog(LLEV,"fwtksyserr: failed to bind port %d, %s", port, strerror(errno));
		syslog(LLEV,"Sleeping for 2 minutes");
		setproctitle("MASTER DAEMON (inactive - cannot bind %d)",port);
		sleep(120);
	}
	setproctitle("MASTER DAEMON port=%d", port);
	if (listen(s,SOMAXCONN) < 0) {
		syslog(LLEV,"fwtksyserr: listen: %s", strerror(errno));
		exit(1);
	}

	signal(SIGCHLD,waitwaitwait);

	while (1) {
		if (hup_flag) {
			syslog(LLEV,"Got SIGHUP, reconfiguring");
			cfg_free(proxy_confp);
			if ((proxy_confp = cfg_read(proxy_name)) == (Cfg *)-1) {
                        	syslog(LLEV,"fwtkcfgerr: no netperm-table records for %.128s",proxy_name);
                        	exit(1);
                	}
                	proxy_timeout = proxy_conf_int(proxy_confp,"timeout",1,INT_MAX,PROXY_TIMEOUT);
                	proxy_stats.child_limit = proxy_conf_int(proxy_confp,"maxchildren",1,4096,0);
			proxy_nodns = (proxy_conf_string(proxy_confp,"nodns") != NULL);
			magic_close(proxy_magic);
			proxy_magic = magic_open(MAGIC_MIME);
        		magic_load(proxy_magic,NULL);
			syslog(LLEV,"Reconfiguration finished");
			signal(SIGHUP, hup_catch);
			hup_flag = 0;
		}

		socklen_t salen = sizeof(sa);

		siginterrupt(SIGHUP,1);
		if ((csock = accept(s,(struct sockaddr *)&sa,&salen)) < 0) {
			siginterrupt(SIGHUP,0);
			if( errno == EINTR) { 
				hup_flag = 1; 
				continue;
			}
			syslog(LLEV,"fwtksyserr: accept: %s", strerror(errno));
			exit(1);
		}
		siginterrupt(SIGHUP,0);
		if ((proxy_stats.child_limit) && 
		    (proxy_stats.child_count > proxy_stats.child_limit)) {
			syslog(LLEV,"deny host=%.100s/%.100s use of gateway: child limit exceeded",ip2name(sa.sin_addr),inet_ntoa(sa.sin_addr));
			close(csock);
			continue;
		}
		switch(fork()) {
			case -1:
				syslog(LLEV,"fwtksyserr: fork failed");
				close(csock);
				continue;
				
			case 0:
				/* we may need to fork further, should we
				   count? let's not to. 		*/
				setsid();
				/* do like inetd			*/
				/* we disabled signals, but let's check */
				if (close(s) || close(0) || close(1) || close(2)) {
					syslog(LLEV,"fwtksyserr: close() failed");
					exit(1);
				}
				dup(csock);
				dup(csock);
				dup(csock);
				if (close(csock)) {
					syslog(LLEV,"fwtksyserr: close() failed");
					exit(1);
				}
				signal(SIGHUP, hup_exit);
				return(0);
			default:
				proxy_stats.child_count++;
				close(csock);
		}
	}
}

void strtrm(char *s) 
{
  char *p;
  if(s == NULL) return;

  for(p=s;isspace(*p) && p != '\0';p++)  ;
  memmove(s, p, strlen(p) + 1);

  for(p=s + strlen(s) - 1; p>=s && isspace(*p) ; p--) ;

  *(p+1) = '\0';
}
