Logo Search packages:      
Sourcecode: tcpreen version File versions  Download package

tcpreen.cpp

/*
 * 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;
}

Generated by  Doxygen 1.6.0   Back to index