/*
* tcpreen.cpp - TCP connection reverse engineering tool.
* $Id: tcpreen.cpp,v 1.34 2005/03/09 10:08:30 rdenisc Exp $
*/
/***********************************************************************
* Copyright (C) 2002-2005 Remi Denis-Courmont. *
* This program is free software; you can redistribute and/or modify *
* it under the terms of the GNU General Public License as published *
* by the Free Software Foundation; version 2 of the license. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, you can get it from: *
* http://www.gnu.org/copyleft/gpl.html *
***********************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h> // exit()
#include <string.h> // strerror(), strsignal()
#include <stdarg.h>
#include <signal.h> // signal()
#include <limits.h> // INT_MAX
#include <sys/types.h> // uid_t, pid_t
#include <sys/time.h>
#include <unistd.h> // close(), fork(), sleep()
#if HAVE_SYS_WAIT_H
# include <sys/wait.h> // wait()
#endif
#if HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#include <errno.h> // errno, EINTR
#include <fcntl.h> // fcntl()
#if HAVE_SYSLOG_H
# include <syslog.h> // FIXME: move
#endif
#include "gettext.h"
#include "tcpreen.h"
#include "bridge.h"
#include "log.h"
#include "libsolve/sockprot.h"
static int mode;
static uid_t user;
static pid_t mainpid;
static int niflags;
static void
bridge_printf (int level, const char *fmt, ...)
{
#if HAVE_VSYSLOG
if (mode & tcpreen_daemon)
{
va_list ap;
va_start (ap, fmt);
vsyslog (level, fmt, ap);
va_end (ap);
}
#endif
if (mode & tcpreen_verbose)
{
va_list ap;
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
}
}
static void
bridge_perror (const char *str, int level = LOG_ERR)
{
bridge_printf (level, "%s: %s\n", str, strerror (errno));
}
inline void
bridge_fatal (void)
{
bridge_perror (_("Fatal error"), LOG_CRIT);
}
static void
bridge_hosterror (const SocketAddress& host, int l = LOG_ERR)
{
if (*(host.Node ()))
bridge_printf (l, _("%s port %s: %s\n"), host.Node (),
host.Service (), host.StrError ());
else
bridge_printf (l, _("port %s: %s\n"), host.Service (),
host.StrError ());
}
/*
* Accepts a socket passive connection.
*/
static int
bridge_accept (int listenfd)
{
int fd = accept (listenfd, NULL, 0);
if (fd != -1)
{
const int val = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val));
#ifdef O_NONBLOCK
int flags = fcntl (fd, F_GETFL);
fcntl (fd, F_SETFL, flags | O_NONBLOCK);
#endif
}
return fd;
}
static int
bridge_listen (const SocketAddress& iface)
{
int fd;
SocketAddress real = iface;
seteuid (0);
fd = real.Bind ();
seteuid (user);
if (fd == -1)
{
bridge_hosterror (real);
return -1;
}
if (real.GetSockName (fd, niflags)) // very strange error
{
bridge_hosterror (real);
close (fd);
return -1;
}
if (listen (fd, INT_MAX) == 0)
{
bridge_printf (LOG_NOTICE, _("Listening on: %s port %s\n"),
real.Node (), real.Service ());
return fd;
}
real.SetError ();
close (fd);
bridge_hosterror (real);
return -1;
}
/*
* SIGNAL HANDLING
*/
static void
bridge_child_signal_handler (int signum)
{
/* This is not like SIG_IGN: it still interrupts select() & sleep()
* which is what matters */
signal (signum, bridge_child_signal_handler);
}
static volatile int bridge_signaled = 0;
static void
bridge_parent_signal_handler (int signum)
{
if (!bridge_signaled)
bridge_signaled = signum;
if (getpid () == mainpid)
signal (signum, bridge_parent_signal_handler);
}
static void
bridge_wait (int& numclients)
{
#ifndef WINSOCK
sleep (30000);
if (bridge_signaled)
return;
int val;
pid_t child = wait (&val);
if (child != (pid_t)(-1))
{
if (WIFEXITED (val))
{
val = WEXITSTATUS (val);
if (val)
bridge_printf (LOG_WARNING, _(
"Child %d returned an error (%d)\n"),
(int)child, val);
}
else
if (WIFSIGNALED (val))
{
val = WTERMSIG (val);
bridge_printf (LOG_WARNING, _(
"Child %d killed by signal %d (%s)\n"),
(int)child, val,
#if HAVE_STRSIGNAL
strsignal(val)
#else
""
#endif
);
}
numclients--;
}
#endif
}
/*
* Establishes a bridge between a TCP listener and a TCP active
* connection.
* The listening socket is dropped as soon as the first incoming
* connection is established.
*
* Returns 0 on success, true on error.
*/
#ifdef WINSOCK
# define signal( num, hd ) (0)
# define fork( ) (0)
# define exit( val ) continue
# define kill( pid, sig ) (0)
# define SIGHUP SIGBREAK
#endif
int
bridge_main (const struct bridgeconf *conf)
{
/* explicitly bufferized settings */
mode = conf->mode; // static global variable
user = conf->user;
mainpid = getpid ();
niflags = (mode & tcpreen_numeric) ? NI_NUMERICHOST|NI_NUMERICSERV : 0;
// Socket addresses
SocketAddress svrif, cltif;
if (svrif.SetByName (conf->servername, conf->serverservice,
(mode & tcpreen_listen_server) ? AI_PASSIVE : 0,
conf->serveraf, SOCK_STREAM))
{
bridge_hosterror (svrif);
return -1;
}
if (cltif.SetByName (conf->bridgename, conf->bridgeservice,
(mode & tcpreen_listen_client) ? AI_PASSIVE : 0,
conf->bridgeaf, SOCK_STREAM))
{
bridge_hosterror (cltif);
return -1;
}
// Captures deadly signal
// (done after name resolution, which is blocking)
signal (SIGHUP, bridge_parent_signal_handler);
signal (SIGINT, bridge_parent_signal_handler);
signal (SIGTERM, bridge_parent_signal_handler);
#ifndef WINSOCK
signal (SIGQUIT, bridge_parent_signal_handler);
signal (SIGUSR1, SIG_IGN);
signal (SIGUSR2, SIG_IGN);
signal (SIGCHLD, bridge_child_signal_handler);
#endif
// Things that must not be goto-bypassed
int in = -1, out = -1, numclients = 0, retval = -1,
maxclients = conf->maxclients;
long countdown = conf->totalclients;
// Sets up server socket(s)
if (mode & tcpreen_listen_client)
{
in = bridge_listen (cltif);
if (in == -1)
goto bridge_abort;
}
if (mode & tcpreen_listen_server)
{
out = bridge_listen (svrif);
if (out == -1)
goto bridge_abort;
}
/* Definitely drops priviledges */
if (seteuid (user) || setuid (user))
{
bridge_perror (_("UID setting"), LOG_CRIT);
goto bridge_abort;
}
#if HAVE_DAEMON
// Goes in the background
if ((mode & tcpreen_daemon) && daemon (0, 0))
{
bridge_perror (_("Background mode"), LOG_CRIT);
goto bridge_abort;
}
#endif
/*
* SERVER LOOP
*/
retval = 0;
while ((!bridge_signaled) && countdown)
{
/*
* Simple pre-forking server model: there are always a fixed
* predefined number of processes running. This will not work
* fine with a broken OS.
*/
if (numclients >= maxclients)
{
bridge_wait (numclients);
continue;
}
if (countdown > 0)
countdown--; /* one connection attempt */
pid_t pid = fork ();
if (pid == (pid_t)(-1))
{
bridge_perror (_("Process creation"));
sleep (5); // prevent CPU usage from bursting
continue;
}
else
if (pid != 0)
{
numclients++;
continue; // back to server loop
}
/*
* CHILD PROCESS
*/
// some blocking calls follow, so die upon signal
signal (SIGHUP, SIG_DFL);
signal (SIGINT, SIG_DFL);
signal (SIGTERM, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
signal (SIGUSR1, SIG_IGN);
signal (SIGUSR2, SIG_IGN);
if (bridge_signaled)
exit (EXIT_SUCCESS);
int fd[2];
/* settinp up client socket... */
if (in != -1)
{
fd[0] = bridge_accept (in);
#ifndef WINSOCK
close (in);
#endif
}
else
fd[0] = cltif.Connect ();
if (fd[0] == -1)
{
#ifdef EINTR
if (errno == EINTR)
exit (EXIT_SUCCESS);
#endif
bridge_hosterror (cltif);
exit (1);
}
/* Computes client address */
SocketAddress clt;
clt.GetPeerName (fd[0], niflags);
if (!clt)
bridge_hosterror (clt);
if (mode & tcpreen_daemon)
syslog (LOG_INFO, _("Connection from: %s port %s\n"),
clt.Node (), clt.Service ());
/* setting up server socket... */
if (out != -1)
{
fd[1] = bridge_accept (out);
#ifndef WINSOCK
close (out);
#endif
}
else
fd[1] = svrif.Connect ();
if (fd[1] == -1)
{
#ifdef EINTR
if (errno == EINTR)
{
close (fd[0]);
exit (0);
}
#endif
bridge_hosterror (svrif);
close (fd[0]);
exit (2);
}
/* Computes server address */
SocketAddress svr;
svr.GetPeerName (fd[1], niflags);
if (!svr)
bridge_hosterror (svr);
if (mode & tcpreen_daemon)
syslog (LOG_INFO, _("Connection to: %s port %s\n"),
svr.Node (), svr.Service ());
/* Both sides of the bridge are now properly connected! */
// from then on, do not exit when receiving a signal
signal (SIGHUP, bridge_child_signal_handler);
signal (SIGINT, bridge_child_signal_handler);
signal (SIGTERM, bridge_child_signal_handler);
signal (SIGQUIT, bridge_child_signal_handler);
DataLogList *logs = conf->logsmaker->MakeLogList (clt.Node (),
clt.Service ());
if (logs == NULL)
{
bridge_perror (_("Log file error"));
close (fd[1]);
close (fd[0]);
exit (3);
}
if (!!(*conf->logsmaker))
logs->Connect (svr.Node (), svr.Service(),
clt.Node (), clt.Service ());
retval = monitor_bridge (fd, logs, conf->bytelimit);
close (fd[1]);
close (fd[0]);
delete logs;
exit (retval ? EXIT_FAILURE : EXIT_SUCCESS);
/* END OF CHILD PROCESS */
}
/* Waits for pending clients */
while (numclients && !bridge_signaled)
bridge_wait (numclients);
bridge_abort:
if (bridge_signaled)
{
kill (-getpid (), bridge_signaled); // kill children
#if HAVE_STRSIGNAL
bridge_printf (LOG_NOTICE, "%s: %s\n", _("Caught signal"),
strsignal (bridge_signaled));
#else
bridge_printf (LOG_NOTICE, "%s %d\n", _("Caught signal"),
bridge_signaled);
#endif
}
if (mode & tcpreen_listen_client)
cltif.CleanUp ();
if (mode & tcpreen_listen_server)
svrif.CleanUp ();
if (out != -1)
close (out);
if (in != -1)
close (in);
return retval;
}
syntax highlighted by Code2HTML, v. 0.9.1