/*
   3APA3A simpliest proxy server
   (c) 2002-2021 by Vladimir Dubrovin <3proxy@3proxy.org>

   please read License Agreement

*/

#include "proxy.h"


int clientnegotiate(struct chain * redir, struct clientparam * param, struct sockaddr * addr, unsigned char * hostname){
	unsigned char *buf;
	unsigned char *username;
	int res;
	int len=0;
	unsigned char * user, *pass;


	user = redir->extuser;
	pass = redir->extpass;
	if (!param->srvbufsize){
		param->srvbufsize = SRVBUFSIZE;
		param->srvbuf = myalloc(param->srvbufsize);
		if(!param->srvbuf) return 21;
	}
	buf = param->srvbuf;
	username = buf + 2048;
	if(user) {
		if (*user == '*') {
			if(!param->username) return 4;
			user = param->username;
			pass = param->password;
		}
	}
	switch(redir->type){
		case R_TCP:
		case R_HTTP:
			return 0;
		case R_CONNECT:
		case R_CONNECTP:
		{
			len = sprintf((char *)buf, "CONNECT ");
			if(redir->type == R_CONNECTP && hostname) {
				char * needreplace;
				needreplace = strchr((char *)hostname, ':');
				if(needreplace) buf[len++] = '[';
				len += sprintf((char *)buf + len, "%.256s", (char *)hostname);
				if(needreplace) buf[len++] = ']';
			}
			else {
				if(*SAFAMILY(addr) == AF_INET6) buf[len++] = '[';
				len += myinet_ntop(*SAFAMILY(addr), SAADDR(addr), (char *)buf+len, 256);
				if(*SAFAMILY(addr) == AF_INET6) buf[len++] = ']';
			}
			len += sprintf((char *)buf + len,
				":%hu HTTP/1.0\r\nConnection: keep-alive\r\n", ntohs(*SAPORT(addr)));
			if(user){
				len += sprintf((char *)buf + len, "Proxy-Authorization: Basic ");
				sprintf((char *)username, "%.128s:%.128s", user, pass?pass:(unsigned char *)"");
				en64(username, buf+len, (int)strlen((char *)username));
				len = (int)strlen((char *)buf);
				len += sprintf((char *)buf + len, "\r\n");
			}
			len += sprintf((char *)buf + len, "\r\n");
			if(socksend(param, param->remsock, buf, len, conf.timeouts[CHAIN_TO]) != (int)strlen((char *)buf))
				return 31;
			param->statssrv64+=len;
			param->nwrites++;
			if((res = sockgetlinebuf(param, SERVER,buf,13,'\n',conf.timeouts[CHAIN_TO])) < 13)
				return 32;
			if(buf[9] != '2') return 33;
			while((res = sockgetlinebuf(param, SERVER,buf,1023,'\n', conf.timeouts[CHAIN_TO])) > 2);
			if(res <= 0) return 34;
			return 0;
		}
		case R_SOCKS4:
		case R_SOCKS4P:
		case R_SOCKS4B:
		{

			if(*SAFAMILY(addr) != AF_INET) return 44;
			buf[0] = 4;
			buf[1] = 1;
			memcpy(buf+2, SAPORT(addr), 2);
			if(redir->type == R_SOCKS4P && hostname) {
				buf[4] = buf[5] = buf[6] = 0;
				buf[7] = 3;
			}
			else memcpy(buf+4, SAADDR(addr), 4);
			if(!user)user = (unsigned char *)"anonymous";
			len = (int)strlen((char *)user) + 1;
			memcpy(buf+8, user, len);
			len += 8;
			if(redir->type == R_SOCKS4P && hostname) {
				int hostnamelen;

				hostnamelen = (int)strlen((char *)hostname) + 1;
				if(hostnamelen > 255) hostnamelen = 255;
				memcpy(buf+len, hostname, hostnamelen);
				len += hostnamelen;
			}
			if(socksend(param, param->remsock, buf, len, conf.timeouts[CHAIN_TO]) < len){
				return 41;
			}
			param->statssrv64+=len;
			param->nwrites++;
			if((len = sockgetlinebuf(param, SERVER, buf, (redir->type == R_SOCKS4B)? 3:8, EOF, conf.timeouts[CHAIN_TO])) != ((redir->type == R_SOCKS4B)? 3:8)){
				return 42;
			}
			if(buf[1] != 90) {
				return 43;
			}

		}
		return 0;

		case R_SOCKS5:
		case R_SOCKS5P:
		case R_SOCKS5B:
		{
		 int inbuf = 0;
			buf[0] = 5;
			buf[1] = 1;
			buf[2] = user? 2 : 0;
			if(socksend(param, param->remsock, buf, 3, conf.timeouts[CHAIN_TO]) != 3){
				return 51;
			}
			param->statssrv64+=len;
			param->nwrites++;
			if(sockgetlinebuf(param, SERVER, buf, 2, EOF, conf.timeouts[CHAIN_TO]) != 2){
				return 52;
			}
			if(buf[0] != 5) {
				return 53;
			}
			if(buf[1] != 0 && !(buf[1] == 2 && user)){
				return 54;
			}
			if(buf[1] == 2){
				buf[inbuf++] = 1;
				buf[inbuf] = (unsigned char)strlen((char *)user);
				memcpy(buf+inbuf+1, user, buf[inbuf]);
				inbuf += buf[inbuf] + 1;
				buf[inbuf] = pass?(unsigned char)strlen((char *)pass):0;
				if(pass)memcpy(buf+inbuf+1, pass, buf[inbuf]);
				inbuf += buf[inbuf] + 1;
				if(socksend(param, param->remsock, buf, inbuf, conf.timeouts[CHAIN_TO]) != inbuf){
					return 51;
				}
				param->statssrv64+=inbuf;
				param->nwrites++;
				if(sockgetlinebuf(param, SERVER, buf, 2, EOF, 60) != 2){
					return 55;
				}
				if(buf[0] != 1 || buf[1] != 0) {
					return 56;
				}
			}
			buf[0] = 5;
			buf[1] = 1;
			buf[2] = 0;
			if(redir->type == R_SOCKS5P && hostname) {
				buf[3] = 3;
				len = (int)strlen((char *)hostname);
				if(len > 255) len = 255;
				buf[4] = len;
				memcpy(buf + 5, hostname, len);
				len += 5;
			}
			else {
				len = 3;
				buf[len++] = (*SAFAMILY(addr) == AF_INET)? 1 : 4;
				memcpy(buf+len, SAADDR(addr), SAADDRLEN(addr));
				len += SAADDRLEN(addr);
			}
			memcpy(buf+len, SAPORT(addr), 2);
			len += 2;
			if(socksend(param, param->remsock, buf, len, conf.timeouts[CHAIN_TO]) != len){
				return 51;
			}
			param->statssrv64+=len;
			param->nwrites++;
			if(sockgetlinebuf(param, SERVER, buf, 4, EOF, conf.timeouts[CHAIN_TO]) != 4){
				return 57;
			}
			if(buf[0] != 5) {
				return 53;
			}
			if(buf[1] != 0) {
				return 60 + (buf[1] % 10);
			}
			switch (buf[3]) {
			case 1:
			    if (redir->type == R_SOCKS5B ||  sockgetlinebuf(param, SERVER, buf, 6, EOF, conf.timeouts[CHAIN_TO]) == 6)
				    break;
			    return 59;
			case 3:
			    if (sockgetlinebuf(param, SERVER, buf, 1, EOF, conf.timeouts[CHAIN_TO]) != 1) return 59;
			    len = (unsigned char)buf[0];
			    if (sockgetlinebuf(param, SERVER, buf, len, EOF, conf.timeouts[CHAIN_TO]) != len) return 59;
			    break;
			case 4:
			    if (sockgetlinebuf(param, SERVER, buf, 18, EOF, conf.timeouts[CHAIN_TO]) == 18)
				    break;
			    return 59;
			default:
			    return 58;
			}
			return 0;
		}

		default:

			return 30;
	}
}


