3 * Port forwarding thingy
5 * (c) 1999 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the `fwd' port forwarder.
12 * `fwd' is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * `fwd' is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with `fwd'; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 /*----- Global variables --------------------------------------------------*/
31 sel_state *sel; /* Multiplexor for nonblocking I/O */
33 /*----- Static variables --------------------------------------------------*/
35 typedef struct conffile {
36 struct conffile *next;
40 static unsigned flags = 0; /* Global state flags */
41 static unsigned active = 0; /* Number of active things */
42 static conffile *conffiles = 0; /* List of configuration files */
48 /*----- Configuration parsing ---------------------------------------------*/
52 * Arguments: @scanner *sc@ = pointer to scanner definition
56 * Use: Parses a configuration file from the scanner.
59 static source_ops *sources[] =
60 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
61 static target_ops *targets[] =
62 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
64 void parse(scanner *sc)
69 if (sc->t == CTOK_EOF)
71 if (sc->t != CTOK_WORD)
72 error(sc, "parse error, keyword expected");
74 /* --- Handle a forwarding request --- */
76 if (strcmp(sc->d.buf, "forward") == 0 ||
77 strcmp(sc->d.buf, "fwd") == 0 ||
78 strcmp(sc->d.buf, "from") == 0) {
84 /* --- Read a source description --- */
89 /* --- Try to find a source type which understands --- */
92 for (sops = sources; *sops; sops++) {
93 if ((s = (*sops)->read(sc)) != 0)
96 error(sc, "unknown source name `%s'", sc->d.buf);
98 /* --- Read any source-specific options --- */
103 while (sc->t == CTOK_WORD) {
104 if (!s->ops->option || !s->ops->option(s, sc)) {
105 error(sc, "unknown %s source option `%s'",
106 s->ops->name, sc->d.buf);
112 error(sc, "parse error, missing `}'");
117 /* --- Read a destination description --- */
119 if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
120 strcmp(sc->d.buf, "->") == 0))
126 /* --- Try to find a target which understands --- */
129 for (tops = targets; *tops; tops++) {
130 if ((t = (*tops)->read(sc)) != 0)
133 error(sc, "unknown target name `%s'", sc->d.buf);
135 /* --- Read any target-specific options --- */
140 while (sc->t == CTOK_WORD) {
141 if (!t->ops->option || !t->ops->option(t, sc)) {
142 error(sc, "unknown %s target option `%s'",
143 t->ops->name, sc->d.buf);
149 error(sc, "parse error, `}' expected");
154 /* --- Combine the source and target --- */
156 s->ops->attach(s, sc, t);
161 /* --- Include configuration from a file --- *
163 * Slightly tricky. Scan the optional semicolon from the including
164 * stream, not the included one.
167 else if (strcmp(sc->d.buf, "include") == 0) {
172 conf_name(sc, '/', &d);
173 if ((fp = fopen(d.buf, "r")) == 0)
174 error(sc, "can't include `%s': %s", d.buf, strerror(errno));
178 scan_push(sc, scan_file(fp, d.buf, 0));
181 continue; /* Don't parse a trailing `;' */
184 /* --- Other configuration is handled elsewhere --- */
188 /* --- First try among the sources --- */
193 for (sops = sources; *sops; sops++) {
194 if ((*sops)->option && (*sops)->option(0, sc))
199 /* --- Then try among the targets --- */
204 for (tops = targets; *tops; tops++) {
205 if ((*tops)->option && (*tops)->option(0, sc))
210 /* --- Nobody wants the option --- */
212 error(sc, "unknown global option or prefix `%s'", sc->d.buf);
222 /*----- General utility functions -----------------------------------------*/
224 /* --- @fw_log@ --- *
226 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
227 * @const char *fmt@ = format string to fill in
228 * @...@ = other arguments
232 * Use: Logs a connection.
235 void fw_log(time_t t, const char *fmt, ...)
241 if (flags & FW_QUIET)
248 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
250 dstr_vputf(&d, fmt, &ap);
252 if (flags & FW_SYSLOG)
253 syslog(LOG_NOTICE, "%s", d.buf);
256 dstr_write(&d, stderr);
261 /* --- @fw_inc@, @fw_dec@ --- *
267 * Use: Increments or decrements the active thing count. `fwd' won't
268 * quit while there are active things.
271 void fw_inc(void) { flags |= FW_SET; active++; }
272 void fw_dec(void) { if (active) active--; }
274 /* --- @fw_exit@ --- *
280 * Use: Exits when appropriate.
283 static void fw_exit(void)
289 /* --- @fw_tidy@ --- *
291 * Arguments: @int n@ = signal number
292 * @void *p@ = an uninteresting argument
296 * Use: Handles various signals and causes a clean tidy-up.
299 static void fw_tidy(int n, void *p)
303 case SIGTERM: sn = "SIGTERM"; break;
304 case SIGINT: sn = "SIGINT"; break;
308 fw_log(NOW, "closing down gracefully on %s", sn);
312 /* --- @fw_die@ --- *
314 * Arguments: @int n@ = signal number
315 * @void *p@ = an uninteresting argument
319 * Use: Handles various signals and causes an abrupt shutdown.
322 static void fw_die(int n, void *p)
326 case SIGQUIT: sn = "SIGQUIT"; break;
330 fw_log(NOW, "closing down abruptly on %s", sn);
335 /* --- @fw_reload@ --- *
337 * Arguments: @int n@ = a signal number
338 * @void *p@ = an uninteresting argument
342 * Use: Handles a hangup signal by re-reading configuration files.
345 static void fw_reload(int n, void *p)
353 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
356 fw_log(NOW, "reloading configuration files...");
359 for (cf = conffiles; cf; cf = cf->next) {
360 if ((fp = fopen(cf->name, "r")) == 0)
361 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
363 scan_add(&sc, scan_file(fp, cf->name, 0));
366 fw_log(NOW, "... reload completed OK");
369 /*----- Startup and options parsing ---------------------------------------*/
371 /* --- Standard GNU help options --- */
373 static void version(FILE *fp)
375 pquis(fp, "$ version " VERSION "\n");
378 static void usage(FILE *fp)
380 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
383 static void help(FILE *fp)
389 An excessively full-featured port-forwarder, which subsumes large chunks\n\
390 of the functionality of inetd, netcat, and normal cat.\n\
392 Configuration may be supplied in one or more configuration files, or on\n\
393 the command line (or both). If no `-f' option is present, and no\n\
394 configuration is given on the command line, the standard input stream is\n\
397 Configuration is free-form. Comments begin with a `#' character and\n\
398 continue to the end of the line. Each command line argument is considered\n\
399 to be a separate line.\n\
401 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
402 For a summary of the various options, run `$ --options'.\n\
404 Options available are:\n\
407 -h, --help Display this help message.\n\
408 -v, --version Display the program's version number.\n\
409 -u, --usage Display a terse usage summary.\n\
411 Configuration summary:\n\
412 -G, --grammar Show a summary of the configuration language.\n\
413 -O, --options Show a summary of the source and target options.\n\
416 -f, --file=FILE Read configuration from a file.\n\
417 -q, --quiet Don't emit any logging information.\n\
418 -d, --daemon Fork into background after initializing.\n\
419 -p, --pidfile=FILE Write process-id to the named FILE.\n\
420 -l, --syslog Send log output to the system logger.\n\
421 -s, --setuid=USER Change uid to USER after initializing sources.\n\
422 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
426 /* --- Other helpful options --- */
428 static void grammar(FILE *fp)
431 fputs("\nGrammar summary\n\n", fp);
432 fputs(grammar_text, fp);
435 static void options(FILE *fp)
438 fputs("\nOption summary\n\n", fp);
439 fputs(option_text, fp);
444 * Arguments: @int argc@ = number of command line arguments
445 * @char *argv[]@ = vector of argument strings
449 * Use: Simple port-forwarding server.
452 int main(int argc, char *argv[])
456 sig s_term, s_quit, s_int, s_hup;
460 const char *pidfile = 0;
461 conffile *cf, **cff = &conffiles;
468 /* --- Initialize things --- */
478 fattr_init(&fattr_global);
483 /* --- Parse command line options --- */
486 static struct option opts[] = {
488 /* --- Standard GNU help options --- */
490 { "help", 0, 0, 'h' },
491 { "version", 0, 0, 'v' },
492 { "usage", 0, 0, 'u' },
494 /* --- Other help options --- */
496 { "grammar", 0, 0, 'G' },
497 { "options", 0, 0, 'O' },
499 /* --- Other useful arguments --- */
501 { "file", OPTF_ARGREQ, 0, 'f' },
502 { "fork", 0, 0, 'd' },
503 { "daemon", 0, 0, 'd' },
504 { "pidfile", OPTF_ARGREQ, 0, 'p' },
505 { "syslog", 0, 0, 'l' },
506 { "log", 0, 0, 'l' },
507 { "quiet", 0, 0, 'q' },
508 { "setuid", OPTF_ARGREQ, 0, 's' },
509 { "uid", OPTF_ARGREQ, 0, 's' },
510 { "setgid", OPTF_ARGREQ, 0, 'g' },
511 { "gid", OPTF_ARGREQ, 0, 'g' },
513 /* --- Magic terminator --- */
517 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
543 if (strcmp(optarg, "-") == 0)
544 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
547 if ((fp = fopen(optarg, "r")) == 0)
548 die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
549 cf = CREATE(conffile);
553 scan_add(&sc, scan_file(fp, optarg, 0));
570 if (isdigit((unsigned char )optarg[0])) {
572 drop = strtol(optarg, &q, 0);
574 die(1, "bad uid `%s'", optarg);
576 struct passwd *pw = getpwnam(optarg);
578 die(1, "unknown user `%s'", optarg);
583 if (isdigit((unsigned char )optarg[0])) {
585 dropg = strtol(optarg, &q, 0);
587 die(1, "bad gid `%s'", optarg);
589 struct group *gr = getgrnam(optarg);
591 die(1, "unknown group `%s'", optarg);
607 /* --- Deal with the remaining arguments --- */
610 scan_add(&sc, scan_argv(argv + optind));
613 else if (!isatty(STDIN_FILENO))
614 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
616 moan("no configuration given and stdin is a terminal.");
617 moan("type `%s --help' for usage information.", QUIS);
621 /* --- Parse the configuration now gathered --- */
625 /* --- Set up some signal handlers --- *
627 * Don't enable @SIGINT@ if the caller already disabled it.
633 sig_add(&s_term, SIGTERM, fw_tidy, 0);
634 sig_add(&s_quit, SIGQUIT, fw_die, 0);
635 sigaction(SIGINT, 0, &sa);
636 if (sa.sa_handler != SIG_IGN)
637 sig_add(&s_int, SIGINT, fw_tidy, 0);
638 sig_add(&s_hup, SIGHUP, fw_reload, 0);
641 /* --- Fork into the background --- */
648 die(1, "couldn't fork: %s", strerror(errno));
652 close(0); close(1); close(2);
654 die(1, "couldn't change to root directory: %s", strerror(errno));
662 FILE *fp = fopen(pidfile, "w");
664 die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
665 pidfile, strerror(errno));
667 fprintf(fp, "%lu\n", (unsigned long)getpid());
668 if (fflush(fp) || ferror(fp) || fclose(fp)) {
669 die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
670 pidfile, strerror(errno));
676 openlog(QUIS, 0, LOG_DAEMON);
679 /* --- Drop privileges --- */
681 if (drop != (uid_t)-1)
683 #ifdef HAVE_SETGROUPS
684 if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
685 (drop != (uid_t)-1 && setuid(drop)))
686 die(1, "couldn't drop privileges: %s", strerror(errno));
688 if ((dropg != (gid_t)-1 && setgid(dropg)) ||
689 (drop != (uid_t)-1 && setuid(drop)))
690 die(1, "couldn't drop privileges: %s", strerror(errno));
693 /* --- Let rip --- */
695 if (!(flags & FW_SET))
696 moan("nothing to do!");
697 signal(SIGPIPE, SIG_IGN);
702 if (!sel_select(sel))
704 else if (errno != EINTR && errno != EAGAIN) {
705 fw_log(NOW, "error from select: %s", strerror(errno));
708 fw_log(NOW, "too many consecutive select errors: bailing out");
718 /*----- That's all, folks -------------------------------------------------*/