/*
 * $Id: tunnel.c,v 1.66 2001/07/30 15:10:19 coelho Exp $
 *
 *
 * COPYRIGHT
 *
 * (c) Fabien Coelho <fabien@coelho.net> 2000-2001
 *     sometimes on the web as http://www.coelho.net/
 *
 *
 * LICENSE
 * 
 * THIS PROGRAM IS DISTRIBUTED AS IS, WITHOUT ANY WARRANTY, UNDER
 * THE TERMS OF THE GNU GENERAL PUBLIC LICENSE (GNU-GPL).
 *
 * This means NO WARRANTY what so ever.
 *
 * Don't even expect the program to work for any particular purpose:
 * Maybe it is going to reboot your machine when run, or destroy your most 
 * precious data, or make you hairs becoming gray. Maybe all your friends 
 * are going to think you're a jerk because of the use of this program. 
 * So use at your own risks.
 *
 * You may modify the source code and distribute it as you want, provided
 * that the initial copyright is preserved and acknowledged and that your 
 * changes are clearly documented and signed in this section. Bug fixes, 
 * comments or additionnal features may be sent to the author who will
 * do whatever pleases him with them, including but not limited to, nothing.
 *
 * See http://www.gnu.org/ for more information.
 *
 *
 * DESCRIPTION
 * 
 * It does basic single process single thread TCP/IP tunnelling in C.
 * It is expected to be quite small and maybe efficient (?).
 * It is believed to be reasonnably coded with respect to MY standards;-)
 *
 * The process listens to a single ip/port.
 * Every incoming connexion is forwarded to a fixed destination.
 * SIGHUP (signal 1) may display the tunnel status.
 * No fork, no threads, the tunnel parallelism is managed thru select() calls.
 * There is a maximum number of simultaneous connexions.
 * There are many options that I needed for various purposes.
 * It does echo instead of tunnel if no destination is specified.
 *
 * 
 * DOCUMENTATION
 *
 * You hold it! It is this very source code!
 * See 'tunnel -h' for a list of available options.
 * See source code for the list of available compile-time options.
 * The comments in the source are not necessarily pertinent.
 *
 *
 * SEE ALSO
 * 
 * The 'bounce' program does the same (with forks and no options).
 * The 'Zedebee' program provides an compressed encrypted tcp/udp tunnel.
 * Many other programs or kernel/routeur settings to do a similar job.
 *
 *
 * COMPILATION
 *
 * This software is known to have run for me under 
 *   Linux/Intel, Solaris/Intel, Solaris/Sparc, SunOS/Sparc.
 *
 * Linux:   gcc -O2 -Wall                          tunnel.c -o tunnel
 * Solaris: gcc -O2 -Wall -lsocket -lnsl -DSOLARIS tunnel.c -o tunnel
 * SunOS:   gcc -O2 -Wall                -DSUNOS   tunnel.c -o tunnel
 * Anyway:  strip tunnel
 *
 */

/*********************************************************** C/UNIX INCLUDES */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>

/*************************************************************** PORTABILITY */

#if defined(SOLARIS)
#define herror perror
#endif /* SOLARIS */

#if defined(SUNOS)
/* getopt stuff */
extern char * optarg;
extern int optind, opterr;
/* quite partial compatibility... it won't work correctly. */
#define strtoul(a,b,c) atoi(a)
#define herror perror
typedef unsigned int ssize_t;
#endif /* SUNOS */

/******************************************************* COMPILATION OPTIONS */

/* whether SIGHUP can display the tunnel status. comment out to disable. */
#define SIGNAL_TO_STATUS 1

/* whether SNOOP option is compiled in. comment out to disable. */
#define MAY_SNOOP_TRAFFIC 1

/* whether to drop all output or allow some. comment out to disable.
 * if not set, both previous defines are also disabled.
 */
#define ALLOW_SOME_OUTPUT 1

/* whether to check an fd for writability alone,
 * or to put it globally in the full loop.
 * I don't know which one should be more efficient.
 * depends on select internals I guess?
 * on linux, even with a large max_connexions, it is better without.
 */
#undef CHECK_WRITE_ALONE

/* default connexion. */
#define LHOST "localhost" /* this really means 127.0.0.1, thus no network! */
#define LPORT "2023"
/* DHOST: <same as chosen LHOST> */
#define DPORT "23"        /* telnet port */

/*************************************************************** USEFUL DEFS */

#if !defined(ALLOW_SOME_OUTPUT)
/* maybe these function returns should be checked for errors. */
#define fputc(c,f) 
#define fputs(s,f) 
#define fprintf(f,a...) /* GCC only. */
#define fwrite(b,s,n,f)
#define perror(s) 
#define herror(s) 
#define gettimeofday(x,y)

