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

main.cc

/*
 * main.cc - main() function for tcpreen - command line parsing and
 * basic sanity checks.
 * $Id: main.cc,v 1.12 2003/02/27 11:55:33 rdenisc Exp $
 */

/***********************************************************************
 *  Copyright (C) 2002-2003 Rémi 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 Pulic 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> /* getenv(), strtol(), stroul(), on Windows: exit() */
#include <string.h> /* strchr(), memcpy(), strncpy(), memset(), strcmp() */
#include <limits.h> /* ULONG_MAX */
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h> /* geteuid(), getuid(), seteuid() */
#endif
#ifdef HAVE_GETOPT_H
/*
 * Temporary dirty fix for C++ incompatible GNU <getopt.h>
 * on non-GNU platforms (namely FreeBSD+libgnugetopt).
 *
 * I hope I can get rid of that silly thing soon.
 */
# if (defined(HAVE_GETOPT_LONG) && !defined(__GNU_LIBRARY__))
#  define getopt gnugetopt_broken
# endif
# include <getopt.h> /* getopt_long() */
# undef getopt /* to be removed one day */
#endif
#ifdef HAVE_PWD_H
# include <pwd.h> /* getpwnam(), getpwuid() */
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h> /* AF_*, PF_*, SOCK_STREAM */
#endif

#define begin_socket() (void)0
#define end_socket() (void)0

#ifdef HAVE_NETDB_H
# include <netdb.h> /* struct addrinfo */
#endif
#ifndef PF_INET6
# define PF_INET6 10
#endif

#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif

#include "tcpreen.h"
#include "log.h"
#include "basiclog.h"

// Possible return values
#define MAIN_NOERR      0
#define MAIN_SHORTCIRCUIT -1 // for internal use
#define MAIN_PARMPROB   1 // parameter problem
#define MAIN_IOERR      2 // I/O error

int
usage(void)
{
      puts(_(
"Usage: tcpreen [OPTION]... SERVER_PORT [LOCAL_PORT]\n"
"Establishes a bridge between two TCP ports then monitors a TCP session.\n"
"\n"
"  -a, --bind     listen for connections on the following address\n"
"  -b, --bytes    limit maximum TCP session length in bytes; e.g.: -b 1024\n"
"  -c, --connect  connect to the client instead of listening for it\n"
"  -C, --C        encode log file a bit like a C source file (default)\n"
"  -d, --daemon   run in the background\n"
"  -f, --force    enforce unusual options combination\n"
"  -F, --fork     enable multi-process operation; e.g.: -F 10 (10 processes)\n"
"  -h, --help     display this help and exit\n"
"  -H, --hex      encode log file entirely in hexadecimal\n"
"  -l, --listen   listen for the server instead of connecting to it\n"
"  -L, --syslog   use syslog facility (when run in the background)\n"
"  -m, --multi    monitor multiple subsequent connections\n"
"  -n, --numeric  disable reverse DNS lookup\n"
"  -N, --null     do not monitor data, only connections themselves\n"
"  -o, --output   open a log file; e.g.: -o mylog.txt\n"
"  -p, --protocol use the following protocol for connections\n"
"  -q, --quiet    do not write to stdout (default)\n"
"  -R, --raw      do not encode the log at all\n"
"  -s, --server   connect to this host (local host by default)\n"
"  -S, --strip    strip non-printable characters when writing to log file\n"
"  -u, --uid      specify an unprivilieged UID/username to be used\n"
"  -v, --verbose  increase verbosity -- cumulative\n"
"  -V, --version  display program version and exit\n"));
      printf(_("Report any bug to: <%s>.\n"), PACKAGE_BUGREPORT);
      return MAIN_SHORTCIRCUIT;
}

int
version(void)
{
#ifndef VERSION
# define VERSION "unknown"
#endif
      puts(
"tcpreen "VERSION"\n"
"Copyright (C) 2002-2003 Rémi Denis-Courmont");
      puts(_(
"This is free software; see the source for copying conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or\n"
"FITNESS FOR A PARTICULAR PURPOSE.\n"));
      printf(_("Written by %s.\n"), "Rémi Denis-Courmont");
      return MAIN_SHORTCIRCUIT;
}

