/* $Id$
 *
 * redir	- a utility for redirecting tcp connections
 *
 * Author:	Nigel Metheringham
 *		Nigel.Metheringham@ThePLAnet.net
 *
 * Based on, but much modified from, code originally written by
 * sammy@freenet.akron.oh.us - original header is below.
 *
 * redir is released under the GNU General Public license,
 * version 2, or at your discretion, any later version.
 *
 */

/* 
 * redir is currently maintained by Sam Creasey (sammy@oh.verio.com).
 * Please send patches, etc. there.
 *
 */

/* 980601: dl9sau
 * added some nice new features:
 *
 *   --bind_addr=my.other.ip.address
 *       forces to use my.other.ip.address for the outgoing connection
 *   
 *   you can also specify, that redir listens not on all IP addresses of
 *   your system but only for the given one, i.e.:
 *      if my host has the addresses
 *        irc.thishost.my.domain  and  mail.thishost.my.domain
 *      but you want that your users do connect for the irc redir service
 *      only on irc.thishost.my.domain, then do it this way:
 *        redir irc.fu-berlin.de irc.thishost.mydomain:6667 6667
 *   my need was that:
 *        addr1.first.domain  6667 redirects to irc.first.net  port 6667
 *   and  addr2.second.domain 6667 redirects to irc.second.net port 6667
 *   while addr1 and addr2 are the same maschine and the ports can be equal.
 *
 *  enjoy it!
 *    - thomas  <thomas@x-berg.in-berlin.de>, <dl9sau@db0tud.ampr.org>
 *
 *  btw: i tried without success implementing code for the following scenario:
 *    redir --force_addr irc.fu-berlin.de 6667 6667
 *  if "--force_addr" is given and a user connects to my system, that address
 *  of my system will be used on the outgoing connection that the user
 *  connected to.
 *  i was not successful to determine, to which of my addresses the user
 *  has connected.
 */
 
/* 990320 added support for ftp connection done by the client, now this code 
 *        should work for all ftp clients.
 *	  
 *   - harald <harald.holzer@eunet.at>
 */
 
/* 991221 added options to simulate a slow connection and to limit
 *	  bandwidth.
 *
 *   - Emmanuel Chantréau <echant@maretmanu.org>
 */

#define  VERSION "2.2.1"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>

#ifdef USE_TCP_WRAPPERS
#include <tcpd.h>
#endif

#define debug(x)	if (dodebug) fprintf(stderr, x)
#define debug1(x,y)	if (dodebug) fprintf(stderr, x, y)

/* let's set up some globals... */
int dodebug = 0;
int dosyslog = 0;
unsigned char reuse_addr = 1;
unsigned char linger_opt = 0;
char * bind_addr = NULL;
struct sockaddr_in addr_out;
int timeout = 0;

#ifndef NO_FTP
int ftp = 0;
#endif

int transproxy = 0;

#ifndef NO_SHAPER
int max_bandwidth = 0;
int random_wait = 0;
int wait_in_out=3; /* bit 0: wait for "in", bit 1: wait for "out" */
int wait_in=1;
int wait_out=1;
#endif

unsigned int bufsize=4096;
char *connect_str = NULL;	/* CONNECT string passed to proxy */
char * ident = NULL;

#ifndef NO_FTP
/* what ftp to redirect */
#define FTP_PORT 1
#define FTP_PASV 2
#endif

#ifdef USE_TCP_WRAPPERS
struct request_info request;
int     allow_severity = LOG_INFO;
int     deny_severity = LOG_WARNING;
#endif /* USE_TCP_WRAPPERS */

#ifdef NEED_STRRCHR
#define strrchr rindex
#endif /* NEED_STRRCHR */

#define REDIR_IN 1
#define REDIR_OUT 0

/* prototype anything needing it */
void do_accept(int servsock, struct sockaddr_in *target);
int bindsock(char *addr, int port, int fail);

#ifndef NO_SHAPER
/* Used in this program to write something in a socket, it has the same
   parameters and return value as "write", but with the flag "in": true if
   it's the "in" socket and false if it's the "out" socket */
