/*
 * Copyright (c) 2001 by Jos Visser (a.k.a. the Muppet) <josv@osp.nl>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND JOS VISSER DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL JOS VISSER
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>

/*
 * The version number
 */
#define VERSION		"1.0.2"

/*
 * Command line parameters
 */
char *proxy_host;
int proxy_port;
char *proxy_user;
char *proxy_pass;
char *ssh_host;
int ssh_port;

/* 
 * Some other variables
 */
int sd;				/* The tunnel's socket descriptor */
int verbose;			/* verbosity flag */

#define SIZE 80
char basicauth[SIZE];		/* Buffer to hold the proxy's basic authentication screen */

#define SIZE2 65536
char buf[SIZE2];		/* Data transfer buffer */

/*
 * Kill the program (signal handler)
 */
void signal_handler(int signal) {
	close(0);
	close(1);

	if (sd!=0) close(sd);

	fprintf(stderr,"Tunnel closed on signal %d\n",signal);
	exit(1);
}

/*
 * Create and connect the socket that connects to the proxy. After
 * this routine the sd socket is connected to the proxy.
 */
void tunnel_connect() {
	struct sockaddr_in 	sa;
	struct hostent		*he;
	int			n;

	/*
	 * Create the socket
	 */
	if ((sd=socket(AF_INET,SOCK_STREAM,0))<0) {
		perror("Can not create socket");
		exit(1);
	}

	/* 
	 * Lookup the IP address of the proxy
	 */
	if (!(he=gethostbyname(proxy_host))) {
		perror("Proxy host not found");
		exit(1);
	}
 
 	if (verbose)
 		fprintf(stderr,"%s is %d.%d.%d.%d\n",proxy_host,
			he->h_addr[0], he->h_addr[1], he->h_addr[2], he->h_addr[3]);

	/*
	 * Set up the structure to connect to the proxy port of the proxy host
	 */
	memset (&sa, '\0', sizeof(sa));
  	sa.sin_family=AF_INET;
  	memcpy(&sa.sin_addr.s_addr,he->h_addr,4);
  	sa.sin_port= htons(proxy_port);
  
  	/*
	 * Connect the socket
	 */
  	if (connect(sd,(struct sockaddr*) &sa,sizeof(sa))<0) {
		perror("connect() failed");
		exit(1);
	}

	fprintf(stderr,"Connected to %s:%d\n",proxy_host,proxy_port);
}

/*
 * Create the HTTP basic authentication cookie for use by the proxy. Result
 * is stored in basicauth.
 */
void make_basicauth() {
	int len=strlen(proxy_user)+strlen(proxy_pass)+2;
	char *p=(char *)malloc(len);

	/*
	 * Set up the cookie in clear text
	 */
	sprintf(p,"%s:%s",proxy_user,proxy_pass);

	/*
	 * Base64 encode the clear text cookie to create the HTTP base64
	 * authentication cookie
	 */
	lutil_b64_ntop(p,strlen(p),basicauth,SIZE);

	if (verbose)
		fprintf(stderr,"Proxy basic authentication is %s\n",basicauth);

	free(p);
}

/* 
 * Read one line of data from the tunnel. Line is terminated by a
 * newline character. Result is stored in buf.
 */
void readline() {
	char *p=buf;
	char c=0;
	int i=0;

	/*
	 * Read one character at a time into buf, until a newline is
	 * encountered.
	 */
	while (c!=10 && i<SIZE2-1) {
		if (recv(sd,&c,1,0)<0) {
			perror("Socket read error");
			exit(1);
		}

		*p=c;
		p++;
		i++;
	}

	*p=0;

	if (verbose)
		fprintf(stderr,buf);
}

/*
 * Analyze the proxy's HTTP response. This must be a HTTP/1.? 200 OK type
 * header
 */
void analyze_HTTP() {
	char *p=strtok(buf," ");

	if (strcmp(p,"HTTP/1.0")!=0 && strcmp(p,"HTTP/1.1")!=0) {
		fprintf(stderr,"Unsupported HTTP version number %s\n",p);
		exit(1);
	}

	p=strtok(0," ");

	if (strcmp(p,"200")!=0) {
		fprintf(stderr,"HTTP return code: '%s'\n",p);
		p+=strlen(p)+1;
		fprintf(stderr, "%s\n",p);
		exit(1);
	}
}

/*
 * Execute the basic proxy protocol of CONNECT and response, until the
 * last line of the response has been read. The tunnel is then open.
 */