int handleredirect(struct clientparam * param, struct ace * acentry){
	int connected = 0;
	int weight = 1000;
	int res;
	int done = 0;
	int ha = 0;
	struct chain * cur;
	struct chain * redir = NULL;
	int r2;

	if(param->remsock != INVALID_SOCKET) {
		return 0;
	}
	if(SAISNULL(&param->req) || !*SAPORT(&param->req)) {
		return 100;
	}

	r2 = (myrand(param, sizeof(struct clientparam))%1000);

	for(cur = acentry->chains; cur; cur=cur->next){
		if(((weight = weight - cur->weight) > r2)|| done) {
			if(weight <= 0) {
				weight += 1000;
				done = 0;
				r2 = (myrand(param, sizeof(struct clientparam))%1000);
			}
			continue;
		}
		param->redirected++;
		done = 1;
		if(weight <= 0) {
			weight += 1000;
			done = 0;
			r2 = (myrand(param, sizeof(struct clientparam))%1000);
		}
		if(!connected){
			if(cur->type == R_EXTIP){
				param->sinsl = cur->addr;
				if(SAISNULL(&param->sinsl))param->sinsl = param->sincr;
#ifndef NOIPV6
				else if(cur->cidr && *SAFAMILY(&param->sinsl) == AF_INET6){
					uint16_t c;
					int i;

					for(i = 0; i < 8; i++){
						if(i==4)myrand(&param->sincr, sizeof(param->sincr));
						else if(i==6) myrand(&param->req, sizeof(param->req));

						if(i*16 >= cur->cidr) ((uint16_t *)SAADDR(&param->sinsl))[i] |= rand();
						else if ((i+1)*16 >  cur->cidr){
							c = rand();
							c >>= (cur->cidr - (i*16));
							c |= ntohs(((uint16_t *)SAADDR(&param->sinsl))[i]);
							((uint16_t *)SAADDR(&param->sinsl))[i] = htons(c);
						}
					}
				}
#endif
				if(cur->next)continue;
				return 0;
			}
			else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
				int i;
				if(cur->extuser){
					if(param->extusername)
						myfree(param->extusername);
					param->extusername = (unsigned char *)mystrdup((char *)((*cur->extuser == '*' && param->username)? param->username : cur->extuser));
					if(cur->extpass){
						if(param->extpassword)
							myfree(param->extpassword);
						param->extpassword = (unsigned char *)mystrdup((char *)((*cur->extuser == '*' && param->password)?param->password : cur->extpass));
					}
					if(*cur->extuser == '*' && !param->username) return 4;
				}
				
				for(i=0; redirs[i].name; i++){
				    if(cur->type == redirs[i].redir) {
					param->redirectfunc = redirs[i].func;
					break;
				    }
				}
				if(cur->type == R_HA){
				    ha = 1;
				}
				if(cur->next)continue;
				if(!ha) return 0;
			}
			else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
				unsigned short port = *SAPORT(&param->sinsr);
				param->sinsr = cur->addr;
				*SAPORT(&param->sinsr) = port;
			}
			else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(&param->sinsr) = *SAPORT(&cur->addr);
			else {
				param->sinsr = cur->addr;
			}

			if((res = alwaysauth(param))){
				return (res >= 10)? res : 60+res;
			}
			if(ha) {
			    char buf[128];
			    int len; 
			    len = sprintf(buf, "PROXY %s ",
				*SAFAMILY(&param->sincr) == AF_INET6 ? "TCP6" : "TCP4");
			    len += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), buf+len, sizeof(param->sincr));
			    buf[len++] = ' ';
			    len += myinet_ntop(*SAFAMILY(&param->sincl), SAADDR(&param->sincl), buf+len, sizeof(param->sincl));
			    len += sprintf(buf + len, " %hu %hu\r\n",
				ntohs(*SAPORT(&param->sincr)),
				ntohs(*SAPORT(&param->sincl))
			    );
			    if(socksend(param, param->remsock, (unsigned char *)buf, len, conf.timeouts[CHAIN_TO])!=len) return 39;
			    return 0;
			}
		}
		else {
			res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&cur->addr, cur->exthost):0;
			if(res) return res;
		}
		redir = cur;
		param->redirtype = redir->type;
		if(redir->type == R_TCP || redir->type ==R_HTTP) {
			if(cur->extuser){
				if(*cur -> extuser == '*' && !param->username) return 4;
				if(param->extusername)
					myfree(param->extusername);
				param->extusername = (unsigned char *)mystrdup((char *)((*cur->extuser == '*' && param->username)? param->username : cur->extuser));
				if(cur->extpass){
					if(param->extpassword)
						myfree(param->extpassword);
					param->extpassword = (unsigned char *)mystrdup((char *)((*cur->extuser == '*' && param->password)?param->password : cur->extpass));
				}
			}
			return 0;
		}
		connected = 1;
	}

	if(!connected || !redir) return 0;
	return clientnegotiate(redir, param, (struct sockaddr *)&param->req, param->hostname);
}

