#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>
#include <net/if_arp.h>
#include <linux/sockios.h>
#include <sys/types.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)
{
	hex = toupper(hex);
	hex -= (hex - 48 > 9) ? 55 : 48;
	return hex;
}

/* 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);

#ifdef DEBUG
	show_buffer(buf, len);
#endif

	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 hexstr_to_int(char *hex)
{
	int i, j;

	union {
		int l;
		unsigned char ch[4];
	} num;

	j = 0;
	for (i = 0; i < 8; i += 2)
		num.ch[j++] = hex2dec(hex[i]) * 16 + hex2dec(hex[i + 1]);

	return htonl(num.l);
}

char *get_gateway_str(void)
{
	FILE *fp;
	char *rt_file = "/proc/net/route";
	char str[256];

	char dst[9];
	char *dst_bgn;

	static char gw_str[9];
	char *gw_bgn;

	fp = fopen(rt_file, "r");
	if (fp == NULL) {
		printf("Cannot open file: %s\n", rt_file);
		perror("fopen()");
		exit(EXIT_FAILURE);
	}

	while (fgets(str, 255, fp) != NULL) {
		dst_bgn = strchr(str, '\11') + 1;
		memcpy(dst, dst_bgn, 8);
		dst[8] = '\0';
		if (strcmp(dst, "00000000") == 0) {
			gw_bgn = dst_bgn + 8 + 1;
			memcpy(gw_str, gw_bgn, 8);
		}
	}

	fclose(fp);
	return gw_str;
}

static char *get_gateway_iface(void)
{
	FILE *fp;
	char *rt_file = "/proc/net/route";
	char str[256];

	char dst[9];
	char *dst_bgn;

	static char iface_str[9];

	fp = fopen(rt_file, "r");
	if (fp == NULL) {
		printf("Cannot open file: %s\n", rt_file);
		perror("fopen()");
		exit(EXIT_FAILURE);
	}

	while (fgets(str, 255, fp) != NULL) {
		dst_bgn = strchr(str, '\11') + 1;
		memcpy(dst, dst_bgn, 8);
		dst[8] = '\0';
		if (strcmp(dst, "00000000") == 0) {
			memcpy(iface_str, str, dst_bgn - str - 1);
			iface_str[dst_bgn - str - 1] = '\0';
		}
	}

	fclose(fp);
	return iface_str;
}

static unsigned char *get_mac_arpcache(char *ip_str, char *iface)
{
	int s;
	struct arpreq areq;
	struct sockaddr_in *sin;
	struct in_addr ipaddr;

	/* static unsigned char *mac_bin = (unsigned char *) areq.arp_ha.sa_data; */
	static unsigned char *mac_bin;
	mac_bin = (unsigned char *)areq.arp_ha.sa_data;

	/* Get an internet domain socket. */
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	/* Make the ARP request. */
	memset(&areq, 0, sizeof(areq));
	sin = (struct sockaddr_in *)&areq.arp_pa;
	sin->sin_family = AF_INET;

	if (inet_aton(ip_str, &ipaddr) == 0) {
		fprintf(stderr, "-- Error: invalid IP address %s.\n", ip_str);
		exit(1);
	}

	sin->sin_addr = ipaddr;
	sin = (struct sockaddr_in *)&areq.arp_ha;
	sin->sin_family = ARPHRD_ETHER;

	strncpy(areq.arp_dev, iface, 15);

	if (ioctl(s, SIOCGARP, (caddr_t) & areq) == -1) {
		perror("-- Error: unable to make ARP request, error");
		exit(1);
	}

	return mac_bin;
}

int get_dev_mac(char *dev, unsigned char mac[6])
{
	int i;
	int status;
	int sock;
	struct ifreq ifr;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == -1) {
		perror("socket() ERROR");
		return -1;
	}

	strcpy(ifr.ifr_name, dev);

	status = ioctl(sock, SIOCGIFHWADDR, (char *)&ifr);
	if (status == -1) {
		perror("ioctl(SIOCGIFHWADDR) ERROR");
		return -1;
	}

	memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
	close(sock);
	return 0;
}

unsigned char *create_eth_header(void)
{
	int i;
	char *ip;
	char *iface;
	unsigned char gw_mac[6];
	unsigned char eth_mac[6];
	static unsigned char eth_header[14];

	/* Findout gateway IP address */
	ip = (char *)inet_ntoa(hexstr_to_int(get_gateway_str()));

	/* Findout gateway interface name */
	iface = get_gateway_iface();

	/* Read gateway mac address from arp cache */
	memcpy(gw_mac, get_mac_arpcache(ip, iface), 6);

	/* Findout local eth mac addrsss */
	get_dev_mac(iface, eth_mac);

	for (i = 0; i < 6; i++) {
		eth_header[i] = gw_mac[i];
		eth_header[i + 6] = eth_mac[i];
	}
	eth_header[12] = 8;
	eth_header[13] = 0;

	return eth_header;
}

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;
/*      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);

	header = create_eth_header();

	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("%5d", 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);
			}
#ifdef DEBUG
			printf("\nConnection closed. killing pid %d.",
			       (int)getpid());
#endif

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

		close(client_sockfd);
	}
}