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