int IPInentry(struct sockaddr *sa, struct iplist *ipentry){
	int addrlen;
	unsigned char *ip, *ipf, *ipt;


	if(!sa || ! ipentry || *SAFAMILY(sa) != ipentry->family) return 0;

	ip = (unsigned char *)SAADDR(sa);
	ipf = (unsigned char *)&ipentry->ip_from;
	ipt = (unsigned char *)&ipentry->ip_to;


	addrlen = SAADDRLEN(sa);
	
	if(memcmp(ip,ipf,addrlen) < 0 || memcmp(ip,ipt,addrlen) > 0) return 0;
	return 1;
	
}

int ACLmatches(struct ace* acentry, struct clientparam * param){
	struct userlist * userentry;
	struct iplist *ipentry;
	struct portlist *portentry;
	struct period *periodentry;
	unsigned char * username;
	struct hostname * hstentry=NULL;
	int i;
	int match = 0;
	
	username = param->username?param->username:(unsigned char *)"-";
	if(acentry->src) {
	 for(ipentry = acentry->src; ipentry; ipentry = ipentry->next)
		if(IPInentry((struct sockaddr *)&param->sincr, ipentry)) {
			break;
		}
	 if(!ipentry) return 0;
	}
	if((acentry->dst && (!SAISNULL(&param->req) || param->operation == UDPASSOC || param->operation==BIND)) || (acentry->dstnames && param->hostname)) {
	 for(ipentry = acentry->dst; ipentry; ipentry = ipentry->next)
		if(IPInentry((struct sockaddr *)&param->req, ipentry)) {
			break;
		}
	 if(!ipentry) {
		 if(acentry->dstnames && param->hostname){
			for(i=0; param->hostname[i]; i++){
				param->hostname[i] = tolower(param->hostname[i]);
			}
			while(i > 5 && param->hostname[i-1] == '.') param->hostname[i-1] = 0;
			for(hstentry = acentry->dstnames; hstentry; hstentry = hstentry->next){
				int lname, lhost;
				switch(hstentry->matchtype){
					case 0:
#ifndef _WIN32
					if(strcasestr((char *)param->hostname, (char *)hstentry->name)) match = 1;
#else
					if(strstr((char *)param->hostname, (char *)hstentry->name)) match = 1;
#endif
					break;

					case 1:
					if(!strncasecmp((char *)param->hostname, (char *)hstentry->name, strlen((char *)hstentry->name)))
						match = 1;
					break;

					case 2:
					lname = strlen((char *)hstentry->name);
					lhost = strlen((char *)param->hostname);
					if(lhost > lname){
						if(!strncasecmp((char *)param->hostname + (lhost - lname),
							(char *)hstentry->name,
							lname))
								match = 1;
					}
					break;

					default:
					if(!strcasecmp((char *)param->hostname, (char *)hstentry->name)) match = 1;
					break;
        			}
				if(match) break;
			}
		 }
	 }
	 if(!ipentry && !hstentry) return 0;
	}
	if(acentry->ports && (*SAPORT(&param->req) || param->operation == UDPASSOC || param->operation == BIND)) {
	 for (portentry = acentry->ports; portentry; portentry = portentry->next)
		if(ntohs(*SAPORT(&param->req)) >= portentry->startport &&
			   ntohs(*SAPORT(&param->req)) <= portentry->endport) {
			break;
		}
		if(!portentry) return 0;
	}
	if(acentry->wdays){
		if(!(acentry -> wdays & wday)) return 0;
	}
	if(acentry->periods){
	 int start_time = (int)(param->time_start - basetime);
	 for(periodentry = acentry->periods; periodentry; periodentry = periodentry -> next)
		if(start_time >= periodentry->fromtime && start_time < periodentry->totime){
			break;
		}
		if(!periodentry) return 0;
	}
	if(acentry->users){
	 for(userentry = acentry->users; userentry; userentry = userentry->next)
		if(!strcmp((char *)username, (char *)userentry->user)){
			break;
		}
	 if(!userentry) return 0;
	}
	if(acentry->operation) {
		if((acentry->operation & param->operation) != param->operation){
				 return 0;
		}
	}
	if(acentry->weight && (acentry->weight < param->weight)) return 0;
	return 1;
}


int startconnlims (struct clientparam *param){
	struct connlim * ce;
	time_t delta;
	uint64_t rating;
	int ret = 0;

	param->connlim = 1;
	pthread_mutex_lock(&connlim_mutex);
	for(ce = conf.connlimiter; ce; ce = ce->next) {
		if(ACLmatches(ce->ace, param)){
			if(ce->ace->action == NOCONNLIM)break;
			if(!ce->period){
				if(ce->rate <= ce->rating) {
					ret = 1;
					break;
				}
				ce->rating++;
				continue;
			}
			delta = conf.time - ce->basetime;
			if(ce->period <= delta || ce->basetime > conf.time){
				ce->basetime = conf.time;
				ce->rating = 0x100000;
				continue;
			}
			rating = delta? ((ce->rating * (ce->period - delta)) / ce->period) + 0x100000 : ce->rating + 0x100000;
			if (rating > (ce->rate<<20)) {
				ret = 2;
				break;
			}
			ce->rating = rating;
			ce->basetime = conf.time;
		}
	}
	pthread_mutex_unlock(&connlim_mutex);
	return ret;
}

void stopconnlims (struct clientparam *param){
	struct connlim * ce;

	pthread_mutex_lock(&connlim_mutex);
	for(ce = conf.connlimiter; ce; ce = ce->next) {
		if(ACLmatches(ce->ace, param)){
			if(ce->ace->action == NOCONNLIM)break;
			if(!ce->period && ce->rating){
				ce->rating--;
				continue;
			}
		}
	}
	pthread_mutex_unlock(&connlim_mutex);
}

static void initbandlims (struct clientparam *param){
	struct bandlim * be;
	int i;

	param->bandlimfunc = NULL;
	param->bandlims[0] = NULL;
	param->bandlimsout[0] = NULL;
	if(!conf.bandlimfunc || (!conf.bandlimiter && !conf.bandlimiterout)) return;
	for(i=0, be = conf.bandlimiter; be && i<MAXBANDLIMS; be = be->next) {
		if(ACLmatches(be->ace, param)){
			if(be->ace->action == NOBANDLIM) {
				break;
			}
			param->bandlims[i++] = be;
			param->bandlimfunc = conf.bandlimfunc;
		}
	}
	if(i<MAXBANDLIMS)param->bandlims[i] = NULL;
	for(i=0, be = conf.bandlimiterout; be && i<MAXBANDLIMS; be = be->next) {
		if(ACLmatches(be->ace, param)){
			if(be->ace->action == NOBANDLIM) {
				break;
			}
			param->bandlimsout[i++] = be;
			param->bandlimfunc = conf.bandlimfunc;
		}
	}
	if(i<MAXBANDLIMS)param->bandlimsout[i] = NULL;
	param->bandlimver = conf.bandlimver;
}

