#include <sys/types.h>
#include <syslog.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <ctype.h>
#if (defined(sun) && !defined(SOLARIS)) || defined(sgi)
#include <strings.h>
#else
#include <string.h>
#endif
#include "socks.h"

#define STREQ(a, b)	(strcmp(a, b) == 0)

char *socks_conf = SOCKS_CONF;
extern	void	socks_mkargs();
extern	int	socks_GetAddr();
extern	int	socks_GetQuad();
extern	long	socks_GetPort();
extern	int	socks_check_user();
extern	char	socks_src_user[];
extern char	*socks_server;
extern struct sockaddr_in	socks_nsin;
extern u_int32 SocksHost;
extern char *socks_def_server;
extern char *socks_serverlist;

static struct config *confPtr, **confNtries = NULL;
static int Ntries = 0;
static int no_conf = 0;

socks_check_cconf(src, dst)
/* Return 0 if sockd should be used,
	  1 if direct connection should be made,
	 -1 if the connection request should be denied.
 */
struct sockaddr_in	*src, *dst;
{
	unsigned short	dst_sin_port = ntohs(dst->sin_port); 
	int i;
 	struct	in_addr self;
  
  
 	self.s_addr = inet_addr("127.0.0.1");
 	if ((dst->sin_addr.s_addr == self.s_addr) || (dst->sin_addr.s_addr == 0))
 		return(1);


	if (no_conf || ((confNtries == NULL) && (read_cconf() == -1)))
		return 1;

	for (i = 0; i < Ntries; i++) {
		confPtr = confNtries[i];

		socks_serverlist = confPtr->serverlist;
	/* comparisons of port numbers must be done in host order */

		if((confPtr->daddr.s_addr & confPtr->dmask.s_addr) == (dst->sin_addr.s_addr & confPtr->dmask.s_addr) &&
		    socks_check_user(confPtr->userlist, socks_src_user)) {
			if (confPtr->tst == e_nil) 
				goto GotIt;
			if ((confPtr->tst == e_eq) && (dst_sin_port == confPtr->dport))
				goto GotIt;
			if ((confPtr->tst == e_neq) && (dst_sin_port != confPtr->dport))
				goto GotIt;
			if ((confPtr->tst == e_lt) && (dst_sin_port < confPtr->dport))
				goto GotIt;
			if ((confPtr->tst == e_gt) && (dst_sin_port > confPtr->dport))
				goto GotIt;
			if ((confPtr->tst == e_le) && (dst_sin_port <= confPtr->dport))
				goto GotIt;
			if ((confPtr->tst == e_ge) && (dst_sin_port >= confPtr->dport))
				goto GotIt;
		}
	}

	return -1;

GotIt:
	if (confPtr->cmdp != (char *)0)
		socks_shell_cmd(confPtr->cmdp, src, dst);
	return confPtr->action;
	
}

