5 * Port forwarding thingy
7 * (c) 1999 Straylight/Edgeware
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of the `fw' port forwarder.
14 * `fw' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * `fw' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with `fw'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 /*----- Header files ------------------------------------------------------*/
49 #include <mLib/bres.h>
50 #include <mLib/dstr.h>
51 #include <mLib/mdwopt.h>
52 #include <mLib/quis.h>
53 #include <mLib/report.h>
70 /*----- Global variables --------------------------------------------------*/
72 sel_state *sel; /* Multiplexor for nonblocking I/O */
74 /*----- Static variables --------------------------------------------------*/
76 typedef struct conffile {
77 struct conffile *next;
81 static unsigned flags = 0; /* Global state flags */
82 static unsigned active = 0; /* Number of active things */
83 static conffile *conffiles = 0; /* List of configuration files */
89 /*----- Configuration parsing ---------------------------------------------*/
93 * Arguments: @scanner *sc@ = pointer to scanner definition
97 * Use: Parses a configuration file from the scanner.
100 static source_ops *sources[] =
101 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
102 static target_ops *targets[] =
103 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
105 void parse(scanner *sc)
110 if (sc->t == CTOK_EOF)
112 if (sc->t != CTOK_WORD)
113 error(sc, "parse error, keyword expected");
115 /* --- Handle a forwarding request --- */
117 if (strcmp(sc->d.buf, "forward") == 0 ||
118 strcmp(sc->d.buf, "fw") == 0 ||
119 strcmp(sc->d.buf, "from") == 0) {
125 /* --- Read a source description --- */
130 /* --- Try to find a source type which understands --- */
133 for (sops = sources; *sops; sops++) {
134 if ((s = (*sops)->read(sc)) != 0)
137 error(sc, "unknown source name `%s'", sc->d.buf);
139 /* --- Read any source-specific options --- */
144 while (sc->t == CTOK_WORD) {
145 if (!s->ops->option || !s->ops->option(s, sc)) {
146 error(sc, "unknown %s source option `%s'",
147 s->ops->name, sc->d.buf);
153 error(sc, "parse error, missing `}'");
158 /* --- Read a destination description --- */
160 if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
161 strcmp(sc->d.buf, "->") == 0))
167 /* --- Try to find a target which understands --- */
170 for (tops = targets; *tops; tops++) {
171 if ((t = (*tops)->read(sc)) != 0)
174 error(sc, "unknown target name `%s'", sc->d.buf);
176 /* --- Read any target-specific options --- */
181 while (sc->t == CTOK_WORD) {
182 if (!t->ops->option || !t->ops->option(t, sc)) {
183 error(sc, "unknown %s target option `%s'",
184 t->ops->name, sc->d.buf);
190 error(sc, "parse error, `}' expected");
195 /* --- Combine the source and target --- */
197 s->ops->attach(s, sc, t);
202 /* --- Include configuration from a file --- *
204 * Slightly tricky. Scan the optional semicolon from the including
205 * stream, not the included one.
208 else if (strcmp(sc->d.buf, "include") == 0) {
213 conf_name(sc, '/', &d);
214 if ((fp = fopen(d.buf, "r")) == 0)
215 error(sc, "can't include `%s': %s", d.buf, strerror(errno));
219 scan_push(sc, scan_file(fp, d.buf, 0));
222 continue; /* Don't parse a trailing `;' */
225 /* --- Other configuration is handled elsewhere --- */
229 /* --- First try among the sources --- */
234 for (sops = sources; *sops; sops++) {
235 if ((*sops)->option && (*sops)->option(0, sc))
240 /* --- Then try among the targets --- */
245 for (tops = targets; *tops; tops++) {
246 if ((*tops)->option && (*tops)->option(0, sc))
251 /* --- Nobody wants the option --- */
253 error(sc, "unknown global option or prefix `%s'", sc->d.buf);
263 /*----- General utility functions -----------------------------------------*/
265 /* --- @fw_log@ --- *
267 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
268 * @const char *fmt@ = format string to fill in
269 * @...@ = other arguments
273 * Use: Logs a connection.
276 void fw_log(time_t t, const char *fmt, ...)
282 if (flags & FW_QUIET)
289 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
291 dstr_vputf(&d, fmt, &ap);
293 if (flags & FW_SYSLOG)
294 syslog(LOG_NOTICE, "%s", d.buf);
297 dstr_write(&d, stderr);
302 /* --- @fw_inc@, @fw_dec@ --- *
308 * Use: Increments or decrements the active thing count. `fw' won't
309 * quit while there are active things.
312 void fw_inc(void) { flags |= FW_SET; active++; }
313 void fw_dec(void) { if (active) active--; }
315 /* --- @fw_exit@ --- *
321 * Use: Exits when appropriate.
324 static void fw_exit(void)
330 /* --- @fw_tidy@ --- *
332 * Arguments: @int n@ = signal number
333 * @void *p@ = an uninteresting argument
337 * Use: Handles various signals and causes a clean tidy-up.
340 static void fw_tidy(int n, void *p)
344 case SIGTERM: sn = "SIGTERM"; break;
345 case SIGINT: sn = "SIGINT"; break;
349 fw_log(-1, "closing down gracefully on %s", sn);
353 /* --- @fw_die@ --- *
355 * Arguments: @int n@ = signal number
356 * @void *p@ = an uninteresting argument
360 * Use: Handles various signals and causes an abrupt shutdown.
363 static void fw_die(int n, void *p)
367 case SIGQUIT: sn = "SIGQUIT"; break;
371 fw_log(-1, "closing down abruptly on %s", sn);
376 /* --- @fw_reload@ --- *
378 * Arguments: @int n@ = a signal number
379 * @void *p@ = an uninteresting argument
383 * Use: Handles a hangup signal by re-reading configuration files.
386 static void fw_reload(int n, void *p)
394 fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
397 fw_log(-1, "reloading configuration files...");
400 for (cf = conffiles; cf; cf = cf->next) {
401 if ((fp = fopen(cf->name, "r")) == 0)
402 fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
404 scan_add(&sc, scan_file(fp, cf->name, 0));
407 fw_log(-1, "... reload completed OK");
410 /*----- Startup and options parsing ---------------------------------------*/
412 /* --- Standard GNU help options --- */
414 static void version(FILE *fp)
416 pquis(fp, "$ version " VERSION "\n");
419 static void usage(FILE *fp)
421 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
424 static void help(FILE *fp)
430 An excessively full-featured port-forwarder, which subsumes large chunks\n\
431 of the functionality of inetd, netcat, and normal cat.\n\
433 Configuration may be supplied in one or more configuration files, or on\n\
434 the command line (or both). If no `-f' option is present, and no\n\
435 configuration is given on the command line, the standard input stream is\n\
438 Configuration is free-form. Comments begin with a `#' character and\n\
439 continue to the end of the line. Each command line argument is considered\n\
440 to be a separate line.\n\
442 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
443 For a summary of the various options, run `$ --options'.\n\
445 Options available are:\n\
448 -h, --help Display this help message.\n\
449 -v, --version Display the program's version number.\n\
450 -u, --usage Display a terse usage summary.\n\
452 Configuration summary:\n\
453 -G, --grammar Show a summary of the configuration language.\n\
454 -O, --options Show a summary of the source and target options.\n\
457 -f, --file=FILE Read configuration from a file.\n\
458 -q, --quiet Don't emit any logging information.\n\
459 -d, --daemon Fork into background after initializing.\n\
460 -p, --pidfile=FILE Write process-id to the named FILE.\n\
461 -l, --syslog Send log output to the system logger.\n\
462 -s, --setuid=USER Change uid to USER after initializing sources.\n\
463 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
467 /* --- Other helpful options --- */
469 static void grammar(FILE *fp)
472 fputs("\nGrammar summary\n\n", fp);
473 fputs(grammar_text, fp);
476 static void options(FILE *fp)
479 fputs("\nOption summary\n\n", fp);
480 fputs(option_text, fp);
485 * Arguments: @int argc@ = number of command line arguments
486 * @char *argv[]@ = vector of argument strings
490 * Use: Simple port-forwarding server.
493 int main(int argc, char *argv[])
497 sig s_term, s_quit, s_int, s_hup;
501 const char *pidfile = 0;
502 conffile *cf, **cff = &conffiles;
509 /* --- Initialize things --- */
519 fattr_init(&fattr_global);
524 /* --- Parse command line options --- */
527 static struct option opts[] = {
529 /* --- Standard GNU help options --- */
531 { "help", 0, 0, 'h' },
532 { "version", 0, 0, 'v' },
533 { "usage", 0, 0, 'u' },
535 /* --- Other help options --- */
537 { "grammar", 0, 0, 'G' },
538 { "options", 0, 0, 'O' },
540 /* --- Other useful arguments --- */
542 { "file", OPTF_ARGREQ, 0, 'f' },
543 { "fork", 0, 0, 'd' },
544 { "daemon", 0, 0, 'd' },
545 { "pidfile", OPTF_ARGREQ, 0, 'p' },
546 { "syslog", 0, 0, 'l' },
547 { "log", 0, 0, 'l' },
548 { "quiet", 0, 0, 'q' },
549 { "setuid", OPTF_ARGREQ, 0, 's' },
550 { "uid", OPTF_ARGREQ, 0, 's' },
551 { "setgid", OPTF_ARGREQ, 0, 'g' },
552 { "gid", OPTF_ARGREQ, 0, 'g' },
554 /* --- Magic terminator --- */
558 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
584 if (strcmp(optarg, "-") == 0)
585 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
588 if ((fp = fopen(optarg, "r")) == 0)
589 die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
590 cf = CREATE(conffile);
594 scan_add(&sc, scan_file(fp, optarg, 0));
611 if (isdigit((unsigned char )optarg[0])) {
613 drop = strtol(optarg, &q, 0);
615 die(1, "bad uid `%s'", optarg);
617 struct passwd *pw = getpwnam(optarg);
619 die(1, "unknown user `%s'", optarg);
624 if (isdigit((unsigned char )optarg[0])) {
626 dropg = strtol(optarg, &q, 0);
628 die(1, "bad gid `%s'", optarg);
630 struct group *gr = getgrnam(optarg);
632 die(1, "unknown group `%s'", optarg);
648 /* --- Deal with the remaining arguments --- */
651 scan_add(&sc, scan_argv(argv + optind));
654 else if (!isatty(STDIN_FILENO))
655 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
657 moan("no configuration given and stdin is a terminal.");
658 moan("type `%s --help' for usage information.", QUIS);
662 /* --- Parse the configuration now gathered --- */
666 /* --- Set up some signal handlers --- *
668 * Don't enable @SIGINT@ if the caller already disabled it.
674 sig_add(&s_term, SIGTERM, fw_tidy, 0);
675 sig_add(&s_quit, SIGQUIT, fw_die, 0);
676 sigaction(SIGINT, 0, &sa);
677 if (sa.sa_handler != SIG_IGN)
678 sig_add(&s_int, SIGINT, fw_tidy, 0);
679 sig_add(&s_hup, SIGHUP, fw_reload, 0);
682 /* --- Fork into the background --- */
689 die(1, "couldn't fork: %s", strerror(errno));
693 close(0); close(1); close(2);
702 FILE *fp = fopen(pidfile, "w");
704 die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
705 pidfile, strerror(errno));
707 fprintf(fp, "%lu\n", (unsigned long)getpid());
708 if (fflush(fp) || ferror(fp) || fclose(fp)) {
709 die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
710 pidfile, strerror(errno));
716 openlog(QUIS, 0, LOG_DAEMON);
719 /* --- Drop privileges --- */
721 if (drop != (uid_t)-1)
723 #ifdef HAVE_SETGROUPS
724 if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
725 (drop != (uid_t)-1 && setuid(drop)))
726 die(1, "couldn't drop privileges: %s", strerror(errno));
728 if ((dropg != (gid_t)-1 && setgid(dropg)) ||
729 (drop != (uid_t)-1 && setuid(drop)))
730 die(1, "couldn't drop privileges: %s", strerror(errno));
733 /* --- Let rip --- */
735 if (!(flags & FW_SET))
736 moan("nothing to do!");
737 signal(SIGPIPE, SIG_IGN);
742 if (!sel_select(sel))
744 else if (errno != EINTR && errno != EAGAIN) {
745 fw_log(-1, "error from select: %s", strerror(errno));
748 fw_log(-1, "too many consecutive select errors: bailing out");
758 /*----- That's all, folks -------------------------------------------------*/