unsigned bandlimitfunc(struct clientparam *param, unsigned nbytesin, unsigned nbytesout){
	unsigned sleeptime = 0, nsleeptime;
	time_t sec;
	unsigned msec;
	unsigned now;
	int i;

#ifdef _WIN32
	struct timeb tb;

	ftime(&tb);
	sec = (unsigned)tb.time;
	msec = (unsigned)tb.millitm*1000;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);

	sec = tv.tv_sec;
	msec = tv.tv_usec;
#endif
	
	if(!nbytesin && !nbytesout) return 0;
	pthread_mutex_lock(&bandlim_mutex);
	if(param->bandlimver != conf.bandlimver){
		initbandlims(param);
		param->bandlimver = conf.bandlimver;
	}
	for(i=0; nbytesin&& i<MAXBANDLIMS && param->bandlims[i]; i++){
		if( !param->bandlims[i]->basetime || 
			param->bandlims[i]->basetime > sec ||
			param->bandlims[i]->basetime < (sec - 120)
		  )
		{
			param->bandlims[i]->basetime = sec;
			param->bandlims[i]->nexttime = 0;
			continue;
		}
		now = (unsigned)((sec - param->bandlims[i]->basetime) * 1000000) + msec;
		nsleeptime = (param->bandlims[i]->nexttime > now)?
			param->bandlims[i]->nexttime - now : 0;
		sleeptime = (nsleeptime > sleeptime)? nsleeptime : sleeptime;
		param->bandlims[i]->basetime = sec;
		param->bandlims[i]->nexttime = msec + nsleeptime + ((nbytesin > 512)? ((nbytesin+32)/64)*(((64*8*1000000)/param->bandlims[i]->rate)) : ((nbytesin+1) * (8*1000000))/param->bandlims[i]->rate);
	}
	for(i=0; nbytesout && i<MAXBANDLIMS && param->bandlimsout[i]; i++){
		if( !param->bandlimsout[i]->basetime || 
			param->bandlimsout[i]->basetime > sec ||
			param->bandlimsout[i]->basetime < (sec - 120)
		  )
		{
			param->bandlimsout[i]->basetime = sec;
			param->bandlimsout[i]->nexttime = 0;
			continue;
		}
		now = (unsigned)((sec - param->bandlimsout[i]->basetime) * 1000000) + msec;
		nsleeptime = (param->bandlimsout[i]->nexttime > now)?
			param->bandlimsout[i]->nexttime - now : 0;
		sleeptime = (nsleeptime > sleeptime)? nsleeptime : sleeptime;
		param->bandlimsout[i]->basetime = sec;
		param->bandlimsout[i]->nexttime = msec + nsleeptime + ((nbytesout > 512)? ((nbytesout+32)/64)*((64*8*1000000)/param->bandlimsout[i]->rate) : ((nbytesout+1)* (8*1000000))/param->bandlimsout[i]->rate);
	}
	pthread_mutex_unlock(&bandlim_mutex);
	return sleeptime/1000;
}

void trafcountfunc(struct clientparam *param){
	struct trafcount * tc;
	int countout = 0;

	pthread_mutex_lock(&tc_mutex);
	for(tc = conf.trafcounter; tc; tc = tc->next) {
		if(ACLmatches(tc->ace, param)){

			if(tc->ace->action == NOCOUNTIN) {
				countout = 1;
				break;
			}
			if(tc->ace->action == NOCOUNTALL) break;
			if(tc->ace->action != COUNTIN && tc->ace->action != COUNTALL) {
				countout = 1;
				continue;
			}
			tc->traf64 += param->statssrv64;
			tc->updated = conf.time;
		}
	}
	if(countout) for(tc = conf.trafcounter; tc; tc = tc->next) {
		if(ACLmatches(tc->ace, param)){
			if(tc->ace->action == NOCOUNTOUT || tc->ace->action == NOCOUNTALL) break;
			if(tc->ace->action != COUNTOUT && tc->ace->action != COUNTALL ) {
				continue;
			}
			tc->traf64 += param->statscli64;
			tc->updated = conf.time;
		}
	}

	pthread_mutex_unlock(&tc_mutex);
}

int alwaysauth(struct clientparam * param){
	int res;
	struct trafcount * tc;
	int countout = 0;


	if(conf.connlimiter && !param->connlim  && startconnlims(param)) return 10;
	res = doconnect(param);
	if(!res){
		if(conf.bandlimfunc && (conf.bandlimiter||conf.bandlimiterout)){
			pthread_mutex_lock(&bandlim_mutex);
			initbandlims(param);
			pthread_mutex_unlock(&bandlim_mutex);
		}

		if(conf.trafcountfunc && conf.trafcounter) {
			pthread_mutex_lock(&tc_mutex);
			for(tc = conf.trafcounter; tc; tc = tc->next) {
				if(tc->disabled) continue;
				if(ACLmatches(tc->ace, param)){
					if(tc->ace->action == NOCOUNTIN) {
						countout = 1;
						break;
					}
					if(tc->ace->action == NOCOUNTALL) break;
					if(tc->ace->action != COUNTIN) {
						countout = 1;
						if(tc->ace->action != COUNTALL) continue;
					}
					if(tc->traflim64 <= tc->traf64) {
					    pthread_mutex_unlock(&tc_mutex);
					    return 10;
					}
					param->trafcountfunc = conf.trafcountfunc;
					param->maxtrafin64 = tc->traflim64 - tc->traf64; 
				}
			}
			if(countout)for(tc = conf.trafcounter; tc; tc = tc->next) {
				if(tc->disabled) continue;
				if(ACLmatches(tc->ace, param)){
					if(tc->ace->action == NOCOUNTOUT || tc->ace->action == NOCOUNTALL) break;
					if(tc->ace->action != COUNTOUT && tc->ace->action !=  COUNTALL) {
						continue;
					}
					if(tc->traflim64 <= tc->traf64) {
					    pthread_mutex_unlock(&tc_mutex);
					    return 10;
					}
					param->trafcountfunc = conf.trafcountfunc;
					param->maxtrafout64 = tc->traflim64 - tc->traf64; 
				}
			}
			pthread_mutex_unlock(&tc_mutex);
		}
	}
	return res;
}