void proxy_protocol() {
	/*
	 * Create the proxy CONNECT command into buf
	 */
	sprintf(buf,"CONNECT %s:%d HTTP/1.0\r\nProxy-authorization: Basic %s \r\nProxy-Connection: Keep-Alive\r\n\r\n",
		ssh_host,ssh_port,basicauth);
	
	if (verbose)
		fprintf(stderr,buf);
	
	/*
	 * Send the CONNECT instruction to the proxy
	 */
	if (send(sd,buf,strlen(buf),0)<0) {
		perror("Socket write error");
		exit(1);
	}

	/*
	 * Read the first line of the response and analyze it
	 */
	readline();
	analyze_HTTP();

	/*
	 * Then, repeat reading lines of the responses until a blank line
	 * (which signifies the end of the response) is encountered.
	 */
	do {
		readline();
	} while (strcmp(buf,"\r\n")!=0);
}

/*
 * Copy a block of data from one socket descriptor to another. A true
 * return code signifies EOF on the from socket descriptor.
 */
int copy(int from, int to) {
	int n;

	/* 
	 * Read a buffer from the source socket
	 */
	if ((n=read(from,buf,SIZE2))<0) {
		perror("Socket read error");
		exit(1);
	}

	/*
	 * If we have read 0 bytes, there is an EOF on src
	 */
	if (n==0) return 1;

	/*
	 * Write the buffer to the destination socket
	 */
	if (write(to,buf,n)!=n) {
		perror("Socket write error");
		exit(1);
	}

	/*
	 * We're not yet at EOF
	 */
	return 0;
}

/*
 * Move into a loop of copying data to and from the tunnel.
 * stdin (fd 0) and stdout (fd 1) are the file descriptors
 * for the connected application, sd is the file descriptor
 * of the tunnel.
 */
void cpio() {
	fd_set	readfds;
	fd_set	writefds;
	fd_set	exceptfds;

	/*
	 * We're never interested in sockets being available for write.
	 */
	FD_ZERO(&writefds);

	fprintf(stderr,"Starting tunnel\n");

	/*
	 * Only diamonds are forever :-)
	 */
	while (1==1) {
		/*
		 * Clear the interesting socket sets
		 */
		FD_ZERO(&readfds);
		FD_ZERO(&exceptfds);

		/*
		 * We want to know whether stdin or sd is ready for reading
		 */
		FD_SET(0,&readfds);
		FD_SET(sd,&readfds);

		/*
		 * And we want to know about exceptional conditions on either
		 * stdin, stdout or the tunnel
		 */
		FD_SET(0,&exceptfds);
		FD_SET(1,&exceptfds);
		FD_SET(sd,&exceptfds);

		/*
		 * Wait until something happens on one of the registered sockets/files
		 */
		if (select(sd+1,&readfds,&writefds,&exceptfds,0)<0) {
			perror("select error");
			exit(1);
		}

		/*
		 * Is stdin ready for read? If so, copy a block of data from stdin to the tunnel.
		 * Or else if the tunnel socket is ready for read, copy a block of data from the
		 * tunnel to stdout. Otherwise an exceptional condition is flagged and the program
		 * is terminated.
		 */
		if (FD_ISSET(0,&readfds)) {
			if (copy(0,sd)) break;
		} else if (FD_ISSET(sd,&readfds)) {
			if (copy(sd,1)) break;
		} else {
			perror("Exceptional condition");
			break;
		}
	}

	/*
	 * Close all files we deal with
	 */
	close(0);
	close(1);
	close(sd);

	fprintf(stderr,"Tunnel closed\n");
}

/*
 * We begin at the beginning
 */
int main(int argc, char *argv[]) {

	/*
	 * Check the number of arguments
	 */
	fprintf(stderr,"proxytunnel %s: Created by the Muppet, enhancements by Maniac\n", VERSION);
	if (argc!=7) {
		fprintf(stderr,"syntax: %s <proxy_host> <proxy_port> <proxy_user> <proxy_pass> <ssh_host> <ssh_port>\n",
			argv[0]);
		exit(1);
	}

	/*
	 * The world's worst command line processing code
	 */
	proxy_host=argv[1];
	proxy_port=atoi(argv[2]);
	proxy_user=argv[3];
	proxy_pass=argv[4];
	ssh_host=argv[5];
	ssh_port=atoi(argv[6]);

	/*
	 * This is what we do:
	 * - Set a signal for the hangup (HUP) signal
	 * - Create the proxy basic authenticcation cookie
	 * - Connect the sd socket to the proxy
	 * - Execute the proxy protocol to connect it to the origin server
	 * - Enter copy in-out mode to channel data hence and forth
	 */
	signal(SIGHUP,signal_handler);
	make_basicauth();
	tunnel_connect();
	proxy_protocol();
	cpio();
}