/* definition coherency.
 */
#if defined(MAY_SNOOP_TRAFFIC)
#undef MAY_SNOOP_TRAFFIC
#endif /* MAY_SNOOP_TRAFFIC */

#if defined(SIGNAL_TO_STATUS)
#undef SIGNAL_TO_STATUS
#endif /* SIGNAL_TO_STATUS */
#endif /* ALLOW_SOME_OUTPUT */

/* very theoretical IP packet max size is 2^16==65536.
 * must be a multiple of sizeof(long) for the scramble loop.
 */
#define BUFFER_SIZE (1<<16)

/* maximum number of simultaneous connexions.
   could be a moved as an option with a default?
   is there a maximum? well, it depends on the operating system.
   there can be 1024 (0..1023) sockets for a process under my linux.
 */
#define DEFAULT_MAX_CONNEXIONS (512)

/* prefix when logging. */
#define LOG "[tunnel:%d] "

/* Left Circular Shift on a long by b bits. */
#define LCS(l,b) ((l<<b)|(((1<<b)-1)&(l>>(8*sizeof(long)-b))))

/* some convenient typedef that I like. */
typedef enum { false, true } boolean;
typedef char * string;

/******************************************************************* GLOBALS */

static boolean verbose = false;      /* -v/-q (verbose => log) */

#if (defined(ALLOW_SOME_OUTPUT))

static boolean silent = false;       /* -s to be very silent... */

#else

static boolean silent = true;

#endif /* ALLOW_SOME_OUTPUT */

static boolean debug  = false;       /* -d (debug => verbose) */
static boolean log    = false;       /* -l */

/* optionnal scramble with a simple xor. easy because stateless. */
static unsigned long scramble = 0x0; /* set with -p pass or -x long */

/* process id for logging purposes. */
static int pid;

/* echo instead of tunnel */
static boolean echo   = false;

#if defined(MAY_SNOOP_TRAFFIC)

/* Whether to snoop the INCOMING stream. 
 * This is intended as a way to log commands if the tunnel
 * is directed to a telnet port. This is not a sniffer, although
 * if used as described, it would collect passwords going thru.
 * Also useful for my HTTP practical exercise, to avoid the use
 * of tcpdump or snoop by the students.
 * Use OTP or SSH for something serious.
 * May insure that the snoop data are 'readable', by switching 
 * unprintable characteres to '.' with the -r option.
 */
static boolean snoop = false;        /* -L (snoop => log) */
static boolean printable = false;    /* -r */

/* size of snoop buffer. default is no buffer. */
static int snoopsize = 0;            /* -b size (=> snoop) */

#endif /* MAY_SNOOP_TRAFFIC */

/********************************************************* SOCKET CONNEXIONS */

static int serv_socket;              /* server socket which is listenned to. */

static struct sockaddr_in serv_addr; /* fixed server address */
static struct sockaddr_in dest_addr; /* fixed destination address */

/* describe a current connexion handled by the process. */
struct socket_connexion 
{
  boolean open;                   /* whether it is open/available */
  int index;                      /* index in array for log messages */
  
  /* connexion descriptor: a pair of socket and addr
   */
  struct sockaddr_in client_addr; /* to client side */
  int client;                     /* client socket */
  boolean client_r;               /* ready to read */
  boolean client_w;               /* ready to write */

  struct sockaddr_in dest_addr;   /* to destination side */
  int dest;                       /* dest socket */
  boolean dest_r;                 /* ready to read */
  boolean dest_w;                 /* ready to write */

  /* statistics for the connexion 
   */
  int requests;                   /* amount of requests client->dest. */
  int nreq;                       /* number of request paquets. */
  int responses;                  /* amount of responses dest->client. */
  int nres;                       /* number of response paquets. */
  
#if defined(MAY_SNOOP_TRAFFIC)
  
  /* snoop buffer for incoming traffic.
   */
  int sindex;                     /* index in the buffer. */
  char * snooped;                 /* buffer of snooped stuff. [snoopsize] */
  
#endif /* MAY_SNOOP_TRAFFIC */
  
};

/* array of current connexions. */
static struct socket_connexion * connexions;

/* maximum number of allowed connexions. */
static int max_connexions = DEFAULT_MAX_CONNEXIONS;

/* current number of connexions. */
static int number_of_connexions;

/* maximum index of open connexion. */
static int max_index_of_connexions;