int checkACL(struct clientparam * param){
	struct ace* acentry;

	if(!param->srv->acl) {
		return 0;
	}
	for(acentry = param->srv->acl; acentry; acentry = acentry->next) {
		if(ACLmatches(acentry, param)) {
			param->nolog = acentry->nolog;
			param->weight = acentry->weight;
			if(acentry->action == 2) {
				struct ace dup;
				int res=60,i=0;

				if(param->operation < 256 && !(param->operation & CONNECT)){
					continue;
				}
				if(param->redirected && acentry->chains && SAISNULL(&acentry->chains->addr) && !*SAPORT(&acentry->chains->addr)) {
					continue;
				}
				if(param->remsock != INVALID_SOCKET) {
					return 0;
				}
				for(; i < conf.parentretries; i++){
					dup = *acentry;
					res = handleredirect(param, &dup);
					if(!res) break;
					if(param->remsock != INVALID_SOCKET) param->srv->so._closesocket(param->sostate, param->remsock);
					param->remsock = INVALID_SOCKET;
				}
				return res;
			}
			return acentry->action;
		}
	}
	return 3;
}

struct authcache {
	char * username;
	char * password;
	time_t expires;
#ifndef NOIPV6
	struct sockaddr_in6 sa, sinsl;
#else
	struct sockaddr_in sa, sinsl;
#endif
	struct ace *acl;
	struct authcache *next;
} *authc = NULL;

int cacheauth(struct clientparam * param){
	struct authcache *ac, *last=NULL;

	pthread_mutex_lock(&hash_mutex);
	for(ac = authc; ac; ){
		if(ac->expires <= conf.time){
			if(ac->username)myfree(ac->username);
			if(ac->password)myfree(ac->password);
			if(!last){
				authc = ac->next;
				myfree(ac);
				ac = authc;
			}
			else {
				last->next = ac->next;
				myfree(ac);
				ac = last->next;
			}
			continue;
			
		}
		if(
		 (!(conf.authcachetype&2) || (param->username && ac->username && !strcmp(ac->username, (char *)param->username))) &&
		 (!(conf.authcachetype&4) || (ac->password && param->password && !strcmp(ac->password, (char *)param->password))) &&
		 (!(conf.authcachetype&16) || (ac->acl == param->srv->acl))
		) {

			if(!(conf.authcachetype&1)
				|| ((*SAFAMILY(&ac->sa) ==  *SAFAMILY(&param->sincr) 
				   && !memcmp(SAADDR(&ac->sa), SAADDR(&param->sincr), SAADDRLEN(&ac->sa))))){

				if(conf.authcachetype&32) {
					param->sinsl = ac->sinsl;
				}
				if(param->username){
					myfree(param->username);
				}
				param->username = (unsigned char *)mystrdup(ac->username);
				pthread_mutex_unlock(&hash_mutex);
				return 0;
			}
			else if ((conf.authcachetype&1) && (conf.authcachetype&8)) {
				pthread_mutex_unlock(&hash_mutex);
				return 10;
			}
		}
		last = ac;
		ac = ac->next;
	}

	pthread_mutex_unlock(&hash_mutex);
	return 4;
}

int doauth(struct clientparam * param){
	int res = 0;
	struct auth *authfuncs;
	struct authcache *ac;
	char * tmp;
	int ret = 0;

	for(authfuncs=param->srv->authfuncs; authfuncs; authfuncs=authfuncs->next){
		res = authfuncs->authenticate?(*authfuncs->authenticate)(param):0;
		if(!res) {
			if(authfuncs->authorize &&
				(res = (*authfuncs->authorize)(param)))
					return res;
			if(conf.authcachetype && authfuncs->authenticate && authfuncs->authenticate != cacheauth && param->username && (!(conf.authcachetype&4) || (!param->pwtype && param->password))){
				pthread_mutex_lock(&hash_mutex);
				for(ac = authc; ac; ac = ac->next){
					if(
					   (!(conf.authcachetype&2) || !strcmp(ac->username, (char *)param->username)) &&
					   (!(conf.authcachetype&1) || (*SAFAMILY(&ac->sa) ==  *SAFAMILY(&param->sincr) && !memcmp(SAADDR(&ac->sa), SAADDR(&param->sincr), SAADDRLEN(&ac->sa))))  &&
					   (!(conf.authcachetype&4) || (ac->password && !strcmp(ac->password, (char *)param->password))) &&
					   (!(conf.authcachetype&16) || (ac->acl == param->srv->acl))
					) {
						ac->expires = conf.time + conf.authcachetime;
						if(strcmp(ac->username, (char *)param->username)){
							tmp = ac->username;
							ac->username = mystrdup((char *)param->username);
							myfree(tmp);
						}
						if((conf.authcachetype&4)){
							tmp = ac->password;
							ac->password = mystrdup((char *)param->password);
							myfree(tmp);
						}
						ac->sa = param->sincr;
						if(conf.authcachetype&32) {
							ac->sinsl = param-> sinsl;
							*SAPORT(&ac->sinsl) = 0;
						}

						break;
					}
				}
				if(!ac){
					ac = myalloc(sizeof(struct authcache));
					if(ac){
						ac->expires = conf.time + conf.authcachetime;
						ac->username = param->username?mystrdup((char *)param->username):NULL;
						ac->sa = param->sincr;
						ac->password = NULL;
						if((conf.authcachetype&4) && param->password) ac->password = mystrdup((char *)param->password);
						if(conf.authcachetype&32) {
							ac->sinsl = param->sinsl;
							*SAPORT(&ac->sinsl) = 0;
						}
					}
					ac->next = authc;
					authc = ac;
				}
				pthread_mutex_unlock(&hash_mutex);
			}
			break;
		}
		if(res > ret) ret = res;
		if(ret > 9) return ret;
	}
	if(!res){
		ret = alwaysauth(param);
		if (param->afterauthfilters){
		    FILTER_ACTION action;
    
		    action = handleafterauthflt(param);
		    if(action != PASS) return 19;
		}
	}


	return ret;
}


int ipauth(struct clientparam * param){
	int res;
	unsigned char *username;
	username = param->username;
	param->username = NULL;
	res = checkACL(param);
	param->username = username;
	return res;
}

int userauth(struct clientparam * param){
	return (param->username)? 0:4;
}

