/*
   3APA3A simpliest proxy server
   (c) 2002-2003 by ZARAZA <3APA3A@security.nnov.ru>

   please read License Agreement

   $Id: 3proxy.c,v 1.15 2003/11/29 10:58:28 vlad Exp $
*/

#include "proxy.h"

#ifndef DEFAULTCONFIG
#ifndef _WIN32
#define DEFAULTCONFIG stringtable[15]
#else
#define DEFAULTCONFIG stringtable[16]
#endif 
#endif

typedef int (*MAINFUNC)(int, char**);

extern struct bandlim * bandlimiter;
extern struct trafcount * trafcounter;
extern pthread_mutex_t bandlim_mutex;


int counterd = -1;

struct counter_header {
	char sig[3];
	time_t updated;
} cheader = {"3CF", (time_t)0};

struct counter_record {
	unsigned long traf;
	unsigned long trafgb;
	time_t cleared;
	time_t updated;
} crecord;

struct child {
	int argc;
	unsigned char **argv;
	MAINFUNC f;
};

#define STRINGBUF 65535
#define NPARAMS	  4096

int rotate = 0;
time_t logtime = 0, t = 0;
unsigned char *logname=NULL, **archiver=NULL;
unsigned char ** args = 0;
unsigned char * buf = 0;
ROTATION logtype = NONE;
ROTATION countertype = NONE;
char * counterfile = NULL;
int bufsize = STRINGBUF*2;
int inbuf = 0;
int archiverc = 0;
char tmpbuf[1024];

extern unsigned char *strings[];

int timetoexit = 0;

#ifdef _WIN32

#undef close

void cyclestep(void);
SERVICE_STATUS_HANDLE hSrv;
DWORD dwCurrState;
int SetStatus( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
    SERVICE_STATUS srvStatus;
    srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    srvStatus.dwCurrentState = dwCurrState = dwState;
    srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
    srvStatus.dwWin32ExitCode = dwExitCode;
    srvStatus.dwServiceSpecificExitCode = 0;
    srvStatus.dwCheckPoint = dwProgress;
    srvStatus.dwWaitHint = 3000;
    return SetServiceStatus( hSrv, &srvStatus );
}

void __stdcall CommandHandler( DWORD dwCommand )
{
    switch( dwCommand )
    {
    case SERVICE_CONTROL_STOP:
    case SERVICE_CONTROL_SHUTDOWN:
        SetStatus( SERVICE_STOP_PENDING, 0, 1 );
	paused = 1;
	timetoexit = 1;
	Sleep(2000);
        SetStatus( SERVICE_STOPPED, 0, 0 );
#ifndef NOODBC
	close_sql();
#endif
        break;
    case SERVICE_CONTROL_PAUSE:
        SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
	paused = 1;
        SetStatus( SERVICE_PAUSED, 0, 0 );
        break;
    case SERVICE_CONTROL_CONTINUE:
        SetStatus( SERVICE_CONTINUE_PENDING, 0, 1 );
	paused = 0;
        SetStatus( SERVICE_RUNNING, 0, 0 );
        break;
    default: ;
    }
}


void __stdcall ServiceMain(int argc, unsigned char* argv[] )
{

    hSrv = RegisterServiceCtrlHandler(stringtable[1], (LPHANDLER_FUNCTION)CommandHandler);
    if( hSrv == 0 ) return;

    SetStatus( SERVICE_START_PENDING, 0, 1 );
    SetStatus( SERVICE_RUNNING, 0, 0 );
    cyclestep();
}

#else

void mysigpause (int sig){
	paused = !paused;
}

void mysigterm (int sig){
	paused = 1;
	usleep(3000*SLEEPTIME);
#ifndef NOODBC
	close_sql();
#endif
	timetoexit = 1;
}

#endif


