/*

	Generic proxy proggy

*/
#define SCO
/*-----------------*/
#define PORTTEST 8000
#include <stdio.h>
#ifdef AIX
#include <sys/time.h>
#endif
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <netdb.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/resource.h>
#ifdef AIX
#include <sys/m_wait.h>
#endif
#ifdef SCO
#define __SCO_WAIT3__
#endif
#include <sys/wait.h>

#define NONE (fd_set *) NULL
#define NEVER (struct timeval *) NULL
#define IGNORE (struct sockaddr *) NULL

int destport = 24;
char *desthost = (char *)0;
int debug = 0;
char buffer[4096];
int s;
fd_set active;
struct sockaddr_in sc_in;
FILE *lfp;

fdsend(int fd,  char *c)
{
	write(fd,c,strlen(c),sizeof(char)); 
}

fdserve(int fd, struct sockaddr_in *assi)
{
	/* serve requests on fd */
	char buf2[512];
	struct sockaddr_in sin;
	int rfd; /* request destination */
	int n;
	fd_set rfds;
	fd_set xfds;
	struct hostent *shes;
	char *url;
	char *host;
	char *h,*rest;
	int portnum;
	struct timeval tv;	
	extern int h_errno;
	char cmd[16];
	int i;
	char *sp;

	if(debug) printf("* Spawning **************************\n");

	if(desthost) {
		host=desthost;
	} else {

	fdsend(fd,"Proxy Service\n-------------\nPlease give name of destination host:");
	
	tv.tv_sec = 60;
	tv.tv_usec = 0;
	FD_ZERO(&rfds);
	FD_SET(fd,&rfds);
	if (select(FD_SETSIZE, &rfds, NONE, NONE, &tv) < 0)  {
		if(debug) printf("Client dropped.\n");
		if(lfp) fprintf(lfp,"Timeout in client read, or error.\n");
		fdsend(fd,"\nTimeout\n");

		close(fd);
		exit(0);
	}

	n=read(fd,buffer,sizeof(buffer));
	if(n<1) {
		if(lfp) fprintf(lfp,"*** read() failed in child process.***\n");
		if(debug) {
			if(n<0) perror("read");
			printf("Nothing read.\n");
		}
		return;
	}
	FD_ZERO(&rfds);
	FD_SET(fd,&rfds);

	host=buffer;
	while(*host && (*host==' ')) host++;
	sp=host; while(*sp && (*sp!=' ') && (*sp!='\n') && (*sp!='\r')) sp++;
	*sp='\0';

	}

	/* connect to server */
	if ((rfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		if(debug) perror("socket");
		if(lfp) fprintf(lfp,"*** socket() failed in child process. ***\n");
		return 0;
	}
	if(debug>2) printf("rfd is on fd %d\n",rfd);
	
	if(debug) {
		printf("gethostbyname(%s)=",host);
	}
	shes = gethostbyname(host);
	if(debug) {
		if(!shes) {
			printf("null\n");
		}else{
			printf("%d.%d.%d.%d\n",
				(unsigned char)shes->h_addr[0],
				(unsigned char)shes->h_addr[1],
				(unsigned char)shes->h_addr[2],
				(unsigned char)shes->h_addr[3]
			);
		}
	}
	if(!shes) {
		if(debug) printf("Host %s not found\n",host);
		if(lfp){ fprintf(lfp,"Host %s was not found.\n",host);
			fflush(lfp); }
		switch(h_errno) {
			case HOST_NOT_FOUND:
				sprintf(buf2,"Sorry, this host could not be identified by the DNS server.\nMaybe the address is wrong or mistyped.\n");
				break;
			case TRY_AGAIN:
				sprintf(buf2,"Sorry, there is a temporary problem with the DNS server.  Please try again.\n");
				break;
			case NO_DATA:
				sprintf(buf2,"Sorry, this is a virtual host without an IP address.\n");
				break;
			default:
				sprintf(buf2,"Unable to resolve this host name to an IP address.\n");
		}
		fdsend(fd,buf2);
		close(fd);
		if(lfp) {fprintf(lfp,"** Host %s not found.\n",host);
			fflush(lfp); }
		return 0;
	}
	if(!desthost) fdsend(fd,"Host identified by DNS\n");
	sprintf(buf2,"You are calling from %d.%d.%d.%d, trying to get to %d.%d.%d.%d\n",
		(unsigned char)((char *)&(assi->sin_addr.s_addr))[0],
		(unsigned char)((char *)&(assi->sin_addr.s_addr))[1],
		(unsigned char)((char *)&(assi->sin_addr.s_addr))[2],
		(unsigned char)((char *)&(assi->sin_addr.s_addr))[3],
		(unsigned char)shes->h_addr[0],
		(unsigned char)shes->h_addr[1],
		(unsigned char)shes->h_addr[2],
		(unsigned char)shes->h_addr[3]);
	if( (unsigned char)((char *)&(assi->sin_addr.s_addr))[0] !=
		(unsigned char) 192 ) {
		fdsend(fd,buf2);
		fdsend(fd,"ILLEGAL ENTRY ATTEMPT: proxy not valid from that subnet.\n");
		close(fd);
		return(1);
	}
	if((unsigned char)shes->h_addr[0] == (unsigned char)192) {
		fdsend(fd,buf2);
		fdsend(fd,"ILLEGAL ENTRY ATTEMPT: may not proxy within firewall.\n");
		close(fd);
		return(1);
	}
	if(debug) printf("Trying %s on port %d...\n",host,
		 destport);
	sin.sin_family = AF_INET;
	bcopy(shes->h_addr,&sin.sin_addr,sizeof(struct in_addr)) ;
	sin.sin_port = htons((u_short) destport) ;
	if(connect(rfd, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) < 0) {
		if(debug) perror("connect");
		if(lfp) { fprintf(lfp,"Connect failed to host %s [%d]\n",host,errno);
			fflush(lfp); }
		switch(errno) {
			case ECONNREFUSED:
				sprintf(buf2,"Sorry, this host is refusing connections from this machine on the specified port.  This is possibly due to the host not running a server, or an incorrect port number being specified.\n");
				break;
			case EHOSTUNREACH:
			case ENETUNREACH:
				sprintf(buf2,"Sorry, this host is currently unreachable from this network.\n");
				break;
			case EHOSTDOWN:
			case ENETDOWN:
				sprintf(buf2,"Sorry, this host is down.  Try again later.\n");
				break;
			case ETIMEDOUT:
				sprintf(buf2,"Sorry, the connection to this host was timed out.  This is most likely due to network overload.  Try again later.\n");
				break;
			default:
				sprintf(buf2,"Sorry, this host is not responding.  This may be due to either the remote host being down, to 'netlag' (slow connections), or to an incorrect port number.\n");
		}
		fdsend(fd,buf2);
		close(fd);
		return 0;
	}

	if(debug) printf("* Connected to %s on port %d\n",host,destport);
	if(lfp) { 
		fprintf(lfp,"Connected to %s:%d\n",host,destport);	
		fflush(lfp);
	}
	if(!desthost) fdsend(fd,"Connected.\n");

	/* phew. now send anything back and forth until we run out. */
	for(;;) {
		tv.tv_sec = 60;
		tv.tv_usec = 0;

		FD_ZERO(&rfds); FD_ZERO(&xfds);
		FD_SET(rfd,&rfds);
		FD_SET(rfd,&xfds);
		FD_SET(fd,&rfds);
		FD_SET(fd,&xfds);
		if (select(FD_SETSIZE, &rfds, NONE, &xfds, NEVER) < 0)  {
			
			if(debug) { 
				perror("select"); puts("timeout");	
			}
			break;
		}
		if( FD_ISSET( rfd, &rfds ) ) {
			/* data coming in */
			if(debug) printf("+");
			if((n=read(rfd,buffer,sizeof(buffer)))<1) break;
			if(write(fd,buffer,n)<n) break;
		} else {
			/* data going out */
			if(debug) printf("-");
			if((n=read(fd,buffer,sizeof(buffer)))<1) break;
			if(write(rfd,buffer,n)<n) break;
		}
	}

	if(lfp) { fprintf(lfp,"Transfer complete.\n"); fflush(lfp); }
	close(rfd);
	close(fd);
}

main(int argc, char *argv[])
{
	int j;
	int children= 0;
	union wait uw;	
	struct timeval tv;
	extern int optind;
	extern char *optarg;
	int c,port;
	char statbuf[32];
	struct servent *sss;
	char *logfile=(char *)0;
	int last;
	char **info;
	int asss;
	struct sockaddr_in ass;

	info=argv+1;
	port=0;	
	destport=0;
	while((c=getopt(argc,argv,"dp:l:h:P:"))!=-1) 
		switch(c) {
			case 'd': debug++; break;
			case 'p': 
				if(isdigit((int)*optarg)) {
					port=atoi(optarg); 
				} else {
					sss = getservbyname(optarg,"tcp");
					if(!sss) {
						printf("Service name %s not recognised.\n",optarg);
						exit(1);
					}
					port=(u_short)htons(sss->s_port);
				}
				break;
			case 'P':
				if(isdigit((int)*optarg)) {
					destport=atoi(optarg); 
				} else {
					sss = getservbyname(optarg,"tcp");
					if(!sss) {
						printf("Service name %s not recognised.\n",optarg);
						exit(1);
					}
					destport=(u_short)htons(sss->s_port);
				}
				break;
			case 'h':
				desthost=optarg;
				break;
			case 'l': logfile = optarg; break;
			default: printf("Usage: %s [-d] [-p demonport] [-P destport] [-h host] [-l logfile]\n",argv[0]);
				exit(1);
		}


	if(logfile) {
		lfp=fopen(logfile,"a");
		if(!lfp) perror(logfile);
	}

	if(!port) {
		sss = getservbyname("telnet-proxy","tcp");
		port=(sss?(u_short)htons(sss->s_port):2424);
	}
	if(debug && sss) printf("Using proxy port [%-10.10s] %d\n",sss->s_name,port);
	argv[1]=statbuf;

/*	signal(SIGCHLD,wait); */
	signal(SIGHUP,SIG_IGN); 

	if(!destport) {
		sss = getservbyname("telnet","tcp");
		destport=(sss?(u_short)htons(sss->s_port):24);
		if(debug) 
			printf("Using telnet port [%-10.10s] %d\n",sss->s_name,destport);
	}

	/* open port to listen to */
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		if(debug) perror("socket");
		return 0;
	}
	sc_in.sin_family = AF_INET;
	sc_in.sin_addr.s_addr = INADDR_ANY;
	sc_in.sin_port = htons((u_short) port);
	if (bind(s, (struct sockaddr *) &sc_in, sizeof(sc_in)) < 0) {
		perror("bind: proxy");
		if(lfp) fprintf(lfp,"bind failed.\n");
		return 0;
	}
	if (listen(s, 25) < 0) {
		perror("listen");
		return 0;
	}

	/* now try and set the socket option to allow reuse of address */
	{
		int b;
		b=-1;
		if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&b,4)) {
			perror("setsockopt");
			if(lfp) fprintf(lfp,"setsockopt failed.\n");
			exit(1);
		}
	}

	/* set up fd mask */
	FD_ZERO(&active);
	FD_SET(s,&active);

	/* dont setsid if in debug mode */
	if(!debug)  {
		if( fork() ) exit(0);
		setsid(); /* this function may not exist on all UNIXs. */
		/* if you dont have setsid(), then do the following:
		close all fds, set process group this getpid(), use
		ioctl to set NOTTY on stdin. This latter is not *vital*,
		everything should still work anyway.*/
	}

	if(lfp) { fprintf(lfp,"*** Program starting.\n"); fflush(lfp); }
	/* SELECT LOOP */
	/* we loop until something happens! */
	children=0;
	last = s;
	for( ;; ) {
		sprintf(statbuf,"port %d, %d procs, last=%d" ,port,children,last);
		if(debug) printf("** Waiting..\n");
		while(wait3(&uw,WNOHANG,NULL)>0) {
			children--;
			if(debug) printf("** Removed zombie\n");
		}
		if(debug) printf("** Selecting..\n");
		tv.tv_sec = 30;
		tv.tv_usec = 0;
		FD_ZERO(&active);
		FD_SET(s,&active);
		if (select(FD_SETSIZE, &active, NONE, NONE, &tv) < 0) {
			if(debug)
			perror("select");
			if(lfp) fprintf(lfp,"*** SELECT FAILED ***\n");
			break;
		}
		if( FD_ISSET(s,&active) ) {
			asss=sizeof(ass);
			if ((j = accept(s, (struct sockaddr *)&ass, &asss)) < 0) {
				if(debug) perror("accept");
				if(lfp) { fprintf(lfp,"* accept() failed\n"); fflush(lfp);}
			} else {
				last=j;
				children++;
				if(!fork()) {
					close(s);
					fdserve(j,&ass);
					if(debug) printf("* Child exiting.\n");
					exit(0);
				}
				close(j);
			}
		}
	}
	if(debug) printf("** Bye.\n");
	if(lfp) fprintf(lfp,"***** EXITING PROGRAM *****\n");
	exit(0);
}

