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 */
32 unsigned flags
= 0; /* Global state flags */
34 /*----- Static variables --------------------------------------------------*/
36 typedef struct conffile
{
37 struct conffile
*next
;
41 static unsigned active
= 0; /* Number of active things */
42 static conffile
*conffiles
= 0; /* List of configuration files */
44 /*----- Configuration parsing ---------------------------------------------*/
48 * Arguments: @scanner *sc@ = pointer to scanner definition
52 * Use: Parses a configuration file from the scanner.
55 static source_ops
*sources
[] =
56 { &xsource_ops
, &fsource_ops
, &ssource_ops
, 0 };
57 static target_ops
*targets
[] =
58 { &xtarget_ops
, &ftarget_ops
, &starget_ops
, 0 };
60 void parse(scanner
*sc
)
65 if (sc
->t
== CTOK_EOF
)
67 if (sc
->t
!= CTOK_WORD
)
68 error(sc
, "parse error, keyword expected");
70 /* --- Handle a forwarding request --- */
72 if (strcmp(sc
->d
.buf
, "forward") == 0 ||
73 strcmp(sc
->d
.buf
, "fwd") == 0 ||
74 strcmp(sc
->d
.buf
, "from") == 0) {
80 /* --- Read a source description --- */
85 /* --- Try to find a source type which understands --- */
88 for (sops
= sources
; *sops
; sops
++) {
89 if ((s
= (*sops
)->read(sc
)) != 0)
92 error(sc
, "unknown source name `%s'", sc
->d
.buf
);
94 /* --- Read any source-specific options --- */
99 while (sc
->t
== CTOK_WORD
) {
100 if (!s
->ops
->option
|| !s
->ops
->option(s
, sc
)) {
101 error(sc
, "unknown %s source option `%s'",
102 s
->ops
->name
, sc
->d
.buf
);
108 error(sc
, "parse error, missing `}'");
113 /* --- Read a destination description --- */
115 if (sc
->t
== CTOK_WORD
&& (strcmp(sc
->d
.buf
, "to") == 0 ||
116 strcmp(sc
->d
.buf
, "->") == 0))
122 /* --- Try to find a target which understands --- */
125 for (tops
= targets
; *tops
; tops
++) {
126 if ((t
= (*tops
)->read(sc
)) != 0)
129 error(sc
, "unknown target name `%s'", sc
->d
.buf
);
131 /* --- Read any target-specific options --- */
136 while (sc
->t
== CTOK_WORD
) {
137 if (!t
->ops
->option
|| !t
->ops
->option(t
, sc
)) {
138 error(sc
, "unknown %s target option `%s'",
139 t
->ops
->name
, sc
->d
.buf
);
145 error(sc
, "parse error, `}' expected");
150 /* --- Combine the source and target --- */
152 s
->ops
->attach(s
, sc
, t
);
157 /* --- Include configuration from a file --- *
159 * Slightly tricky. Scan the optional semicolon from the including
160 * stream, not the included one.
163 else if (strcmp(sc
->d
.buf
, "include") == 0) {
168 conf_name(sc
, '/', &d
);
169 if ((fp
= fopen(d
.buf
, "r")) == 0)
170 error(sc
, "can't include `%s': %s", d
.buf
, strerror(errno
));
174 scan_push(sc
, scan_file(fp
, d
.buf
, 0));
177 continue; /* Don't parse a trailing `;' */
180 /* --- Other configuration is handled elsewhere --- */
184 /* --- First try among the sources --- */
189 for (sops
= sources
; *sops
; sops
++) {
190 if ((*sops
)->option
&& (*sops
)->option(0, sc
))
195 /* --- Then try among the targets --- */
200 for (tops
= targets
; *tops
; tops
++) {
201 if ((*tops
)->option
&& (*tops
)->option(0, sc
))
206 /* --- Nobody wants the option --- */
208 error(sc
, "unknown global option or prefix `%s'", sc
->d
.buf
);
218 /*----- General utility functions -----------------------------------------*/
220 /* --- @fw_log@ --- *
222 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
223 * @const char *fmt@ = format string to fill in
224 * @...@ = other arguments
228 * Use: Logs a connection.
231 void fw_log(time_t t
, const char *fmt
, ...)
237 if (flags
& FW_QUIET
)
244 d
.len
+= strftime(d
.buf
, d
.sz
, "%Y-%m-%d %H:%M:%S %z ", tm
);
246 dstr_vputf(&d
, fmt
, &ap
);
248 if (flags
& FW_SYSLOG
)
249 syslog(LOG_NOTICE
, "%s", d
.buf
);
252 dstr_write(&d
, stderr
);
257 /* --- @fw_inc@, @fw_dec@ --- *
263 * Use: Increments or decrements the active thing count. `fwd' won't
264 * quit while there are active things.
267 void fw_inc(void) { flags
|= FW_SET
; active
++; }
268 void fw_dec(void) { if (active
) active
--; }
270 /* --- @fw_exit@ --- *
276 * Use: Exits when appropriate.
279 static void fw_exit(void)
285 /* --- @fw_tidy@ --- *
287 * Arguments: @int n@ = signal number
288 * @void *p@ = an uninteresting argument
292 * Use: Handles various signals and causes a clean tidy-up.
295 static void fw_tidy(int n
, void *p
)
299 case SIGTERM
: sn
= "SIGTERM"; break;
300 case SIGINT
: sn
= "SIGINT"; break;
304 fw_log(NOW
, "closing down gracefully on %s", sn
);
308 /* --- @fw_die@ --- *
310 * Arguments: @int n@ = signal number
311 * @void *p@ = an uninteresting argument
315 * Use: Handles various signals and causes an abrupt shutdown.
318 static void fw_die(int n
, void *p
)
322 case SIGQUIT
: sn
= "SIGQUIT"; break;
326 fw_log(NOW
, "closing down abruptly on %s", sn
);
331 /* --- @fw_reload@ --- *
333 * Arguments: @int n@ = a signal number
334 * @void *p@ = an uninteresting argument
338 * Use: Handles a hangup signal by re-reading configuration files.
341 static void fw_reload(int n
, void *p
)
349 fw_log(NOW
, "no configuration files to reload: ignoring SIGHUP");
352 fw_log(NOW
, "reloading configuration files...");
355 for (cf
= conffiles
; cf
; cf
= cf
->next
) {
356 if ((fp
= fopen(cf
->name
, "r")) == 0)
357 fw_log(NOW
, "error loading `%s': %s", cf
->name
, strerror(errno
));
359 scan_add(&sc
, scan_file(fp
, cf
->name
, 0));
362 fw_log(NOW
, "... reload completed OK");
365 /*----- Startup and options parsing ---------------------------------------*/
367 /* --- Standard GNU help options --- */
369 static void version(FILE *fp
)
371 pquis(fp
, "$ version " VERSION
"\n");
374 static void usage(FILE *fp
)
376 pquis(fp
, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
379 static void help(FILE *fp
)
385 An excessively full-featured port-forwarder, which subsumes large chunks\n\
386 of the functionality of inetd, netcat, and normal cat.\n\
388 Configuration may be supplied in one or more configuration files, or on\n\
389 the command line (or both). If no `-f' option is present, and no\n\
390 configuration is given on the command line, the standard input stream is\n\
393 Configuration is free-form. Comments begin with a `#' character and\n\
394 continue to the end of the line. Each command line argument is considered\n\
395 to be a separate line.\n\
397 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
398 For a summary of the various options, run `$ --options'.\n\
400 Options available are:\n\
403 -h, --help Display this help message.\n\
404 -v, --version Display the program's version number.\n\
405 -u, --usage Display a terse usage summary.\n\
407 Configuration summary:\n\
408 -G, --grammar Show a summary of the configuration language.\n\
409 -O, --options Show a summary of the source and target options.\n\
412 -f, --file=FILE Read configuration from a file.\n\
413 -q, --quiet Don't emit any logging information.\n\
414 -d, --daemon Fork into background after initializing.\n\
415 -p, --pidfile=FILE Write process-id to the named FILE.\n\
416 -l, --syslog Send log output to the system logger.\n\
417 -s, --setuid=USER Change uid to USER after initializing sources.\n\
418 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
422 /* --- Other helpful options --- */
424 static void grammar(FILE *fp
)
427 fputs("\nGrammar summary\n\n", fp
);
428 fputs(grammar_text
, fp
);
431 static void options(FILE *fp
)
434 fputs("\nOption summary\n\n", fp
);
435 fputs(option_text
, fp
);
440 * Arguments: @int argc@ = number of command line arguments
441 * @char *argv[]@ = vector of argument strings
445 * Use: Simple port-forwarding server.
448 int main(int argc
, char *argv
[])
452 sig s_term
, s_quit
, s_int
, s_hup
;
456 const char *pidfile
= 0;
457 conffile
*cf
, **cff
= &conffiles
;
464 /* --- Initialize things --- */
474 fattr_init(&fattr_global
);
479 /* --- Parse command line options --- */
482 static struct option opts
[] = {
484 /* --- Standard GNU help options --- */
486 { "help", 0, 0, 'h' },
487 { "version", 0, 0, 'v' },
488 { "usage", 0, 0, 'u' },
490 /* --- Other help options --- */
492 { "grammar", 0, 0, 'G' },
493 { "options", 0, 0, 'O' },
495 /* --- Other useful arguments --- */
497 { "file", OPTF_ARGREQ
, 0, 'f' },
498 { "fork", 0, 0, 'd' },
499 { "daemon", 0, 0, 'd' },
500 { "pidfile", OPTF_ARGREQ
, 0, 'p' },
501 { "syslog", 0, 0, 'l' },
502 { "log", 0, 0, 'l' },
503 { "quiet", 0, 0, 'q' },
504 { "setuid", OPTF_ARGREQ
, 0, 's' },
505 { "uid", OPTF_ARGREQ
, 0, 's' },
506 { "setgid", OPTF_ARGREQ
, 0, 'g' },
507 { "gid", OPTF_ARGREQ
, 0, 'g' },
509 /* --- Magic terminator --- */
513 int i
= mdwopt(argc
, argv
, "+hvu" "GO" "f:dp:lqs:g:", opts
, 0, 0, 0);
539 if (strcmp(optarg
, "-") == 0)
540 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
543 if ((fp
= fopen(optarg
, "r")) == 0)
544 die(1, "couldn't open file `%s': %s", optarg
, strerror(errno
));
545 cf
= CREATE(conffile
);
549 scan_add(&sc
, scan_file(fp
, optarg
, 0));
566 if (isdigit((unsigned char )optarg
[0])) {
568 drop
= strtol(optarg
, &q
, 0);
570 die(1, "bad uid `%s'", optarg
);
572 struct passwd
*pw
= getpwnam(optarg
);
574 die(1, "unknown user `%s'", optarg
);
579 if (isdigit((unsigned char )optarg
[0])) {
581 dropg
= strtol(optarg
, &q
, 0);
583 die(1, "bad gid `%s'", optarg
);
585 struct group
*gr
= getgrnam(optarg
);
587 die(1, "unknown group `%s'", optarg
);
603 /* --- Deal with the remaining arguments --- */
606 scan_add(&sc
, scan_argv(argv
+ optind
));
609 else if (!isatty(STDIN_FILENO
))
610 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
612 moan("no configuration given and stdin is a terminal.");
613 moan("type `%s --help' for usage information.", QUIS
);
617 /* --- Parse the configuration now gathered --- */
621 /* --- Set up some signal handlers --- *
623 * Don't enable @SIGINT@ if the caller already disabled it.
629 sig_add(&s_term
, SIGTERM
, fw_tidy
, 0);
630 sig_add(&s_quit
, SIGQUIT
, fw_die
, 0);
631 sigaction(SIGINT
, 0, &sa
);
632 if (sa
.sa_handler
!= SIG_IGN
)
633 sig_add(&s_int
, SIGINT
, fw_tidy
, 0);
634 sig_add(&s_hup
, SIGHUP
, fw_reload
, 0);
637 /* --- Fork into the background --- */
644 die(1, "couldn't fork: %s", strerror(errno
));
648 close(0); close(1); close(2);
650 die(1, "couldn't change to root directory: %s", strerror(errno
));
658 FILE *fp
= fopen(pidfile
, "w");
660 die(EXIT_FAILURE
, "couldn't create pidfile `%s': %s",
661 pidfile
, strerror(errno
));
663 fprintf(fp
, "%lu\n", (unsigned long)getpid());
664 if (fflush(fp
) || ferror(fp
) || fclose(fp
)) {
665 die(EXIT_FAILURE
, "couldn't write pidfile `%s': %s",
666 pidfile
, strerror(errno
));
672 openlog(QUIS
, 0, LOG_DAEMON
);
675 /* --- Drop privileges --- */
677 if (drop
!= (uid_t
)-1)
679 #ifdef HAVE_SETGROUPS
680 if ((dropg
!= (gid_t
)-1 && (setgid(dropg
) || setgroups(1, &dropg
))) ||
681 (drop
!= (uid_t
)-1 && setuid(drop
)))
682 die(1, "couldn't drop privileges: %s", strerror(errno
));
684 if ((dropg
!= (gid_t
)-1 && setgid(dropg
)) ||
685 (drop
!= (uid_t
)-1 && setuid(drop
)))
686 die(1, "couldn't drop privileges: %s", strerror(errno
));
689 /* --- Let rip --- */
691 if (!(flags
& FW_SET
))
692 moan("nothing to do!");
693 signal(SIGPIPE
, SIG_IGN
);
698 if (!sel_select(sel
))
700 else if (errno
!= EINTR
&& errno
!= EAGAIN
) {
701 fw_log(NOW
, "error from select: %s", strerror(errno
));
704 fw_log(NOW
, "too many consecutive select errors: bailing out");
714 /*----- That's all, folks -------------------------------------------------*/