3 * Port forwarding thingy
5 * (c) 1999 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the `fw' port forwarder.
12 * `fw' 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 * `fw' 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 `fw'; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 /*----- Header files ------------------------------------------------------*/
47 #include <mLib/bres.h>
48 #include <mLib/dstr.h>
49 #include <mLib/mdwopt.h>
50 #include <mLib/quis.h>
51 #include <mLib/report.h>
68 /*----- Global variables --------------------------------------------------*/
70 sel_state *sel; /* Multiplexor for nonblocking I/O */
72 /*----- Static variables --------------------------------------------------*/
74 typedef struct conffile {
75 struct conffile *next;
79 static unsigned flags = 0; /* Global state flags */
80 static unsigned active = 0; /* Number of active things */
81 static conffile *conffiles = 0; /* List of configuration files */
87 /*----- Configuration parsing ---------------------------------------------*/
91 * Arguments: @scanner *sc@ = pointer to scanner definition
95 * Use: Parses a configuration file from the scanner.
98 static source_ops *sources[] =
99 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
100 static target_ops *targets[] =
101 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
103 void parse(scanner *sc)
108 if (sc->t == CTOK_EOF)
110 if (sc->t != CTOK_WORD)
111 error(sc, "parse error, keyword expected");
113 /* --- Handle a forwarding request --- */
115 if (strcmp(sc->d.buf, "forward") == 0 ||
116 strcmp(sc->d.buf, "fw") == 0 ||
117 strcmp(sc->d.buf, "from") == 0) {
123 /* --- Read a source description --- */
128 /* --- Try to find a source type which understands --- */
131 for (sops = sources; *sops; sops++) {
132 if ((s = (*sops)->read(sc)) != 0)
135 error(sc, "unknown source name `%s'", sc->d.buf);
137 /* --- Read any source-specific options --- */
142 while (sc->t == CTOK_WORD) {
143 if (!s->ops->option || !s->ops->option(s, sc)) {
144 error(sc, "unknown %s source option `%s'",
145 s->ops->name, sc->d.buf);
151 error(sc, "parse error, missing `}'");
156 /* --- Read a destination description --- */
158 if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
159 strcmp(sc->d.buf, "->") == 0))
165 /* --- Try to find a target which understands --- */
168 for (tops = targets; *tops; tops++) {
169 if ((t = (*tops)->read(sc)) != 0)
172 error(sc, "unknown target name `%s'", sc->d.buf);
174 /* --- Read any target-specific options --- */
179 while (sc->t == CTOK_WORD) {
180 if (!t->ops->option || !t->ops->option(t, sc)) {
181 error(sc, "unknown %s target option `%s'",
182 t->ops->name, sc->d.buf);
188 error(sc, "parse error, `}' expected");
193 /* --- Combine the source and target --- */
195 s->ops->attach(s, sc, t);
200 /* --- Include configuration from a file --- *
202 * Slightly tricky. Scan the optional semicolon from the including
203 * stream, not the included one.
206 else if (strcmp(sc->d.buf, "include") == 0) {
211 conf_name(sc, '/', &d);
212 if ((fp = fopen(d.buf, "r")) == 0)
213 error(sc, "can't include `%s': %s", d.buf, strerror(errno));
217 scan_push(sc, scan_file(fp, d.buf, 0));
220 continue; /* Don't parse a trailing `;' */
223 /* --- Other configuration is handled elsewhere --- */
227 /* --- First try among the sources --- */
232 for (sops = sources; *sops; sops++) {
233 if ((*sops)->option && (*sops)->option(0, sc))
238 /* --- Then try among the targets --- */
243 for (tops = targets; *tops; tops++) {
244 if ((*tops)->option && (*tops)->option(0, sc))
249 /* --- Nobody wants the option --- */
251 error(sc, "unknown global option or prefix `%s'", sc->d.buf);
261 /*----- General utility functions -----------------------------------------*/
263 /* --- @fw_log@ --- *
265 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
266 * @const char *fmt@ = format string to fill in
267 * @...@ = other arguments
271 * Use: Logs a connection.
274 void fw_log(time_t t, const char *fmt, ...)
280 if (flags & FW_QUIET)
287 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
289 dstr_vputf(&d, fmt, &ap);
291 if (flags & FW_SYSLOG)
292 syslog(LOG_NOTICE, "%s", d.buf);
295 dstr_write(&d, stderr);
300 /* --- @fw_inc@, @fw_dec@ --- *
306 * Use: Increments or decrements the active thing count. `fw' won't
307 * quit while there are active things.
310 void fw_inc(void) { flags |= FW_SET; active++; }
311 void fw_dec(void) { if (active) active--; }
313 /* --- @fw_exit@ --- *
319 * Use: Exits when appropriate.
322 static void fw_exit(void)
328 /* --- @fw_tidy@ --- *
330 * Arguments: @int n@ = signal number
331 * @void *p@ = an uninteresting argument
335 * Use: Handles various signals and causes a clean tidy-up.
338 static void fw_tidy(int n, void *p)
342 case SIGTERM: sn = "SIGTERM"; break;
343 case SIGINT: sn = "SIGINT"; break;
347 fw_log(-1, "closing down gracefully on %s", sn);
351 /* --- @fw_die@ --- *
353 * Arguments: @int n@ = signal number
354 * @void *p@ = an uninteresting argument
358 * Use: Handles various signals and causes an abrupt shutdown.
361 static void fw_die(int n, void *p)
365 case SIGQUIT: sn = "SIGQUIT"; break;
369 fw_log(-1, "closing down abruptly on %s", sn);
374 /* --- @fw_reload@ --- *
376 * Arguments: @int n@ = a signal number
377 * @void *p@ = an uninteresting argument
381 * Use: Handles a hangup signal by re-reading configuration files.
384 static void fw_reload(int n, void *p)
392 fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
395 fw_log(-1, "reloading configuration files...");
398 for (cf = conffiles; cf; cf = cf->next) {
399 if ((fp = fopen(cf->name, "r")) == 0)
400 fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
402 scan_add(&sc, scan_file(fp, cf->name, 0));
405 fw_log(-1, "... reload completed OK");
408 /*----- Startup and options parsing ---------------------------------------*/
410 /* --- Standard GNU help options --- */
412 static void version(FILE *fp)
414 pquis(fp, "$ version " VERSION "\n");
417 static void usage(FILE *fp)
419 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
422 static void help(FILE *fp)
428 An excessively full-featured port-forwarder, which subsumes large chunks\n\
429 of the functionality of inetd, netcat, and normal cat.\n\
431 Configuration may be supplied in one or more configuration files, or on\n\
432 the command line (or both). If no `-f' option is present, and no\n\
433 configuration is given on the command line, the standard input stream is\n\
436 Configuration is free-form. Comments begin with a `#' character and\n\
437 continue to the end of the line. Each command line argument is considered\n\
438 to be a separate line.\n\
440 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
441 For a summary of the various options, run `$ --options'.\n\
443 Options available are:\n\
446 -h, --help Display this help message.\n\
447 -v, --version Display the program's version number.\n\
448 -u, --usage Display a terse usage summary.\n\
450 Configuration summary:\n\
451 -G, --grammar Show a summary of the configuration language.\n\
452 -O, --options Show a summary of the source and target options.\n\
455 -f, --file=FILE Read configuration from a file.\n\
456 -q, --quiet Don't emit any logging information.\n\
457 -d, --daemon Fork into background after initializing.\n\
458 -p, --pidfile=FILE Write process-id to the named FILE.\n\
459 -l, --syslog Send log output to the system logger.\n\
460 -s, --setuid=USER Change uid to USER after initializing sources.\n\
461 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
465 /* --- Other helpful options --- */
467 static void grammar(FILE *fp)
470 fputs("\nGrammar summary\n\n", fp);
471 fputs(grammar_text, fp);
474 static void options(FILE *fp)
477 fputs("\nOption summary\n\n", fp);
478 fputs(option_text, fp);
483 * Arguments: @int argc@ = number of command line arguments
484 * @char *argv[]@ = vector of argument strings
488 * Use: Simple port-forwarding server.
491 int main(int argc, char *argv[])
495 sig s_term, s_quit, s_int, s_hup;
499 const char *pidfile = 0;
500 conffile *cf, **cff = &conffiles;
507 /* --- Initialize things --- */
517 fattr_init(&fattr_global);
522 /* --- Parse command line options --- */
525 static struct option opts[] = {
527 /* --- Standard GNU help options --- */
529 { "help", 0, 0, 'h' },
530 { "version", 0, 0, 'v' },
531 { "usage", 0, 0, 'u' },
533 /* --- Other help options --- */
535 { "grammar", 0, 0, 'G' },
536 { "options", 0, 0, 'O' },
538 /* --- Other useful arguments --- */
540 { "file", OPTF_ARGREQ, 0, 'f' },
541 { "fork", 0, 0, 'd' },
542 { "daemon", 0, 0, 'd' },
543 { "pidfile", OPTF_ARGREQ, 0, 'p' },
544 { "syslog", 0, 0, 'l' },
545 { "log", 0, 0, 'l' },
546 { "quiet", 0, 0, 'q' },
547 { "setuid", OPTF_ARGREQ, 0, 's' },
548 { "uid", OPTF_ARGREQ, 0, 's' },
549 { "setgid", OPTF_ARGREQ, 0, 'g' },
550 { "gid", OPTF_ARGREQ, 0, 'g' },
552 /* --- Magic terminator --- */
556 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
582 if (strcmp(optarg, "-") == 0)
583 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
586 if ((fp = fopen(optarg, "r")) == 0)
587 die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
588 cf = CREATE(conffile);
592 scan_add(&sc, scan_file(fp, optarg, 0));
609 if (isdigit((unsigned char )optarg[0])) {
611 drop = strtol(optarg, &q, 0);
613 die(1, "bad uid `%s'", optarg);
615 struct passwd *pw = getpwnam(optarg);
617 die(1, "unknown user `%s'", optarg);
622 if (isdigit((unsigned char )optarg[0])) {
624 dropg = strtol(optarg, &q, 0);
626 die(1, "bad gid `%s'", optarg);
628 struct group *gr = getgrnam(optarg);
630 die(1, "unknown group `%s'", optarg);
646 /* --- Deal with the remaining arguments --- */
649 scan_add(&sc, scan_argv(argv + optind));
652 else if (!isatty(STDIN_FILENO))
653 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
655 moan("no configuration given and stdin is a terminal.");
656 moan("type `%s --help' for usage information.", QUIS);
660 /* --- Parse the configuration now gathered --- */
664 /* --- Set up some signal handlers --- *
666 * Don't enable @SIGINT@ if the caller already disabled it.
672 sig_add(&s_term, SIGTERM, fw_tidy, 0);
673 sig_add(&s_quit, SIGQUIT, fw_die, 0);
674 sigaction(SIGINT, 0, &sa);
675 if (sa.sa_handler != SIG_IGN)
676 sig_add(&s_int, SIGINT, fw_tidy, 0);
677 sig_add(&s_hup, SIGHUP, fw_reload, 0);
680 /* --- Fork into the background --- */
687 die(1, "couldn't fork: %s", strerror(errno));
691 close(0); close(1); close(2);
700 FILE *fp = fopen(pidfile, "w");
702 die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
703 pidfile, strerror(errno));
705 fprintf(fp, "%lu\n", (unsigned long)getpid());
706 if (fflush(fp) || ferror(fp) || fclose(fp)) {
707 die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
708 pidfile, strerror(errno));
714 openlog(QUIS, 0, LOG_DAEMON);
717 /* --- Drop privileges --- */
719 if (drop != (uid_t)-1)
721 #ifdef HAVE_SETGROUPS
722 if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
723 (drop != (uid_t)-1 && setuid(drop)))
724 die(1, "couldn't drop privileges: %s", strerror(errno));
726 if ((dropg != (gid_t)-1 && setgid(dropg)) ||
727 (drop != (uid_t)-1 && setuid(drop)))
728 die(1, "couldn't drop privileges: %s", strerror(errno));
731 /* --- Let rip --- */
733 if (!(flags & FW_SET))
734 moan("nothing to do!");
735 signal(SIGPIPE, SIG_IGN);
740 if (!sel_select(sel))
742 else if (errno != EINTR && errno != EAGAIN) {
743 fw_log(-1, "error from select: %s", strerror(errno));
746 fw_log(-1, "too many consecutive select errors: bailing out");
756 /*----- That's all, folks -------------------------------------------------*/