/* various global statistics. */
static unsigned int total_number_of_connexions;
static unsigned int total_number_of_events;
static unsigned int total_number_of_bytes;

/* all connexions are closed on startup. zeros stats. */
static void initialize_connexions(void)
{
  int i;
  
  number_of_connexions = 0;
  total_number_of_connexions = 0;
  total_number_of_bytes = 0;
  total_number_of_events = 0;
  max_index_of_connexions = 0;

  connexions = (struct socket_connexion *) 
    malloc(max_connexions*sizeof(struct socket_connexion));
  
  if (!connexions) abort();

  for (i=0; i<max_connexions; i++)
  {
    connexions[i].open    = false;
    connexions[i].index   = i;

#if defined(MAY_SNOOP_TRAFFIC)

    connexions[i].sindex  = 0;
    connexions[i].snooped = snoopsize? (char *) malloc(snoopsize): NULL;

#endif /* MAY_SNOOP_TRAFFIC */

  }
}

static void set_max_index_of_connexions(int index)
{
  while (index>=0 && !connexions[index].open)
    index--;
  max_index_of_connexions = index+1;
}

/* returns a free chunk if any, or NULL */
static struct socket_connexion * available_connexion(void)
{
  int i;
  for (i=0; i<max_connexions; i++)
    if (!connexions[i].open)
      return &connexions[i];
  
  if (verbose) fputs("no more available connexions\n", stderr);

  return NULL;
}

/* shutdown the socket and check the result */
static void shutdown_socket(int socket)
{
  if (debug) 
    fprintf(stderr, "shuting down and closing socket %d\n", socket);

  if (shutdown(socket, 2) && verbose)
    perror("shutdown()");

  /* close the socket, otherwise it is kept until some timeout is reached. */
  if (close(socket) && verbose)
    perror("close()");
}

#if defined(MAY_SNOOP_TRAFFIC)

/* actually prints a buffer to stderr. */
static void print_snooped_buffer(char * buffer, int size, int src)
{
  fprintf(stderr, LOG "%d #%d: ", pid, src, size);
  fwrite(buffer, 1, size, stderr);
  fputc('\n', stderr);
}

/* flush snooped data to log and reset. */
static void flush_and_reset_snooped(struct socket_connexion * scp)
{
  if (snoop && scp->snooped) 
  {
    if (printable) /* switch non printable characters to '.' */
    {
      int i;
      for (i=0; i<scp->sindex; i++)
      {
	register char ci = scp->snooped[i];
	if ((ci<32 && ci!=10) || (ci > 126)) /* keep 10 + 32..126 */
	  scp->snooped[i] = '.';
      }
    }
    print_snooped_buffer(scp->snooped, scp->sindex, scp->client);
    scp->sindex = 0;
  }
}

/* keep or print snooped data */
static void append_snooped_data(struct socket_connexion * scp, 
				char * buffer, int size)
{
  if (scp->snooped)
  {
    /* pre-empty buffer is not large enough */
    if (scp->sindex && scp->sindex+size>=snoopsize)
      flush_and_reset_snooped(scp);
    
    /* put in snoop buffer */
    while (size)
    {
      while (scp->sindex<snoopsize && size)
	scp->snooped[scp->sindex++] = *buffer++, size--;
      
      if (scp->sindex==snoopsize)	
	flush_and_reset_snooped(scp);
    }
  }
  else /* no buffer */
  {
    print_snooped_buffer(buffer, size, scp->client);
  }
}

#endif /* MAY_SNOOP_TRAFFIC */

/* close the connexion. */
static void shutdown_connexion(struct socket_connexion * scp, string why)
{
  if (scp->open)
  {
    if (debug) 
      fprintf(stderr, "shuting down %d and %d\n", scp->client, scp->dest);

    if (scp->client!=-1) 
      shutdown_socket(scp->client);
      
#if defined(MAY_SNOOP_TRAFFIC)
      
    flush_and_reset_snooped(scp);
	
#endif /* MAY_SNOOP_TRAFFIC */

    if (scp->dest!=-1 && scp->dest!=scp->client) 
      shutdown_socket(scp->dest);

    if (log)
    {
      struct timeval tv;
      gettimeofday(&tv, NULL);
      /* close could show collected statistics? */
      fprintf(stderr, 
	      LOG "#%d (%d,%d) %s 0x%08x:%d/0x%08x:%d at %ld.%06ld\n",
	      pid, scp->index, scp->client, scp->dest, why,
	      ntohl(scp->client_addr.sin_addr.s_addr),
	      ntohs(scp->client_addr.sin_port),
	      ntohl(scp->dest_addr.sin_addr.s_addr),
	      ntohs(scp->dest_addr.sin_port),
	      tv.tv_sec, tv.tv_usec);
    }

    scp->open   = false;
    scp->client = -1;
    scp->dest   = -1;
    number_of_connexions--; 
    if (max_index_of_connexions == scp->index+1)
      set_max_index_of_connexions(scp->index);
  }
}

  
/* connect the connexion or close the client
 */