#ifdef _WIN32
DWORD WINAPI startsrv(LPVOID data) {
#else
void * startsrv(void * data) {
#endif
  struct child *d = (struct child *)data;
   d->f(d->argc, (char **)d->argv);
  return 0;
}

int included =0;

int parsestr (unsigned char *str, unsigned char **argm, int nitems){
	int argc = 0;
	int space = 1;
	int comment = 0;
	unsigned char * incbegin = 0;
	int fd;
	int res, len;
	int i = 1;

	for(;;str++)switch(*str){
		case '\0': 
			if(comment) return -1;
			argm[argc] = 0;
			return argc;
		case '\"':
			*str = 0;
			comment = !comment;
			break;
		case '$':
			if(!comment && !included){
				incbegin = str;
				*str = 0;
			}
			break;
		case '\r':
		case '\n':
		case '\t':
		case ' ':
			if(!comment){
				*str = 0;
				space = 1;
				i = 0;
				if(incbegin){
					argc--;
					if((fd = open((char *)incbegin+1, O_RDONLY)) <= 0){
						fprintf(stderr, "Failed to open %s\n", incbegin+1);
						break;
					}
					if((bufsize - inbuf) <STRINGBUF){
						bufsize += STRINGBUF;
						if(!(buf = realloc(buf, bufsize))){
							fprintf(stderr, "Failed to allocate memory for %s\n", incbegin+1);
							CLOSE(fd);
							break;
						}
					}
					len = 0;
					if(argm[argc]!=(incbegin+1)) {
						len = strlen((char *)argm[argc]);
						memcpy(buf+inbuf, argm[argc], len);
					}
					if(!(res = read(fd, buf+inbuf+len, STRINGBUF-(1+len)))) {
						fprintf(stderr, "Failed to read %s [%d]\n ", incbegin+1, len);
						CLOSE(fd);
						break;
					}
					CLOSE(fd);
					buf[inbuf+res+len] = 0;
					incbegin = buf + inbuf;
					inbuf += (res + len + 1);
					included++;
					argc+=parsestr(incbegin, argm + argc, nitems - argc);
					included--;
					incbegin = NULL;

				}
				break;
			}
		default:
			i++;
			if(space) {
				space = 0;
				argm[argc++] = str;
				if(argc >= nitems) return argc;
			}
			else if (i > 2048) {
				*str = 0;	/* Cut the word to 256 unsigned characters */
			}
	}
}

unsigned char * dologname (unsigned char *buf, const unsigned char *name, const unsigned char *ext, ROTATION lt, time_t t) {
	struct tm *ts;
	ts = localtime(&t);
	switch(lt){
		case NONE:
			sprintf((char *)buf, "%s", name);
			break;
		case MONTHLY:
			sprintf((char *)buf, "%s.%04d.%02d", name, ts->tm_year+1900, ts->tm_mon+1);
			break;
		case WEEKLY:
			t = t - (ts->tm_wday * (60*60*24));
			ts = localtime(&t);
			sprintf((char *)buf, "%s.%04d.%02d.%02d", name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday);
			break;
		case DAILY:
			sprintf((char *)buf, "%s.%04d.%02d.%02d", name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday);
			break;
		case HOURLY:
			sprintf((char *)buf, "%s.%04d.%02d.%02d-%02d", name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday, ts->tm_hour);
			break;
		case MINUTELY:
			sprintf((char *)buf, "%s.%04d.%02d.%02d-%02d.%02d", name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min);
			break;
		default:
			break;
	}
	if(ext){
		strcat((char *)buf, ".");
		strcat((char *)buf, (char *)ext);
	}
	return buf;
}

int timechanged (time_t oldtime, time_t newtime, ROTATION lt){
	struct tm tmold;
	struct tm *tm;

	tm = localtime(&oldtime);
	memcpy(&tmold, tm, sizeof(tmold));
	tm = localtime(&newtime);
	switch(lt){
		case MINUTELY:
			if(tm->tm_min != tmold.tm_min)return 1;
			break;
		case HOURLY:
			if(tm->tm_hour != tmold.tm_hour)return 1;
			break;
		case DAILY:
			if(tm->tm_yday != tmold.tm_yday)return 1;
			break;
		case MONTHLY:
			if(tm->tm_mon != tmold.tm_mon)return 1;
			break;
		case WEEKLY:
			if(((newtime - oldtime) > (60*60*24*7))
				|| tm->tm_wday < tmold.tm_wday
				|| (tm->tm_wday == tmold.tm_wday && (newtime - oldtime) > (60*60*24*6))
				)return 1;
			break;
		default:
			break;	
	}
	return 0;
}

void cyclestep(void){
 for(;;){
	usleep(SLEEPTIME*1000);
	fflush(stdout);
	if(logname) {
		t = time(0);
		if(timechanged(logtime, t, logtype)) {
			freopen((char *)dologname (tmpbuf, logname, NULL, logtype, t), "a", stdout);
			fseek(stdout, 0L, SEEK_END);
			usleep(SLEEPTIME);
			logtime = t;
			if(logtype != NONE && rotate) {
				t = 1;
				switch(logtype){
					case MONTHLY:
						t = t * 4;
					case WEEKLY:
						t = t * 7;
					case DAILY:
						t = t * 24;
					case HOURLY:
						t = t * 60;
					case MINUTELY:
						t = t * 60;
					default:
						break;
				}
				if(rotate) {
					dologname (tmpbuf, logname, (archiver)?archiver[1]:NULL, logtype, (logtime - t*rotate));
					remove ((char *)tmpbuf);
				}
				if(archiver) {
					int i;
					*tmpbuf = 0;
					for(i = 2; i < archiverc && strlen((char *)tmpbuf) < 512; i++){
						strcat((char *)tmpbuf, " ");
						if(!strcmp((char *)archiver[i], "%A")){
							strcat((char *)tmpbuf, "\"");
							dologname (tmpbuf + strlen((char *)tmpbuf), logname, archiver[1], logtype, (logtime - t));
							strcat((char *)tmpbuf, "\"");
						}
						else if(!strcmp((char *)archiver[i], "%F")){
							strcat((char *)tmpbuf, "\"");
							dologname (tmpbuf+strlen((char *)tmpbuf), logname, NULL, logtype, (logtime-t));
							strcat((char *)tmpbuf, "\"");
						}
						else
							strcat((char *)tmpbuf, (char *)archiver[i]);
					}
					system((char *)tmpbuf+1);
				}
			}
		}
	}
	if(counterd >= 0) {
		time(&t);
		if(timechanged(cheader.updated, t, MINUTELY)){
			struct trafcount *tl;
			if(countertype && timechanged(cheader.updated, t, countertype)){
				FILE * cfp;
				
				cfp = fopen(dologname(tmpbuf, counterfile, NULL, countertype, t), "w");
				if(cfp){
					for(tl = trafcounter; cfp && tl; tl = tl->next){
						if(tl->type >= countertype)
							fprintf(cfp, "%05d %010lu %010lu\n", tl->number, tl->trafgb, tl->traf);
					}
					fclose(cfp);
				}
			}
			for(tl = trafcounter; tl; tl = tl->next){
				if(tl->number){
					if(tl->type!=NEVER && timechanged(tl->cleared, t, tl->type)){
						tl->cleared = t;
						tl->traf = 0;
						tl->trafgb = 0;
					}
					lseek(counterd, 
						sizeof(struct counter_header) + (tl->number - 1) * sizeof(struct counter_record),
						SEEK_SET);
					crecord.traf = tl->traf;
					crecord.trafgb = tl->trafgb;
					crecord.cleared = tl->cleared;
					crecord.updated = tl->updated;
					write(counterd, &crecord, sizeof(struct counter_record));
				}
			}
			cheader.updated = t;
			lseek(counterd, 0, SEEK_SET);
			write(counterd, &cheader, sizeof(struct counter_header));			
		}
	}
	if(timetoexit)return;
 }
}


#define RETURN(x) {res = x; goto CLEARRETURN;}

int main(int argc, char * argv[]) {
  unsigned char * arg;
  FILE * fp;
  pthread_t thread;
  struct child chdata;
  struct child * ch=&chdata;
  int cargc;
  int i;
  struct ace *acl = NULL;
  struct passwords *pwl = NULL;
  struct chain *chains;
  int res = 0;

#ifdef _WIN32
  int service = 0;
  HANDLE h;
#endif

  stringtable = strings;
  if( !(buf = malloc(STRINGBUF * 2)) || ! (ch->argv = args = malloc(NPARAMS * sizeof(unsigned char *) + 1)) ) {
		fprintf(stderr, "No memory for configuration");
		RETURN(10);
  }
#ifdef _WIN32
  if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--install")) {
	SC_HANDLE sch;

	if(!(sch = OpenSCManager(NULL, NULL, GENERIC_WRITE|SERVICE_START ))){
		perror("Failed to open Service Manager");
		RETURN(101);
	}
	*buf = '\"';
	if (!(res = SearchPath(NULL, argv[0], ".exe", 256, buf+1, (char **)&arg))) {
		perror("Failed to find executable filename");
		RETURN(102);
	}
	strcat((char *)buf, "\" \"");
	if(!(res = GetFullPathName ((argc == 3)?argv[2]:(char*)DEFAULTCONFIG, 256, buf+res+4, (char **)&arg))){
		perror("Failed to find config filename");
		RETURN(103);
	}
	strcat((char *)buf, "\"");
	if (!(sch = CreateService(sch, stringtable[1], stringtable[2], GENERIC_EXECUTE, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, buf, NULL, NULL, NULL, NULL, NULL))){
		perror("Failed to create service");
		RETURN(103);
	}
	if (!StartService(sch, 0, NULL)) {
		perror("Failed to start service");
		RETURN(103);
	}
	return 0;
  }
  if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--remove")) {
	SC_HANDLE sch;

	if(!(sch = OpenSCManager(NULL, NULL, GENERIC_WRITE))){
		perror("Failed to open Service Manager\n");
		RETURN(101);
	}
	if (!(sch = OpenService(sch, stringtable[1], DELETE))){
		perror("Failed to open service");
		RETURN(103);
	}
	if (!DeleteService(sch)){
		perror("Failed to delete service");
		RETURN(104);
	}
	RETURN(0);
  }
