#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/signal.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>

#undef INADDR_NONE
#define INADDR_NONE -1

#define MAXPROXY 16
#define XBUFSIZ 16384
#define SADR (struct sockaddr *)

static char         xferbuf[XBUFSIZ];
int                 childleft = 0;
int                 debugflag;
int                 sigflag = 0;
int                 curcon = -1;
int                 askuser[MAXPROXY];
char                thisuser[256];
int                 nlstn;
int                 lstn[MAXPROXY];
char                conncert[MAXPROXY][128];
int                 reverse[MAXPROXY];
char                hostname[256] = "";
char                error[256] = "";

char                pidpath[128] = "/var/run";
char                logfile[128] = "/var/log/edssl";
char                usercerts[128] = "/var/log/usercerts.pem";
char                usersfile[128] = "/etc/edssl.users";
char                goodcerts[128] = "/etc/goodcerts";
char                certbrwsr[128] = "/usr/local/ssl/certs/universal.pem";
char                mycert[128] = "/usr/local/ssl/certs/edssl.pem";

/* match common names using shell expressions */
int                 shellexp(char *t, char *m)
{
	static char        *lastx;
	int                 good, revflag;
	char               *lastt, *tref;

	tref = t;
	lastt = t;
	while (*m) {
		good = 0;
		switch (*m) {
		case '~':
			return (!shellexp(tref, ++m));
		case '$':
			return (!(*t));
		case '*':
			while (*m && *m == '*')
				m++;
			if (!*m)
				return 1;
			while (*t)
				if (shellexp(t++, m))
					return 1;
			return 0;
		case '[':
			if ((revflag = (*++m == '^')))
				m++;
			while (!good && *m && *m != ']') {
				switch (*m) {
				case '-':
					if (*t >= m[-1] && *t <= m[1])
						good = 1;
					m++;
					m++;
					break;
				case '\\':
					if (!*++m)
						return 0;
				default:
					if (*++m == *t)
						good = 1;
					break;
				}
			}
			if (good == revflag)
				return 0;
			while (*m != ']')
				if (!*++m)
					return 0;
		case '?':	/* with fallthrough */
			m++, t++;
			break;
		case '(':
			do {
				m++;
				if (shellexp(t, m))
					good = 1, lastt = lastx;
				while (*m && *m != ')' && *m != '|')
					m++;
			} while (*m == '|');
			if (!good || *m++ != ')')
				return 0;
			t = lastt;
			break;
		case '|':
		case ')':
			lastx = t;
			return 1;
		case '\\':
			if (!*++m)
				return 0;
		default:	/* with fallthrough */
			if (tolower(*m++) != tolower(*t++))
				return 0;
		}
	}
	return (!(*t));
}

/* match cert oneline v.s. hostname */
int                 namematch(char *thisuser, char *hostname)
{
	char               *cp;
	char                cbuf[255];
	int                 n;
	FILE               *tmpfd;

       /* look for good cert override: file format is oneline-space-hostname */
	cbuf[0] = 0;
	n = strlen(thisuser);
	tmpfd = fopen(goodcerts, "r");
	if (tmpfd != NULL) {
		while (!feof(tmpfd)) {
			fgets(cbuf, 255, tmpfd);
			if (!strncasecmp(thisuser, cbuf, n) && !strncasecmp(hostname, &cbuf[n + 1], strlen(hostname)))
				break;
		}
		fclose(tmpfd);
	}
	if (!strncasecmp(thisuser, cbuf, n) && !strncasecmp(hostname, &cbuf[n + 1], strlen(hostname)))
		return 1;
	cp = strstr(thisuser, "/CN=");	/* move to common name */
	if (cp == NULL)
		return 0;
	cbuf[0] = '\0';
	cp += 4;
	n = strlen(hostname);
	strcpy(cbuf, cp);
	if ((cp = strchr(cbuf, '/')))
		*cp = '\0';
	return (shellexp(hostname, cbuf));
}
/* look up user cert oneline */
int                 usermatch(char *thisuser)
{
	char                cbuf[255];
	int                 n;
	FILE               *tmpfd;

	cbuf[0] = 0;
	n = strlen(thisuser);
	tmpfd = fopen(usersfile, "r");
	if (tmpfd == NULL)
		return 0;
	while (!feof(tmpfd)) {
		fgets(cbuf, 255, tmpfd);
		if (!strncasecmp(thisuser, cbuf, n))
			break;
	}
	fclose(tmpfd);

	if (strncasecmp(thisuser, cbuf, n)) {
		sprintf(error, "User Cert not in file");
		return 0;
	}
	return 1;
}