static void open_connexion_or_shutdown(struct socket_connexion * scp)
{
  /* let's be optimistic... */
  scp->open      = true;
  scp->client_r  = false;
  scp->client_w  = false;
  scp->dest_r    = false;
  scp->dest_w    = false;
  scp->requests  = 0;
  scp->nreq      = 0;
  scp->responses = 0;
  scp->nres      = 0;

  number_of_connexions++;
  total_number_of_connexions++;
  if (scp->index >= max_index_of_connexions)
    max_index_of_connexions = scp->index+1;

  /* something to do if tunnel, nothing on echo. */
  if (scp->dest==-1)
  {
    if ((scp->dest = socket(AF_INET, SOCK_STREAM,  0))==-1)
    {
      if (verbose) perror("socket()");
      shutdown_connexion(scp, "error[socket]");
      return;
    }
    
    if (connect(scp->dest, 
		(struct sockaddr *) &scp->dest_addr, sizeof(struct sockaddr)))
    {
      if (verbose) perror("connect()");
      shutdown_connexion(scp, "error[connect]");
      return;
    }
  }

  if (log)
  {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    
    fprintf(stderr, LOG "#%d (%d,%d) open 0x%08x:%d/0x%08x:%d at %ld.%06ld\n",
	    pid, scp->index, scp->client, scp->dest, 
	    ntohl(scp->client_addr.sin_addr.s_addr),
	    ntohs(scp->client_addr.sin_port),
	    ntohl(scp->dest_addr.sin_addr.s_addr),
	    ntohs(scp->dest_addr.sin_port),
	    tv.tv_sec, tv.tv_usec);
  }
}

#if defined(CHECK_WRITE_ALONE)

/* quick check whether fd is available for writing */
static boolean can_write_now(int fd)
{
  struct timeval timeout;
  fd_set s;

  FD_ZERO(&s);
  FD_SET(fd, &s);

  timeout.tv_sec = 0;
  timeout.tv_usec = 0;
  
  if (select(fd+1, NULL, &s, NULL, &timeout)==-1)
    return false;
  
  return FD_ISSET(fd, &s)? true: false;
}

#endif /* CHECK_WRITE_ALONE */

/* transmit data if any
   src: source socket
   dst: destination socket to transmit data if any
   amount: pointer to byte statistics, or NULL
   n: pointer to event statistics, or NULL
   scp: pointer to socket_connexion if to be snooped.
 */
static boolean transmit(
    int src, 
    int dst, 
    int * amount, 
    int * n,
    struct socket_connexion * scp)
{
  static char buffer[BUFFER_SIZE]; /* yes, static, thus not on stack. */
  ssize_t rsize, wsize;
  
  if ((rsize = read(src, buffer, BUFFER_SIZE))==-1)
  {
    if (verbose) perror("read()");
    return false;
  }
  
  if (debug)
    fprintf(stderr, "transmit() %d -> %d, size=%d\n", src, dst, rsize);
  
  /* it may happen that an empty packet comes??? */
  if (rsize==0)
  {
    if (verbose) fputs("no data to read...\n", stderr);
    return false;
  }
  
#if defined(MAY_SNOOP_TRAFFIC)
  
  if (scp) append_snooped_data(scp, buffer, rsize);
  
#endif /* MAY_SNOOP_TRAFFIC */
    
  /* Optional scrambling (a simple 32 bits xor against a constant).
   * It is not intended as a cypher, but just to prevent basic dumps.
   */
  if (scramble) 
  {
    char * ptr = buffer;
    for (; ptr < buffer+rsize; ptr+=sizeof(long))
      * ((unsigned long *) ptr) ^= scramble;
  }
  
  if ((wsize = write(dst, buffer, rsize))==-1) /* should loop? */
  {
    if (verbose) perror("write()");
    return false;
  }
  
  if (wsize!=rsize)
  {
    if (verbose) 
      fprintf(stderr, "transmit sizes r=%d w=%d\n", rsize, wsize);
    return false;
  }
  
  /* update global and connexion statistics. */
  total_number_of_bytes += rsize;
  total_number_of_events++;
  if (amount) (*amount) += rsize;
  if (n) (*n)++;
  
