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

/*
 * Generic tcp plug proxy functionally similar to TIS plug-gw
 *
 * (C) Copyright 2001 ArkanoiD <ark@eltex.ru>
 *
 * XXXX encryption routines are functionally embryionic, speed nightmare 
 * and vulnerable to replay attacks. To be replaced with something more
 * useful later. Until that, be sure your passphrase is REALLY good
 * against dictionary attack. Only bf encryption and md5 hash implemented
 * so far.
 */

#include	<stdio.h>
#include	<string.h>
#include	<strings.h>
#include	<syslog.h>
#include	<sys/types.h>
#include	<sys/ioctl.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<netdb.h>
#include	<netinet/in.h>
#include	<arpa/inet.h>
#include	<errno.h>
#include	<time.h>
#include	<inttypes.h>
#include	<sys/stat.h>

#ifdef	USE_SSL
#include 	<openssl/x509.h>
#include 	<openssl/err.h>
#include 	<openssl/ssl.h>
#endif

#include	"firewall.h"
#include	"auth.h"
#ifdef	OpenBSD
#include	<ssl/blowfish.h>
#include	<ssl/md5.h>
#else
#include	<openssl/blowfish.h>
#include	<openssl/md5.h>
#endif
#include	"fwfunc.h"
#include	"firewall2.h"
#ifdef	USE_SSL
#include	"sslfunc.h"
#endif

#ifndef FIONREAD
/*	It's probably here	*/
#include	<sys/filio.h>
#endif


#define		CRYPT_NONE      000
#define		CRYPT_BF        001
#define		CRYPT_3DES      002
#define		CRYPT_GOST      003


static	char		**validests = (char **)0;
static	int		extendperm = 0;
static	int		transparent = 0;
static	int		privport = 0;
static 	int		net_write();

static	BF_KEY		cl_bfkey, srv_bfkey;
static	unsigned char	cl_bfiiv[8]  = "\0\0\0\0\0\0\0\0";
static	unsigned char	cl_bfoiv[8]  = "\0\0\0\0\0\0\0\0";
static	unsigned char	srv_bfiiv[8] = "\0\0\0\0\0\0\0\0";
static	unsigned char	srv_bfoiv[8] = "\0\0\0\0\0\0\0\0";

static	char		cencm[128],sencm[128];
static	char		cenckey[1024],senckey[1024];
static	int		cdscp = 0;
static	int		sdscp = 0;

static	char		authrequser[AUTH_USIZ] = "";

#ifdef	USE_SSL
static	int		cssl = 0;
static	int		sssl = 0;
static	char		cvrfy[128], svrfy[128];
static	SSL		*cconn	= NULL;
static	SSL		*sconn	= NULL;
#endif

extern	char		proxy_name[MAX_STR];
extern	struct	fwstats	proxy_stats;
extern	Cfg		*proxy_confp;
extern	int		proxy_timeout;
static	fwparm	options[] = {
	{	FWPARM_STRING,	"-plug-to",		proxy_stats.dst	     },
	{	FWPARM_STRING,	"-client-encrypt",	cencm		     },
	{	FWPARM_STRING,	"-client-md5key",	cenckey		     },
	{	FWPARM_STRING,	"-server-encrypt",	sencm		     },
	{	FWPARM_STRING,	"-server-md5key",	senckey		     },
	{	FWPARM_BOOL,	"-privport",		(char*) &privport    },
	{	FWPARM_PORT,	"-port",	(char*) &proxy_stats.port    },
	{	FWPARM_STRING,	"-authuser",		proxy_stats.authuser },
	{	FWPARM_STRING,	"-authreq",		authrequser 	     },
	{	FWPARM_BOOL,	"-extnd",		(char*)	&extendperm  },
	{	FWPARM_BOOL,	"-transparent",		(char*)	&transparent },
	{	FWPARM_BOOL,	"-dest",		(char*)	&validests   },
	{	FWPARM_DSCP,	"-client-dscp",		(char*) &cdscp	     },
	{	FWPARM_DSCP,	"-server-dscp",		(char*) &sdscp	     },
#ifdef	USE_SSL
	{	FWPARM_BOOL,	"-ssl-client",		(char*) &cssl	     },
	{	FWPARM_BOOL,	"-ssl-server",		(char*) &sssl	     },
	{	FWPARM_STRING,	"-client-verify",	cvrfy		     },	
	{	FWPARM_STRING,	"-server-verify",	svrfy		     },	
#endif
	{	0,		0,			0		     }};


