Tunnel_C/tunnel.c

1209 lines
30 KiB
C
Raw Permalink Normal View History

2023-07-18 01:39:09 +00:00
/*
* $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, "socket same error[socket]");
return;
}
if (connect(scp->dest, (struct sockaddr *) &scp->dest_addr, sizeof(struct sockaddr)))
{
if (verbose) perror("connect()");
shutdown_connexion(scp, "error2 [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. */
}