int dnsauth(struct clientparam * param){
        char buf[128];
	char addr[16];
	char dig[]="0123456789abcdef";

	unsigned u;
	int i;

	if(*SAFAMILY(&param->sincr)!=AF_INET){
		char *s = buf;
		for(i=15; i>=0; i--){
			unsigned char c=((unsigned char *)SAADDR(&param->sincr))[i];
			*s++ = dig[(c&0xf)];
			*s++ = '.';
			*s++ = dig[(c>>4)];
			*s++ = '.';
		}
		sprintf(s, "ip6.arpa");
	}
	else {
		u = ntohl(*(unsigned long *)SAADDR(&param->sincr));

		sprintf(buf, "%u.%u.%u.%u.in-addr.arpa", 
			((u&0x000000FF)),
			((u&0x0000FF00)>>8),
			((u&0x00FF0000)>>16),
			((u&0xFF000000)>>24));
	
	}
	if(!udpresolve(*SAFAMILY(&param->sincr), (unsigned char *)buf, (unsigned char *)addr, NULL, param, 1)) {
		return 3;
	}
	if(memcmp(SAADDR(&param->sincr), addr, SAADDRLEN(&param->sincr))) {
		return 3;
	}

	return param->username? 0:3;
}

int strongauth(struct clientparam * param){
	struct passwords * pwl;
	unsigned char buf[256];


	if(!param->username) return 4;
	pthread_mutex_lock(&pwl_mutex);
	for(pwl = conf.pwl; pwl; pwl=pwl->next){
		if(!strcmp((char *)pwl->user, (char *)param->username)) switch(pwl->pwtype) {
			case CL:
				if(!pwl->password || !*pwl->password){
					break;
				}
				else if (!param->pwtype && param->password && !strcmp((char *)param->password, (char *)pwl->password)){
					break;
				}
				pthread_mutex_unlock(&pwl_mutex);
				return 6;
#ifndef NOCRYPT
			case CR:
				if(param->password && !param->pwtype && !strcmp((char *)pwl->password, (char *)mycrypt(param->password, pwl->password,buf))) {
					break;
				}
				pthread_mutex_unlock(&pwl_mutex);
				return 7;
			case NT:
				if(param->password && !param->pwtype && !memcmp(pwl->password, ntpwdhash(buf,param->password, 1), 32)) {
					break;
				}
				pthread_mutex_unlock(&pwl_mutex);
				return 8;
#endif				
			default:
				pthread_mutex_unlock(&pwl_mutex);
				return 999;
		}
		else continue;
		pthread_mutex_unlock(&pwl_mutex);
		return 0;
	}
	pthread_mutex_unlock(&pwl_mutex);
	return 5;
}

int radauth(struct clientparam * param);

struct auth authfuncs[] = {
	{authfuncs+1, NULL, NULL, ""},
	{authfuncs+2, ipauth, NULL, "iponly"},
	{authfuncs+3, userauth, checkACL, "useronly"},
	{authfuncs+4, dnsauth, checkACL, "dnsname"},
	{authfuncs+5, strongauth, checkACL, "strong"},
	{authfuncs+6, cacheauth, checkACL, "cache"},
#ifndef NORADIUS
#define AUTHOFFSET 1
	{authfuncs+7, radauth, checkACL, "radius"},
#else
#define AUTHOFFSET 0
#endif
	{authfuncs+7+AUTHOFFSET, NULL, NULL, "none"},
	{NULL, NULL, NULL, ""}
};



struct hashtable dns_table = {0, 4, {0,0,0,0}, NULL, NULL, NULL};
struct hashtable dns6_table = {0, 16, {0,0,0,0}, NULL, NULL, NULL};


void nametohash(const unsigned char * name, unsigned char *hash, unsigned char *rnd){
	unsigned i, j, k;
	memcpy(hash, rnd, sizeof(unsigned)*4);
	for(i=0, j=0, k=0; name[j]; j++){
		hash[i] += (toupper(name[j]) - 32)+rnd[((toupper(name[j]))*29277+rnd[(k+j+i)%16]+k+j+i)%16];
		if(++i == sizeof(unsigned)*4) {
			i = 0;
			k++;
		}
	}
}

unsigned hashindex(struct hashtable *ht, const unsigned char* hash){
	unsigned t1, t2, t3, t4;
	t1 = *(unsigned *)hash;
	t2 = *(unsigned *)(hash + sizeof(unsigned));
	t3 = *(unsigned *)(hash + (2*sizeof(unsigned)));
	t4 = *(unsigned *)(hash + (3*sizeof(unsigned)));
	return (t1 + (t2 * 7) + (t3 * 17) + (t4 * 29) ) % (ht->hashsize >> 2);
}


void destroyhashtable(struct hashtable *ht){
	pthread_mutex_lock(&hash_mutex);
	if(ht->hashtable){
		myfree(ht->hashtable);
		ht->hashtable = NULL;
	}
	if(ht->hashvalues){
		myfree(ht->hashvalues);
		ht->hashvalues = NULL;
	}
	ht->hashsize = 0;
	pthread_mutex_unlock(&hash_mutex);
}

#define hvalue(I) ((struct hashentry *)((char *)ht->hashvalues + (I)*(sizeof(struct hashentry) + ht->recsize - 4)))
int inithashtable(struct hashtable *ht, unsigned nhashsize){
	unsigned i;
	clock_t c;


#ifdef _WIN32
	struct timeb tb;

	ftime(&tb);

#else
	struct timeval tb;
	struct timezone tz;
	gettimeofday(&tb, &tz);
#endif
	c = clock();

	if(nhashsize<4) return 1;
	pthread_mutex_lock(&hash_mutex);
	if(ht->hashtable){
		myfree(ht->hashtable);
		ht->hashtable = NULL;
	}
	if(ht->hashvalues){
		myfree(ht->hashvalues);
		ht->hashvalues = NULL;
	}
	ht->hashsize = 0;
	if(!(ht->hashtable = myalloc((nhashsize>>2) *  sizeof(struct hashentry *)))){
		pthread_mutex_unlock(&hash_mutex);
		return 2;
	}
	if(!(ht->hashvalues = myalloc(nhashsize * (sizeof(struct hashentry) + (ht->recsize-4))))){
		myfree(ht->hashtable);
		ht->hashtable = NULL;
		pthread_mutex_unlock(&hash_mutex);
		return 3;
	}
	ht->hashsize = nhashsize;
	ht->rnd[0] = myrand(&tb, sizeof(tb));
	ht->rnd[1] = myrand(ht->hashtable, sizeof(ht->hashtable));
	ht->rnd[2] = myrand(&c, sizeof(c));
	ht->rnd[3] = myrand(ht->hashvalues,sizeof(ht->hashvalues));
	memset(ht->hashtable, 0, (ht->hashsize>>2) * sizeof(struct hashentry *));
	memset(ht->hashvalues, 0, ht->hashsize * (sizeof(struct hashentry) + ht->recsize -4));

	for(i = 0; i< (ht->hashsize - 1); i++) {
		hvalue(i)->next = hvalue(i+1);
	}
	ht->hashempty = ht->hashvalues;
	pthread_mutex_unlock(&hash_mutex);
	return 0;
}