/*
 * Generic error handling
 */
static int
error_gen (const char *path, const char *msg)
{
      fprintf (stderr, "%s: %s.\n", path, _(msg));
      return MAIN_PARMPROB;
}

static int
error_qty (const char *quantity)
{
      return error_gen (quantity,
                        N_("invalid number (or capacity exceeded)"));
}

static int
error_extra (const char *extra)
{
      return error_gen (extra, N_("unexpected extra parameter"));
}

static int
parse_strncpy (char *buf, const char *str, int len)
{
      if (buf[0])
            return error_extra (str);
      buf[len - 1] = 0;
      strncpy (buf, str, len);
      if (buf[len - 1])
      {
            fprintf (stderr, _("%s: parameter too long.\n"), str);
            return MAIN_PARMPROB;
      }
      return 0;
}


/*
 * Protocol name to number translation
 */
struct protocol_info
{
      const char *p_name;
      int p_family;
      int p_type;
      int p_proto;
};

struct protocol_info proto_names[] = 
{
#ifdef PF_UNSPEC
      { "any",   PF_UNSPEC, SOCK_STREAM, 0 },
#endif
#ifdef PF_LOCAL
      { "local", PF_LOCAL,  SOCK_STREAM, 0 }, /* won't work */
#endif
#ifdef PF_UNIX
      { "unix",  PF_UNIX,   SOCK_STREAM, 0 }, /* won't work */
#endif
#ifdef PF_INET
      { "tcp",   PF_INET,   SOCK_STREAM, 0 },
#endif
#ifdef PF_INET6
      { "tcp6",  PF_INET6,  SOCK_STREAM, 0 },
#endif
      { NULL, 0, 0, 0 }
};
#define MAX_PROTONAME 8 /* bytes */

struct protocol_info *
findprotobyname (const char *name)
{
      struct protocol_info *info;
      
      for (info = proto_names; info->p_name != NULL; info ++)
            if (!strcmp (info->p_name, name))
                  return info;

      return NULL;
}

int
parse_proto (const char *parm, struct protocol_info* infos[2])
{
      const char *ptr;

      ptr = strchr (parm, '/');
      if (ptr == NULL)
      {
            /* use same protocol twice */
            infos[0] = findprotobyname(parm);
            if (infos[0] == NULL)
                  return -1;
            infos[1] = infos[0];
      }
      else
      {
            size_t len;

            len = ptr - parm;
            if ((len) > MAX_PROTONAME)
                  return -1;
            else
            {
                  /* use two different protocols */
                  char name[MAX_PROTONAME];

                  memcpy (name, parm, len);
                  name[len] = 0;

                  if (((infos[0] = findprotobyname (name)) == NULL)
                   || ((infos[1] = findprotobyname (++ptr)) == NULL))
                        return -1;
            }
      }

      return 0;
}

/*
 * Parses an user name.
 * Returns (uid_t)(-1) on failure.
 */
uid_t parse_user (const char *username)
{
      if ((username == NULL) || (username[0] == 0))
            return (uid_t)(-1);
      
      char *end;
      uid_t uid = strtol (username, &end, 0);

      struct passwd *pw = (*end) ? getpwnam (username) : getpwuid (uid);
      return (pw == NULL) ? (uid_t)(-1) : pw->pw_uid;
}


/*
 * Command line options parsing.
 * argc, argv and loglist must be kept valid at all cost.
 * servername and bridgename MUST be NI_MAXHOST bytes long,
 * serverservice and bridgeservice MUST be NI_MAXSERV bytes long.
 */