static inline ssize_t redir_write (int fd, const void *buf, size_t size, int in)
{
  ssize_t result;
  int wait;

  wait=in ? wait_in : wait_out;
  if( random_wait > 0 && wait) {
    fd_set empty;
    struct timeval waitbw; /* for bandwidth */
    int rand_time;
    
    FD_ZERO(&empty);

    rand_time=rand()%(random_wait*2);
    debug1("random wait: %u\n", rand_time);
    waitbw.tv_sec=rand_time/1000;
    waitbw.tv_usec=rand_time%1000;

    select (1, &empty, NULL, NULL, &waitbw);
  }

  result=write(fd, buf, size);

  if( max_bandwidth > 0 && wait) {
    fd_set empty;
    unsigned long bits;
    struct timeval waitbw; /* for bandwidth */

    FD_ZERO(&empty);

      /* wait to be sure tu be below the allowed bandwidth */
    bits=size*8;
    debug1("bandwidth wait: %lu\n", 1000*bits/max_bandwidth);
    waitbw.tv_sec=bits/max_bandwidth;
    waitbw.tv_usec=(1000*(bits%max_bandwidth))/max_bandwidth;

    select (1, &empty, NULL, NULL, &waitbw);
  }

  return result;
}
#else
/* macro if traffic shaper is disabled */
#define redir_write(fd, buf, size, in) write(fd, buf,size)
#endif


#ifdef NEED_STRDUP
char *
strdup(char * str)
{
	char * result;

	if (result = (char *) malloc(strlen(str) + 1))
		strcpy(result, str);

	return result;
}
#endif /* NEED_STRDUP */

void
redir_usage(char *name)
{
	fprintf(stderr,"usage:\n");
	fprintf(stderr, 
		"\t%s --lport=<n> --cport=<n> [options]\n", 
		name);
	fprintf(stderr, "\t%s --inetd --cport=<n>\n", name);
	fprintf(stderr, "\n\tOptions are:-\n");
	fprintf(stderr, "\t\t--lport=<n>\t\tport to listen on\n");
	fprintf(stderr, "\t\t--laddr=IP\t\taddress of interface to listen on\n");
	fprintf(stderr, "\t\t--cport=<n>\t\tport to connect to\n");
	fprintf(stderr, "\t\t--caddr=<host>\t\tremote host to connect to\n");
	fprintf(stderr, "\t\t--inetd\t\trun from inetd\n");
	fprintf(stderr, "\t\t--debug\t\toutput debugging info\n");
	fprintf(stderr, "\t\t--timeout=<n>\tset timeout to n seconds\n");
	fprintf(stderr, "\t\t--syslog\tlog messages to syslog\n");
	fprintf(stderr, "\t\t--name=<str>\ttag syslog messages with 'str'\n");
	fprintf(stderr, "\t\t--connect=<str>\tCONNECT string passed to proxy server\n");
#ifdef USE_TCP_WRAPPERS
	fprintf(stderr, "\t\t            \tAlso used as service name for TCP wrappers\n");
#endif /* USE_TCP_WRAPPERS */
	fprintf(stderr, "\t\t--bind_addr=IP\tbind() outgoing IP to given addr\n");

#ifndef NO_FTP
	fprintf(stderr, "\t\t--ftp=<type>\t\tredirect ftp connections\n");
	fprintf(stderr, "\t\t\twhere type is either port, pasv, both\n");
#endif

	fprintf(stderr, "\t\t--transproxy\trun in linux's transparent proxy mode\n");
#ifndef NO_SHAPER
        /* options for bandwidth */
        fprintf(stderr, "\t\t--bufsize=<octets>\tsize of the buffer\n");
        fprintf(stderr, "\t\t--maxbandwidth=<bit-per-sec>\tlimit the bandwidth\n");
        fprintf(stderr, "\t\t--random_wait=<millisec>\twait before each packet\n");
        fprintf(stderr, "\t\t--wait_in_out=<flag>\t1 wait for in, 2 out, 3 in&out\n");
        /* end options for bandwidth */
#endif
	fprintf(stderr, "\n\tVersion %s.\n", VERSION);
	exit(2);
}