static read_cconf()
{
	FILE		*fd;
	static char	buf[1024];
	char		*bp;
	int		linenum = 0;
	char	*argv[10];
	int	argc;
	struct in_addr	daddr, dmask;
	int	next_arg;
	int	action;
	char	*userlist, *server_list;
	long	p;
	unsigned short dport;
	char	*cmdp;
 	struct	in_addr self;
	Portcmp	tst;

	if ((fd = fopen(socks_conf, "r")) == NULL) {
		no_conf = 1;
		return -1;
	}

	while (fgets(buf, sizeof(buf) - 1, fd) != NULL) {
		linenum++;
		/*
		**  Comments start with a '#' anywhere on the line
		*/
		cmdp = (char *)0;
		if ((bp = index(buf, '\n')) != NULL)
			*bp = '\0';
		for (bp = buf; *bp != '\0'; bp++) {
			if (*bp == ':') {
				*bp++ = '\0';
				cmdp = bp;
				break;
			} else if (*bp == '#') {
				*bp = '\0';
				break;
			} else if (*bp == '\t')
				*bp = ' ';
		}
		if (strlen(buf) == 0) continue;
		socks_mkargs(buf, &argc, argv, 7);
		if (argc == 0) {
			continue;
		}
/* #ifdef hpux */
		if (STREQ(*argv, "domain") || STREQ(*argv, "nameserver") ||
			STREQ(*argv, "bind") || STREQ(*argv, "findserver"))
				continue;
/* #endif */ /* #ifdef hpux */
		if ((argc < 3) || (argc > 7)) {
			syslog(LOG_HIGH, "Invalid entry at line %d in file %s", linenum, socks_conf);
			exit(1);
		}
		
		/* parse the whole entry now, once. */
		next_arg = 1;
		server_list = (char *)0;

		if (STREQ(*argv, "sockd")) {
			server_list = socks_def_server;
			action = 0;
			if (strncmp(*(argv +next_arg), "@=", 2) == 0) {
				server_list = *(argv +next_arg) + 2;
				if(*server_list == '\0')
					server_list = socks_def_server;
				next_arg++;
			}
		} else if (strncmp(*argv, "sockd@", 6) == 0) {
			action = 0;
			server_list = *(argv) + 6;
			if (*server_list == '\0')
				server_list = socks_def_server;
		} else if (STREQ(*argv, "direct")) {
			action = 1;
		} else if (STREQ(*argv, "deny")) {
			action = -1;
		} else {
			syslog(LOG_HIGH, "Invalid sockd/direct/deny field at line %d in file %s", linenum, socks_conf);
			exit(1);
		}

		userlist = (char *)0;
		if (strncmp(*(argv +next_arg), "*=", 2) == 0) {
			if (*(argv +next_arg) +2) userlist = *(argv +next_arg) + 2;
			next_arg++;
		}
		if(argc <= next_arg+1) {
			syslog(LOG_HIGH, "Invalid entry at line %d in file %s", linenum, socks_conf);
			exit(1);
		}
		if (socks_GetAddr(*(argv +next_arg++), &daddr) == -1){
			syslog(LOG_HIGH, "illegal destination field at line %d in file %s", linenum, socks_conf);
			exit(1);
		}
		if (socks_GetQuad(*(argv +next_arg++), &dmask) == -1) {
			syslog(LOG_HIGH, "illegal destination mask at line %d in file %s", linenum, socks_conf);
			exit(1);
		}
		if (argc > next_arg + 1) {
			if (STREQ(*(argv +next_arg), "eq"))
				tst = e_eq;
			else if (STREQ(*(argv +next_arg), "neq"))
				tst = e_neq;
			else if (STREQ(*(argv +next_arg), "lt"))
				tst = e_lt;
			else if (STREQ(*(argv +next_arg), "gt"))
				tst = e_gt;
			else if (STREQ(*(argv +next_arg), "le"))
				tst = e_le;
			else if (STREQ(*(argv +next_arg), "ge"))
				tst = e_ge;
			else {
				syslog(LOG_HIGH, "Invalid comparison at line %d in file %s", linenum, socks_conf);
				exit(1);
			}
				
			if (((p = socks_GetPort(*(argv +next_arg+1))) < 0) ||
				(p >= (1L << 16))) {
				syslog(LOG_HIGH, "Invalid port number at line %d in file %s", linenum, socks_conf);
				exit(1);
			} else {
				dport = p;
			}
		} else {
			tst = e_nil;
			dport = 0;
		}

#ifdef DEBUG
		{
			char msg[1024];
			if (userlist) 
				sprintf(msg,"%s %s 0x%08x 0x%08x %s %u",
					*argv, userlist, daddr.s_addr, dmask,
						tst == e_eq ? "==" :
						tst == e_neq ? "!=" :
						tst == e_lt ? "<" :
						tst == e_gt ? ">" :
						tst == e_le ? "<=" :
						tst == e_ge ? ">=" : "NIL",
						dport);
			else
				sprintf(msg,"%s 0x%08x 0x%08x %s %u",
					*argv, daddr.s_addr, dmask,
						tst == e_eq ? "==" :
						tst == e_neq ? "!=" :
						tst == e_lt ? "<" :
						tst == e_gt ? ">" :
						tst == e_le ? "<=" :
						tst == e_ge ? ">=" : "NIL",
						dport);
			syslog(LOG_HIGH, "%s", msg);
		}
#endif
		/* we have a parsed line.  cache it. */
		if (!confNtries || confPtr - *confNtries >= Ntries) {
			/* some systems can't be counted on to handle
			 * realloc(NULL, ...) correctly.
			 */
			if (confNtries == NULL)  confNtries =
			  (struct config **) malloc(CONF_INCR *sizeof(struct config **));
			else confNtries = (struct config **)
			  realloc(confNtries, (Ntries +CONF_INCR) *sizeof(struct config));
		}
		*(confNtries +Ntries) = (struct config *) malloc(sizeof(struct config));
		confPtr = *(confNtries +Ntries);
		Ntries++;
		confPtr->action = action;
		confPtr->tst = tst;
		if (server_list) {
			confPtr->serverlist = (char *)malloc(strlen(server_list) +1);
			strcpy(confPtr->serverlist, server_list);
		}
		else confPtr->serverlist = NULL;
		if (userlist) {
			confPtr->userlist = (char *)malloc(strlen(userlist) +1);
			strcpy(confPtr->userlist, userlist);
		}
		else confPtr->userlist = NULL;
		confPtr->daddr.s_addr = daddr.s_addr;
		confPtr->dmask.s_addr = dmask.s_addr;
		if (cmdp) {
			confPtr->cmdp = (char *) malloc(strlen(cmdp) +1);
			strcpy(confPtr->cmdp, cmdp);
		}
		else confPtr->cmdp = NULL;
		confPtr->dport = dport;		

	}
	fclose(fd);
	if (confNtries == NULL) {
		syslog(LOG_HIGH, "No valid entires in file %s", socks_conf);
		exit(1);
	}
	return 0;
}
