/*
 *  datapipe - a simpe TCP proxy with multithreading and IPv6 support
 *  Copyright (C) 2004  Matteo Croce  <rootkit85@yahoo.it>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <string.h>
#define BUFFER_SIZE 4096

void * start_routine(void *);

struct sockaddr dst_addr;
size_t dst_addrlen;
int dst_ai_family;

int main(int argc, char *argv[])
{
	struct sockaddr_storage caddr;
	struct addrinfo hints, *res, *ressave;
	int caddrlen = sizeof(caddr), lsock = -1, csock, nbyt;

	if(argc != 5)
	{
		fprintf(stderr, "Usage: %s localaddress localport remotehost remoteport\n"
				"\texample: %s 0.0.0.0 8080 www.debian.org 80\n", argv[0], argv[0]);
		return 1;
	}

	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	nbyt = getaddrinfo(argv[3], argv[4], &hints, &res);
	if(nbyt < 0)
	{
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(nbyt));
		return 1;
	}
	ressave = res;
	while(res)
	{
		if((lsock = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) >= 0)
			break;
		res = res->ai_next;
	}
	memcpy(&dst_addr, res->ai_addr, sizeof(struct sockaddr));
	dst_addrlen = res->ai_addrlen;
	dst_ai_family = res->ai_family;
	freeaddrinfo(ressave);
	if(lsock < 0)
	{
		perror("socket");
		return 1;
	}
	close(lsock);
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	nbyt = getaddrinfo(argv[1], argv[2], &hints, &res);
	if(nbyt < 0)
	{
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(nbyt));
		return 1;
	}
	ressave = res;
	while(res)
	{
		if((lsock = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) >= 0)
			break;
		res = res->ai_next;
	}
	freeaddrinfo(ressave);
	if(bind(lsock, res->ai_addr, res->ai_addrlen))
	{
		close(lsock);
		perror("bind");
		return 1;
	}
	if(lsock < 0)
	{
		perror("socket");
		return 1;
	}
	if(listen(lsock, 1))
	{
		perror("listen");
		return 1;
	}
	if(fork())
		return 0;
	while((csock = accept(lsock, (struct sockaddr *)&caddr, &caddrlen)) != -1)
	{
		pthread_t thread;
		pthread_attr_t attr;

		memset(&attr, 0, sizeof(attr));
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
		pthread_create(&thread, &attr, start_routine, (void*)csock);
	}
	return 1;
}

void * start_routine(void *data)
{
	int csock = (int)data, osock = -1;
	FILE *cfile = fdopen(csock, "r+");

	if((osock = socket(dst_ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		fprintf(cfile, "500 socket: %s\n", strerror(errno));
		goto quit1;
	}
	if(connect(osock, &dst_addr, dst_addrlen))
	{
		fprintf(cfile, "500 connect: %s\n", strerror(errno));
		goto quit1;
	}
	while(1)
	{
		fd_set fdsr, fdse;
		char buf[BUFFER_SIZE];
		int nbyt;
		FD_ZERO(&fdsr);
		FD_ZERO(&fdse);
		FD_SET(csock, &fdsr);
		FD_SET(csock, &fdse);
		FD_SET(osock, &fdsr);
		FD_SET(osock, &fdse);
		if(select(20, &fdsr, NULL, &fdse, NULL) == -1)
		{
			fprintf(cfile, "500 select: %s\n", strerror(errno));
			goto quit2;
		}
		if(FD_ISSET(csock, &fdsr) || FD_ISSET(csock, &fdse))
		{
			if((nbyt = read(csock, buf, BUFFER_SIZE)) <= 0 || 
			(write(osock, buf,nbyt)) <= 0)
				goto quit2;
		}
		else if(FD_ISSET(osock, &fdsr) || FD_ISSET(osock, &fdse))
		{
			if((nbyt = read(osock, buf, BUFFER_SIZE)) <= 0 ||
			(write(csock, buf, nbyt)) <= 0)
				goto quit2;
		}
	}
quit2:
	shutdown(osock, SHUT_RDWR);
	close(osock);
quit1:
	fflush(cfile);
	shutdown(csock, SHUT_RDWR);
	fclose(cfile);
	return 0;
}