void hashadd(struct hashtable *ht, const unsigned char* name, unsigned char* value, time_t expires){
        struct hashentry * hen, *he;
        struct hashentry ** hep;

	unsigned index;
	
	pthread_mutex_lock(&hash_mutex);
	if(!ht||!value||!name||!ht->hashtable||!ht->hashempty) {
		pthread_mutex_unlock(&hash_mutex);
		return;
	}
	hen = ht->hashempty;
	ht->hashempty = ht->hashempty->next;
	nametohash(name, hen->hash, (unsigned char *)ht->rnd);
	memcpy(hen->value, value, ht->recsize);
	hen->expires = expires;
	hen->next = NULL;
	index = hashindex(ht, hen->hash);

	for(hep = ht->hashtable + index; (he = *hep)!=NULL; ){
		if(he->expires < conf.time || !memcmp(hen->hash, he->hash, sizeof(he->hash))) {
			(*hep) = he->next;
			he->expires = 0;
			he->next = ht->hashempty;
			ht->hashempty = he;
		}
		else hep=&(he->next);
	}
	hen->next = ht->hashtable[index];
	ht->hashtable[index] = hen;
	pthread_mutex_unlock(&hash_mutex);
}

unsigned long hashresolv(struct hashtable *ht, const unsigned char* name, unsigned char* value, unsigned *ttl){
	unsigned char hash[sizeof(unsigned)*4];
        struct hashentry ** hep;
	struct hashentry *he;
	unsigned index;

	pthread_mutex_lock(&hash_mutex);
	if(!ht || !ht->hashtable || !name) {
		pthread_mutex_unlock(&hash_mutex);
		return 0;
	}
	nametohash(name, hash, (unsigned char *)ht->rnd);
	index = hashindex(ht, hash);
	for(hep = ht->hashtable + index; (he = *hep)!=NULL; ){
		if(he->expires < conf.time) {
			(*hep) = he->next;
			he->expires = 0;
			he->next = ht->hashempty;
			ht->hashempty = he;
		}
		else if(!memcmp(hash, he->hash, sizeof(unsigned)*4)){
			if(ttl) *ttl = (unsigned)(he->expires - conf.time);
			memcpy(value, he->value, ht->recsize);
			pthread_mutex_unlock(&hash_mutex);
			return 1;
		}
		else hep=&(he->next);
	}
	pthread_mutex_unlock(&hash_mutex);
	return 0;
}

struct nserver nservers[MAXNSERVERS] = {{{0},0}, {{0},0}, {{0},0}, {{0},0}, {{0},0}};
struct nserver authnserver;


unsigned long udpresolve(int af, unsigned char * name, unsigned char * value, unsigned *retttl, struct clientparam* param, int makeauth){

	int i,n;
	unsigned long retval;

	if((af == AF_INET) && (retval = hashresolv(&dns_table, name, value, retttl))) {
		return retval;
	}
	if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) {
		return retval;
	}
	n = (makeauth && !SAISNULL(&authnserver.addr))? 1 : numservers;
	for(i=0; i<n; i++){
		unsigned short nq, na;
		unsigned char b[4098], *buf, *s1, *s2;
		int j, k, len, flen;
		SOCKET sock;
		unsigned ttl;
#ifndef NOIPV6
		struct sockaddr_in6 addr;
		struct sockaddr_in6 *sinsr, *sinsl;
#else
		struct sockaddr_in addr;
		struct sockaddr_in *sinsr, *sinsl;
#endif
		int usetcp = 0;
		unsigned short serial = 1;

		buf = b+2;

		sinsl = (param && !makeauth)? &param->sinsl : &addr;
		sinsr = (param && !makeauth)? &param->sinsr : &addr;
		memset(sinsl, 0, sizeof(addr));
		memset(sinsr, 0, sizeof(addr));
		

		if(makeauth && !SAISNULL(&authnserver.addr)){
			usetcp = authnserver.usetcp;
			*SAFAMILY(sinsl) = *SAFAMILY(&authnserver.addr);
		}
		else {
			usetcp = nservers[i].usetcp;
			*SAFAMILY(sinsl) = *SAFAMILY(&nservers[i].addr);
		}
		if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break;
		if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){
			so._shutdown(so.state, sock, SHUT_RDWR);
			so._closesocket(so.state, sock);
			break;
		}
		if(makeauth && !SAISNULL(&authnserver.addr)){
			*sinsr = authnserver.addr;
		}
		else {
			*sinsr = nservers[i].addr;
		}
		if(usetcp){
			if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),CONNECT_TO)) {
				so._shutdown(so.state, sock, SHUT_RDWR);
				so._closesocket(so.state, sock);
				break;
			}
#ifdef TCP_NODELAY
			{
				int opt = 1;
				setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, sizeof(opt));
			}