  if (debug) fprintf(stderr, "transmit done\n");

  return true;
}

/* transmit data <-> for a connexion, and set fd_set as needed. */
static void transmit_connexion(
    struct socket_connexion * scp, 
    fd_set * ptoread,
    fd_set * ptowrite,
    fd_set * ptoexcept,
    int * n)
{
  int client = scp->client, dest = scp-> dest; /* saved as may be cleared */
  
  if (FD_ISSET(client, ptoread))
    scp->client_r = true;

  if (FD_ISSET(dest, ptowrite))
    scp->dest_w = true;

  if (!echo)
  {
    if (FD_ISSET(dest, ptoread))
      scp->dest_r = true;
    
    if (FD_ISSET(client, ptowrite))
      scp->client_w = true;
  }

  if (debug)
    fprintf(stderr, "IN transmit_connexion() of #%d (%d/%d%d,%d/%d%d)\n", 
	    scp->index, 
	    scp->client, scp->client_r, scp->client_w,
	    scp->dest, scp->dest_r, scp->dest_w);

#if defined(CHECK_WRITE_ALONE)

  if (scp->client_r && can_write_now(dest))
    scp->dest_w = true;

#endif /* CHECK_WRITE_ALONE */

  if (scp->open && scp->client_r && scp->dest_w)
  {
    if(!transmit(client, dest, &scp->requests, &scp->nreq, 

#if defined(MAY_SNOOP_TRAFFIC)

		 snoop? scp: NULL

#else

		 NULL

#endif /* MAY_SNOOP_TRAFFIC */

		 ))
      shutdown_connexion(scp, "close[client]");
    
    scp->client_r = false;
    scp->dest_w = false;
  }
     
  if (!echo)
  {

#if defined(CHECK_WRITE_ALONE)

    if (scp->dest_r && can_write_now(client))
      scp->client_w = true;

#endif /* CHECK_WRITE_ALONE */

    if (scp->open && scp->dest_r && scp->client_w)
    {
      if (!transmit(dest, client, &scp->responses, &scp->nres, NULL))
	shutdown_connexion(scp, "close[dest]");
      
      scp->dest_r = false;
      scp->client_w = false;
    }
  }

  /* update fd_set */
  if (scp->open)
  {
    /* still open */
    if (scp->client_r)
    {
      FD_CLR(client, ptoread);
      FD_SET(dest, ptowrite);
    }
    else
    {
      FD_SET(client, ptoread);
      FD_CLR(dest, ptowrite);
    }

    if (!echo)
    {
      if (scp->dest_r)
      {
	FD_CLR(dest, ptoread);
	FD_SET(client, ptowrite);
      }
      else
      {
	FD_SET(dest, ptoread);
	FD_CLR(client, ptowrite);
      }
    }

    FD_SET(dest, ptoexcept);
    FD_SET(client, ptoexcept);

    if (*n<client) *n = client;
    if (*n<dest) *n = dest;
  }
  else
  {
    /* the connexion was closed. nothing to wait for anymore. */
    FD_CLR(client, ptoread);
    FD_CLR(client, ptowrite);
    FD_CLR(dest, ptoread);
    FD_CLR(dest, ptowrite);
  }

  if (debug)
    fprintf(stderr, "OUT transmit_connexion() of #%d (%d/%d%d,%d/%d%d)\n", 
	    scp->index,
	    scp->client, scp->client_r, scp->client_w,
	    scp->dest, scp->dest_r, scp->dest_w);
}

/******************************************************************** STATUS */

#if defined(SIGNAL_TO_STATUS)

/* generate a report about current status and cumulated statistics to stderr */
static void info(int sig)
{
  struct timeval tv;
  int i;

  if (silent) return; /* we don't care now and latter. */

  gettimeofday(&tv, NULL);
  
  fprintf(stderr, 
	  LOG "status at %ld.%06ld: #con=%d/%d, #bytes=%d, #events=%d\n",
	  pid, tv.tv_sec, tv.tv_usec,
	  number_of_connexions, total_number_of_connexions,
	  total_number_of_bytes, total_number_of_events);
 
  for (i=0; i<max_index_of_connexions; i++)
  {
    struct socket_connexion * p = &connexions[i];
    if (p->open)
    {
      fprintf(stderr, LOG
	      "#%d (%d/%d%d,%d/%d%d):"
	      " 0x%08x:%d(e=%d,b=%d)/0x%08x:%d(e=%d,b=%d)\n",
	      pid, i, 
	      p->client, p->client_r, p->client_w,
	      p->dest, p->dest_r, p->dest_w, 
	      ntohl(p->client_addr.sin_addr.s_addr),
	      ntohs(p->client_addr.sin_port),
	      p->nreq, p->requests,
	      ntohl(p->dest_addr.sin_addr.s_addr),
	      ntohs(p->dest_addr.sin_port),
	      p->nres, p->responses);
    }
  }
  
  if (sig) signal(sig, info); /* rearm signal (linux, not as BSD) */
}