int
main(ac,av)
int	ac;
char	*av[];
{
	Cfg		*cf;
unsigned int		myport;
	int		match = 0;
	fd_set		rfds;
	fd_set		xfds;
	fd_set		iced;
	int		serfd;
	int		privport = 0;
static	unsigned char	buf[1024 * 4];
static 	unsigned char	encbuf[1024 * 4];
	int		client_encrypt = CRYPT_NONE;
        int		server_encrypt = CRYPT_NONE;
        MD5_CTX		md5_ctx;
unsigned char		md5_digest[16];
	uint16_t	blksize;
	int		srv_bfiinum = 0, srv_bfoinum = 0;
	int		cl_bfiinum  = 0, cl_bfoinum  = 0;

	bzero(cencm,sizeof(cencm));
	bzero(sencm,sizeof(sencm));
	bzero(cenckey,sizeof(cenckey));
	bzero(senckey,sizeof(senckey));

	proxy_init(ac,av);

	while ((ac > 2) && (!strcmp(av[1], "-daemon") || !strcmp(av[1], "-fastdaemon") ||
			   !strcmp(av[1], "-as"))) {
                ac -= 2;
                av += 2;
	}

#ifdef	USE_SSL
	proxy_SSLinit();
#endif

	proxy_chroot_setugid();

	/* if called with an argument, it is the connecting service name */
	if(ac > 1)
		myport = str_to_port(av[1]);
	else {
		socklen_t		len;
        	struct sockaddr_in      to_addr;

        	bzero((char *)&to_addr, sizeof(to_addr));

        	len = sizeof(to_addr);
        	if (getsockname(0, (struct sockaddr *)&to_addr, &len) < 0) {
                	syslog(LLEV,"fwtksyserr: getsockname() failed: %s", strerror(errno));
                	exit(1);
        	}
		myport = ntohs(to_addr.sin_port);
	}

/*
 *	We accept both new configuration format (more generic with
 *	permit-hosts lines and options, having services distinguished 
 *	by argv[0]) and legacy one TIS plug-gw used to have, with 
 *	"port <port> <host> [options.. ]" syntax.
 *	Quite PITA to parse because does not fit our new API well but..
 *	We lived that way for years.
 */
	for (cf = cfg_get("port",proxy_confp); cf ; cf = cfg_get("port",NULL)) {
		int 	x;

		if(cf->argc < 2) {
			syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
			exit(1);
		}
		
		if(strcmp(cf->argv[0],"any")) {
			if(myport != str_to_port(cf->argv[0]))
				continue;
		}
		/* if ports match, now see if calling host matches */

		for(x = 1; x < cf->argc - 1; x++) {
			if (cf->argv[x][0] == '-')
				break;	/* end of host list */
			if(hostmatch(cf->argv[x],proxy_stats.riaddr)) {
				proxy_parse_options(cf,options);
				match = 1;
			}
			if(cf->flags & PERM_DENY) {
				syslog (LLEV,"deny host=%s/%s use of gateway",
					proxy_stats.rladdr,proxy_stats.riaddr);
				exit(1);
			} 
		}
	}

	if (!match && (cf = proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,
			      		 proxy_stats.riaddr))) {

			proxy_parse_options(cf,options);
			match = 1;
	}

	if (!match) 
		exit(1);

	if (!proxy_stats.port)
		proxy_stats.port = myport;

	proxy_update_status();
	if (cdscp)
		proxy_set_dscp(0,cdscp);