void
parse_args(int argc,
	   char * argv[],
	   char ** target_addr,
	   int * target_port,
           char ** local_addr,
	   int * local_port,
	   int * timeout,
	   int * dodebug,
	   int * inetd,
	   int * dosyslog,
	   char ** bind_addr,
#ifndef NO_FTP
	   int * ftp,
#endif
	   int *transproxy,
#ifndef NO_SHAPER
           unsigned int * bufsize,
           int * max_bandwidth,
           int * random_wait,
           int * wait_in_out,
#endif
	   char **connect_str)
{
	static struct option long_options[] = {
		{"lport", required_argument, 0, 'l'},
		{"laddr", required_argument, 0, 'a'},
		{"cport", required_argument, 0, 'r'},
		{"caddr", required_argument, 0, 'c'},
		{"bind_addr", required_argument, 0, 'b'},
		{"debug",    no_argument,       0, 'd'},
		{"timeout",  required_argument, 0, 't'},
		{"inetd",    no_argument,       0, 'i'},
		{"ident",    required_argument, 0, 'n'},
		{"name",     required_argument, 0, 'n'},
		{"syslog",   no_argument,       0, 's'},
		{"ftp",      required_argument,       0, 'f'},
		{"transproxy", no_argument,     0, 'p'},
		{"connect", required_argument, 0, 'x'},
                {"bufsize",  required_argument,       0, 'z'},
                {"max_bandwidth",  required_argument,       0, 'm'},
                {"random_wait",  required_argument,       0, 'w'},
                {"wait_in_out",  required_argument,       0, 'o'},
		{0,0,0,0}		/* End marker */
	};
	
	int option_index = 0;
	extern int optind;
	int opt;
	struct servent *portdesc;
	char *lport = NULL;
	char *tport = NULL;
#ifndef NO_FTP
	char *ftp_type = NULL;
#endif
 
	*local_addr = NULL;
	*target_addr = NULL;
	*target_port = 0;
	*local_port = 0;

	while ((opt = getopt_long(argc, argv, "disfpn:t:b:a:l:r:c:x:z:m:w:o:", 
				  long_options, &option_index)) != -1) {
		switch (opt) {
		case 'x':
			*connect_str = optarg;
			break;
		case 'a':
			*local_addr = optarg;
			break;

		case 'l':
			lport = optarg;
			break;

		case 'r':
			tport = optarg;
			break;

		case 'c':
			*target_addr = optarg;
			break;

		case 'b':
			*bind_addr = optarg;
			break;

		case 'd':
			(*dodebug)++;
			break;

		case 't':
			*timeout = atol(optarg);
			break;

		case 'i':
			(*inetd)++;
			break;

		case 'n':
			/* This is the ident which is added to syslog messages */
			ident = optarg;
			break;

		case 's':
			(*dosyslog)++;
			break;

#ifndef NO_FTP	    
		case 'f':
			ftp_type = optarg;
			if(!ftp_type) {
				redir_usage(argv[0]);
				exit(1);
			}
			break;
#endif	     

		case 'p':
			(*transproxy)++;
			break;

#ifndef NO_SHAPER
                case 'z':
                  *bufsize = (unsigned int)atol(optarg);
                  break;
 
                case 'm':
                  *max_bandwidth = atol(optarg);
                  break;
 
                case 'w':
                  *random_wait = atol(optarg);
                  break;
 
                case 'o':
                  *wait_in_out = atol(optarg);
                  wait_in=*wait_in_out & 1;
                  wait_out=*wait_in_out & 2;
                  break;
#endif 
		default:
			redir_usage(argv[0]);
			exit(1);
			break;
		}
	}

	if(tport == NULL)
	{
		redir_usage(argv[0]);
		exit(1);
	}

	if ((portdesc = getservbyname(tport, "tcp")) != NULL) {
		*target_port = ntohs(portdesc->s_port);
	} else {
		*target_port = atol(tport);
	}
    
	/* only check local port if not running from inetd */
	if(!(*inetd)) {
		if(lport == NULL)
		{
			redir_usage(argv[0]);
			exit(1);
		}
	 
		if ((portdesc = getservbyname(lport, "tcp")) != NULL) 
			*local_port = ntohs(portdesc->s_port);
		else
			*local_port = atol(lport);
	} /* if *inetd */

	if (!ident) {
		if ((ident = (char *) strrchr(argv[0], '/'))) {
			ident++;
		} else {
			ident = argv[0];
		}
	}

#ifndef NO_FTP
	/* some kind of ftp being forwarded? */
	if(ftp_type) {
		if(!strncasecmp(ftp_type, "port", 4)) 
			*ftp = FTP_PORT;
		else if(!strncasecmp(ftp_type, "pasv", 4))
			*ftp = FTP_PASV;
		else if(!strncasecmp(ftp_type, "both", 4))
			*ftp = FTP_PORT | FTP_PASV;
		else {
			redir_usage(argv[0]);
			exit(1);
		}
	}
#endif	      
    
	openlog(ident, LOG_PID, LOG_DAEMON);

	return;
}