#endif
		}
		len = (int)strlen((char *)name);
		
		serial = myrand(name,len);
		*(unsigned short*)buf = serial; /* query id */
		buf[2] = 1; 			/* recursive */
		buf[3] = 0;
		buf[4] = 0;
		buf[5] = 1;			/* 1 request */
		buf[6] = buf[7] = 0;		/* no replies */
		buf[8] = buf[9] = 0;		/* no ns count */
		buf[10] = buf[11] = 0;		/* no additional */
		if(len > 255) {
			len = 255;
		}
		memcpy(buf + 13, name, len);
		len += 13;
		buf[len] = 0;
		for(s2 = buf + 12; (s1 = (unsigned char *)strchr((char *)s2 + 1, '.')); s2 = s1)*s2 = (unsigned char)((s1 - s2) - 1);
		*s2 = (len - (int)(s2 - buf)) - 1;
		len++;
		buf[len++] = 0;
		buf[len++] = (makeauth == 1)? 0x0c : (af==AF_INET6? 0x1c:0x01);	/* PTR:host address */
		buf[len++] = 0;
		buf[len++] = 1;			/* INET */
		if(usetcp){
			buf-=2;
			*(unsigned short*)buf = htons(len);
			len+=2;
		}

		if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, conf.timeouts[SINGLEBYTE_L]*1000) != len){
			so._shutdown(so.state, sock, SHUT_RDWR);
			so._closesocket(so.state, sock);
			continue;
		}
		if(param) param->statscli64 += len;
		len = sockrecvfrom(NULL, sock, (struct sockaddr *)sinsr, buf, 4096, conf.timeouts[DNS_TO]*1000);
		so._shutdown(so.state, sock, SHUT_RDWR);
		so._closesocket(so.state, sock);
		if(len <= 13) {
			continue;
		}
		if(param) param->statssrv64 += len;
		if(usetcp){
			unsigned short us;
			us = ntohs(*(unsigned short*)buf);
			len-=2;
			buf+=2;
			if(us > 4096 || us < len || (us > len && sockrecvfrom(NULL, sock, (struct sockaddr *)sinsr, buf+len, us-len, conf.timeouts[DNS_TO]*1000) != us-len)) {
				continue;
			}
		}
		if(*(unsigned short *)buf != serial)continue;
		if((na = buf[7] + (((unsigned short)buf[6])<<8)) < 1) {
			return 0;
		}
		nq = buf[5] + (((unsigned short)buf[4])<<8);
		if (nq != 1) {
			continue;			/* we did only 1 request */
		}
		for(k = 13; k<len && buf[k]; k++) {
		}
		k++;
		if( (k+4) >= len) {
			continue;
		}
		k += 4;
		if(na > 255) na = 255;			/* somebody is very evil */
		for (j = 0; j < na; j++) {		/* now there should be answers */
			while(buf[k] < 192 && buf[k] !=0 && (k+buf[k]+14) < len) k+= (buf[k] + 1);
			if(!buf[k]) k--;
			if((k+(af == AF_INET6?28:16)) > len) {
				break;
			}
			flen = buf[k+11] + (((unsigned short)buf[k+10])<<8);
			if((k+12+flen) > len) {
				break;
			}
			if(makeauth != 1){
				if(buf[k+2] != 0 || buf[k+3] != (af == AF_INET6?0x1c:0x1) || flen != (af == AF_INET6?16:4)) {
					k+= (12 + flen);
					continue; 		/* we need A IPv4 */
				}
				ttl = ntohl(*(unsigned long *)(buf + k + 6));
				memcpy(value, buf + k + 12, af == AF_INET6? 16:4);
				if(ttl < 0 || ttl > (3600*12)) ttl = 3600*12;
				if(!ttl) ttl = 1;
				hashadd(af == AF_INET6?&dns6_table:&dns_table, name, value, conf.time+ttl);
				if(retttl) *retttl = ttl;
				return 1;
			}
			else {
				
				if(buf[k+2] != 0 || buf[k+3] != 0x0c) {
					k+= (12 + flen);
					continue; 		/* we need A PTR */
				}
				for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
					s1 = s2 + ((unsigned)*s2) + 1;
					*s2 = '.';
					s2 = s1;
				}
				*s2 = 0;
				if(param->username)myfree(param->username);
				param->username = (unsigned char *)mystrdup ((char *)buf + k + 13);
				
				return udpresolve(af,param->username, value, NULL, NULL, 2);
			}
		}
	}
	return 0;
}

unsigned long myresolver(int af, unsigned char * name, unsigned char * value){
 return udpresolve(af, name, value, NULL, NULL, 0);
}

unsigned long fakeresolver (int af, unsigned char *name, unsigned char * value){
 memset(value, 0, af == AF_INET6? 16 : 4);
 if(af == AF_INET6){
	memset(value, 0, 16);
	value[15] = 2;
 }
 else {
	value[0] = 127;
	value[1] = 0;
	value[2] = 0;
	value[3] = 2;
 }
 return 1;
}

#ifndef NOODBC

SQLHENV  henv = NULL;
SQLHSTMT hstmt = NULL;
SQLHDBC hdbc = NULL;
char * sqlstring = NULL;


void close_sql(){
	if(hstmt) {
		SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
		hstmt = NULL;
	}
	if(hdbc){
		SQLDisconnect(hdbc);
		SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
		hdbc = NULL;
	}
	if(henv) {
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		henv = NULL;
	}
}

int attempt = 0;
time_t attempt_time = 0;

int init_sql(char * s){
	SQLRETURN  retcode;
	char * datasource;
	char * username;
	char * password;
	char * string;

	if(!s) return 0;
	if(!sqlstring || strcmp(sqlstring, s)){
		string = sqlstring;
		sqlstring=mystrdup(s);
		if(string)myfree(string);
	}

	if(hstmt || hdbc || henv) close_sql();
	attempt++;
	attempt_time = time(0);
	if(!henv){
		retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
		if (!henv || (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)){
			henv = NULL;
			return 0;
		}
		retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 

		if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
			return 0;
		}
	}
	if(!hdbc){
		retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); 
		if (!hdbc || (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)) {
			hdbc = NULL;
			SQLFreeHandle(SQL_HANDLE_ENV, henv);
			henv = NULL;
			return 0;
		}
	       	SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (void*)15, 0);
	}
	string = mystrdup(sqlstring);
	if(!string) return 0;
	datasource = strtok(string, ",");
	username = strtok(NULL, ",");
	password = strtok(NULL, ",");
	

         /* Connect to data source */
        retcode = SQLConnect(hdbc, (SQLCHAR*) datasource, (SQLSMALLINT)strlen(datasource),
                (SQLCHAR*) username, (SQLSMALLINT)((username)?strlen(username):0),
                (SQLCHAR*) password, (SQLSMALLINT)((password)?strlen(password):0));

	myfree(string);
	if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
		SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
		hdbc = NULL;
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		henv = NULL;
		return 0;
	}
        retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); 
        if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO){
		close_sql();
		return 0;
	}
	return 1;
}

void sqlerr (char *buf){
	if(conf.stdlog){
		fprintf(conf.stdlog, "%s\n", buf);
		fflush(conf.stdlog);
	}
	pthread_mutex_unlock(&log_mutex);
}

unsigned char statbuf[8192];

void logsql(struct clientparam * param, const unsigned char *s) {
	SQLRETURN ret;
	int len;


	if(param->nolog) return;
	pthread_mutex_lock(&log_mutex);
	len = dobuf(param, statbuf, s, (unsigned char *)"\'");

	if(attempt > 5){
		time_t t;

		t = time(0);
		if (t - attempt_time < 180){
			sqlerr((char *)statbuf);
			return;
		}
	}
	if(!hstmt){
		if(!init_sql(sqlstring)) {
			sqlerr((char *)statbuf);
			return;
		}
	}
	if(hstmt){
		ret = SQLExecDirect(hstmt, (SQLCHAR *)statbuf, (SQLINTEGER)len);
		if(ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO){
			close_sql();
			if(!init_sql(sqlstring)){
				sqlerr((char *)statbuf);
				return;
			}
			if(hstmt) {
				ret = SQLExecDirect(hstmt, (SQLCHAR *)statbuf, (SQLINTEGER)len);
				if(ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO){
					sqlerr((char *)statbuf);
					return;
				}
				attempt = 0;
			}
		}
		attempt = 0;
	}
	pthread_mutex_unlock(&log_mutex);
}

#endif