#include "hooks.c"

/* signal handling */
#ifndef LINUX
#define __sighandler_t (void *)
#endif
__sighandler_t      sighandle(int signum, __sighandler_t h)
{
int                 status;

	if (signum == SIGCHLD) {
		wait(&status);
		childleft--;
		signal(SIGCHLD, (__sighandler_t) sighandle);
	}
	else
		sigflag = signum;

	return SIG_IGN;
}

/* convert string (dns or dotted decimal) to address */
int                 getaddr(char *toaddr, struct sockaddr_in *sin)
{
	struct hostent     *host;
	int                 n;

	n = inet_addr(toaddr);
	if (n != INADDR_NONE)
		memcpy(&sin->sin_addr, &n, sizeof(n));
	else {
		host = gethostbyname(toaddr);
		if (host == NULL)
			return 1;
		memcpy(&sin->sin_addr, host->h_addr, host->h_length);
	}
	return 0;
}

int                 main(int argc, char *argv[])
{
	int                 n, m;
	int                 acpt, rnet, rfd, wfd, maxfd;
	int                 snewsflag = 0;
	fd_set              fds;
	struct sockaddr_in  sin, rsin[MAXPROXY];
	FILE               *tmpfd;
	char                pidfile[256];
	char                logline[256];
	char                option[64], param[128], toaddr[64], toport[64];
	char               *cp, *pp = NULL;
	char                lyproxcmd[32];
	struct timeval      tv;
	int                 timeout = 90;
	struct servent     *serv;

	while (sigflag != SIGINT) {
		debugflag = 0;
		nlstn = 0;
		maxfd = 0;
		if (argc > 1)
			tmpfd = fopen(argv[1], "r");
		else
			tmpfd = fopen("/etc/edssl.conf", "r");
		if (tmpfd == NULL)
			exit(-1);
		while (!feof(tmpfd)) {
			if (fgets(xferbuf, 255, tmpfd) == NULL)
				break;
			n = sscanf(xferbuf, "%63s %127s %63s %63s", option, param, toaddr, toport);
			if (n <= 0)
				continue;
			if (option[0] == '#')
				continue;
			if (n == 1) {
				if (!strcmp(option, "debug"))
					debugflag = 1;
				continue;
			}
			if (n == 2) {
				if (!strcmp(option, "brcert"))
					strncpy(certbrwsr, param, 128);
				else if (!strcmp(option, "cert"))
					strncpy(mycert, param, 128);
				else if (!strcmp(option, "usercerts"))
					strncpy(usercerts, param, 128);
				else if (!strcmp(option, "users"))
					strncpy(usersfile, param, 128);
				else if (!strcmp(option, "goodcerts"))
					strncpy(goodcerts, param, 128);
				else if (!strcmp(option, "pidpath"))
					strncpy(pidpath, param, 128);
				else if (!strcmp(option, "logfile"))
					strncpy(logfile, param, 128);
				else if (!strcmp(option, "timeout"))
					timeout = atoi(param);
				continue;
			}
			askuser[nlstn] = 0;
			reverse[nlstn] = 0;
			strncpy(conncert[nlstn], mycert, 64);
			if (n >= 3) {
				if (!strcmp(option, "ask"))
					askuser[nlstn] = 1;
				else if (!strcmp(option, "grab"))
					askuser[nlstn] = 2;
				else if (!strcmp(option, "verify"))
					askuser[nlstn] = 3;
				else if (!strcmp(option, "reverse"))
					reverse[nlstn] = 1;
				else if (!strcmp(option, "lynxproxy"))
					reverse[nlstn] = 2;
				else if (!strcmp(option, "netscape"))
					reverse[nlstn] = 3;
				else if (strcmp(option, "secure"))
					continue;
			}
			memset((char *) &sin, 0, sizeof(sin));
			sin.sin_family = AF_INET;
			rsin[nlstn] = sin;
			rsin[nlstn].sin_addr.s_addr = inet_addr("127.0.0.1");
			serv = getservbyname(param, "tcp");
			if (serv != NULL)
				sin.sin_port = serv->s_port;
			else
				sin.sin_port = htons(atoi(param));
			sin.sin_addr.s_addr = INADDR_ANY;
			if (getaddr(toaddr, &rsin[nlstn])) {
				fprintf(stderr, "Bad Hostname %s\n", toaddr);
				exit(-1);
			}
			if (reverse[nlstn] >= 2)
				sin.sin_addr.s_addr = rsin[nlstn].sin_addr.s_addr;
			else {
				serv = getservbyname(toport, "tcp");
				if (serv != NULL)
					rsin[nlstn].sin_port = serv->s_port;
				else
					rsin[nlstn].sin_port = htons(atoi(toport));
			}
			if ((lstn[nlstn] = socket(AF_INET, SOCK_STREAM, 0)) < 0)
				return lstn[nlstn];
			if (lstn[nlstn] > maxfd)
				maxfd = lstn[nlstn];
			n = -1;
			if ((n = setsockopt(lstn[nlstn], SOL_SOCKET, SO_REUSEADDR, &n, sizeof(int))) < 0)
				                    return n;

			if ((n = bind(lstn[nlstn], SADR & sin, sizeof(sin))) < 0)
				return n;
			nlstn++;
		}
		fclose(tmpfd);
		if (!nlstn)
			exit(-1);
		n = getpid();
		sprintf(pidfile, "%s/edssl.pid", pidpath);
		tmpfd = fopen(pidfile, "w");
		fprintf(tmpfd, "%d\n", n);
		fclose(tmpfd);

	       /* listen on all ports */
		for (n = 0; n < nlstn; n++)
			if ((m = listen(lstn[n], 10)) < 0)
				return m;
		maxfd++;
		prefix();
		signal(SIGINT, (__sighandler_t) sighandle);
		signal(SIGHUP, (__sighandler_t) sighandle);
		signal(SIGCHLD, (__sighandler_t) sighandle);
		sigflag = 0;
		while (!sigflag) {
			FD_ZERO(&fds);
			for (n = 0; n < nlstn; n++)
				FD_SET(lstn[n], &fds);
			if ((n = select(maxfd, &fds, NULL, NULL, NULL)) < 0) {
				if (errno != EINTR)
					break;	/* exit(-1); */
				continue;
			}
			for (curcon = 0; curcon < nlstn; curcon++) {
				if (!FD_ISSET(lstn[curcon], &fds))
					continue;
			       /* accept connection and fork to process */
				n = sizeof(sin);
				acpt = accept(lstn[curcon], SADR & sin, &n);
				if ((n = fork())) {
					childleft++;
					close(acpt);
					continue;
				}
				if (debugflag)
					fprintf(stderr, "Connect from %15s:%u (%d)\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port), acpt);
				if (reverse[curcon] >= 2) {	/* proxy handling */
					m = 0;
					for (;;) {	/* get entire http header */
						tv.tv_sec = timeout;
						tv.tv_usec = 0;
						FD_ZERO(&fds);
						FD_SET(acpt, &fds);
						if (select(acpt + 1, &fds, NULL, NULL, &tv) <= 0)
							exit(-1);	/* timeout */
						if ((n = read(acpt, &xferbuf[m], XBUFSIZ - m)) < 0)
							break;
						m += n;
						xferbuf[m] = 0;
						if (strstr(xferbuf, "\r\n\r\n"))
							break;
					}
					if (n < 0)
						exit(-1);
					if (!(cp = strchr(xferbuf, ' ')))	/* point at site or url */
						exit(-1);
				       /* cp points to URL - isolate URL */
					if ((pp = strchr(++cp, ' ')))
						*pp = 0;
					strncpy(lyproxcmd, xferbuf, 16);
					if ((pp = strchr(lyproxcmd, ' ')))
						*pp = '\0';
					if (strncmp(lyproxcmd, "CONNECT", 7)) {		/* not netscape, so do lynx proxies */
						if (!strncasecmp(cp, "https://", 8))
							rsin[curcon].sin_port = htons(443);
						else if (!strncasecmp(cp, "snews://", 8)) {
							rsin[curcon].sin_port = htons(563);
							snewsflag = 1;
							if ((pp = strchr(cp, '\r')))	/* isolate hostname */
								*pp++ = 0;
						}
						else
							exit(-1);
						cp += 8;
					}
					if ((pp = strchr(cp, ':'))) {	/* find port - getservbyname? */
						*pp++ = 0;
						rsin[curcon].sin_port = htons(atoi(pp));
					}
					if ((pp = strchr(cp, '/')))	/* isolate hostname */
						*pp++ = 0;
					else
						pp = cp + strlen(cp);	/* URL has no path */
					strncpy(hostname, cp, 128);
				       /* restore and correct header for lynx proxies */
					pp[strlen(pp)] = ' ';	/* end of URL */
					pp -= 2 + strlen(lyproxcmd);
					strcpy(pp, lyproxcmd);	/* command */
					pp[strlen(pp)] = ' ';
					pp[strlen(pp)] = '/';
					if (debugflag)
						fprintf(stderr, "Secure proxy to %s:%d\n", hostname, htons(rsin[curcon].sin_port));
					if (getaddr(hostname, &rsin[curcon]))
						exit(-1);
				}
			       /* open remote connection */
				if ((rnet = socket(AF_INET, SOCK_STREAM, 0)) < 0)
					exit(-1);
				n = connect(rnet, SADR & (rsin[curcon]), sizeof(sin));
				if (debugflag)
					fprintf(stderr, "          to %15s:%u (%d)\n", inet_ntoa(rsin[curcon].sin_addr), htons(rsin[curcon].sin_port), n);
				if (n < 0)
					exit(-1);
			       /* get connection stats */
				sprintf(logline, "%s:%u ", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
				n = sizeof(sin);
				getsockname(acpt, SADR & sin, &n);
				sprintf(&logline[strlen(logline)], "%s:%u ", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
				n = sizeof(sin);
				getsockname(rnet, SADR & sin, &n);
				sprintf(&logline[strlen(logline)], "%s:%u ", inet_ntoa(sin.sin_addr), htons(sin.sin_port));
				sprintf(&logline[strlen(logline)], "%s:%u ", inet_ntoa(rsin[curcon].sin_addr), htons(rsin[curcon].sin_port));
			       /* if bidirectional, open browser session */
				if (reverse[curcon] == 3) {
					write(acpt, "HTTP/1.0 200 Ok\n\n", 17);		/* tell netscape to continue */
					fixconn(1, certbrwsr, acpt, 0, 0);
				}
			       /* correct for direction */
				rfd = acpt,
				  wfd = rnet;
				if (reverse[curcon])
					rfd = rnet,
					  wfd = acpt;
			       /* set up secure connection */
				thisuser[0] = 0;
				if (!error[0])
					fixconn(0, conncert[curcon], rfd, askuser[curcon], reverse[curcon]);
				sprintf(&logline[strlen(logline)], "%s", thisuser);
			       /* append stts to log file */
				tmpfd = fopen(logfile, "a");
				n = time(NULL);
				strcpy(pidfile, ctime((time_t *) & n));
				pidfile[strlen(pidfile) - 6] = 0;
				fprintf(tmpfd, "%s %s %s\n", &pidfile[4], logline, error);
				fclose(tmpfd);
				if (error[0])
					exit(-1);
			       /* create pid file for spawned task */
				n = getpid();
				sprintf(pidfile, "%s/edssl.%d", pidpath, n);
				tmpfd = fopen(pidfile, "a");
				fprintf(tmpfd, "%s\n", logline);
				fclose(tmpfd);
			       /* for lynx proxy https, forward edited header */
				if (reverse[curcon] == 2 && !snewsflag)
					n = remote_write(0, pp, strlen(pp));
			       /* copy data between connections */
				for (;;) {
					FD_ZERO(&fds);
					FD_SET(rfd, &fds);
					FD_SET(wfd, &fds);
					n = rfd > wfd ? rfd : wfd;
					tv.tv_sec = timeout;
					tv.tv_usec = 0;
					if ((n = select(++n, &fds, NULL, NULL, &tv)) <= 0)
						break;	/* timeout */
					if (FD_ISSET(rfd, &fds)) {
						if ((n = remote_read(0, xferbuf, XBUFSIZ)) <= 0)
							break;
						if (reverse[curcon] == 3)
							n = remote_write(1, xferbuf, n);
						else
							n = write(wfd, xferbuf, n);
						if (n <= 0)
							break;
					}
					if (FD_ISSET(wfd, &fds)) {
						if (reverse[curcon] == 3)
							n = remote_read(1, xferbuf, XBUFSIZ);
						else
							n = read(wfd, xferbuf, XBUFSIZ);
						if (n <= 0)
							break;
						if ((n = remote_write(0, xferbuf, n)) <= 0)
							break;
					}
				}
				if (!debugflag)
					unlink(pidfile);
				exit(0);
			}
		}
		for (n = 0; n < nlstn; n++)
			close(lstn[n]);
		while (childleft) {
			fprintf(stderr, "Waiting for %d child processes to complete\n", childleft);
			sleep(3);
		}
	}
	unlink(pidfile);
	return (0);
}
