#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>

#define DEBUG

#define PORT 8181
#define LISTENQ 1024
#define BUFSIZE 65535
#define ETH_DEVICE "eth0"
#define MAX_ETH_STR 30
#define ERR_IOCTL -2
#define ERR_BIND -3

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);
}

/*  Read a line from a socket  */

ssize_t Readline(int sockd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char c, *buffer;

	buffer = vptr;

	for (n = 1; n < maxlen; n++) {

		if ((rc = read(sockd, &c, 1)) == 1) {
			*buffer++ = c;
			if (c == '\n')
				break;
		} else if (rc == 0) {
			if (n == 1)
				return 0;
			else
				break;
		} else {
			if (errno == EINTR)
				continue;
			return -1;
		}
	}

	*buffer = 0;
	return n;
}

/*  Write a line to a socket  */

ssize_t Writeline(int sockd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *buffer;

	buffer = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nwritten = write(sockd, buffer, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}
		nleft -= nwritten;
		buffer += nwritten;
		printf("LOOP");
		fflush(stdout);
	}

	return n;
}

/* Convert a single digit Hexadeciaml to Decimal */

unsigned char hex2dec(unsigned char hex)
{
	unsigned char ch;
	ch = hex - 48;
	if (ch > 9)
		ch -= 39;
	return ch;
}

/* Decode a URL to a binary buffer */

unsigned char *decode(unsigned char *buf, int len)
{
	int i, j;
	unsigned char *bin;

	bin = (char *)malloc(len / 3);

	show_buffer(buf, len);

	j = 0;
	for (i = 0; i < len; i += 3) {
		bin[j] = hex2dec(buf[i + 1]) * 16 + hex2dec(buf[i + 2]);
		j++;
	}

	return bin;
}

int open_eth_device(const char *device)
{
	struct ifreq ifr;
	int sock_fd;
	int if_index;
	struct sockaddr_ll sll;

	/* linux/if_ether.h: ETH_P_ALL: Every packet */
	sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

	strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));

	/* netdevice(7): Retrieve the interface index */
	if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) < 0) {
		fprintf(stderr, "Error! Can not retrieve interface index.\n");
		perror("ioctl SIOCGIFINDEX");
		return ERR_IOCTL;
	}
	if_index = ifr.ifr_ifindex;

	/* netdevice(7): Get the active flag word of the device. */
	if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) < 0) {
		perror("Error Getting the active flag word of the device "
		       "(SIOCGIFFLAGS ioctl)");
		return ERR_IOCTL;
	}

	/* Promiscuous mode */
	ifr.ifr_flags |= IFF_PROMISC;

	/* netdevice(7): Set the active flag word of the device. */
	if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) < 0) {
		perror("Error setting promiscuous mode (SIOCSIFFLAGS ioctl)");
		return ERR_IOCTL;
	}

	/* packet(7): netpacket/packet.h */
	memset(&sll, 0, sizeof(sll));
	sll.sll_family = AF_PACKET;
	sll.sll_ifindex = if_index;
	sll.sll_protocol = htons(ETH_P_ALL);
	if (bind(sock_fd, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
		fprintf(stderr, "Error while binding to %s ", device);
		perror("[bind()]");
		return ERR_BIND;
	}
	return sock_fd;
}

int raw_send(unsigned char *buf, int len)
{
	char eth_device[MAX_ETH_STR + 1] = ETH_DEVICE;

	int nfd, leidos;
	fd_set fds;
	int i;
	unsigned char header[] = { 0x00, 0x24, 0x01, 0x0f, 0x18, 0xd8, 0x00,
		0x26, 0x5a, 0x70, 0xb6, 0x6e, 0x08, 0x00
	};
	unsigned char *packet = (unsigned char *)malloc(len + 14);

	memcpy(packet, header, 14);
	memcpy(packet + 14, buf, len);

	if ((nfd = open_eth_device(eth_device)) < 0) {
		fprintf(stderr, "Error opening ETH device\n");
		return -1;
	}

	if (write(nfd, packet, len + 14) == -1)
		perror("write() error");
	else
		printf("%d ", len);
	fflush(stdout);
	close(nfd);
	return 0;
}

int main()
{
	int server_sockfd, client_sockfd;
	struct sockaddr_in server_address;
	int status;

	/*  Create the listening socket  */

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

	/* Load up address structs */

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

	/* Bind to the port */

	status = bind(server_sockfd, (struct sockaddr *)&server_address,
		      sizeof(server_address));
	if (status == -1) {
		perror("bind() ERROR");
		exit(EXIT_FAILURE);
	}

	/* Start listening to the port */

	status = listen(server_sockfd, LISTENQ);
	if (status == -1) {
		perror("listen() ERROR");
		exit(EXIT_FAILURE);
	}

	signal(SIGCHLD, SIG_IGN);

	while (1) {

		/* Accept an incoming connection */

		client_sockfd = accept(server_sockfd, NULL, NULL);
		if (client_sockfd == -1) {
			perror("accept() ERROR");
			exit(EXIT_FAILURE);
		}

		/* Create child process */

		if (fork() == 0) {

			int i, len;
			char buffer[BUFSIZE];
			unsigned char *packet;

			/* Read browser request */

			len = Readline(client_sockfd, buffer, BUFSIZE - 1);

			if (len > 0 && len < BUFSIZE)
				buffer[len] = 0;
			else
				buffer[0] = 0;

			/* Check HTTP method */
			if (strncmp(buffer, "GET ", 4) &&
			    strncmp(buffer, "get ", 4))
				continue;

			/* buffer shuold look like 'GET URL HTTP/1.0' */
			/* remove everything after URL */
			for (i = 4; i < BUFSIZE; i++) {
				if (buffer[i] == ' ') {
					buffer[i] = 0;
					break;
				}
			}

			len = strlen(buffer);
			/* printf("%s\n",&buffer[5]); */
			/* fflush (stdout); */
			packet = decode(&buffer[5], strlen(&buffer[5]));
			raw_send(packet, strlen(&buffer[5]) / 3);

			sprintf(buffer, "HTTP/1.1 404 Not Found\r\n"
				"Server: nginx\r\n"
				"Content-Type: text/html\r\n\r\n"
				"<html>\n"
				"<head><title>404 Not Found</title></head>\n"
				"<body bgcolor=\"white\">\n"
				"<center><h1>404 Not Found</h1></center>\n"
				"<hr><center>nginx</center>\n"
				"</body>\n" "</html>\n\r\n");

			len = send(client_sockfd, buffer, strlen(buffer), 0);
#ifdef DEBUG
			printf("sent len: %d\n", len);
#endif

			/*  Close the connected socket  */
			if (close(client_sockfd) < 0) {
				perror("close() ERROR");
				exit(EXIT_FAILURE);
			}

			printf("\nConnection closed. killing pid %d.",
			       (int)getpid());

			/* End of child process */
			exit(0);
		}

		close(client_sockfd);
	}
}