#ifndef NO_FTP
/* with the --ftp option, this one changes passive mode replies from
   the ftp server to point to a new redirector which we spawn,
   now it also change the PORT commando when the client accept the
   dataconnection */
   
void ftp_clean(int send, char *buf, unsigned long *bytes, int ftpsrv)
{

	char *port_start;
	int rporthi, lporthi;
	int lportlo, rportlo;
	int lport, rport;
	int remip[4];
	int localsock;
	int socksize = sizeof(struct sockaddr_in);

	struct sockaddr_in newsession;
	struct sockaddr_in sockname;

	if (ftpsrv == 0)
	{
		/* is this a port commando ? */
		if(strncmp(buf, "PORT", 4)) {
			redir_write(send, buf, (*bytes), REDIR_OUT);
			return;
		}
		/* parse the old address out of the buffer */
		port_start = strchr(buf, ' ');

		sscanf(port_start, " %d,%d,%d,%d,%d,%d", &remip[0], &remip[1],
		       &remip[2], &remip[3], &rporthi, &rportlo);
	} else {
		/* is this a passive mode return ? */
		if(strncmp(buf, "227", 3)) {
			redir_write(send, buf, (*bytes), REDIR_OUT);
			return;
		}
		
		/* parse the old address out of the buffer */
		port_start = strchr(buf, '(');
		
		sscanf(port_start, "(%d,%d,%d,%d,%d,%d", &remip[0], &remip[1],
		       &remip[2], &remip[3], &rporthi, &rportlo);
	}
    
	/* get the outside interface so we can listen */
	if(getsockname(send, (struct sockaddr *)&sockname, &socksize) != 0) {
		perror("getsockname");
		exit(1);
	}

	rport = (rporthi << 8) | rportlo;

	/* we need to listen on a port for the incoming connection.
	   we will use the port 0, so let the system pick one. */

	localsock = bindsock(inet_ntoa(sockname.sin_addr), 0, 1);

	
	/* get the real info */
	if(getsockname(localsock, (struct sockaddr *)&sockname, &socksize) < 0) {
		perror("getsockname");
		if (dosyslog)
			syslog(LOG_ERR, "getsockname failed: %m");
		exit(1);
	}

	lport = ntohs(sockname.sin_port);

	lporthi=(lport >> 8 ) & 0xff;
	lportlo=lport & 0xff;

	/* check to see if we bound */
	if(localsock == -1) {
		fprintf(stderr, "ftp: unable to bind new listening address\n");
		exit(1);
	}
	if (ftpsrv == 0) {
		/* send the new port and ipaddress to the server */
		(*bytes) = sprintf(buf, "PORT %d,%d,%d,%d,%d,%d\n",
				   sockname.sin_addr.s_addr & 0xff, 
				   (sockname.sin_addr.s_addr >> 8) & 0xff, 
				   (sockname.sin_addr.s_addr >> 16) & 0xff,
				   sockname.sin_addr.s_addr >> 24, lporthi, lportlo);
	} else {
		/* send the new port and ipaddress to the client */
		(*bytes) = sprintf(buf, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\n",
				   sockname.sin_addr.s_addr & 0xff, 
				   (sockname.sin_addr.s_addr >> 8) & 0xff, 
				   (sockname.sin_addr.s_addr >> 16) & 0xff,
				   sockname.sin_addr.s_addr >> 24, lporthi, lportlo);
	}
	newsession.sin_port = htons(rport);
	newsession.sin_family = AF_INET;
	newsession.sin_addr.s_addr = remip[0] | (remip[1] << 8)
		| (remip[2] << 16) | (remip[3] << 24);

	debug1("ftpdata server ip: %s\n", inet_ntoa(newsession.sin_addr));
	debug1("ftpdata server port: %d\n", rport);
	debug1("listening for ftpdata on port %d\n", lport);
	debug1("listening for ftpdata on addr %s\n", inet_ntoa(sockname.sin_addr));


	/* now that we're bound and listening, we can safely send the new
	   string without fear of them getting a connection refused. */
	redir_write(send, buf, (*bytes), REDIR_OUT);     

	/* make a new process to handle the dataconnection correctly,
	   for the PASV mode this isn't a problem because after sending the 
	   PASV command, the data connection, get active. For the PORT command
	   the server must send a success, if starting here with the copyloop
	   the success command never arrive the client.*/
	
	switch(fork())
	{
     	case -1: /* Error */
     		syslog(LOG_ERR, "Couldn't fork: %m");
     		_exit(1);
     	case 0:  /* Child */
	{
		/* turn off ftp checking while the data connection is active */
		ftp = 0;
		do_accept(localsock, &newsession);
		close(localsock);
     		_exit(0);
	}
     	default: /* Parent */
     	{ close(localsock); }
	}
	return;
}
#endif