#define VERBOSE_MAX 10
int
parse_args (int argc, char *argv[], struct bridgeconf *conf)
{
      int check, enforce = 0, verbose = 1, count = 0;
      struct sockhostinfo *curinfo;
      struct option longopts[] =
      {
            { "bind",   1, NULL, 'a' },
            { "bytes",  1, NULL, 'b' },
            { "connect",      0, NULL, 'c' },
            { "C",            0, NULL, 'C' },
            { "daemon", 0, NULL, 'd' },
            { "force",  0, NULL, 'f' },
            { "fork",   1, NULL, 'F' },
            { "help",   0, NULL, 'h' },
            { "hex",    0, NULL, 'H' },
            { "listen", 0, NULL, 'l' },
            { "syslog", 0, NULL, 'L' },
            { "multi",  2, NULL, 'm' },
            { "numeric",      0, NULL, 'n' },
            { "null",   0, NULL, 'N' },
            { "output", 1, NULL, 'o' },
            { "protocol",     1, NULL, 'p' },
            { "quiet",  0, NULL, 'q' },
            { "raw",    0, NULL, 'R' },
            { "server", 1, NULL, 's' },
            { "strip",  0, NULL, 'S' },
            { "uid",    1, NULL, 'u' },
            { "user",   1, NULL, 'u' }, // alias for `--uid'
            { "verbose",      0, NULL, 'v' },
            { "version",      0, NULL, 'V' },
            { NULL,     0, NULL, 0 }
      };
      DataLog *(*maker) (void) = CDataLogMaker;
            
      /* No parameters at all? */
      if (argc <= 1)
            return usage();

      curinfo = &conf->server;
      
      while ((check = getopt_long (argc, argv,
            "a:b:cCdfF:hHlLm::nNo:p:qRs:Su:vV", longopts, NULL)) != EOF)
      {
            switch (check)
            {
                  case 'a':
                        if (parse_strncpy (conf->bridge.hostname,
                                          optarg, NI_MAXHOST))
                              return MAIN_PARMPROB;
                        break;

                  case 'b':
                  {
                        char *end;
                        long lim;

                        lim = strtol (optarg, &end, 0);
                        if ((*end) || (lim < 0) || (lim == LONG_MAX))
                              return error_qty (optarg);
                        conf->bytelimit = lim;
                  }
                        break;

                  case 'c':
                        conf->mode |= tcpreen_connect_both;
                        break;

                  case 'C':
                        maker = CDataLogMaker;
                        break;

                  case 'd':
                        conf->mode |= tcpreen_daemon;
                        conf->totalclients = -1;
                        break;

                  case 'f':
                        enforce = 1;
                        break;

                  case 'F':
                  {
                        char *end;
                        long num;

                        num = strtoul (optarg, &end, 0);
                        if (*end || (num > INT_MAX))
                              return error_qty (optarg);
                        conf->maxclients = (int)num;
                  }
                        break;

                  case 'h': /* help */
                        return usage();

                  case 'H':
                        maker = HexDataLogMaker;
                        break;

                  case 'l':
                        conf->mode |= tcpreen_listen_both;
                        break;

                  case 'L':
                        conf->mode |= tcpreen_syslog;
                        break;

                  case 'm':
                        if (optarg != NULL)
                        {
                              char *end;
                              conf->totalclients = strtol(optarg, &end, 0);
                              if (*end)
                                    return error_qty(optarg);
                        }
                        else
                              conf->totalclients = -1;
                        break;

                  case 'n':
                        conf->mode |= tcpreen_numeric;
                        break;

                  case 'N':
                        maker = CountDataLogMaker;
                        break;

                  case 'o':
                  {
                        int val;

                        val = (strcmp (optarg, "-")
                              ? conf->logmaker->AddLogMaker (maker,
                                    optarg)
                              : conf->logmaker->AddLogMaker (maker));

                        if (val)
                        {
                              perror (_("Fatal error"));
                              return MAIN_IOERR;
                        }
                  }
                        break;

                  case 'p':
                  {
                        struct protocol_info *infos[2];

                        if (parse_proto (optarg, infos))
                              return error_gen (optarg, N_("unrecognized protocol name"));
                        conf->server.family = infos[0]->p_family;
                        conf->server.socktype = infos[0]->p_type;
                        conf->server.protocol = infos[0]->p_proto;
                        conf->bridge.family = infos[1]->p_family;
                        conf->bridge.socktype = infos[1]->p_type;
                        conf->bridge.protocol = infos[1]->p_proto;
                  }
                        break;

                  case 'q':
                        verbose = 0;
                        break;

                  case 'R':
                        maker = RawDataLogMaker;
                        break;

                  case 's':
                        if (parse_strncpy(conf->server.hostname,
                                          optarg, NI_MAXHOST))
                              return MAIN_PARMPROB;
                        break;

                  case 'S':
                        maker = StrippedDataLogMaker;
                        break;

                  case 'u':
                        if (conf->unpriv)
                              return error_gen (optarg, N_("only root can select an user"));
                        else
                        {
                              uid_t uid = parse_user (optarg);
                              if (uid == (uid_t)(-1))
                                    return error_gen (optarg, N_("invalid user"));
                              conf->unpriv = uid;
                        }     
                        break;

                  case 'v':
                        verbose ++;
                        if (verbose > VERBOSE_MAX)
                              verbose = VERBOSE_MAX;
                        break;

                  case 'V':
                        return version();

                  default: // never happens
                  case '?': // error: unrecognized option
                        return MAIN_PARMPROB;
            }
            if (count == 1)
                        curinfo = &conf->bridge;
      }

      if ((count == 0) && (optind < argc))
      {
            if (parse_strncpy (conf->server.service, argv[optind],
                              NI_MAXSERV))
                  return MAIN_PARMPROB;
            count++;
            optind++;
      }
      if ((count == 1) && (optind < argc))
      {
            if (parse_strncpy(conf->bridge.service, argv[optind],
                              NI_MAXSERV))
                  return MAIN_PARMPROB;
            /* count++; */
            optind++;
      }
      if (optind < argc)
            return error_extra(argv[optind]);

      // Handles verbosity setting
      if (conf->mode & tcpreen_daemon)
            verbose = 0;
      if (verbose)
      {
            conf->mode |= tcpreen_verbose;
            int check = conf->logmaker->AddLogMaker ((verbose > 1)
                        ? maker : CountDataLogMaker);
            if (check)
            {
                  perror (_("Fatal error"));
                  return MAIN_IOERR;
            }
      }

      /*
       * Sanity checks
       */
      if (!(*conf->server.service) && !(conf->mode & tcpreen_listen_both))
            return error_gen (argv[0], N_("no server port specified"));
      if (!(*conf->bridge.service) && (conf->mode & tcpreen_connect_both))
            return error_gen (argv[0], N_("no client port specified with -c option"));
      if ((conf->mode & tcpreen_syslog) && !(conf->mode & tcpreen_daemon))
            return error_gen (argv[0], N_("syslog can only be used in daemon mode (option -d)"));
      
      if (!enforce)
      {
            const char *warn_msg = NULL;

#ifdef HAVE_SETUID
            if (conf->unpriv == 0)
                  warn_msg = N_("insecure use with root privileges (you should use -u)");
            else
#endif
            if ((conf->mode & tcpreen_reversed) == tcpreen_reversed)
                  warn_msg = N_("probably erroneous use of both -c and -l options");
            else if ((conf->mode & tcpreen_connect_both) && (conf->totalclients != 1))
                  warn_msg = N_("dangerous use of both -c and -m options");
            else if (!(conf->mode & tcpreen_speak)
                  && (!(conf->bridge.service[0])
                   || !(conf->server.service[0])))
                  warn_msg = N_("dynamic port but nowhere to tell");
            else if ((conf->totalclients >= 0) && (conf->maxclients > conf->totalclients))
                  warn_msg = N_("option -F should be used together with option -m,\n"
                              "(with a value higher than or equal to that of -m)");

            if (warn_msg != NULL) {
                  fprintf(stderr, _("%s: %s;\nUse -f to override.\n"),
                        argv[0], _(warn_msg));
                  return MAIN_PARMPROB;
            }
      }
      
      return MAIN_NOERR;
}


