/* * main.cpp - main() function for tcpreen - command line parsing and * basic sanity checks. * $Id: main.cpp,v 1.16 2004/06/27 15:24:30 rdenisc Exp $ */ /*********************************************************************** * Copyright (C) 2002-2004 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 "secstdio.h" #include /* getenv(), strtol(), stroul() */ #include /* memset() */ #include /* LONG_MAX, INT_MAX */ #include #include /* geteuid(), getuid(), seteuid() */ #ifdef HAVE_GETOPT_H /* * Temporary dirty fix for C++-incompatible GNU * 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_long() */ # undef getopt #endif #ifdef HAVE_PWD_H # include /* getpwnam() */ #endif #ifdef HAVE_SYSLOG_H # include #endif #ifdef HAVE_LOCALE_H # include #endif #include "gettext.h" #include "tcpreen.h" #include "format.h" #include "proto.h" #include "host.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 static int usage (void) { puts (_( "Usage: tcpreen [OPTION]... SERVER_PORT [LOCAL_PORT]\n" "Establishes a bridge between two TCP ports then monitors TCP sessions.\n" "\n" " -a, --bind listen for connections on the following address\n" " -b, --bytes limit maximum sessions length in bytes; e.g.: -b 1024\n" " -c, --connect connect to the client instead of listening for it\n" " -d, --daemon run in the background\n" " -f, --format selects log file(s) output format\n" " -F, --fork specify number of client processes to spawn\n" " -h, --help display this help and exit\n" " -l, --listen listen for the server instead of connecting to it\n" " -m, --maxconn limit number of monitored connections (faster)\n" " -n, --numeric disable reverse DNS lookup\n" /* " -i, --iface specify which network interface(s) to use (if applicable)\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\n" " -s, --server connect to this host (local host by default)\n" " -u, --user specify an unprivilieged username to be used\n" " -v, --verbose increase verbosity -- cumulative\n" " -V, --version display program version and exit\n")); fprint_proto_list (stdout); fprint_format_list (stdout); fputc ('\n', stdout); printf (_("Report any bug to: <%s>.\n"), PACKAGE_BUGREPORT); return MAIN_SHORTCIRCUIT; } static int version (void) { #ifndef VERSION # define VERSION "unknown version" #endif puts ( "TCP re-engineering tool "VERSION" ("PACKAGE_HOST")\n" " built "__DATE__" on "PACKAGE_BUILD_HOSTNAME" ("PACKAGE_BUILD")\n" "Copyright (C) 2002-2004 Remi 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.\nConfigured with: %s\n"), "Remi Denis-Courmont", PACKAGE_CONFIGURE_INVOCATION); return MAIN_SHORTCIRCUIT; } static int quick_usage (void) { fputs (_("Try `tcpreeen -h | more' for more information.\n"), stderr); return MAIN_PARMPROB; } /* * Generic error handling */ static int error_gen (const char *path, const char *msg) { fprintf (stderr, "%s: %s.\n", path, _(msg)); return quick_usage (); } 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")); } /* * Parses an user name. * Returns (uid_t)(-1) on failure. */ static uid_t parse_user (const char *username) { if ((username == NULL) || (username[0] == 0)) return (uid_t)(-1); struct passwd *pw = getpwnam (username); return (pw != NULL) ? pw->pw_uid : (uid_t)(-1); } /* * Command line options parsing. * argc, argv and loglist must be kept valid at all cost. */ #define VERBOSE_MAX 10 static int parse_args (int argc, char *argv[], struct bridgeconf *conf, DataLogListMaker& logsmaker) { int check, verbose = 1; struct option longopts[] = { { "accept", 1, NULL, 'a' }, { "bind", 1, NULL, 'a' }, { "bytes", 1, NULL, 'b' }, { "connect", 0, NULL, 'c' }, { "daemon", 0, NULL, 'd' }, { "engine", 1, NULL, 'e' }, { "format", 1, NULL, 'f' }, { "fork", 1, NULL, 'F' }, { "help", 0, NULL, 'h' }, { "listen", 0, NULL, 'l' }, //{ "iface", 1, NULL, 'i' }, //{ "interface", 1, NULL, 'i' }, { "max", 1, NULL, 'm' }, { "maxconn", 1, NULL, 'm' }, { "numeric", 0, NULL, 'n' }, { "output", 1, NULL, 'o' }, { "protocol", 1, NULL, 'p' }, { "quiet", 0, NULL, 'q' }, { "server", 1, NULL, 's' }, { "user", 1, NULL, 'u' }, { "verbose", 0, NULL, 'v' }, { "version", 0, NULL, 'V' }, { NULL, 0, NULL, 0 } }; DataLog *(*logmaker) (void) = NULL; while ((check = getopt_long (argc, argv, "a:b:cde:f:F:hlLm:no:p:qs:u:vV", longopts, NULL)) != EOF) { switch (check) { case 'a': conf->bridgename = optarg; 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_listen_client; if (conf->totalclients == -1) conf->totalclients = 1; break; case 'd': conf->mode |= tcpreen_daemon; break; case 'f': logmaker = findlogmakerbyname (optarg); if (logmaker == NULL) return error_gen (optarg, _("Unrecognized log format")); break; case 'F': { char *end; long num; num = strtoul (optarg, &end, 0); if (*end || (num > INT_MAX) || (num == 0)) return error_qty (optarg); conf->maxclients = (int)num; } break; case 'h': /* help */ return usage(); case 'l': conf->mode |= tcpreen_listen_server; break; case 'm': { char *end; conf->totalclients = strtol (optarg, &end, 0); if (*end) return error_qty (optarg); } break; case 'n': conf->mode |= tcpreen_numeric; break; case 'o': { if (logmaker == NULL) logmaker = default_format (1); int val = (strcmp (optarg, "-") ? logsmaker.AddLogMaker (logmaker, optarg) : logsmaker.AddLogMaker (logmaker)); if (val) { perror (_("Fatal error")); return MAIN_IOERR; } } break; case 'p': if (parse_proto (optarg, &conf->serveraf, &conf->bridgeaf)) return error_gen (optarg, N_("unknown or unsupported protocol(s)")); break; case 'q': verbose = 0; break; case 's': conf->servername = optarg; break; case 'u': if (conf->user) 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->user = 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 quick_usage (); } } // Reads service names/port numbers conf->serverservice = (optind < argc) ? argv[optind++] : NULL; conf->bridgeservice = (optind < argc) ? argv[optind++] : NULL; if (optind < argc) return error_extra (argv[optind]); // Handles verbosity setting if (conf->mode & tcpreen_daemon) verbose = 0; if (verbose) { conf->mode |= tcpreen_verbose; if (logmaker == NULL) logmaker = default_format (verbose > 1); int check = logsmaker.AddLogMaker (logmaker); if (check) { perror (_("Fatal error")); return MAIN_IOERR; } } /* * Sanity checks */ if ((conf->serverservice == NULL) && !(conf->mode & tcpreen_listen_server)) return error_gen (argv[0], N_("no server port specified")); if ((conf->bridgeservice == NULL) && !(conf->mode & tcpreen_listen_client)) return error_gen (argv[0], N_("no client port specified with -c option")); if (!(conf->mode & tcpreen_speak) && ((conf->bridgeservice == NULL) || (conf->serverservice == NULL))) return error_gen (argv[0], N_("dynamically allocated port but nowhere to tell you which one")); return MAIN_NOERR; } /* * Default security settings */ static int preconf_security (bridgeconf *conf) { uid_t user = getuid (), effuser = geteuid (); if (user == 0) { if (effuser) /* In case we are Real UID root and Set UID non-root */ user = effuser; else { /* Support for sudo (http://www.sudo.ws/) * (if, and only if, we are running as **REAL** * UID root) */ const char *sudo_user = getenv ("SUDO_USER"); if (sudo_user != NULL) { user = parse_user (sudo_user); if (user == (uid_t)(-1)) return -1; } } } conf->user = user; return 0; } /* * Main function */ #ifdef WINSOCK extern "C" #endif int main (int argc, char *argv[]) { struct bridgeconf conf; int val; /* Default settings */ memset (&conf, 0, sizeof (conf)); conf.bytelimit = -1; conf.totalclients = -1; conf.maxclients = 1; conf.mode = tcpreen_listen_client; if (preconf_security (&conf)) return 1; /* Use low privileges during initialization * * (in particular to open log files) */ if (seteuid (conf.user)) return 1; /* Now we can assume that "seteuid (conf.user)" will always work. */ /* Initialization */ setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); DataLogListMaker logsmaker; conf.logsmaker = &logsmaker; /* Command line parsing and checking */ val = parse_args (argc, argv, &conf, logsmaker); /* Let's come to serious things... */ if (!val) { // Opens system log if (conf.mode & tcpreen_daemon) { openlog ("tcpreen", LOG_PID, LOG_DAEMON); syslog (LOG_NOTICE, _("starting\n")); } val = bridge_main (&conf); if (conf.mode & tcpreen_daemon) { syslog (LOG_NOTICE, _("stopping\n")); closelog (); } } return (val >= 0) ? val : 0; }