void
copyloop(int insock, 
	 int outsock,
	 int timeout_secs)
{
	fd_set iofds;
	fd_set c_iofds;
	int max_fd;			/* Maximum numbered fd used */
	struct timeval timeout;
	unsigned long bytes;
	unsigned long bytes_in = 0;
	unsigned long bytes_out = 0;
	unsigned int start_time, end_time;
	char buf[bufsize];

	/* Record start time */
	start_time = (unsigned int) time(NULL);

	/* Set up timeout */
	timeout.tv_sec = timeout_secs;
	timeout.tv_usec = 0;

	/* file descriptor bits */
	FD_ZERO(&iofds);
	FD_SET(insock, &iofds);
	FD_SET(outsock, &iofds);

    
	if (insock > outsock) {
		max_fd = insock;
	} else {
		max_fd = outsock;
	}

	debug1("Entering copyloop() - timeout is %d\n", timeout_secs);
	while(1) {
		(void) memcpy(&c_iofds, &iofds, sizeof(iofds));


		if (select(max_fd + 1,
			   &c_iofds,
			   (fd_set *)0,
			   (fd_set *)0,
			   (timeout_secs ? &timeout : NULL)) <= 0) {
			/*	    syslog(LLEV,"connection timeout: %d sec",timeout.tv_sec);*/
			break;
		}

		if(FD_ISSET(insock, &c_iofds)) {
			if((bytes = read(insock, buf, sizeof(buf))) <= 0)
				break;
#ifndef NO_FTP
			if (ftp & FTP_PORT)
				/* if we're correcting FTP, lookup for a PORT commando
				   in the buffer, if yes change this and establish 
				   a new redirector for the data */
				ftp_clean(outsock, buf, &bytes,0); 
			else
#endif
				if(redir_write(outsock, buf, bytes, REDIR_OUT) != bytes)
					break;
			bytes_out += bytes;
		}
		if(FD_ISSET(outsock, &c_iofds)) {
			if((bytes = read(outsock, buf, sizeof(buf))) <= 0)
				break;
			/* if we're correcting for PASV on ftp redirections, then
			   fix buf and bytes to have the new address, among other
			   things */
#ifndef NO_FTP
			if(ftp & FTP_PASV)
				ftp_clean(insock, buf, &bytes,1);
			else 
#endif
				if(redir_write(insock, buf, bytes, REDIR_IN) != bytes)
					break;
			bytes_in += bytes;
		}
	}
	debug("Leaving main copyloop\n");

/*
  setsockopt(insock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
  setsockopt(insock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); 
  setsockopt(outsock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
  setsockopt(outsock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); 
*/

	shutdown(insock,0);
	shutdown(outsock,0);
	close(insock);
	close(outsock);
	debug("copyloop - sockets shutdown and closed\n");
	end_time = (unsigned int) time(NULL);
	debug1("copyloop - connect time: %8d seconds\n", end_time - start_time);
	debug1("copyloop - transfer in:  %8ld bytes\n", bytes_in);
	debug1("copyloop - transfer out: %8ld bytes\n", bytes_out);
	if (dosyslog) {
		syslog(LOG_NOTICE, "disconnect %d secs, %ld in %ld out",
		       (end_time - start_time), bytes_in, bytes_out);
	}
	return;
}

