/*
 * kev proxy
 * it's not big, but then, it's not that clever either.
 *
 * compile with cc -o kp kp.c -lpthread
 * tested on Red Hat 8, should work on most Linux
 *
 * kp listen_port target_ip target_port <source_port> <v>
 *
 * kp will listen on the listen_port and relay bi-directional data
 * between this port and the target_port on the target_ip.
 * The optional source_port is to set the source port on the outbound
 * connection to the target_ip.  Useful for getting around ACLs in
 * routers and firewalls.
 * 'v' indicates verbose mode for extra info.
 *
 * Note: it does not operate as a 'real' HTTP proxy, although it can
 * proxy HTTP as well as any other TCP protocol; just don't let your
 * browser know it's talking to a proxy ;) (unless, of course, you're
 * proxying for an HTTP proxy!)
*/


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>


int listen_port, target_port, source_port, verbose;
char target_ip[1024];

void * kp(void *);

void die(int sig)
{
	pthread_exit(NULL);
}

void usage()
{
	printf("kp listen_port target_ip target_port <source_port> <v>\n");
}

int getMax(int q1, int q2)
{
	if (q1 > q2) return q1; else return q2;
}

int main(int argc, char **argv)
{
	int fd, fd1;
	const int on = 1;
	struct sockaddr_in fd_sock, fd_sock1;
	socklen_t listenlen;
	pthread_t ptConnection;

	(void) signal (SIGINT, die);

	verbose = 0;
	source_port = 0;

	if ((argc < 4) || (argc > 6))
	{
		usage();
		exit(1);
	}

	printf("kevproxy\n");

	listen_port = atoi(argv[1]);
	target_port = atoi(argv[3]);
	if (argc > 4) {
		if (strcmp(argv[4], "v") == 0)
		{
			if (argc > 5)
			{
				usage();
				exit(1);
			}
			verbose = 1;
			source_port = 0;
		} else {
			source_port = atoi(argv[4]);
			if (argc > 5)
			{
				if (strcmp(argv[5], "v") == 0)
				{
					verbose = 1;
				} else {
					usage();
					exit(1);
				}
			}
		}
	} else {
		source_port = 0;
	}

	strcpy(target_ip, argv[2]);

	printf("Listening on %d, sending to %s:%d", listen_port, target_ip, target_port);
	if (source_port != 0) {
		printf(", source port %d\n", source_port);
	} else {
		printf("\n");
	}

	// fd_sock is listener
	fd_sock.sin_family = AF_INET;
	fd_sock.sin_port = htons(listen_port);
	fd_sock.sin_addr.s_addr = INADDR_ANY;

	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd <0) {
		perror("fd: opening stream socket");
		return -1;
	}
	if (verbose) printf("socket fd made\n");

	if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) != 0)
	{
		perror("fd: setsockopt failed");
	}
	if (verbose) printf("socket fd option set\n");

	if (bind(fd, (struct sockaddr *)&fd_sock, sizeof fd_sock) <0)
	{
		return 0;
	}
	if (verbose) printf("Bound fd!\n");

	if (listen(fd, 1024) < 0)
	{
		return 0;
	}
	if (verbose) printf("fd: listening!\n");

	for (;;)
	{
		// fd_sock1 is the accepted conx
		fd_sock1.sin_family = AF_INET;
		fd_sock1.sin_port = INADDR_ANY;
		fd_sock1.sin_addr.s_addr = INADDR_ANY;
	
		listenlen = sizeof fd_sock1;
		fd1 = accept(fd, (struct sockaddr *)&fd_sock1, &listenlen);

		if (fd1 < 0)
		{
			return 0;
		}
		if (verbose) printf("fd1: accepted!\n");

		if (pthread_create (&ptConnection, NULL, kp, &fd1) != 0)
		{
			perror("could not create thread");
			return 0;
		}
		if (verbose) printf("thread created\n");

		if ( (pthread_detach(ptConnection)) != 0)
		{
			perror("could not detach thread");
		}
		if (verbose) printf("thread detached\n");
	}
}

void closesocks(int sock1, int sock2)
{
	while (close(sock1) != 0);
	if (verbose) printf("sock1 closed\n");
	while (close(sock2) != 0);
	if (verbose) printf("sock2 closed\n");
}

void * kp(void *fd_in)
{
	fd_set socks;
	int selectret;
	int maxsock;
	int accfd, fd2;
	int num;
	char buff[65100];
	struct sockaddr_in fd_sock2, fd_sock3;

	accfd = * (int *) fd_in;

	if (verbose) printf("accfd = %d\n", accfd);

        // fd_sock2 is local port of outbound conx
	fd_sock2.sin_family = AF_INET;
	fd_sock2.sin_port = htons(source_port);
	fd_sock2.sin_addr.s_addr = INADDR_ANY;

	// fd_sock3 is outbound conx
	fd_sock3.sin_addr.s_addr=inet_addr(target_ip);
	fd_sock3.sin_port = htons(target_port);
	fd_sock3.sin_family = AF_INET;

	fd2 = socket(AF_INET, SOCK_STREAM, 0);
	if (fd2 <0) {
		perror("fd2: opening stream socket");
		return NULL;
	}
	if (verbose) printf("socket fd2 made\n");

	if (source_port != 0) {
		if (bind(fd2, (struct sockaddr *)&fd_sock2, sizeof fd_sock2) < 0)
		{
			perror("fd2: bind failed");;
		} else {
			if (verbose) printf("Bound fd2!\n");
		}
	}

	if (connect(fd2, (struct sockaddr *)&fd_sock3, sizeof fd_sock3) < 0)
	{
		perror("fd2: connect");
		return  NULL;
	}
	if (verbose) printf("Connected fd2!\n");

	maxsock = getMax(accfd, fd2);

	while (1) {
		//printf(".");
		FD_SET (accfd, &socks);
		FD_SET (fd2, &socks);

		selectret = select (maxsock+1, &socks, NULL, NULL, NULL);
		if (selectret == -1)
		{
			perror("select failed");
			break;
		}

		if (FD_ISSET (accfd, &socks))
		{
			num = read(accfd, buff, 65000);
			if (num <=0)
			{
				closesocks(accfd,fd2);
				break;
			}
			if (write(fd2, buff, num) != num)
			{
				perror("fd2 write error");
			}
			if (verbose) printf("accfd -> fd2, %d bytes\n", num);
		}

		if (FD_ISSET (fd2, &socks))
		{
			num = read(fd2, buff, 65100);
			if (num <=0)
			{
				closesocks(accfd,fd2);
				break;
			}
			if (write(accfd, buff, num) != num)
			{
				perror("accfd write error");
			}
			if (verbose) printf("fd2 -> accfd, %d bytes\n", num);
		}

	}
	if (verbose) printf("thread exiting\n");
	pthread_exit(NULL);
	return NULL;
}