/*
 * Default security settings
 */
static void
preconf_security (bridgeconf *conf)
{
      uid_t unpriv = getuid ();
      conf->priv = geteuid ();

      /* Support for sudo (http://www.sudo.ws/)
       * (if, and only if, we are running as **real** UID root) */
      if (unpriv == 0)
            unpriv = parse_user (getenv ("SUDO_UID"));

      conf->unpriv = (unpriv != (uid_t)(-1)) ? unpriv : 0;
}


/*
 * Main function
 */
int
main (int argc, char *argv[])
{
      struct bridgeconf conf;
      int val;

      /* Default settings */
      memset(&conf, 0, sizeof (conf));
      conf.bytelimit = -1;
      conf.totalclients = 1;
      preconf_security (&conf);
      conf.server.socktype = conf.bridge.socktype = SOCK_STREAM;
      try
      {
            conf.logmaker = new DataLogListMaker;
      }
      catch (...)
      {
            perror (_("Fatal error"));
            return 1;
      }

      /* Use low privileges during initialization *
       * (in particular to open log files)          */
      seteuid(conf.unpriv);

      /* Initialization */
      setlocale(LC_ALL, "");
      bindtextdomain(PACKAGE, LOCALEDIR);
      textdomain(PACKAGE);

      /* Command line parsing and checking */
      val = parse_args(argc, argv, &conf);

      /* Let's come to serious things... */
      if (!val)
            val = bridge_main(&conf);

      delete conf.logmaker;
      return (val >= 0 ) ? val : MAIN_IOERR;
}


