/*
 * Compile : gcc http-client-fork.c -o http-client-fork -lipq
 *
 */

#include <netinet/in.h>
#include <unistd.h>
#include <libipq.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <linux/netfilter.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>

#define DEBUG

#define PORT 8181
#define USERAGENT "HTMLGET 1.0"
#define BUFSIZE 65535

void show_buffer(unsigned char *buf, int len)
{
	int i, j;
	char str[17];

	str[16] = 0;
	printf("\n");
	for (i = 0; i < len; i += 16) {
		fprintf(stdout, "   ");
		for (j = 0; j < 16 && i + j < len; j++) {
			fprintf(stdout, "%02x ", buf[i + j]);

			if (buf[i + j] > 32 && buf[i + j] < 127)
				str[j] = buf[i + j];
			else
				str[j] = '.';
		}
		for (; j < 16; j++) {
			fprintf(stdout, "   ", buf[i + j]);
			str[j] = ' ';
		}
		printf("   |   %s   |\n", str);
	}
	fflush(stdout);
}

static void die(struct ipq_handle *h)
{
	ipq_perror("passer");
	ipq_destroy_handle(h);
	exit(1);
}

unsigned char *encode(unsigned char *buf, int len)
{
	int i, j;
	unsigned char *str;
	unsigned char tmp[4];

	str = (char *)malloc(len * 3 + 1);

	j = 0;
	for (i = 0; i < len; i++) {
		snprintf(tmp, 4, "%%%02x", buf[i]);
		memcpy(str + j, tmp, 3);
		j += 3;
	}
	str[j] = 0;

	return str;
}

int sendall(int s, char *buf, int *len)
{
	int total = 0;		// how many bytes we've sent
	int bytesleft = *len;	// how many we have left to send
	int n;

	while (total < *len) {
		n = write(s, buf + total, bytesleft);
		/* fprintf(stderr, "sent: %d\n", n); */
		if (n == -1) {
			break;
		}
		total += n;
		bytesleft -= n;

	}

	*len = total;		// return number actually sent here

	return n == -1 ? -1 : 0;	// return -1 on failure, 0 on success
}

int create_tcp_socket()
{
	int sock;

	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
		perror("Can't create TCP socket");
		exit(EXIT_FAILURE);
	}

	return sock;
}

char *build_get_query(char *host, char *page)
{
	char *query;
	char *getpage = page;
	char *tpl = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";

	if (getpage[0] == '/') {
		getpage = getpage + 1;
		fprintf(stderr, "Removing leading \"/\", converting %s to %s\n",
			page, getpage);
	}
	// -5 is to consider the %s %s %s in tpl and the ending \0
	query =
	    (char *)malloc(strlen(host) + strlen(getpage) + strlen(USERAGENT) +
			   strlen(tpl) - 5);
	sprintf(query, tpl, getpage, host, USERAGENT);
	return query;
}

int http_send(char *ip, unsigned char *url)
{
	struct sockaddr_in *remote;
	int sock;
	int tmpres;
	char *host;
	char *page;
	char *get;
	char buf[BUFSIZE + 1];
	int len;

	sock = create_tcp_socket();

	remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
	remote->sin_family = AF_INET;
	tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
	if (tmpres < 0) {
		perror("Can't set remote->sin_addr.s_addr");
		exit(EXIT_FAILURE);
	} else if (tmpres == 0) {
		fprintf(stderr, "%s is not a valid IP address\n", ip);
		exit(EXIT_FAILURE);
	}
	remote->sin_port = htons(PORT);

	if (connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) <
	    0) {
		perror("Could not connect");
		exit(1);
	}

	page = url;
	host = ip;
	get = build_get_query(host, page);
#ifdef DEBUG
	fprintf(stderr, "--\n%s\n", url);
