/* * 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 #endif #include #include // exit() #include // strerror(), strsignal() #include #include // signal() #include // INT_MAX #include // uid_t, pid_t #include #include // close(), fork(), sleep() #if HAVE_SYS_WAIT_H # include // wait() #endif #if HAVE_SYS_SOCKET_H # include #endif #include // errno, EINTR #include // fcntl() #if HAVE_SYSLOG_H # include // 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; }