#endif /* SIGNAL_TO_STATUS */

/* is it useful? let say yes if buffering with snoop. */
static void down(int sig)
{
  int i;
  struct timeval tv;
  gettimeofday(&tv, NULL);
  
  if (log) 
    fprintf(stderr, LOG "down (signal=%d) at %ld.%06ld\n", 
	    pid, sig, tv.tv_sec, tv.tv_usec);

#if defined(SIGNAL_TO_STATUS)

  info(0);

#endif /* SIGNAL_TO_STATUS */

  for (i=0; i<max_connexions; i++)
    shutdown_connexion(&connexions[i], "close[shutdown]");
  /* shutdown_socket(serv_socket); // no since no connexion. */
  
  fclose(stderr); /* the end. */
  
  /* if (sig) signal(sig, down); // rearm not needed, it's an exit. */
  exit(sig<<8);
}

/******************************************************************** SERVER */

#define INCOMING_QUEUE_SIZE 10

static int new_server(struct sockaddr * sa)
{
  int sn;
  int one = 1;
  
  if ((sn = socket(AF_INET, SOCK_STREAM,  0))==-1)
  {
    if (!silent) perror("server socket()");
    exit(1);
  }
  
  /* allow later reuse of the socket (without timeout) */
  if (setsockopt(sn, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)))
  {
    if (!silent) perror("setsockopt()");
    exit(2);
  }
  
  if (bind(sn, sa, sizeof(struct sockaddr)))
  {
    if (!silent) perror("bind()");
    exit(3);
  }
  
  if (listen(sn, INCOMING_QUEUE_SIZE))
  {
    if (!silent) perror("listen()");
    exit(4);
  }
  
  return sn;
}

static void dump_fd_set(string name, int n, fd_set * pfds)
{
  int i;
  fprintf(stderr, "%s: ", name);
  for (i=0; i<n; i++)
    if (FD_ISSET(i, pfds)) fprintf(stderr, "%d ", i);
  fprintf(stderr, "\n");
}

static void dump_fd_sets(string msg, int n, fd_set * r, fd_set * w, fd_set * e)
{
  fprintf(stderr, "fd set status '%s':\n", msg);
  dump_fd_set("read", n, r);
  dump_fd_set("write", n, w);
  dump_fd_set("except", n, e);
}

static void usage(string program, int exitcode)
{
  fprintf(stderr, 
	  "Usage: %s [-options...] lochost:port [dsthost:port]\n"
	  "  basic single process TCP/IP tunnel (or echo if no dest)\n"
	  "\t-d: debug mode (=> verbose)\n"
	  "\t-h: print this help\n"
	  "\t-l: log connexion activity to stderr\n"
	  
#if defined(MAY_SNOOP_TRAFFIC) /* snoop option help. */
	  
	  "\t-L: log activity stream to stderr (=> log)\n"
	  "\t-r: log with printable caracters only\n"
	  "\t-b size: buffer size for -L (default no buffer)\n"
	  
#endif /* MAY_SNOOP_TRAFFIC */
	  
	  "\t-q: quiet mode (not verbose, this is the default)\n"
	  "\t-s: silent (no output at all, even on errors)\n"
	  "\t-v: verbose mode (=> log)\n"
	  "\t-V: version\n"
	  "\t-M n: maximum number of simultaneous connexions (%d)\n"
	  "\t-x number: optional basic XOR scrambling\n"
	  "\t-p pass: idem, XOR constant based on pass\n"
	  "\t-m msg: insert message as a header to accepted connexions\n"
	  "\tlochost:port local host ip and tcp port to listen\n"
	  "\tdsthost:port tunnel destination host and tcp port\n"
	  "\tor from environment LHOST LPORT DHOST DPORT\n"
	  "\tor defaults: " LHOST ":" LPORT " <chosen local>:" DPORT "\n"
	  "\texample: %s -l localhost:2023 mir:23\n"
	  "\t  (send localhost:2023 packets to mir:23 and log)\n"
	  
#if defined(MAY_SNOOP_TRAFFIC)
	  
	  "\texample: %s -Lr localhost:1080 proxy:80\n"
	  "\t  (send localhost:1080 packets to proxy:80 and snoop)\n"
	  
#endif /* MAY_SNOOP_TRAFFIC */
	  
	  , program, DEFAULT_MAX_CONNEXIONS, program
	  
#if defined(MAY_SNOOP_TRAFFIC)
	  
	  , program
	  
#endif /* MAY_SNOOP_TRAFFIC */
	  
	  );
  exit(exitcode);
}