#endif

	//Send the query to the server
	int sent = 0;
	len = strlen(get);
	sendall(sock, get, &len);

	//now it is time to receive the page
	memset(buf, 0, sizeof(buf));
	int htmlstart = 0;
	char *htmlcontent;
	/* while((tmpres = read(sock, buf, BUFSIZE)) > 0) { */
	while ((tmpres = recv(sock, buf, BUFSIZE, 0)) > 0) {
#ifdef DEBUG
		show_buffer(buf, strlen(buf));
#endif
		if (htmlstart == 0) {
			// Under certain conditions this will not work.
			// If the \r\n\r\n part is splitted into two messages
			// it will fail to detect the beginning of HTML content
			//
			htmlcontent = strstr(buf, "\r\n\r\n");
			if (htmlcontent != NULL) {
				htmlstart = 1;
				htmlcontent += 4;
			}

		} else {
			htmlcontent = buf;
		}
#ifdef DEBUG
		if (htmlstart) {
			fprintf(stdout, "\n::::HTML::::\n");
			fprintf(stdout, htmlcontent);
			fprintf(stdout, "\n------------\n");
		}
#endif

		memset(buf, 0, tmpres);
	}

	free(get);
	free(url);
	free(remote);
	close(sock);
	fprintf(stdout, "...Connection closed...\n");
	fflush(stdout);
	return 0;
}

void usage()
{
	fprintf(stderr, "USAGE: htmlget host [page]\n\
		\thost: the website hostname. ex: coding.debuntu.org\n\
		\tpage: the page to retrieve. ex: index.html, default: /\n");
}

char *get_ip(char *host)
{
	struct hostent *hent;
	int iplen = 15;		/* XXX.XXX.XXX.XXX */
	char *ip = (char *)malloc(iplen + 1);

	memset(ip, 0, iplen + 1);

	if ((hent = gethostbyname(host)) == NULL) {
		herror("Can't get IP");
		exit(EXIT_FAILURE);
	}

	if (inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, iplen) == NULL) {
		perror("Can't resolve host");
		exit(EXIT_FAILURE);
	}

	return ip;
}

int main(int argc, char **argv)
{
	int status;
	unsigned char buf[BUFSIZE];
	struct ipq_handle *h;
	size_t pack_len;
	unsigned char *url;
	char *host;
	char *ip;

	if (argc == 1) {
		usage();
		exit(2);
	}

	host = argv[1];
	ip = get_ip(host);
	fprintf(stderr, "Sending data to %s\n", ip);

	/*
	 * Initialisation:
	 * IPV4 protocol only
	 */
	h = ipq_create_handle(0, NFPROTO_IPV4);
	if (!h)
		die(h);

	/*
	 * Setting the Queue Mode:
	 * Get packet metadata and packet payloads
	 */
	status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
	if (status < 0)
		die(h);

	signal(SIGCHLD, SIG_IGN);

	/* Receiving Packets from the Queue */
	do {
		status = ipq_read(h, buf, BUFSIZE, 0);
		if (status < 0)
			die(h);

		/* Create child process */
		if (fork() == 0) {
			switch (ipq_message_type(buf)) {
			case NLMSG_ERROR:
				fprintf(stderr, "Received error message %d\n",
					ipq_get_msgerr(buf));
				break;
			case IPQM_PACKET:{
					ipq_packet_msg_t *m =
					    ipq_get_packet(buf);

					/* get packet length */
					pack_len = m->data_len;

					/* Show packet */
					/* show_buffer(m->payload, pack_len); */
					url = encode(m->payload, pack_len);
					http_send(ip, url);

					/* Drop original packet in netfilter */
					status =
					    ipq_set_verdict(h, m->packet_id,
							    NF_DROP, 0, NULL);
					if (status < 0)
						die(h);

					break;
				}
			default:
				fprintf(stderr, "Unknown message type!\n");
				break;
			}
			exit(0);
		}
	} while (1);

	ipq_destroy_handle(h);
	return 0;
}