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

   please read License Agreement

   $Id: socks.c,v 1.9 2003/11/29 10:58:31 vlad Exp $
*/

#include "proxy.h"
#ifndef PORT
#define PORT 3128
#endif

#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }

#define child sockschild

#define SERVICE S_SOCKS

unsigned char * commands[] = {(unsigned char *)"UNKNOWN", (unsigned char *)"CONNECT", (unsigned char *)"BIND", (unsigned char *)"UDPMAP"};

struct datatable socks_table = {
	STRINGTABLE,
	sizeof(commands)/sizeof(unsigned char *),
	(void *)commands
};

#define BUFSIZE 1024
#define LARGEBUFSIZE 67000

void * child(void * data) {
#define param ((struct clientparam*)data)
 int res;
 unsigned i=0;
 SOCKET s;
 unsigned size;
 SASIZETYPE sasize;
 unsigned char * buf=NULL;
 unsigned char c;
 unsigned char command=0;
 struct in_addr reqaddr;
 unsigned short reqport=0;
 fd_set fds;
 struct timeval tv;
 int ver=0;
 int havepass = 0;
 struct sockaddr_in sin;

 reqaddr.s_addr = 0;
 param->service = S_SOCKS;

 if(!(buf = malloc(BUFSIZE))) {RETURN(21);}
 memset(buf, 0, BUFSIZE);
 if ((ver = sockgetchar(param->clisock, timeouts[SINGLEBYTE_L], 0)) != 5 && ver != 4) {
	RETURN(401);
 } /* version */
 param->service = ver;
 if(ver == 5){
	 if ((i = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);} /* nmethods */
	 for (; i; i--) {
		if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
		if (res == 2) {
			havepass = res;
		}
	 }
	 buf[0] = 5;
	 buf[1] = havepass;
	 if(send(param->clisock, buf, 2, 0)!=2){RETURN(402);}
	 if (havepass) {
		if (((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_L], 0))) != 1) {
			RETURN(412);
		}
		if ((i = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
		if (i && (unsigned)(res = sockgetline(param->clisock, buf, i, 0, timeouts[STRING_S])) != i){RETURN(411);};
		buf[i] = 0;
		if(!param->username)param->username = (unsigned char *)strdup((char *)buf);
		if ((i = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
		if (i && (unsigned)(res = sockgetline(param->clisock, buf, i, 0, timeouts[STRING_S])) != i){RETURN(411);};
		buf[i] = 0;
		if(!param->password)param->password = (unsigned char *)strdup((char *)buf);
		buf[0] = 1;
		buf[1] = 0;
		if(send(param->clisock, buf, 2, 0)!=2){RETURN(402);}
	 }
	 if ((c = sockgetchar(param->clisock, timeouts[SINGLEBYTE_L], 0)) != 5) {
		RETURN(401);
         } /* version */
 }
 if( (command = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) < 1 || command > 3){command = 0; RETURN(407);} /* command */
 if(ver == 5){
	 if (sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0) == EOF) {RETURN(411);} /* reserved */
	 c = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0); /* atype */
 }
 else {
	if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
	buf[0] = (unsigned char) res;
	if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
	buf[1] = (unsigned char) res;
	param->sins.sin_port = reqport = *(unsigned short*)buf;
	c = 1;
 }
 
 switch(c) {
	case 1:
		for (i = 0; i<4; i++){
			if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
			buf[i] = (unsigned char)res;
		}
		param->sins.sin_addr.s_addr = reqaddr.s_addr = *(unsigned long *)buf;
		break;
	case 3:
		if ((size = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);} /* nmethods */
		for (i=0; i<size; i++, param->statscli++){ /* size < 256 */
			if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
			buf[i] = (unsigned char)res;
		}
		buf[i] = 0;
		if(param->hostname)free(param->hostname);
		param->hostname = strdup(buf);
		param->sins.sin_addr.s_addr = getip(buf);
		break;
	default:
		RETURN(998);
 }
 if (ver == 5) {
	 if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
	 buf[0] = (unsigned char) res;
	 if ((res = sockgetchar(param->clisock, timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(411);}
	 buf[1] = (unsigned char) res;
	 param->sins.sin_port = reqport = *(unsigned short*)buf;
 }
 else {
	sockgetline(param->clisock, buf, BUFSIZE - 1, 0, timeouts[STRING_S]);
	buf[127] = 0;
	if(*buf && !param->username)param->username = (unsigned char *)strdup((char *)buf);
	if(param->sins.sin_addr.s_addr && ntohl(param->sins.sin_addr.s_addr)<256){
		 param->service = S_SOCKS45;
		sockgetline(param->clisock, buf, BUFSIZE - 1, 0, timeouts[STRING_S]);
		buf[127] = 0;
		if(param->hostname)free(param->hostname);
		param->hostname = strdup(buf);
	}
 }
 param->sins.sin_family = AF_INET;
 switch(command) { 
	case 1:
	 param->operation = CONNECT;
	 break;
 	case 2:
	 param->sins.sin_addr.s_addr = param->extip;
	 param->sins.sin_port = param->extport?param->extport:param->sinc.sin_port;
	 if ((param->remsock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
	 param->operation = BIND;
	 break;
	case 3:
	 param->sins.sin_port = param->extport?param->extport:reqport;
	 param->sins.sin_addr.s_addr = param->extip;
	 if ((param->remsock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {RETURN (11);}
	 param->operation = UDPASSOC;
	 break;
	default:
	 RETURN(997);
 }

 if((res = (*param->authfunc)(param))) {RETURN(res);}

 if(command > 1) {
	if(bind(param->remsock,(struct sockaddr *)&param->sins,sizeof(param->sins))) {
		param->sins.sin_port = 0;
		if(bind(param->remsock,(struct sockaddr *)&param->sins,sizeof(param->sins)))RETURN (12);
#if SOCKSTRACE > 0
fprintf(stderr, "%s:%hu binded to communicate with server\n",
			inet_ntoa(param->sins.sin_addr),
			ntohs(param->sins.sin_port)
	);
fflush(stderr);
#endif
	}
	sasize = sizeof(struct sockaddr_in);
	getsockname(param->remsock, (struct sockaddr *)&param->sins,  &sasize);
	if(command == 3) {
		param->ctrlsock = param->clisock;
		param->clisock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if(param->clisock == INVALID_SOCKET) {RETURN(11);}
		param->sinc.sin_family = AF_INET;
		param->sinc.sin_addr.s_addr = param->intip;
		param->sinc.sin_port = htons(0);
		if(bind(param->clisock,(struct sockaddr *)&param->sinc,sizeof(param->sinc))) {RETURN (12);}
		sasize = sizeof(struct sockaddr_in);
		getsockname(param->clisock, (struct sockaddr *)&param->sinc,  &sasize);
#if SOCKSTRACE > 0
fprintf(stderr, "%s:%hu binded to communicate with client\n",
			inet_ntoa(param->sinc.sin_addr),
			ntohs(param->sinc.sin_port)
	);
fflush(stderr);
#endif
	}
 }
 param->res = 0;

CLEANRET:

 if(param->clisock != INVALID_SOCKET){
	sasize = sizeof(struct sockaddr_in);
	if(command != 3) getsockname(param->remsock, (struct sockaddr *)&sin,  &sasize);
	else getsockname(param->clisock, (struct sockaddr *)&sin,  &sasize);
#if SOCKSTRACE > 0
fprintf(stderr, "Sending confirmation to client with code %d for %s with %s:%hu\n",
			param->res,
			commands[command],
			inet_ntoa(sin.sin_addr),
			ntohs(sin.sin_port)
	);
fflush(stderr);
#endif
	if(ver == 5){
		buf[0] = 5;
		buf[1] = param->res%10;
		buf[2] = 0;
		buf[3] = 1;
		memcpy(buf+4, &sin.sin_addr.s_addr, 4);
		memcpy(buf+8, &sin.sin_port, 2);
		send((command == 3)?param->ctrlsock:param->clisock, buf, 10, 0);
	}
	else{
		buf[0] = 0;
		buf[1] = 90 + (param->res%10);
		memcpy(buf+2, &sin.sin_port, 2);
		memcpy(buf+4, &sin.sin_addr.s_addr, 4);
		send(param->clisock, buf, 8, 0);
	}

	if (param->res == 0) {
		switch(command) {
			case 1:
				if(param->redirectfunc){
					if(buf) free(buf);
					return (*param->redirectfunc)(param);
				}
				param->res = sockmap(param, timeouts[CONNECTION_L]);
				break;
			case 2:
				listen (param->remsock, 1);
				FD_ZERO(&fds);
				FD_SET(param->remsock, &fds);
				FD_SET(param->clisock, &fds);
				tv.tv_sec = timeouts[(reqaddr.s_addr)?CONNECTION_S:CONNECTION_L];
				tv.tv_usec = 0;
				res = select (((param->remsock>param->clisock)?param->remsock:param->clisock)+1, &fds, NULL, NULL, &tv);
				if (res != 1 || FD_ISSET(param->clisock, &fds)) {
					res = 460;
					break;
				}
				sasize = sizeof(param->sins);
				s = accept(param->remsock, (struct sockaddr *)&param->sins, &sasize);
				shutdown(param->remsock, SHUT_RDWR);
				close(param->remsock);
				param->remsock = s;
				if(s == INVALID_SOCKET) {
					param->res = 462;
					break;
				}
				if(reqaddr.s_addr && reqaddr.s_addr != param->sins.sin_addr.s_addr) {
					param->res = 470;
					break;
				}
#if SOCKSTRACE > 0
fprintf(stderr, "Sending incoming connection to client with code %d for %s with %s:%hu\n",
			param->res,
			commands[command],
			inet_ntoa(param->sins.sin_addr),
			ntohs(param->sins.sin_port)
	);
fflush(stderr);
#endif
				if(ver == 5){
					memcpy (buf+4, &param->sins.sin_addr, 4);
					memcpy (buf+8, &param->sins.sin_port, 2);
					send(param->clisock, buf, 10, 0);
				}
				else {
					memcpy (buf+2, &param->sins.sin_port, 2);
					memcpy (buf+4, &param->sins.sin_addr, 4);
					send(param->clisock, buf, 8, 0);
				}

				param->res = sockmap(param, timeouts[CONNECTION_S]);
				break;
			case 3:
				param->sinc.sin_addr.s_addr = reqaddr.s_addr;
				param->sinc.sin_port = reqport;
				free(buf);
				if(!(buf = malloc(LARGEBUFSIZE))) {RETURN(21);}

				for(;;){
					SASIZETYPE sasize;
					struct sockaddr_in sin;
					int len;

					FD_ZERO(&fds);
					FD_SET(param->remsock, &fds);
					FD_SET(param->clisock, &fds);
					FD_SET(param->ctrlsock, &fds);
					s = param->clisock;
					if(param->remsock > s) s = param->remsock;
					if(param->ctrlsock > s) s = param->ctrlsock;
					
					tv.tv_sec = timeouts[CONNECTION_L];
					tv.tv_usec = 0;
					res = select (s+1, &fds, NULL, NULL, &tv);
					if(res <= 0) {
						param->res = 463;
						break;
					}
					if (FD_ISSET(param->ctrlsock, &fds)) {
						param->res = 0;
						break;
					}
					if (FD_ISSET(param->clisock, &fds)) {
						sasize = sizeof(param->sinc);
						if((len = recvfrom(param->clisock, buf, 65535, 0, (param->sinc.sin_addr.s_addr && param->sinc.sin_port)?(struct sockaddr *)&sin:(struct sockaddr *)&param->sinc, &sasize)) <= 10) {
							param->res = 464;
							break;
						}
						if(buf[0] || buf[1] || buf[2]) {
							param->res = 465;
							break;
						}
						switch(buf[3]) {
							case 1:
								i = 8;
								memcpy(&param->sins.sin_addr.s_addr, buf+4, 4);
								break;
							case 3:
								size = buf[4];
								for (i=4; size; i++, size--){
									buf[i] = buf[i+1];
								}
								buf[i++] = 0;
								param->sins.sin_addr.s_addr = getip(buf+4);
								break;
							default:
								RETURN(996);
						 }

						memcpy(&param->sins.sin_port, buf+i, 2);
						i+=2;

						sasize = sizeof(param->sins);
						if(len > (int)i){
							if(sendto(param->remsock, buf+i, len - i, 0, (struct sockaddr *)&param->sins, sasize) <=0){
								param->res = 466;
								break;
							}
							param->statscli+=(len - i);
#if SOCKSTRACE > 1
fprintf(stderr, "UDP packet relayed from client to %s:%hu size %d, header %d\n",
			inet_ntoa(param->sins.sin_addr),
			ntohs(param->sins.sin_port),
			(len - i),
			i
	);
fprintf(stderr, "client address is assumed to be %s:%hu\n",
			inet_ntoa(param->sinc.sin_addr),
			ntohs(param->sinc.sin_port)
	);
fflush(stderr);
#endif
						}

					}
					if (FD_ISSET(param->remsock, &fds)) {
						sasize = sizeof(param->sins);
						buf[0]=buf[1]=buf[2]=0;
						buf[3]=1;
						if((len = recvfrom(param->remsock, buf+10, 65535 - 10, 0, (struct sockaddr *)&sin, &sasize)) <= 0) {
							param->res = 467;
							break;
						}
						memcpy(buf+4, &sin.sin_addr.s_addr, 4);
						memcpy(buf+8, &sin.sin_port, 2);
						if(sendto(param->clisock, buf, len + 10, 0, (struct sockaddr *)&param->sinc, sasize) <=0){
							param->res = 468;
							break;
						}
#if SOCKSTRACE > 1
fprintf(stderr, "UDP packet relayed to client from %s:%hu size %d\n",
			inet_ntoa(sin.sin_addr),
			ntohs(sin.sin_port),
			len
	);
fflush(stderr);
#endif
						param->statssrv+=len;

					}
				}
				break;
			default:
				param->res = 417;
				break;
		}
	}
 }
 
 if(command > 3) command = 0;
 if(buf){
	 sprintf((char *)buf, "%s ", commands[command]);
	 if(param->hostname){
	  sprintf(buf + strlen(buf), "%.265s", param->hostname);
	 }
	 else myinet_ntoa(reqaddr, buf+strlen(buf));
         sprintf(buf+strlen(buf), ":%hu", ntohs(reqport));
	 (*param->logfunc)(param, buf);
	 free(buf);
 }
 freeparam(data);
 return (NULL);
}

#ifdef WITHMAIN
#include "proxymain.c"
#endif