/********************************************************** LET'S DO THE JOB */

int main(int argc, char * argv[])
{
  int client_socket, len, i, code, n, opt;
  unsigned short int lport = 0, dport = 0; /* in_port_t */
  struct in_addr lhost, dhost;  /* in_addr_t */
  struct sockaddr_in client_addr;
  fd_set toread, towrite, toexcept;
  boolean okay = false;
  string lhosts = NULL, lports = NULL, dhosts = NULL, dports = NULL;
  string msg = NULL;
  
  /* option management. */
  boolean help = false;
  
  pid = (int) getpid();
  
  while ((opt=getopt(argc, argv, 

#if defined(MAY_SNOOP_TRAFFIC)

		     "b:Lr" /* snoop options. */

#endif /* MAY_SNOOP_TRAFFIC */

		     "dhlm:M:p:qsvVx:"))!=EOF)
  {
    switch (opt)
    {
    case 'd': debug = true; /* no break, debug => verbose */
    case 'v': verbose = true; /* no break, verbose => log */
    case 'l': log = true; break;
    case 's': silent = true; opterr = 0; break;
    case 'q': verbose = false; break;
      
#if defined(MAY_SNOOP_TRAFFIC)
      
      /* snoop-related option management. */
    case 'L': snoop = true; break;
    case 'r': printable = true; break;
    case 'b': snoopsize = strtoul(optarg, NULL, 0); break;
      
#endif /* MAY_SNOOP_TRAFFIC */

    case 'm': msg = strdup(optarg); break;
    case 'M': max_connexions = atoi(optarg); break;
    case 'x': scramble = strtoul(optarg, NULL, 0); break;
    case 'p': 
      /* 7 bits left circular shift and a XOR */
      while (*optarg) 
	scramble = LCS(scramble, 7)^(*optarg++);
      break;
    case 'h':
    case 'V':
    default: help = true; 
    }
  }
  
  /* these streams are not needed! */
  fclose(stdin); 
  fclose(stdout);

  /* insure option coherency. */
  if (silent) 
  {
    if (help) exit(7);
    verbose = false;
    log     = false;
    help    = false;
    debug   = false;
    fclose(stderr); /* one more socket to use! */
  }

#if defined(MAY_SNOOP_TRAFFIC)
  
  if (snoopsize) snoop = true;
  if (snoop) log = true;
  
#endif /* MAY_SNOOP_TRAFFIC */
  
  if (debug) verbose = true;
  if (verbose) log = true;
  
  /* arguments: lhost:lport dhost:dport or from environment
   * they should be checked?
   */
  if (argc==optind)
  {
    okay   = true;

    /* configure from environment, with defaults. */
    lhosts = getenv("LHOST");
    lports = getenv("LPORT");
    dhosts = getenv("DHOST");
    dports = getenv("DPORT");
  }
  
  if (argc-optind>=1)
  {
    okay = true;
    echo = true;
    
    /* local */
    lhosts = argv[optind];
    lports = strchr(lhosts, ':');
    if (lports) *lports++ = '\0';
  }
  
  if (argc-optind==2)
  {
    okay = true;
    echo = false;

    /* destination */
    dhosts = argv[optind+1];
    dports = strchr(dhosts, ':');
    if (dports) *dports++ = '\0';
  }

  /* set default values (as string for homogeneity) if needed.
   */
  if (!lhosts || !*lhosts) lhosts = LHOST;
  if (!lports || !*lports) lports = LPORT;
  if (!dhosts || !*dhosts) dhosts = lhosts;
  if (!dports || !*dports) dports = DPORT;
  
  if (okay)
  {
    struct hostent * he = gethostbyname(lhosts);
    if (!he) 
    {
      if (!silent) herror("gethostbyname()");
      exit(5);
    }
    lhost.s_addr = *((unsigned int *)he->h_addr_list[0]);
    lport = atoi(lports); /* check? */
    
    he = gethostbyname(dhosts);
    if (!he) 
    {
      if (!silent) herror("gethostbyname()");
      exit(6);
    }
    dhost.s_addr = *((unsigned int *)he->h_addr_list[0]);
    dport = atoi(dports); /* check? */
  }
  else /* no or bad configuration. */
  {
    if (silent) exit(8);
    help = true;
  }
  
  if (verbose || help)
    fputs("TCP/IP tunnel $Revision: 1.66 $\n", stderr);
  
  if (help) usage(argv[0], 0);
  
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(lport);
  serv_addr.sin_addr = lhost;
  
  dest_addr.sin_family = AF_INET;
  dest_addr.sin_port = htons(dport);
  dest_addr.sin_addr = dhost;
  
  if (log) 
  {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    if (echo)
      fprintf(stderr, 
	      LOG "start echo 0x%08x:%d x=0x%08lx at %ld.%06ld\n",
	      pid,
	      ntohl(serv_addr.sin_addr.s_addr), 
	      ntohs(serv_addr.sin_port),
	      scramble, tv.tv_sec, tv.tv_usec);
    else
      fprintf(stderr, 
	      LOG "start tunnel 0x%08x:%d/0x%08x:%d x=0x%08lx at %ld.%06ld\n",
	      pid,
	      ntohl(serv_addr.sin_addr.s_addr), 
	      ntohs(serv_addr.sin_port),
	      ntohl(dest_addr.sin_addr.s_addr), 
	      ntohs(dest_addr.sin_port),
	      scramble, tv.tv_sec, tv.tv_usec);
  }
  
#if defined(SIGNAL_TO_STATUS)
  
  signal(SIGHUP, info);
  
#endif /* SIGNAL_TO_STATUS */
  
  signal(SIGINT,  down);
  signal(SIGQUIT, down);
  signal(SIGABRT, down);
  signal(SIGTERM, down);
  
  initialize_connexions();
  
  serv_socket = new_server((struct sockaddr *) &serv_addr);
  
  /* initial file descriptor set */
  FD_ZERO(&toread);
  FD_ZERO(&towrite);
  FD_ZERO(&toexcept);
  FD_SET(serv_socket, &toread);
  FD_SET(serv_socket, &toexcept);
  n = serv_socket+1;
  
  while ((code=select(n, &toread, &towrite, &toexcept, NULL))!=-1 ||
	 (code==-1 && errno==EINTR) /* allow signals */ ||
	 true) /* on select errors, let us go on anyway? */
  {
    if ((code==-1 && errno!=EINTR) && verbose)
      perror("select()");

    if (code==-1 && errno==EINTR)
    {
      if (debug) fprintf(stderr, "zeroing sets after select() interrupt\n");
      FD_ZERO(&toread);
      FD_ZERO(&towrite);
      FD_ZERO(&toexcept);
    }

    if (debug)
    {
      dump_fd_sets("after select", n, &toread, &towrite, &toexcept);
      fprintf(stderr, "select code=%d\n", code);
    }
    
    if (code!=-1 && FD_ISSET(serv_socket, &toread))
    {
      /* it is a new connexion. */
      total_number_of_events++;
      len = sizeof(struct sockaddr);
      
      client_socket = 
	accept(serv_socket, (struct sockaddr*) &client_addr, &len);
      
      if (client_socket==-1 && verbose)
	perror("accept()"); /* let us ignore... ??? */
      
      if (client_socket>0)
      {
	struct socket_connexion * scp = available_connexion();

	if (scp) 
	{
	  /* should be checked? */
	  if (msg) write(client_socket, msg, strlen(msg));
	  
	  scp->client      = client_socket;
	  scp->client_addr = client_addr;

	  if (echo)
	  {
	    /* back to client */
	    scp->dest_addr = client_addr;
	    scp->dest      = client_socket;
	  }
	  else
	  {
	    /* fixed destination */
	    scp->dest_addr = dest_addr;
	    scp->dest      = -1;
	  }

	  open_connexion_or_shutdown(scp);
	}
	else
	{
	  if (log)
	    fprintf(stderr, LOG "%d client refused, max #connect reached\n", 
		    pid, client_socket);
	  shutdown_socket(client_socket);
	}
      }
    }
    
    FD_ZERO(&toexcept);
    FD_SET(serv_socket, &toexcept);
    FD_SET(serv_socket, &toread);
    n = serv_socket;

    /* transmit if needed. maybe the list of open connexions could be kept? */
    for (i=0; i<max_index_of_connexions; i++)
      if (connexions[i].open)
	transmit_connexion(&connexions[i], &toread, &towrite, &toexcept, &n);

    n++; /* select() expects the largest socket number + 1 */

    if (debug) dump_fd_sets("before select", n, &toread, &towrite, &toexcept);
  }
  
  /* if (!silent) perror("select()"); */
  down(0);
  return 9; /* never reached. */
}
