#include <sys/types.h>	/* basic system data types */
#include <sys/socket.h>	/* basic socket definitions */
#include <sys/time.h>	/* timeval{} for select() */
#include <time.h>		/* timespec{} for pselect() */
#include <netinet/in.h>	/* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h>	/* inet(3) functions */
#include <errno.h>
#include <fcntl.h>		/* for nonblocking */
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>	/* for S_xxx file mode constants */
#include <sys/uio.h>		/* for iovec{} and readv/writev */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/un.h>		/* for Unix domain sockets */
#include <stdarg.h>
#include <math.h>

/* macros */
#ifndef MAX
#define MAX(a,b) a > b ? a : b
#endif
#ifndef MIN
#define MIN(a,b) a < b ? a : b
#endif

char *proxy_ip;
int proxy_port;
char *ssl_server;
int listen_port;

void read_parameters(char **argv, int argc) {
	char *port;
	int i, acc;

	proxy_ip = argv[1];
	
	port = argv[2]; acc = i = 0;
	while(port[i] != 0) {
		acc = acc * 10 + port[i++] - '0';
	}
	proxy_port = acc;

	ssl_server = argv[3];

	port = argv[4]; acc = i = 0;
	while(port[i] != 0) {
		acc = acc * 10 + port[i++] - '0';
	}
	listen_port = acc;

	printf("parameters: %s:%d - %s - :%d\n",
		proxy_ip, proxy_port, ssl_server, listen_port);
}

int prx_connect(void) {
	struct sockaddr_in server;
	struct hostent *host;
	int ssock;
	
	memset((void *)&server,
	       0, sizeof(server));
	
	server.sin_port = htons(proxy_port);
	server.sin_family = AF_INET; /* IPv4 */
	
	ssock = socket(AF_INET, SOCK_STREAM, 0);
	if(ssock < 0)
	{
		fprintf(stderr,
			"init_server_connection: Failed to open socket.\n");
		goto abort;
	}

	// Get host
	if((host = gethostbyname(proxy_ip)) == NULL) {
		fprintf(stderr,
			"Unknown host\n");
		goto abort;
	}

	memcpy(&server.sin_addr, *host->h_addr_list, sizeof(server.sin_addr));
/*	if ( inet_pton(AF_INET, proxy_ip, 
		       &server.sin_addr) <= 0)
	{
		fprintf(stderr, "Failed to convert adr. preTOnum.\n");
		goto abort;
	}
*/
	
	if (connect(ssock,
		    (struct sockaddr *)&server,
		    sizeof(server)) < 0)
	{
		fprintf(stderr, "Failed to connect.\n");
		goto abort;

	}

	printf("Connected to %s:%d\n", proxy_ip, proxy_port);
	
	return ssock;
 abort:

	return -1;
}

int tunafish(int cport) {
	int socket = prx_connect();
	char c[5];
	char message[1024];

	if(socket == -1) return -1;
	snprintf(message, 1024, "CONNECT %s HTTP/1.1\r\n\r\n", ssl_server);

	memset(c, 5, 0);
	
	write(socket, message, strlen(message));
	while(!(c[0] == '\r' &&
		c[1] == '\n' &&
		c[2] == '\r' &&
		c[3] == '\n')) {
		c[0] = c[1];
		c[1] = c[2];
		c[2] = c[3];
		if(read(socket, &c[3], 1) != 1) exit(0xdead);
		printf("%c", c[3]);
	}
	fcntl(socket, F_SETFL, FNDELAY);
	fcntl(cport, F_SETFL, FNDELAY);

	{
		fd_set fdst;
		int max_fd, nready;
		char bfra[1024], bfrb[1024];

		while(1) {
			FD_ZERO(&fdst);
			
			FD_SET(socket, &fdst);
			FD_SET(cport, &fdst);
			max_fd = MAX(socket,
				     cport);
			
			if((nready = select(
				    max_fd + 1,
				    &fdst,
				    NULL, NULL, NULL)) < 1) {
				perror("Select()");
				exit(errno);
			}

			if(FD_ISSET(socket,
				    &fdst)) {
				int start = 0, err;
				int red =
					read(socket, bfra, 1024);
				
			resenda:
				err = write(cport, &bfra[start], red);
				if(err != red && err > 0) {
					fd_set twf;

					start += err;
					red -= err;
					
					FD_ZERO(&twf);
					FD_SET(cport, &twf);
					(void)select(cport + 1,
						     NULL, &twf, NULL,
						     NULL);
					
					goto resenda;
				} else if(err <= 0)
					exit(0xdead);
			}
			if(FD_ISSET(cport,
				    &fdst)) {
				int start = 0, err;
				int red =
					read(cport, bfrb, 1024);
				
			resendb:
				err = write(socket, &bfrb[start], red);
				if(err != red && err > 0) {
					fd_set twf;

					start += err;
					red -= err;
					
					FD_ZERO(&twf);
					FD_SET(socket, &twf);
					(void)select(socket + 1,
						     NULL, &twf, NULL,
						     NULL);
					
					goto resenda;
				} else if(err <= 0)
					exit(0xdead);
			}
		}
	}
}

void listener(void)
{
	int listenp, newport;
	int active; /* active marker */
	socklen_t claddrlen;
	struct sockaddr_in claddr, seraddr;
	int i;

	if((listenp = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		/* Failed to get a socket */
		printf("Failed to open listening socket.\n");
		exit(-1);
	}

	memset(&seraddr, 0, sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	seraddr.sin_port = htons(listen_port);

	if(bind(listenp, (struct sockaddr *)&seraddr, sizeof(seraddr)) != 0){
		/* Failed to bind port */
		printf("Failed to bind defined well-known port to proxy\n");
		exit(-1);
	}

	if(listen(listenp, 10) != 0){
		/* Listen failed */
		printf("Listen failed, aborting.1\n");
		exit(-1);
	}

	active = -1;
	while(active)
	{
		claddrlen = sizeof(claddr);
		if((newport = accept(listenp,
				     (struct sockaddr *)&claddr,
				     &claddrlen)) < 0){
			if(errno == EINTR)
				continue;
			else{
				/* XXX Should I abort or continue??? */
				fprintf(stderr, "Accept failed.\n");
				exit(-1); 
			}
		}
		if(active)
		{
			pid_t mypid;
			if((mypid = fork()) == 0) {
				/* Child */
				tunafish(newport);
				
				close(newport);
				exit(0);
			} else if(mypid == -1) {
				/* Error */
				printf( "fork() call failed,"
					 " aborting!");
				active = 0;
				close(newport);
			} else{ /* Parent */
				close(newport);
			}
		}
		
	}
	close(listenp);
}

int main(int argc, char **argv) {
	if(argc != 5) {
		printf("Wrong number of parameters!\n"
		       "usage:\n"
		       "   http_tuna <proxy host name> <proxy port> <remote host>:<remote ip> <listen to port>\n"
		       "Example, connect to the SSL-port (port 443) of myserver.com:\n"
		       "   http_tuna myproxy.com 8888 myserver.com:443 1234\n\n"
			);
		exit(0);
	}
	read_parameters(argv, argc);
	listener();
}