#endif
  if(argc > 2 || !(fp = fopen((argc==2)?argv[1]:(char*)DEFAULTCONFIG, "r"))) {
	fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
#ifdef _WIN32
	fprintf(stderr, "\n\t%s --install\n\tto install as service\n", argv[0]);
	fprintf(stderr, "\n\t%s --remove\n\tto remove service\n", argv[0]);
#endif
	fprintf(stderr, "\n%s", copyright);

	return 1;
  }

  paused = 1;

  for (i=0; fgets((char *)buf + inbuf, STRINGBUF, fp); i++){
	if(!buf[inbuf] || isspace(buf[inbuf]) || (buf[inbuf]) == '#')continue;
	if(bufsize - inbuf < STRINGBUF) {
		bufsize += STRINGBUF;
		buf = realloc(buf, bufsize);
		if(!buf) {
			fprintf(stderr, "No memory for configuration");
			RETURN(12);
		}
	}
	arg = buf+inbuf;
	inbuf += (strlen(buf+inbuf) + 1);
	cargc = parsestr (arg, args, NPARAMS-1);
	if(cargc < 1) {
		fprintf(stderr, "Parse error line %d\n", i);
		RETURN(21);
	}
	ch->argv[cargc] = NULL;
	ch->argc = cargc;
	if(!strcmp((char *)ch->argv[0], "proxy")) ch->f=proxymain;
	else if(!strcmp((char *)ch->argv[0], "pop3p")) ch->f=pop3pmain;
	else if(!strcmp((char *)ch->argv[0], "socks")) ch->f=socksmain;
	else if(!strcmp((char *)ch->argv[0], "tcppm")) ch->f=tcppmmain;
	else if(!strcmp((char *)ch->argv[0], "udppm")) ch->f=udppmmain;
	else if(!strcmp((char *)ch->argv[0], "internal") && ch->argc == 2) {
		conf.intip = getip((unsigned char *)ch->argv[1]);
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "external") && ch->argc == 2) {
		conf.extip = getip((unsigned char *)ch->argv[1]);
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "log") && ch->argc <= 3) {
		conf.logfunc = logstdout;
		if(ch->argc > 1) {
			if(*ch->argv[1]=='@'){
#ifndef _WIN32
				openlog((char *)ch->argv[1]+1, LOG_PID, LOG_DAEMON);
				conf.logfunc = logsyslog;
#endif
			}
#ifndef NOODBC
			else if(*ch->argv[1]=='&'){
				if((init_sql((char *)ch->argv[1]+1)))
					conf.logfunc = logsql;
			}
#endif
			else {
				if(ch->argc > 2) {
					switch(*ch->argv[2]){
					case 'd':
					case 'D':
						logtype = DAILY;
						break;
					case 'w':
					case 'W':
						logtype = WEEKLY;
						break;
					case 'm':
					case 'M':
						logtype = MONTHLY;
						break;
					case 'h':
					case 'H':
						logtype = HOURLY;
						break;
					case 'c':
					case 'C':
						logtype = MINUTELY;
						break;
					default:
						break;
					}
				}
				logtime = time(0);
				logname = ch->argv[1];
				freopen((char *)dologname (tmpbuf, logname, NULL, logtype, logtime), "a", stdout);
			}
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "service") && ch->argc == 1) {	
#ifdef _WIN32
		service = 1;
#endif
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "daemon") && ch->argc == 1) {	
		if(!demon)daemonize();
		demon = 1;
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "archiver") && ch->argc > 2) {
		int j;

		archiver = malloc(ch->argc * sizeof(char *));
		if(archiver) {
			archiverc = ch->argc;
			for(j = 0; j < archiverc; j++) archiver[j] = ch->argv[j];
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "counter") && ch->argc >= 2) {	
		if(counterd >=0)CLOSE(counterd);
		counterd = open(ch->argv[1], O_BINARY|O_RDWR|O_CREAT);
		if(counterd<0){
			fprintf(stderr, "Unable to open counter file %s, line %d\n", ch->argv[1], i);
			RETURN(18);
		}
		if(ch->argc >=4) {
			switch(*ch->argv[2]){
			case 'd':
			case 'D':
				countertype = DAILY;
				break;
			case 'w':
			case 'W':
				countertype = WEEKLY;
				break;
			case 'm':
			case 'M':
				countertype = MONTHLY;
				break;
			case 'h':
			case 'H':
				countertype = HOURLY;
				break;
			case 'c':
			case 'C':
				countertype = MINUTELY;
				break;
			default:
				fprintf(stderr, "Unknown counter type, line: %d\n", i);
				RETURN(18);
			}
			counterfile = ch->argv[3];
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "rotate") && ch->argc == 2) {	
		rotate = atoi((char *)ch->argv[1]);
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "logformat") && ch->argc == 2 && strlen((char *)ch->argv[1])) {	
		conf.logformat = ch->argv[1];
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "timeouts") && ch->argc > 1) {	
		int j;

		for(j = 0; timeouts[j] && j + 1 < ch->argc; j++) {
			if((timeouts[j] = atoi((char *)ch->argv[j + 1])) <= 0){
				fprintf(stderr, "Invalid timeout: %s, line %d\n", ch->argv[j + 1], i);
				RETURN(19);
			}
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "auth") && ch->argc == 2) {	
		if(!strcmp((char *)ch->argv[1], "none")){
			conf.authfunc = alwaysauth;
		}
		else if (!strcmp((char *)ch->argv[1], "iponly")){
			conf.authfunc = ipauth;
		}
		else if (!strcmp((char *)ch->argv[1], "nbname")){
			conf.authfunc = nbnameauth;
		}
		else if (!strcmp((char *)ch->argv[1], "strong")){
			conf.authfunc = strongauth;
		}
		else {
			fprintf(stderr, "Unknown auth type: '%s' line %d\n", ch->argv[1], i);
			RETURN(22);
		}
		continue;
	}
	else if (!strcmp((char *)ch->argv[0], "users") && ch->argc >= 2) {
		int j;

		for (j = 1; j<ch->argc; j++) {
			if(!conf.pwl)conf.pwl = pwl = malloc(sizeof(struct passwords));
			else {
				pwl->next = malloc(sizeof(struct passwords));
				pwl = pwl->next;
			}
			if(!pwl) {
				fprintf(stderr, "No memory for PWL entry, line %d\n", i);
				RETURN(22);
			}
			memset(pwl, 0, sizeof(struct passwords));
			pwl->user = ch->argv[j];
			arg = (unsigned char *)strchr((char *)ch->argv[j], ':');
			if(!arg||!arg[1]||!arg[2]||arg[3]!=':')	{
				continue;
			}
			pwl->password = arg + 4;
			if(arg[1] == 'C' && arg[2] == 'L')pwl->pwtype = CL;
			else if(arg[1] == 'C' && arg[2] == 'R')pwl->pwtype = CR;
			else if(arg[1] == 'N' && arg[2] == 'T')pwl->pwtype = NT;
			else if(arg[1] == 'L' && arg[2] == 'M')pwl->pwtype = LM;
			else {
				continue;
			}
			*arg = 0;
		}
		continue;
	}
	else if (( (!strcmp((char *)ch->argv[0], "allow") && (res=ALLOW) == ALLOW) || 
				    (!strcmp((char *)ch->argv[0], "deny") && (res=DENY) == DENY) ||
				    (!strcmp((char *)ch->argv[0], "redirect") && ch->argc >= 3 && (res=REDIRECT) ==REDIRECT) ||
				    (!strcmp((char *)ch->argv[0], "bandlimin") && ch->argc >= 2 && (res=BANDLIM) ==BANDLIM) ||
				    (!strcmp((char *)ch->argv[0], "nobandlimin") && (res=NOBANDLIM) ==NOBANDLIM) ||
				    (!strcmp((char *)ch->argv[0], "countin") && ch->argc >= 4 && (res=COUNT) ==COUNT) ||
				    (!strcmp((char *)ch->argv[0], "nocountin") && (res=NOCOUNT) ==NOCOUNT)

				  )) {
		int offset = 0;
		struct iplist *ipl=NULL;
		struct portlist *portl=NULL;
		struct userlist *userl=NULL;

		if(res == REDIRECT) offset = 2;
		else if(res == BANDLIM) offset = 1;
		else if(res == COUNT) offset = 3;
		acl = malloc(sizeof(struct ace));
		if(!acl) {
			fprintf(stderr, "No memory for ACL entry, line %d\n", i);
			RETURN(22);
		}
		memset(acl, 0, sizeof(struct ace));
		acl->action = res;
		if(res == 2){
			acl->chains = malloc(sizeof(struct chain));
			if(!acl->chains) {
				fprintf(stderr, "No memory for ACL entry, line %d\n", i);
				RETURN(22);
			}
			acl->chains->type = R_HTTP;
			acl->chains->redirip = getip(ch->argv[1]);
			acl->chains->redirport = htons((unsigned short)atoi((char *)ch->argv[2]));
			acl->chains->weight = 1000;
			acl->chains->extuser = NULL;
			acl->chains->extpass = NULL;
			acl->chains->next = NULL;
		}
		if(ch->argc - offset >= 2 && strcmp("*", (char *)ch->argv[1 + offset])) {
			arg = (unsigned char *)strtok((char *)ch->argv[1 + offset], ",");
			do {
				if(!acl->users) {
					acl->users = userl = malloc(sizeof(struct userlist));
				}
				else {
					userl->next = malloc(sizeof(struct userlist));
					userl = userl -> next;
				}
				if(!userl) {
					fprintf(stderr, "No memory for ACL entry, line %d\n", i);
					RETURN(22);
				}
				memset(userl, 0, sizeof(struct userlist));
				userl->user=arg;
			} while((arg = (unsigned char *)strtok((char *)NULL, ",")));
		}
		if(ch->argc - offset >= 3 && strcmp("*", (char *)ch->argv[2 + offset])) {
			arg = (unsigned char *)strtok((char *)ch->argv[2 + offset], ",");
			do {
				if(!acl->src) {
					acl->src = ipl = malloc(sizeof(struct iplist));
				}
				else {
					ipl->next = malloc(sizeof(struct iplist));
					ipl = ipl -> next;
				}
				if(!ipl) {
					fprintf(stderr, "No memory for ACL entry, line %d\n", i);
					RETURN(22);
				}
				memset(ipl, 0, sizeof(struct iplist));
				if (!scanaddr(arg, &ipl->ip, &ipl->mask)) {
					fprintf(stderr, "Invalid IP or CIDR, line %d\n", i);
					RETURN(22);
				}
			} while((arg = (unsigned char *)strtok((char *)NULL, ",")));
		}
		if(ch->argc - offset >= 4 && strcmp("*", (char *)ch->argv[3 + offset])) {
			arg = (unsigned char *)strtok((char *)ch->argv[3 + offset], ",");
			do {
				if(!acl->dst) {
					acl->dst = ipl = malloc(sizeof(struct iplist));
				}
				else {
					ipl->next = malloc(sizeof(struct iplist));
					ipl = ipl -> next;
				}
				if(!ipl) {
					fprintf(stderr, "No memory for ACL entry, line %d\n", i);
					RETURN(22);
				}
				memset(ipl, 0, sizeof(struct iplist));
				if (!scanaddr(arg, &ipl->ip, &ipl->mask)) {
					fprintf(stderr, "Invalid IP or CIDR, line %d\n", i);
					RETURN(22);
				}
			} while((arg = (unsigned char *)strtok((char *)NULL, ",")));
		}
		if(ch->argc - offset >= 5 && strcmp("*", (char *)ch->argv[4 + offset])) {
			arg = (unsigned char *)strtok((char *)ch->argv[4 + offset], ",");
			do {
				if(!acl->ports) {
					acl->ports = portl = malloc(sizeof(struct portlist));
				}
				else {
					portl->next = malloc(sizeof(struct portlist));
					portl = portl -> next;
				}
				if(!portl) {
					fprintf(stderr, "No memory for ACL entry, line %d\n", i);
					RETURN(22);
				}
				memset(portl, 0, sizeof(struct portlist));
				res = sscanf((char *)arg, "%hu-%hu", &portl->startport, &portl->endport);
				if(res < 1) {
					fprintf(stderr, "Invalid port or port range, line %d\n", i);
					RETURN(23);
				}
				if (res == 1) portl->endport = portl->startport;
			} while((arg = (unsigned char *)strtok((char *)NULL, ",")));
		}
		if(ch->argc - offset >= 6 && strcmp("*", (char *)ch->argv[5 + offset])) {
			arg = (unsigned char *)strtok((char *)ch->argv[5 + offset], ",");	
			do {
				if(!strcmp((char *)arg, "CONNECT")){
					acl->operation |= CONNECT;
				}
				else if(!strcmp((char *)arg, "BIND")){
					acl->operation |= BIND;
				}
				else if(!strcmp((char *)arg, "UDPASSOC")){
					acl->operation |= UDPASSOC;
				}
				else if(!strcmp((char *)arg, "ICMPASSOC")){
					acl->operation |= ICMPASSOC;
				}
				else if(!strcmp((char *)arg, "HTTP_GET")){
					acl->operation |= HTTP_GET;
				}
				else if(!strcmp((char *)arg, "HTTP_PUT")){
					acl->operation |= HTTP_PUT;
				}
				else if(!strcmp((char *)arg, "HTTP_POST")){
					acl->operation |= HTTP_POST;
				}
				else if(!strcmp((char *)arg, "HTTP_HEAD")){
					acl->operation |= HTTP_HEAD;
				}
				else if(!strcmp((char *)arg, "HTTP_OTHER")){
					acl->operation |= HTTP_OTHER;
				}
				else if(!strcmp((char *)arg, "HTTP_CONNECT")){
					acl->operation |= HTTP_CONNECT;
				}
				else if(!strcmp((char *)arg, "HTTP")){
					acl->operation |= HTTP;
				}
				else if(!strcmp((char *)arg, "HTTPS")){
					acl->operation |= HTTPS;
				}
				else if(!strcmp((char *)arg, "FTP_GET")){
					acl->operation |= FTP_GET;
				}
				else if(!strcmp((char *)arg, "FTP_PUT")){
					acl->operation |= FTP_PUT;
				}
				else if(!strcmp((char *)arg, "FTP_LIST")){
					acl->operation |= FTP_LIST;
				}
				else if(!strcmp((char *)arg, "FTP")){
					acl->operation |= FTP;
				}
				else {
					fprintf(stderr, "Unknown operation type: %s line %d\n", arg, i);
					RETURN(31);
				}
			} while((arg = (unsigned char *)strtok((char *)NULL, ",")));
		}
		if(acl->action == ALLOW || acl->action == DENY || acl->action == REDIRECT) {

			if(!conf.acl) conf.acl = acl;
			else {
				struct ace * acei;

				for(acei = conf.acl; acei->next; acei = acei->next);
				acei->next = acl;
			}
		}
		else if (acl->action == BANDLIM || acl->action == NOBANDLIM) {
			struct bandlim * nbl;
			nbl = malloc(sizeof(struct bandlim));
			if(!nbl) {
				fprintf(stderr, "No memory to create band limit filter\n");
				RETURN(32);
			}
			memset(nbl, 0, sizeof(struct bandlim));
			nbl->ace = acl;
			if(acl->action == BANDLIM) {
				sscanf(ch->argv[1], "%lu", &nbl->rate);
				if(nbl->rate < 300) {
					fprintf(stderr, "Wrong bandwidth specified, line %d\n", i);
					RETURN(33);
				}
			}
			if(!bandlimiter){
				bandlimiter = nbl;
				pthread_mutex_init(&bandlim_mutex, NULL);
			}
			else {
				struct bandlim * bli;

				for(bli = bandlimiter; bli->next; bli = bli->next);
				bli->next = nbl;
			}
			
		}
		else if (acl->action == COUNT || acl->action == NOCOUNT) {
			struct trafcount * tl;
			tl = malloc(sizeof(struct trafcount));
			if(!tl) {
				fprintf(stderr, "No memory to create traffic limit filter\n");
				RETURN(32);
			}
			memset(tl, 0, sizeof(struct trafcount));
			tl->ace = acl;
	
			if(acl->action == COUNT) {
				unsigned long lim;


				tl->comment = ch->argv[1];
				while(isdigit(*tl->comment))tl->comment++;
				if(*tl->comment== '/')tl->comment++;

				sscanf(ch->argv[1], "%u", &tl->number);
				sscanf(ch->argv[3], "%lu", &lim);
				tl->traflim = (lim<<20);
				tl->traflimgb = (lim>>10);
				switch(*ch->argv[2]){
					case 'd':
					case 'D':
						tl->type = DAILY;
						break;
					case 'w':
					case 'W':
						tl->type = WEEKLY;
						break;
					case 'm':
					case 'M':
						tl->type = MONTHLY;
						break;
					case 'n':
					case 'N':
						tl->type = NEVER;
						break;
					default:
						fprintf(stderr, "Unknown rotation type, line: %d\n", i);
						RETURN(34);
				}
				if(!tl->traflim && tl->traflimgb) {
					fprintf(stderr, "Wrong traffic limit specified, line %d\n", i);
					RETURN(35);
				}
				if(tl->number != 0 && counterd >= 0) {
					lseek(counterd, 
						sizeof(struct counter_header) + (tl->number - 1) * sizeof(struct counter_record),
						SEEK_SET);
					memset(&crecord, 0, sizeof(struct counter_record));
					read(counterd, &crecord, sizeof(struct counter_record));
					tl->traf = crecord.traf;
					tl->trafgb = crecord.trafgb;
					tl->cleared = crecord.cleared;
					tl->updated = crecord.updated;
				}
			}
			if(!trafcounter){
				trafcounter = tl;
			}
			else {
				struct trafcount * ntl;

				for(ntl = trafcounter; ntl->next; ntl = ntl->next);
				ntl->next = tl;
			}
/*
			tl->next = trafcounter;
			trafcounter = tl;
*/
			
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "flush") && ch->argc == 1) {	
		conf.acl = NULL;
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "parent") && ch->argc >=5) {
		if(!acl || (acl->action && acl->action != 2)) {
			fprintf(stderr, "Chaining error: last ACL entry was not \"allow\" or \"redirect\" on line %d\n", i);
			RETURN(41);
		}
		acl->action = 2;
		chains = NULL;
		if(!acl->chains) {
			chains = acl->chains = malloc(sizeof(struct chain));
		}
		else {
			chains = acl->chains;
			while(chains->next)chains = chains->next;
			chains->next = malloc(sizeof(struct chain));
			chains = chains->next;
		}
		memset(chains, 0, sizeof(struct chain));
		if(!chains){
			fprintf(stderr, "Chainig error: unable to allocate memory for chain\n");
			RETURN(44);
		}
		chains->weight = (unsigned)atoi((char *)ch->argv[1]);
		if(chains->weight == 0 || chains->weight >1000) {
			fprintf(stderr, "Chaining error: bad chain weight %u line %d\n", chains->weight, i);
			RETURN(42);
		}
		if(!strcmp((char *)ch->argv[2], "tcp"))chains->type = R_TCP;
		else if(!strcmp((char *)ch->argv[2], "http"))chains->type = R_HTTP;
		else if(!strcmp((char *)ch->argv[2], "connect"))chains->type = R_CONNECT;
		else if(!strcmp((char *)ch->argv[2], "socks4"))chains->type = R_SOCKS4;
		else if(!strcmp((char *)ch->argv[2], "socks5"))chains->type = R_SOCKS5;
		else {
			fprintf(stderr, "Chaining error: bad chain type (%s)\n", ch->argv[2]);
			RETURN(43);
		}
		chains->redirip = getip(ch->argv[3]);
		chains->redirport = htons((unsigned short)atoi((char *)ch->argv[4]));
		if(ch->argc > 5) chains->extuser = ch->argv[5];
		if(ch->argc > 6) chains->extpass = ch->argv[6];
		continue;
		
	}
	else if(!strcmp((char *)ch->argv[0], "nserver") && ch->argc == 2) {
		for(res = 0; nservers[res] && res < MAXNSERVERS; res++);
		if(res < MAXNSERVERS) {
			nservers[res] = getip(ch->argv[1]);
		}
		resolvfunc = myresolver;
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "nscache") && ch->argc == 2) {
		res = atoi(ch->argv[1]);
		if(res < 256) {
			fprintf(stderr, "Invalid NS cache size: %d\n", res);
		}
		if(inithashtable((unsigned)res)){
			fprintf(stderr, "Failed to initialize NS cache\n");
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "nsrecord") && ch->argc == 3) {
		hashadd(ch->argv[1], getip(ch->argv[2]), (time_t)0xffffffff);
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "dialer") && ch->argc == 2) {
		demanddialprog = ch->argv[1];
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "system") && ch->argc == 2) {	
		if((res = system((char *)ch->argv[1])) == -1){
			fprintf(stderr, "Failed to start %s\n", ch->argv[1]);
			RETURN(33);
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "pidfile") && ch->argc == 2) {
		FILE *pidf;
		if(!(pidf = fopen(ch->argv[1], "w"))){
			fprintf(stderr, "Failed to open pid file %s\n", ch->argv[1]);
			RETURN(34);
		}
		fprintf(pidf,"%u", (unsigned)getpid());
		fclose(pidf);
		continue;
	}