/*
 *	Authentication
 */

	if (*authrequser && !strcmp(proxy_stats.authuser,"unauth")) {
		proxy_update_operation("AUTHENTICATION");
		if (auth_open(proxy_confp)) {
          		syslog(LLEV,"fwtksyserr: cannot connect to authentication server");
			exit(1);
        	}
      		if (auth_recv(buf,sizeof(buf)) || 
		    strncmp((char*) buf,"Authsrv ready",13)) {
          		syslog(LLEV,"fwtksyserr: cannot read from authentication server");
			exit(1);
        	}
      		snprintf((char*) buf,sizeof(buf),"authorize %.128s '%.64s %.128s/%.128s'",
        		 authrequser,proxy_name,proxy_stats.rladdr,
			 proxy_stats.riaddr);
      		if (auth_send(buf)) {
          		syslog(LLEV,"fwtksyserr: cannot write to authentication server");
			exit(1);
        	}
      		if (auth_recv(buf,sizeof(buf))) {
          		syslog(LLEV,"fwtksyserr: cannot read from authentication server");
			exit(1);
        	}
      		if (strncmp((char*) buf, "ok", 2)) {
			syslog(LLEV,"proxy authentication failed, %.128s", buf);
			proxy_exit();
		}
		syslog(LLEV,"authenticate user=%.100s",authrequser);
        	strlcpy(proxy_stats.authuser,authrequser,sizeof(proxy_stats.authuser));
        	proxy_update_status();
        }

/*
 *	Now we do set up encryption options
 */
	
#ifdef	USE_SSL
	if (cssl) {
		cconn = proxy_SSLaccept(0, proxy_stats.rladdr,
					proxy_stats.riaddr, cvrfy);
	}
#endif
	

	if (*cencm) {
		if (!strcmp(cencm,"blowfish"))
			client_encrypt = CRYPT_BF;
		else {
			syslog(LLEV,"fwtkcfgerr: encryption %s is not implemented",cencm);
			exit(1);
		}
	}

	if (*cenckey) {
		MD5_Init(&md5_ctx);
		MD5_Update(&md5_ctx,cenckey,strlen(cenckey));
		MD5_Final(md5_digest,&md5_ctx);
		switch (client_encrypt) {
		case 	CRYPT_NONE:
			syslog(LLEV,"fwtkcfgerr: -client-encrypt should be specified with -client-md5key");
			exit(1);
		case	CRYPT_BF:
			BF_set_key(&cl_bfkey,16,md5_digest);
		}
	}

	if (*sencm) {
		if (!strcmp(sencm,"blowfish"))
			server_encrypt = CRYPT_BF;
		else {
			syslog(LLEV,"fwtkcfgerr: encryption %s is not implemented",sencm);
			exit(1);
		}
	}

	if (*senckey) {
		MD5_Init(&md5_ctx);
		MD5_Update(&md5_ctx,senckey,strlen(senckey));
		MD5_Final(md5_digest,&md5_ctx);
		switch (server_encrypt) {
		case 	CRYPT_NONE:
			syslog(LLEV,"fwtkcfgerr: -server-encrypt should be specified with -client-md5key");
			exit(1);
		case	CRYPT_BF:
			BF_set_key(&srv_bfkey,16,md5_digest);
		}
	}

	if (transparent)
		proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);

	if (!*proxy_stats.dst) {
		syslog(LLEV,"fwtkcfgerr: not sure what host to connect to");
		exit(1);
	}
	if (!proxy_stats.port) {
		syslog(LLEV,"fwtkcfgerr: not sure what port to connect to");
		exit(1);
	}

	if (proxy_check_dest(validests,extendperm))
		proxy_exit();