void doproxyconnect(int socket)
{
	char buf[128];
	int x;
	/* write CONNECT string to proxy */
	sprintf((char *) &buf, "CONNECT %s HTTP/1.0\n\n", connect_str);
	x = write(socket, (char *) &buf, strlen(buf));
	if (x < 1) {
		perror("doproxyconnect: failed");
		exit(1);
	}
	/* now read result */
	x = read(socket, (char *) &buf, sizeof(buf));
	if (x < 1) {
		perror("doproxyconnect: failed reading fra proxy");
		exit(1);
	}
	/* no more error checking for now -- something should be added later */
	/* HTTP/1.0 200 Connection established */
}


/* lwait for a connection and move into copyloop...  again,
   ftp redir will call this, so we don't dupilcate it. */

void
do_accept(int servsock, struct sockaddr_in *target)
{

	int clisock;
	int targetsock;
	struct sockaddr_in client;
	int clientlen = sizeof(client);
	int accept_errno;
     
	debug("top of accept loop\n");
	if ((clisock = accept(servsock, (struct  sockaddr  *) &client, 
			      &clientlen)) < 0) {

		accept_errno = errno;
		perror("server: accept");

		if (dosyslog)
			syslog(LOG_ERR, "accept failed: %m");

		/* determine if this error is fatal */
		switch(accept_errno) {
			/* non-fatal errors */
		case EHOSTUNREACH:
		case ECONNRESET:
		case ETIMEDOUT:
			return;

			/* all other errors assumed fatal */
		default:
			exit(1);
		}

	}
     
	debug1("peer IP is %s\n", inet_ntoa(client.sin_addr));
	debug1("peer socket is %d\n", client.sin_port);

	/*
	 * Double fork here so we don't have to wait later
	 * This detaches us from our parent so that the parent
	 * does not need to pick up dead kids later.
	 *
	 * This needs to be done before the hosts_access stuff, because
	 * extended hosts_access options expect to be run from a child.
	 */
	switch(fork())
	{
     	case -1: /* Error */
     		perror("(server) fork");

     		if (dosyslog)
     			syslog(LOG_ERR, "(server) fork failed: %m");

     		_exit(1);
     	case 0:  /* Child */
     		break;
     	default: /* Parent */
     	{
     		int status;
	  
     		/* Wait for child (who has forked off grandchild) */
     		(void) wait(&status);

     		/* Close sockets to prevent confusion */
     		close(clisock);
	
     		return;
     	}
	}

	/* We are now the first child. Fork again and exit */
	  
	switch(fork())
	{
     	case -1: /* Error */
     		perror("(child) fork");

     		if (dosyslog)
     			syslog(LOG_ERR, "(child) fork failed: %m");

     		_exit(1);
     	case 0:  /* Child */
     		break;
     	default: /* Parent */
     		_exit(0);
	}
     
	/* We are now the grandchild */

#ifdef USE_TCP_WRAPPERS
	request_init(&request, RQ_DAEMON, ident, RQ_FILE, clisock, 0);
	sock_host(&request);
	sock_hostname(&request);
	sock_hostaddr(&request);

	if (!hosts_access(&request)) {
		refuse(&request);
		_exit(0);
	}

	if (dosyslog)
		syslog(LOG_INFO, "accepted connect from %s", eval_client(&request));
#endif /* USE_TCP_WRAPPERS */

	if ((targetsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	  
		perror("target: socket");
	  
		if (dosyslog)
			syslog(LOG_ERR, "socket failed: %m");
		
		_exit(1);
	}

	if(transproxy) {
		memcpy(&addr_out, &client, sizeof(struct sockaddr_in));
		addr_out.sin_port = 0;
	}
          
	if (bind_addr || transproxy) {
		/* this only makes sense if an outgoing IP addr has been forced;
		 * at this point, we have a valid targetsock to bind() to.. */
		/* also, if we're in transparent proxy mode, this option
		   never makes sense */

		if (bind(targetsock, (struct  sockaddr  *) &addr_out, 
			 sizeof(struct sockaddr_in)) < 0) {
			perror("bind_addr: cannot bind to forcerd outgoing addr");

/* the port parameter fetch the really port we are listening, it should
   only be different if the input value is 0 (let the system pick a 
   port) */
			if (dosyslog)
				syslog(LOG_ERR, "bind failed: %m");

			_exit(1);
		}
		debug1("outgoing IP is %s\n", inet_ntoa(addr_out.sin_addr));
	}

	if (connect(targetsock, (struct  sockaddr  *) target, 
		    sizeof(struct sockaddr_in)) < 0) {
		perror("target: connect");

		if (dosyslog)
			syslog(LOG_ERR, "bind failed: %m");

		_exit(1);
	}
     
	debug1("connected to %s\n", inet_ntoa(target->sin_addr));

	/* thanks to Anders Vannman for the fix to make proper syslogging
	   happen here...  */

	if (dosyslog) {
		char tmp1[20], tmp2[20];
		strcpy(tmp1, inet_ntoa(client.sin_addr));
		strcpy(tmp2, inet_ntoa(target->sin_addr));
	  
		syslog(LOG_NOTICE, "connecting %s/%d to %s/%d",
		       tmp1, client.sin_port,
		       tmp2, target->sin_port);
	}

	/* do proxy stuff */
	if (connect_str)
		doproxyconnect(targetsock);

#ifndef NO_SHAPER
        /* initialise random number if necessary */
        if( random_wait > 0 ) {
          srand(getpid());
        }
#endif

	copyloop(clisock, targetsock, timeout);
	exit(0);	/* Exit after copy */
}

/* bind to a new socket, we do this out here because passive-fixups
   are going to call it too, and there's no sense dupliciting the
   code. */
/* fail is true if we should just return a -1 on error, false if we
   should bail. */

int bindsock(char *addr, int port, int fail) 
{

	int servsock;
	struct sockaddr_in server;
     
	/*
	 * Get a socket to work with.  This socket will
	 * be in the Internet domain, and will be a
	 * stream socket.
	 */
     
	if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		if(fail) {
			return -1;
		}
		else {
			perror("server: socket");

			if (dosyslog)
				syslog(LOG_ERR, "socket failed: %m");

			exit(1);
		}
	}

	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	if (addr != NULL) {
		struct hostent *hp;
	  
		debug1("listening on %s\n", addr);
		if ((hp = gethostbyname(addr)) == NULL) {
			fprintf(stderr, "%s: cannot resolve hostname.\n", addr);
			exit(1);
		}
		memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
	} else {
		debug("local IP is default\n");
		server.sin_addr.s_addr = htonl(inet_addr("0.0.0.0"));
	}
     
	setsockopt(servsock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
	setsockopt(servsock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(SO_LINGER)); 
     
	/*
	 * Try to bind the address to the socket.
	 */
     
	if (bind(servsock, (struct  sockaddr  *) &server, 
		 sizeof(server)) < 0) {
		if(fail) {
			close(servsock);
			return -1;
		} else {
			perror("server: bind");

			if (dosyslog)
				syslog(LOG_ERR, "bind failed: %m");

			exit(1);
		}
	}
     
	/*
	 * Listen on the socket.
	 */
     
	if (listen(servsock, 10) < 0) {
		if(fail) {
			close(servsock);
			return -1;
		} else {
			perror("server: listen");

			if (dosyslog)
				syslog(LOG_ERR, "listen failed: %m");

			exit(1);
		}
	}
     
	return servsock;
}

int
main(int argc, char *argv[])
{

	struct sockaddr_in target;
	char *target_addr;
	int target_port;
	char *local_addr;
	int local_port;
	int inetd = 0;
	char * target_ip;
	char * ip_to_target;

	debug("parse args\n");
	parse_args(argc, argv, &target_addr, &target_port, &local_addr, 
		   &local_port, &timeout, &dodebug, &inetd, &dosyslog, &bind_addr,
#ifndef NO_FTP
		   &ftp, 
#endif
		   &transproxy, 
#ifndef NO_SHAPER
		   &bufsize, &max_bandwidth, &random_wait,
		   &wait_in_out,
#endif
                   &connect_str);

	/* Set up target */
	target.sin_family = AF_INET;
	target.sin_port = htons(target_port);
	if (target_addr != NULL) {
		struct hostent *hp;

		debug1("target is %s\n", target_addr);
		if ((hp = gethostbyname(target_addr)) == NULL) {
			fprintf(stderr, "%s: host unknown.\n", target_addr);
			exit(1);
		}
		memcpy(&target.sin_addr, hp->h_addr, hp->h_length);
	} else {
		debug("target is default\n");
		target.sin_addr.s_addr = htonl(inet_addr("0.0.0.0"));
	}

	target_ip = strdup(inet_ntoa(target.sin_addr));
	debug1("target IP address is %s\n", target_ip);
	debug1("target port is %d\n", target_port);

	/* Set up outgoing IP addr (optional);
	 * we have to wait for bind until targetsock = socket() is done
	 */
	if (bind_addr && !transproxy) {
		struct hostent *hp;

		fprintf(stderr, "bind_addr is %s\n", bind_addr);
		addr_out.sin_family = AF_INET;
		addr_out.sin_port = 0;
		if ((hp = gethostbyname(bind_addr)) == NULL) {
			fprintf(stderr, "%s: cannot resolve forced outgoing IP address.\n", bind_addr);
			exit(1);
		}
		memcpy(&addr_out.sin_addr, hp->h_addr, hp->h_length);

		ip_to_target = strdup(inet_ntoa(addr_out.sin_addr));
		debug1("IP address for target is %s\n", ip_to_target);
	}
           

	if (inetd) {
		int targetsock;
		struct sockaddr_in client;
		int client_size = sizeof(client);

#ifdef USE_TCP_WRAPPERS
		request_init(&request, RQ_DAEMON, ident, RQ_FILE, 0, 0);
		sock_host(&request);
		sock_hostname(&request);
		sock_hostaddr(&request);
	
		if (!hosts_access(&request))
			refuse(&request);
#endif /* USE_TCP_WRAPPERS */

		if (!getpeername(0, (struct sockaddr *) &client, &client_size)) {
			debug1("peer IP is %s\n", inet_ntoa(client.sin_addr));
			debug1("peer socket is %d\n", client.sin_port);
		}
		if ((targetsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			perror("target: socket");

			if (dosyslog)
				syslog(LOG_ERR, "targetsock failed: %m");

			exit(1);
		}

		if(transproxy) {
			memcpy(&addr_out, &client, sizeof(struct sockaddr_in));
			addr_out.sin_port = 0;
		}

		if (bind_addr || transproxy) {
			/* this only makes sense if an outgoing IP addr has been forced;
			 * at this point, we have a valid targetsock to bind() to.. */
			if (bind(targetsock, (struct  sockaddr  *) &addr_out, 
				 sizeof(addr_out)) < 0) {
				perror("bind_addr: cannot bind to forcerd outgoing addr");
				 
				if (dosyslog)
					syslog(LOG_ERR, "bind failed: %m");
				 
				exit(1);
			}
			debug1("outgoing IP is %s\n", inet_ntoa(addr_out.sin_addr));
		}

		if (connect(targetsock, (struct sockaddr *) &target, 
			    sizeof(target)) < 0) {
			perror("target: connect");

			if (dosyslog)
				syslog(LOG_ERR, "connect failed: %m");

			exit(1);
		}

		if (dosyslog) {
			syslog(LOG_NOTICE, "connecting %s/%d to %s/%d",
			       inet_ntoa(client.sin_addr), client.sin_port,
			       target_ip, target.sin_port);
		}

		/* Just start copying - one side of the loop is stdin - 0 */
		copyloop(0, targetsock, timeout);
	} else {
		int servsock;
	
		if(local_addr)
			servsock = bindsock(local_addr, local_port, 0);
		else
			servsock = bindsock(NULL, local_port, 0);

		/*
		 * Accept connections.  When we accept one, ns
		 * will be connected to the client.  client will
		 * contain the address of the client.
		 */

		while (1) 
			do_accept(servsock, &target);
	}

	/* this should really never be reached */

	exit(0);

}


