/* sockd_rdconf */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <syslog.h>
#include "socks.h"

int sockd_rdconf(filename, cfAddrPtr, NcfPtr, no_identd_cmd, bad_id_cmd, useSyslog)
char *filename;
struct config **cfAddrPtr;
int *NcfPtr;
char **no_identd_cmd, **bad_id_cmd;
int useSyslog;
{
	FILE	*fd;
	static char	buf[1024];
	char	*bp, *ch;
	int	linenum = 0, permit;
	char	*argv[10];
	int	argc;
	int	next_arg;
	long	p;
	int Ncf = 0, maxcf = 0;
	struct config *cfAddr, *cp;
	int	has_error = 0;
	int	i;
	int	k;


	if ((fd = fopen(filename, "r")) == NULL) {
		if (useSyslog)
			syslog(LOG_HIGH, "Cannot open configuration file %s: %m\n", filename);
		else
			fprintf(stderr, "Cannot open configuration file %s: %m\n", filename);
		exit(1);
	}

	for (i = 0, cp = *cfAddrPtr; i++ < *NcfPtr; cp++) {
		if (cp->userlist != NULL)
			free(cp->userlist);
		if (cp->cmdp != NULL)
			free(cp->cmdp);
		if (cp->sdomain != NULL)
			free(cp->sdomain);
		if (cp->ddomain != NULL)
			free(cp->ddomain);
	}
	if (*cfAddrPtr)
		free(*cfAddrPtr);
	if (*no_identd_cmd != NULL) {
		free(*no_identd_cmd);
		*no_identd_cmd = NULL;
	}
	if (*bad_id_cmd != NULL) {
		free(*bad_id_cmd);
		*bad_id_cmd = NULL;
	}

	maxcf = CONF_INCR;
	cfAddr = (struct config *) malloc( maxcf * sizeof(struct config));
	if (cfAddr == NULL) {
		goto out_of_memory;
	}
	Ncf = 0;
	cp = cfAddr;

	while (fgets(buf, sizeof(buf) - 1, fd) != NULL) {
		linenum++;
		bzero(cp, sizeof(struct config));
		if ((bp = index(buf, '\n')) != NULL)
			*bp = '\0';
		if ((*bad_id_cmd == NULL) && (strncmp(buf, BAD_ID_STR, strlen(BAD_ID_STR)) == 0)) {
			*bad_id_cmd = strdup(buf + strlen(BAD_ID_STR));
			if (*bad_id_cmd == NULL)
				goto out_of_memory;
			cp->action = BAD_ID;
			cp->cmdp = *bad_id_cmd;
			goto update_count;
		}
		if ((*no_identd_cmd == NULL) && (strncmp(buf, NO_IDENTD_STR, strlen(NO_IDENTD_STR)) == 0)) {
			*no_identd_cmd = strdup(buf + strlen(NO_IDENTD_STR));
			if (*no_identd_cmd == NULL)
				goto out_of_memory;
			cp->action = NO_IDENTD;
			cp->cmdp = *no_identd_cmd;
			goto update_count;
		}
		for (bp = buf; *bp != '\0'; bp++) {
			if (*bp == ':') {
				*bp++ = '\0';
				cp->cmdp = strdup(bp);
				if (cp->cmdp == NULL) {
					goto out_of_memory;
				}
				break;
			} else if (*bp == '#') {
				*bp = '\0';
				break;
			} else if (*bp == '\t')
				*bp = ' ';
		}
		if (strlen(buf) == 0) continue;
		socks_mkargs(buf, &argc, argv, 10);
		if (argc == 0) {
			continue;
		}
		if ((argc < 3) || (argc > 9)) {
			if (useSyslog)
				syslog(LOG_HIGH, "Invalid entry at line %d in file %s", linenum, filename);
			else
				fprintf(stderr, "Invalid entry at line %d in file %s\n", linenum, filename);
			exit(1);
		}
		
		/* first parse all args */
		if (STREQ(argv[0], "permit")) {
			cp->action = SOCKD_PERMIT;
		} else if (STREQ(argv[0], "deny")) {
			cp->action = SOCKD_DENY;
		} else {
			if (useSyslog)
				syslog(LOG_HIGH, "Invalid permit/deny field at line %d in file %s", linenum, filename);
			else
				fprintf(stderr, "Invalid permit/deny field at line %d in file %s\n", linenum, filename);
			has_error = 1;
			continue;
		}

		next_arg = 1;

		k = strlen("?=");
		if (strncmp(argv[next_arg], "?=", k) == 0) {
			switch (*(argv[next_arg++] + k)) {
				case 'I':
					cp->use_identd = 2;
					break;
				case 'i':
					cp->use_identd = 1;
					break;
				case 'n':
				case '\0':
					cp->use_identd = 3;
					break;
				default:
					if (useSyslog)
						syslog(LOG_HIGH, "Invalid ?= field at line %d in file %s", linenum, filename);
					else
						fprintf(stderr, "Invalid ?= field at line %d in file %s\n", linenum, filename);
					has_error = 1;
					continue;
			}
		}

		k = strlen("*=");
		if (strncmp(argv[next_arg], "*=", k) == 0) {
			if (*(argv[next_arg] + k)) {
				cp->userlist = strdup(argv[next_arg] + k);
				if (cp->userlist == NULL)
					goto out_of_memory;
				}
			next_arg++;
		}
		if(argc <= next_arg+1) {
			if (useSyslog)
				syslog(LOG_HIGH, "Invalid entry at line %d in file %s", linenum, filename);
			else
				fprintf(stderr, "Invalid entry at line %d in file %s\n", linenum, filename);
			has_error = 1;
			continue;
		}
		if (socks_GetAddr(argv[next_arg++], &cp->saddr, &cp->sdomain) == -1){
			goto out_of_memory;
		}
		if (socks_GetQuad(argv[next_arg++], &cp->smask) == -1) {
			if (useSyslog)
				syslog(LOG_HIGH, "Illegal destination mask at line %d in file %s", linenum, filename);
			else
				fprintf(stderr, "Illegal destination mask at line %d in file %s\n", linenum, filename);
			has_error = 1;
			continue;
		}
		ch = argv[next_arg];
		if ((argc > next_arg + 1) &&
			!(STREQ(ch, "eq") || STREQ(ch, "neq") ||
			  STREQ(ch, "lt") || STREQ(ch, "gt") ||
			  STREQ(ch, "le") || STREQ(ch, "ge"))) {
			if (socks_GetAddr(argv[next_arg++], &cp->daddr, &cp->ddomain) == -1) {
				if (useSyslog)
					syslog(LOG_HIGH, "Illegal destination IP at line %d in file %s", linenum, filename);
				else
					fprintf(stderr, "Illegal destination IP at line %d in file %s\n", linenum, filename);
				has_error = 1;
				continue;
			}
			if (socks_GetQuad(argv[next_arg++], &cp->dmask) == -1) {
				if (useSyslog)
					syslog(LOG_HIGH, "Illegal destination mask at line %d in file %s", linenum, filename);
				else
					fprintf(stderr, "Illegal destination mask at line %d in file %s\n", linenum, filename);
				has_error = 1;
				continue;
			}

		}
		if (argc > next_arg + 1) {
			ch = argv[next_arg];
			if (STREQ(ch, "eq"))
				cp->tst = e_eq;
			else if (STREQ(ch, "neq"))
				cp->tst = e_neq;
			else if (STREQ(ch, "lt"))
				cp->tst = e_lt;
			else if (STREQ(ch, "gt"))
				cp->tst = e_gt;
			else if (STREQ(ch, "le"))
				cp->tst = e_le;
			else if (STREQ(ch, "ge"))
				cp->tst = e_ge;
			else {
				if (useSyslog)
					syslog(LOG_HIGH, "Invalid comparison at line %d in file %s", linenum, filename);
				else
					fprintf(stderr, "Invalid comparison at line %d in file %s\n", linenum, filename);
				has_error = 1;
				continue;
			}
				
			if (((p = socks_GetPort(argv[next_arg+1])) < 0) ||
				(p >= (1L << 16))) {
				if (useSyslog)
					syslog(LOG_HIGH, "Invalid port number at line %d in file %s", linenum, filename);
				else
					fprintf(stderr, "Invalid port number at line %d in file %s\n", linenum, filename);
				has_error = 1;
				continue;
			} else {
				cp->dport = p;
			}
		} else {
			cp->tst = e_nil;
		}

update_count:
		if (++Ncf >= maxcf) {
			maxcf += CONF_INCR;
			cfAddr = (struct config *) realloc(cfAddr, maxcf * sizeof(struct config));
		}
		if (cfAddr == NULL) {
			goto out_of_memory;
		}
		cp = cfAddr + Ncf;
	}
	fclose(fd);

	if (Ncf == 0) {
		if (useSyslog)
			syslog(LOG_HIGH, "No valid entires in file %s", filename);
		else
			fprintf(stderr, "No valid entires in file %s\n", filename);
		exit(1);
	}
	if (has_error)
		exit(1);
	if (Ncf < maxcf)
		cfAddr = (struct config *) realloc(cfAddr, Ncf * sizeof(struct config));
	*NcfPtr = Ncf;
	*cfAddrPtr = cfAddr;
	return 0;

out_of_memory:
	if(useSyslog)
		syslog(LOG_HIGH, "Out of memory\n");
	else
		fprintf(stderr, "Out of memory\n");
	exit(1);

}
