#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BACKLOG 5
#define MAXLEN 2048
#define HOSTMAXLEN 255
#define HOSTSTR "Host:"
#define LOOPBACK "127.0.0.1"

char           *proxyhost = NULL;

void 
err_quit(void)
{

	perror("error");
	exit(-1);

}

void 
usage(const char *prog)
{

	fprintf(stderr, "Usage: %s [-f logfile] [-p proxy]\n", prog);
	exit(-1);

}

void 
sigchld(int signo)
{

	while (waitpid(-1, NULL, WNOHANG) > 0);

}

int 
max(int a, int b)
{

	return (a > b) ? a : b;

}

void 
loghttp(int logfile, const char *buf)
{

	if (lockf(logfile, F_LOCK, 0) == -1)
		err_quit();

	if (write(logfile, buf, strlen(buf)) == -1)
		err_quit();

	if (lockf(logfile, F_ULOCK, 0) == -1)
		err_quit();

}

char*
parse(const char *buf, int *hostlen)
{

	char           *ptr, *begin;

	*hostlen = 0;
	if ((ptr = strstr(buf, HOSTSTR)) == NULL)
		return NULL;

	ptr += strlen(HOSTSTR);
	while (*ptr == ' ' || *ptr == '\t')
		ptr++;

	begin = ptr;
	while (*ptr != '\n' && *ptr != '\r') {
		(*hostlen)++;
		ptr++;
	}

	return begin;

}

int 
connect2host(const char *hostname)
{

	int             remotefd;
	struct sockaddr_in raddr;
	struct hostent *host;

	if ((remotefd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		return -1;

	memset(&raddr, 0, sizeof(raddr));

	raddr.sin_family = AF_INET;

	if ((host = gethostbyname(hostname)) == NULL)
		return -1;

	memcpy(&raddr.sin_addr, host->h_addr, sizeof(struct in_addr));
	raddr.sin_port = htons(80);

	if (connect(remotefd, (struct sockaddr *) & raddr, sizeof(raddr)) == -1)
		return -1;

	return remotefd;

}

void
child(int localfd, int logfile)
{

	fd_set          rset;
	int             n, w, i, hostlen, len, remotefd = 0;
	char           *ptr, buf[MAXLEN], remotehost[HOSTMAXLEN];

	FD_ZERO(&rset);
	FD_SET(localfd, &rset);

	while (select(max(localfd, remotefd) + 1, &rset, NULL, NULL, NULL) > 0) {

		sleep(1);
		if (FD_ISSET(remotefd, &rset)) {
			if ((n = read(remotefd, buf, MAXLEN - 1)) == -1)
				err_quit();
			else if (n == 0) {
				close(remotefd);
				close(localfd);
				exit(0);
			}
			buf[n] = 0;
			i = 0;
			len = n;

			while ((w = write(localfd, buf, len - i)) > 0) {
				i += w;
				if (w == len)
					break;
			}
			if (w <= 0)
				err_quit();

			loghttp(logfile, buf);

		}
		if (FD_ISSET(localfd, &rset)) {
			if ((n = read(localfd, buf, MAXLEN - 1)) == -1)
				err_quit();
			else if (n == 0) {
				close(localfd);
				close(remotefd);
				exit(0);
			}
			buf[n] = 0;

			memset(remotehost, 0, HOSTMAXLEN);
			if (!proxyhost) {
				if ((ptr = parse(buf, &hostlen)) == NULL) {
					fprintf(stderr, "Impossibile risolvere l'host\n");
					exit(-1);
				}
				memcpy(remotehost, ptr, (hostlen < HOSTMAXLEN) ? hostlen : HOSTMAXLEN - 1);
			} else {
				strncpy(remotehost, proxyhost, HOSTMAXLEN - 1);
				hostlen = strlen(remotehost);
			}

			if ((remotefd = connect2host(remotehost)) == -1) {
				fprintf(stderr, "Impossibile contattare l'host\n");
				exit(-1);
			}
			i = 0;
			len = n;
			while ((w = write(remotefd, buf, len - i)) > 0) {
				i += w;
				if (w == len)
					break;
			}
			if (w <= 0)
				err_quit();

		}
		FD_SET(localfd, &rset);
		FD_SET(remotefd, &rset);
	}

}

int 
main(int argc, char **argv)
{

	pid_t           pid;
	int             listenfd, localfd, logfile = STDOUT_FILENO, opt;
	char           *filename = NULL;
	struct sockaddr_in laddr;
	struct sigaction act;

	if (argc < 1 || argc > 5)
		usage(argv[0]);

	act.sa_handler = sigchld;
	sigemptyset(&act.sa_mask);
	act.sa_flags |= SA_RESTART;

	if (sigaction(SIGCHLD, &act, NULL) == -1)
		err_quit();

	opterr = 0;
	while ((opt = getopt(argc, argv, "f:p:")) != -1) {
		switch (opt) {
		case 'f':
			filename = optarg;
			break;
		case 'p':
			proxyhost = optarg;
			break;
		case '?':
			fprintf(stderr, "Opzione non valida\n");
			exit(0);
		}
	}

	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		err_quit();

	memset(&laddr, 0, sizeof(laddr));

	laddr.sin_family = AF_INET;
	if (inet_aton(LOOPBACK, &laddr.sin_addr) == 0)
		err_quit();
	laddr.sin_port = htons(80);

	if (bind(listenfd, (struct sockaddr *) & laddr, sizeof(laddr)) == -1)
		err_quit();

	if (listen(listenfd, BACKLOG) == -1)
		err_quit();

	if (filename)
		if ((logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY)) == -1)
			err_quit();

	for (;;) {

		if ((localfd = accept(listenfd, NULL, NULL)) == -1)
			err_quit();

		if ((pid = fork()) == -1)
			err_quit();
		else if (pid == 0) {
			close(listenfd);
			child(localfd, logfile);
			exit(0);
		}
		close(localfd);

	}

}

