1 /*
2 * main.cpp - main() function for tcpreen - command line parsing and
3 * basic sanity checks.
4 * $Id: main.cpp 190 2006-03-18 20:16:14Z remi $
5 */
6
7 /***********************************************************************
8 * Copyright (C) 2002-2005 Remi Denis-Courmont. *
9 * This program is free software; you can redistribute and/or modify *
10 * it under the terms of the GNU General Public License as published *
11 * by the Free Software Foundation; version 2 of the license. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
16 * See the GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, you can get it from: *
20 * http://www.gnu.org/copyleft/gpl.html *
21 ***********************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include "secstdio.h"
28 #include <stdlib.h> /* getenv(), strtol(), stroul() */
29 #include <string.h> /* memset() */
30 #include <limits.h> /* LONG_MAX, INT_MAX */
31
32 #include <sys/types.h>
33 #include <unistd.h> /* geteuid(), getuid(), seteuid() */
34 #ifdef HAVE_GETOPT_H
35 /*
36 * Temporary dirty fix for C++-incompatible GNU <getopt.h>
37 * on non-GNU platforms (namely FreeBSD+libgnugetopt).
38 *
39 * I hope I can get rid of that silly thing soon.
40 */
41 # if (defined(HAVE_GETOPT_LONG) && !defined(__GNU_LIBRARY__))
42 # define getopt gnugetopt_broken
43 # endif
44 # include <getopt.h> /* getopt_long() */
45 # undef getopt
46 #endif
47 #ifdef HAVE_PWD_H
48 # include <pwd.h> /* getpwnam() */
49 #endif
50 #ifdef HAVE_SYSLOG_H
51 # include <syslog.h>
52 #endif
53 #ifdef HAVE_LOCALE_H
54 # include <locale.h>
55 #endif
56 #include "gettext.h"
57
58 #include "tcpreen.h"
59 #include "format.h"
60 #include "proto.h"
61
62 // Possible return values
63 #define MAIN_NOERR 0
64 #define MAIN_SHORTCIRCUIT -1 // for internal use
65 #define MAIN_PARMPROB 1 // parameter problem
66 #define MAIN_IOERR 2 // I/O error
67
68 static int
usage(void)69 usage (void)
70 {
71 puts (_(
72 "Usage: tcpreen [OPTION]... SERVER_PORT [LOCAL_PORT]\n"
73 "Establishes a bridge between two TCP ports then monitors TCP sessions.\n"
74 "\n"
75 " -a, --bind listen for connections on the following address\n"
76 " -b, --bytes limit maximum sessions length in bytes; e.g.: -b 1024\n"
77 " -c, --connect connect to the client instead of listening for it\n"
78 " -d, --daemon run in the background\n"
79 " -f, --format selects log file(s) output format\n"
80 " -F, --fork specify number of client processes to spawn\n"
81 " -h, --help display this help and exit\n"
82 " -l, --listen listen for the server instead of connecting to it\n"
83 " -m, --maxconn limit number of monitored connections (faster)\n"
84 " -n, --numeric disable reverse DNS lookup\n"
85 /*
86 " -i, --iface specify which network interface(s) to use (if applicable)\n"
87 */
88 " -o, --output open a log file; e.g.: -o mylog.txt\n"
89 " -p, --protocol use the following protocol for connections\n"
90 " -q, --quiet do not write to stdout\n"
91 " -s, --server connect to this host (local host by default)\n"
92 " -u, --user specify an unprivilieged username to be used\n"
93 " -v, --verbose increase verbosity - cumulative\n"
94 " -V, --version display program version and exit\n"));
95
96 fprint_proto_list (stdout);
97 fprint_format_list (stdout);
98 fputc ('\n', stdout);
99
100 printf (_("Report any bug to: <%s>.\n"), PACKAGE_BUGREPORT);
101 return MAIN_SHORTCIRCUIT;
102 }
103
104 static int
version(void)105 version (void)
106 {
107 #ifndef VERSION
108 # define VERSION "unknown version"
109 #endif
110 puts (
111 "TCP re-engineering tool " VERSION "\n"
112 "Copyright (C) 2002-2004 Remi Denis-Courmont");
113 puts (_(
114 "This is free software; see the source for copying conditions.\n"
115 "There is NO warranty; not even for MERCHANTABILITY or\n"
116 "FITNESS FOR A PARTICULAR PURPOSE.\n"));
117 printf (_("Written by %s.\nConfigured with: %s\n"), "Remi Denis-Courmont",
118 PACKAGE_CONFIGURE_INVOCATION);
119 return MAIN_SHORTCIRCUIT;
120 }
121
122
123 static int
quick_usage(void)124 quick_usage (void)
125 {
126 fputs (_("Try \"tcpreeen -h | more\" for more information.\n"), stderr);
127 return MAIN_PARMPROB;
128 }
129
130
131 /*
132 * Generic error handling
133 */
134 static int
error_gen(const char * path,const char * msg)135 error_gen (const char *path, const char *msg)
136 {
137 fprintf (stderr, "%s: %s.\n", path, _(msg));
138 return quick_usage ();
139 }
140
141
142 static int
error_qty(const char * quantity)143 error_qty (const char *quantity)
144 {
145 return error_gen (quantity,
146 N_("invalid number (or capacity exceeded)"));
147 }
148
149
150 static int
error_extra(const char * extra)151 error_extra (const char *extra)
152 {
153 return error_gen (extra, N_("unexpected extra parameter"));
154 }
155
156
157 /*
158 * Parses an user name.
159 * Returns (uid_t)(-1) on failure.
160 */
161 static uid_t
parse_user(const char * username)162 parse_user (const char *username)
163 {
164 if ((username == NULL) || (username[0] == 0))
165 return (uid_t)(-1);
166
167 struct passwd *pw = getpwnam (username);
168 return (pw != NULL) ? pw->pw_uid : (uid_t)(-1);
169 }
170
171
172 /*
173 * Command line options parsing.
174 * argc, argv and loglist must be kept valid at all cost.
175 */
176 #define VERBOSE_MAX 10
177 static int
parse_args(int argc,char * argv[],struct bridgeconf * conf,DataLogListMaker & logsmaker)178 parse_args (int argc, char *argv[], struct bridgeconf *conf,
179 DataLogListMaker& logsmaker)
180 {
181 int check, verbose = 1;
182 struct option longopts[] =
183 {
184 { "accept", 1, NULL, 'a' },
185 { "bind", 1, NULL, 'a' },
186 { "bytes", 1, NULL, 'b' },
187 { "connect", 0, NULL, 'c' },
188 { "daemon", 0, NULL, 'd' },
189 { "engine", 1, NULL, 'e' },
190 { "format", 1, NULL, 'f' },
191 { "fork", 1, NULL, 'F' },
192 { "help", 0, NULL, 'h' },
193 { "listen", 0, NULL, 'l' },
194 //{ "iface", 1, NULL, 'i' },
195 //{ "interface", 1, NULL, 'i' },
196 { "max", 1, NULL, 'm' },
197 { "maxconn", 1, NULL, 'm' },
198 { "numeric", 0, NULL, 'n' },
199 { "output", 1, NULL, 'o' },
200 { "protocol", 1, NULL, 'p' },
201 { "quiet", 0, NULL, 'q' },
202 { "server", 1, NULL, 's' },
203 { "user", 1, NULL, 'u' },
204 { "verbose", 0, NULL, 'v' },
205 { "version", 0, NULL, 'V' },
206 { NULL, 0, NULL, 0 }
207 };
208 DataLog *(*logmaker) (void) = NULL;
209
210 while ((check = getopt_long (argc, argv,
211 "a:b:cde:f:F:hlLm:no:p:qs:u:vV", longopts, NULL)) != EOF)
212 {
213 switch (check)
214 {
215 case 'a':
216 conf->bridgename = optarg;
217 break;
218
219 case 'b':
220 {
221 char *end;
222 long lim;
223
224 lim = strtol (optarg, &end, 0);
225 if ((*end) || (lim < 0) || (lim == LONG_MAX))
226 return error_qty (optarg);
227 conf->bytelimit = lim;
228 }
229 break;
230
231 case 'c':
232 conf->mode &= ~tcpreen_listen_client;
233 if (conf->totalclients == -1)
234 conf->totalclients = 1;
235 break;
236
237 case 'd':
238 conf->mode |= tcpreen_daemon;
239 break;
240
241 case 'f':
242 logmaker = findlogmakerbyname (optarg);
243 if (logmaker == NULL)
244 return error_gen (optarg, _("Unrecognized log format"));
245 break;
246
247 case 'F':
248 {
249 char *end;
250 long num;
251
252 num = strtoul (optarg, &end, 0);
253 if (*end || (num > INT_MAX) || (num == 0))
254 return error_qty (optarg);
255 conf->maxclients = (int)num;
256 }
257 break;
258
259 case 'h': /* help */
260 return usage();
261
262 case 'l':
263 conf->mode |= tcpreen_listen_server;
264 break;
265
266 case 'm':
267 {
268 char *end;
269 conf->totalclients = strtol (optarg,
270 &end, 0);
271 if (*end)
272 return error_qty (optarg);
273 }
274 break;
275
276 case 'n':
277 conf->mode |= tcpreen_numeric;
278 break;
279
280 case 'o':
281 {
282 if (logmaker == NULL)
283 logmaker = default_format (1);
284
285 int val = (strcmp (optarg, "-") ?
286 logsmaker.AddLogMaker (logmaker, optarg)
287 : logsmaker.AddLogMaker (logmaker));
288
289 if (val)
290 {
291 perror (_("Fatal error"));
292 return MAIN_IOERR;
293 }
294 }
295 break;
296
297 case 'p':
298 if (parse_proto (optarg, &conf->serveraf,
299 &conf->bridgeaf))
300 return error_gen (optarg, N_("unknown or unsupported protocol(s)"));
301
302 break;
303
304 case 'q':
305 verbose = 0;
306 break;
307
308 case 's':
309 conf->servername = optarg;
310 break;
311
312 case 'u':
313 if (conf->user)
314 return error_gen (optarg, N_("only root can select an user"));
315 else
316 {
317 uid_t uid = parse_user (optarg);
318 if (uid == (uid_t)(-1))
319 return error_gen (optarg, N_("invalid user"));
320 conf->user = uid;
321 }
322 break;
323
324 case 'v':
325 verbose ++;
326 if (verbose > VERBOSE_MAX)
327 verbose = VERBOSE_MAX;
328 break;
329
330 case 'V':
331 return version();
332
333 default: // never happens
334 case '?': // error: unrecognized option
335 return quick_usage ();
336 }
337 }
338
339 // Reads service names/port numbers
340 conf->serverservice = (optind < argc) ? argv[optind++] : NULL;
341 conf->bridgeservice = (optind < argc) ? argv[optind++] : NULL;
342
343 if (optind < argc)
344 return error_extra (argv[optind]);
345
346 // Handles verbosity setting
347 if (conf->mode & tcpreen_daemon)
348 verbose = 0;
349 if (verbose)
350 {
351 conf->mode |= tcpreen_verbose;
352 if (logmaker == NULL)
353 logmaker = default_format (verbose > 1);
354
355 int check = logsmaker.AddLogMaker (logmaker);
356 if (check)
357 {
358 perror (_("Fatal error"));
359 return MAIN_IOERR;
360 }
361 }
362
363 /*
364 * Sanity checks
365 */
366 if ((conf->serverservice == NULL)
367 && !(conf->mode & tcpreen_listen_server))
368 return error_gen (argv[0], N_("no server port specified"));
369 if ((conf->bridgeservice == NULL)
370 && !(conf->mode & tcpreen_listen_client))
371 return error_gen (argv[0], N_("no client port specified with -c option"));
372 if (!(conf->mode & tcpreen_speak)
373 && ((conf->bridgeservice == NULL) || (conf->serverservice == NULL)))
374 return error_gen (argv[0], N_("dynamically allocated port but nowhere to tell you which one"));
375
376 return MAIN_NOERR;
377 }
378
379
380 /*
381 * Default security settings
382 */
383 static int
preconf_security(bridgeconf * conf)384 preconf_security (bridgeconf *conf)
385 {
386 uid_t user = getuid (), effuser = geteuid ();
387
388 if (user == 0)
389 {
390 if (effuser)
391 /* In case we are Real UID root and Set UID non-root */
392 user = effuser;
393 else
394 {
395 /* Support for sudo (http://www.sudo.ws/)
396 * (if, and only if, we are running as **REAL**
397 * UID root) */
398 const char *sudo_user = getenv ("SUDO_USER");
399 if (sudo_user != NULL)
400 {
401 user = parse_user (sudo_user);
402 if (user == (uid_t)(-1))
403 return -1;
404 }
405 }
406 }
407
408 conf->user = user;
409 return 0;
410 }
411
412
413 /*
414 * Main function
415 */
416 #ifdef WINSOCK
417 extern "C"
418 #endif
main(int argc,char * argv[])419 int main (int argc, char *argv[])
420 {
421 struct bridgeconf conf;
422 int val;
423
424 /* Default settings */
425 memset (&conf, 0, sizeof (conf));
426 conf.bytelimit = -1;
427 conf.totalclients = -1;
428 conf.maxclients = 1;
429 conf.mode = tcpreen_listen_client;
430 if (preconf_security (&conf))
431 return 1;
432
433 /* Use low privileges during initialization *
434 * (in particular to open log files) */
435 if (seteuid (conf.user))
436 return 1;
437 /* Now we can assume that "seteuid (conf.user)" will always work. */
438
439 /* Initialization */
440 setlocale (LC_ALL, "");
441 bindtextdomain (PACKAGE, LOCALEDIR);
442 textdomain (PACKAGE);
443
444 DataLogListMaker logsmaker;
445 conf.logsmaker = &logsmaker;
446
447 /* Command line parsing and checking */
448 val = parse_args (argc, argv, &conf, logsmaker);
449
450 /* Let's come to serious things... */
451 if (!val)
452 {
453 // Opens system log
454 if (conf.mode & tcpreen_daemon)
455 {
456 openlog ("tcpreen", LOG_PID, LOG_DAEMON);
457 syslog (LOG_NOTICE, _("starting\n"));
458 }
459
460 val = bridge_main (&conf);
461
462 if (conf.mode & tcpreen_daemon)
463 {
464 syslog (LOG_NOTICE, _("stopping\n"));
465 closelog ();
466 }
467 }
468
469 return (val >= 0) ? val : 0;
470 }
471