#ifdef _WINSOCKAPI_
/*
 * Additionnal functions for Winsock support
 */
# undef perror
# include <windows.h>
# include <wincon.h> /* SetConsoleTitle() */
# include <winsock.h>
# include <errno.h>
# ifndef WINSOCK_VERSION
#  define WINSOCK_VERSION     0x0101            // 0x0202
#  define WINSOCK_MODULE      "WSOCK32"   // "WS2_32"
# endif
void
begin_socket (void)
{
      WSADATA wsaData;

      SetConsoleTitle ("TCP re-engineering tool v" PACKAGE_VERSION
                  " for Windows");

      if (!WSAStartup (WINSOCK_VERSION, &wsaData)) {
            if(wsaData.wVersion == WINSOCK_VERSION)
                  return;
            else
                  WSACleanup();
      }

      fputs ("Winsock unavailable or unsupported version. Aborting.\n",
            stderr);
      exit (1);
}


void
end_socket (void)
{
      WSACleanup();
}

/*
 * Unreliable socket-enabled perror() replacement for M$ Windows.
 */
extern "C" void
stub_perror (const char *str)
{
      int num = WSAGetLastError ();

      /*
       * Winsock 1.1's WSOCK32.dll has an undocumented function:
      
         void PASCAL FAR s_perror (LPCSTR str, int errnum);
         
       * that is an useful replacement for perror().
       * It looks like it was removed from Winsock 2.2, and it is not
       * in <winsock.h> so we /try/ to import it manually. It is assumed
       * that the compiler will call LoadLibrary(WINSOCK_MODULE) for us.
       */
      HMODULE winsock = GetModuleHandle (WINSOCK_MODULE);
      void PASCAL FAR (*sock_perror) (LPCSTR, int) = (winsock != NULL)
            ? (void PASCAL FAR (*) (LPCSTR, int))
                  GetProcAddress (winsock, "s_perror")
            : NULL;
      
      if (num)
      {
            if (sock_perror != NULL)
            {
                  fputs (str, stderr);
                  sock_perror ("\nWinsock error", num);
            }
            else
                  fprintf (stderr, "%s: Winsock error %d.\n", str, num);
      }
      else
            perror (str);
}
#endif


Generated by  Doxygen 1.6.0   Back to index