#ifndef _WIN32
	else if(!strcmp((char *)ch->argv[0], "setuid") && ch->argc == 2) {	
		res = atoi((char *)ch->argv[1]);
		if(!res || setuid(res)) {
			fprintf(stderr, "Unable to set uid %d", res);
			RETURN(30);
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "setgid") && ch->argc == 2) {	
		res = atoi((char *)ch->argv[1]);
		if(!res || setgid(res)) {
			fprintf(stderr, "Unable to set gid %d", res);
			RETURN(31);
		}
		continue;
	}
	else if(!strcmp((char *)ch->argv[0], "chroot") && ch->argc == 2) {	
		if(chroot((char *)ch->argv[1])) {
			fprintf(stderr, "Unable to chroot %s", ch->argv[1]);
			RETURN(32);
		}
		continue;
	}
#endif
	else {
		fprintf(stderr, "Unknown command: '%s' line %d\n", ch->argv[0], i);
		RETURN(22);
	}
#ifdef _WIN32
	h = CreateThread((LPSECURITY_ATTRIBUTES )NULL, 0, startsrv, (void *) ch, (DWORD)0, &thread);
	if(h)CloseHandle(h);
#else
	pthread_create(&thread, NULL, startsrv, (void *)ch);
#endif
	usleep(SLEEPTIME*100);
  }
  fclose(fp);

  paused = 0;
#ifdef _WIN32
  
  if(service){
	SERVICE_TABLE_ENTRY ste[] = 
	{
        	{ stringtable[1], (LPSERVICE_MAIN_FUNCTION)ServiceMain},
	        { NULL, NULL }
	};	
 	if(!StartServiceCtrlDispatcher( ste ))cyclestep();
  }
  else {
	cyclestep();
  }
  

#else
	 signal(SIGCONT, mysigpause);
	 signal(SIGTERM, mysigterm);
	 cyclestep();

#endif

CLEARRETURN:

 if(buf) free(buf);
 if(args) free(args);
/*
 if(1) {
   char t[256];
   sprintf(t, "echo result:%d lines: %d>c:\\result.txt", res, i);
   system(t);
 }
*/
 return 0;

}