/*
 * 	All configuration is now set up, let's begin 
 */
	
	proxy_update_operation("CONNECTING");
	if((serfd = conn_server(proxy_stats.dst,proxy_stats.port,privport,(char *)0)) < 0) {
		syslog(LLEV,"cannot connect to server %.512s/%d: %.128s",proxy_stats.dst,proxy_stats.port,strerror(errno));
		exit(1);
	}
	if (sdscp)
		proxy_set_dscp(serfd,sdscp);

#ifdef	USE_SSL
	if (sssl) {
		sconn = proxy_SSLconnect(serfd,proxy_stats.dst,svrfy);
	}
#endif

	FD_ZERO(&iced);
	FD_SET(0,&iced);
	FD_SET(serfd,&iced);

	while(FD_ISSET(0, &iced) && FD_ISSET(serfd, &iced)) {
		struct	timeval	timo;
		int		x;

		(void)bcopy(&iced,&rfds,sizeof(fd_set));
		(void)bcopy(&iced,&xfds,sizeof(fd_set));

		timo.tv_sec = proxy_timeout;
		timo.tv_usec = 0;
		proxy_update_operation("IDLE");
		if(select(serfd + 1,&rfds,(fd_set *)0,&xfds,&timo) <= 0) {
			syslog(LLEV,"Network timeout signal after %d seconds",proxy_timeout);
			break;
		}
		proxy_update_operation("DATA");
		if(FD_ISSET(0, &xfds)) {
			if ((x = recv(0, buf, sizeof(buf), MSG_OOB)) < 0) {
				/* ignore OOB read errors */
			} else {
			int x1;
				if (client_encrypt)
					syslog(LLEV,"securitywarning: OOB data received, client socket %d bytes",x);
				if ((x1=net_write(serfd, buf, x, MSG_OOB))!= x) {
					syslog(LLEV,"OOB write failed, wrote %d",x1);
					break;
				}
				proxy_stats.outbytes += x;
			}
		}
		if(FD_ISSET(serfd, &xfds)) {
			if ((x = recv(serfd, buf, sizeof(buf), MSG_OOB)) < 0) {
				/* ignore OOB read errors */
			} else {
				if (server_encrypt)
					syslog(LLEV,"securitywarning: OOB data received, server socket");
				if (net_write(0, buf, x, MSG_OOB) != x)
					break;
				proxy_stats.inbytes += x;
			}
		}

		if(FD_ISSET(0,&rfds)) {
			ioctl(0,FIONREAD,&x);
			if (x <= 0) {
				break;
			}
#ifdef	USE_SSL
			if (cssl) {
				x = proxy_SSLread(cconn,buf,sizeof(buf)-sizeof(blksize));
				if (x <= 0)
					break;
			} else
#endif
			if (!client_encrypt) {
				if((x = recv(0,buf,sizeof(buf) - sizeof(blksize),0)) <= 0) {
					break;
				}
			} else {
				if(soread(0,(char*)encbuf,sizeof(blksize)) < 0) 
					break;
				BF_cfb64_encrypt( encbuf,
						 (unsigned char *)&blksize,
						 sizeof(blksize),&cl_bfkey,
						 cl_bfiiv,
						 &cl_bfiinum,
						 BF_DECRYPT);
				if ((x = ntohs(blksize)) > sizeof(encbuf)) {
					syslog(LLEV,"fwtksyserr: buffer size exceeded on client socket, protocol failed");
					exit(1);
				} 
				if (soread(0,(char*)encbuf,x) < 0) {
					syslog(LLEV,"error reading encrypted client data");
					break;
				}
				BF_cfb64_encrypt(encbuf, buf,x,&cl_bfkey,
						 cl_bfiiv,
						 &cl_bfiinum,BF_DECRYPT);
			}
#ifdef	USE_SSL
			if (sssl) {
				unsigned int w;
				w = proxy_SSLwrite(sconn,buf,x);
				if (w <= 0)
					break;
			} else 
#endif
			if (server_encrypt) {
				int x1;
				blksize = htons(x);
				BF_cfb64_encrypt((unsigned char *)&blksize,
						 (unsigned char *)&x1,
						 sizeof(blksize),&srv_bfkey,
						 srv_bfoiv,
						 &srv_bfoinum,BF_ENCRYPT);
				BF_cfb64_encrypt(buf,encbuf,x,&srv_bfkey,srv_bfoiv,&srv_bfoinum,BF_ENCRYPT);
				if(sowrite(serfd,(char*) &x1,sizeof(blksize)) < sizeof(blksize))
					break;
				proxy_stats.outbytes += sizeof(blksize);
				bcopy(encbuf,buf,sizeof(buf));
			}
#ifdef	USE_SSL
			if (!sssl)
#endif
			if(FD_ISSET(serfd, &iced) && 
			   (sowrite(serfd,(char*) buf,x) != x))
				break;
			proxy_stats.outbytes += x;
		}
		if(FD_ISSET(serfd,&rfds)) {
			ioctl(serfd,FIONREAD,&x);
			if (x <= 0) 
				break;
#ifdef	USE_SSL
			if (sssl) {
				x = proxy_SSLread(sconn,buf,sizeof(buf)-sizeof(blksize));
				if (x <= 0)
					break;
			} else {
#endif
			if (!server_encrypt) {
				if((x = recv(serfd,buf,sizeof(buf)-sizeof(blksize),0)) <= 0) 
					break;
				buf[x] = 0;
			} else { 
				if(soread(serfd,(char*)encbuf,sizeof(blksize)) 
				   < 0) 
					break;
				BF_cfb64_encrypt(encbuf,
					         (unsigned char *)&blksize,
						 sizeof(blksize),&srv_bfkey,
						 srv_bfiiv,&srv_bfiinum,
						 BF_DECRYPT);
				if ((x = ntohs(blksize)) > sizeof(encbuf)) {
					syslog(LLEV,"fwtksyserr: %d buffer size exceeded on server socket, protocol failed",x);
					exit(1);
				} 
				if (soread(serfd,(char*)encbuf,x) < 0) {
					syslog(LLEV,"error reading encrypted server data");
					break;
				}
				BF_cfb64_encrypt(encbuf,buf,x,&srv_bfkey,srv_bfiiv,&srv_bfiinum,BF_DECRYPT);
			}
#ifdef	USE_SSL
			} if (cssl) {
				unsigned int w;
				w = proxy_SSLwrite(cconn,buf,x);
				if (w <= 0)
					break;
			} else 
#endif
			if (client_encrypt) {
				int x1;
				blksize = htons(x);
				BF_cfb64_encrypt((unsigned char *)&blksize,
						 (unsigned char *)&x1,
						 sizeof(blksize),
						 &cl_bfkey,cl_bfoiv,
						 &cl_bfoinum,BF_ENCRYPT);
				BF_cfb64_encrypt(buf,encbuf,x,&cl_bfkey,cl_bfoiv,&cl_bfoinum,BF_ENCRYPT);
				if(sowrite(0,(char*) &x1,sizeof(blksize)) < sizeof(blksize)) 
					break;
				bcopy(encbuf,buf,sizeof(buf));
				proxy_stats.inbytes += sizeof(blksize);
			}
#ifdef	USE_SSL
			if (!cssl)
#endif
			if(FD_ISSET(serfd, &iced) && 
			   (sowrite(0,(char*) buf,x) != x))
				break;
			proxy_stats.inbytes += x;
		}

	}

	proxy_exit();
}

/*
 *	Those function are pretty generic so i don't think NAI will sue
 *	me for using that ;)
 */

static int
net_write(s, buf, len, flags)
int s;
char *buf;
int len;
int flags;
{
	int nbytes = 0;
	int j;

	while (nbytes < len) {
		j = send(s, buf, len-nbytes, flags);
		if (j <= 0)
			return (nbytes ? nbytes : j);
		buf += j;
		nbytes += j;
	}
	return nbytes;
}

