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
);
155 source_dec(s
); target_dec(t
);
158 /* --- Include configuration from a file --- *
160 * Slightly tricky. Scan the optional semicolon from the including
161 * stream, not the included one.
164 else if (strcmp(sc
->d
.buf
, "include") == 0) {
169 conf_name(sc
, '/', &d
);
170 if ((fp
= fopen(d
.buf
, "r")) == 0)
171 error(sc
, "can't include `%s': %s", d
.buf
, strerror(errno
));
175 scan_push(sc
, scan_file(fp
, d
.buf
, 0));
178 continue; /* Don't parse a trailing `;' */
181 /* --- Other configuration is handled elsewhere --- */
185 /* --- First try among the sources --- */
190 for (sops
= sources
; *sops
; sops
++) {
191 if ((*sops
)->option
&& (*sops
)->option(0, sc
))
196 /* --- Then try among the targets --- */
201 for (tops
= targets
; *tops
; tops
++) {
202 if ((*tops
)->option
&& (*tops
)->option(0, sc
))
207 /* --- Nobody wants the option --- */
209 error(sc
, "unknown global option or prefix `%s'", sc
->d
.buf
);
219 /*----- General utility functions -----------------------------------------*/
221 /* --- @fw_log@ --- *
223 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
224 * @const char *fmt@ = format string to fill in
225 * @...@ = other arguments
229 * Use: Logs a connection.
232 void fw_log(time_t t
, const char *fmt
, ...)
238 if (flags
& FW_QUIET
)
245 d
.len
+= strftime(d
.buf
, d
.sz
, "%Y-%m-%d %H:%M:%S %z ", tm
);
247 dstr_vputf(&d
, fmt
, &ap
);
249 if (flags
& FW_SYSLOG
)
250 syslog(LOG_NOTICE
, "%s", d
.buf
);
253 dstr_write(&d
, stderr
);
258 /* --- @fw_inc@, @fw_dec@ --- *
264 * Use: Increments or decrements the active thing count. `fwd' won't
265 * quit while there are active things.
268 void fw_inc(void) { flags
|= FW_SET
; active
++; }
269 void fw_dec(void) { if (active
) active
--; }
271 /* --- @fw_exit@ --- *
277 * Use: Exits when appropriate.
280 static void fw_exit(void)
286 /* --- @fw_tidy@ --- *
288 * Arguments: @int n@ = signal number
289 * @void *p@ = an uninteresting argument
293 * Use: Handles various signals and causes a clean tidy-up.
296 static void fw_tidy(int n
, void *p
)
300 case SIGTERM
: sn
= "SIGTERM"; break;
301 case SIGINT
: sn
= "SIGINT"; break;
305 fw_log(NOW
, "closing down gracefully on %s", sn
);
309 /* --- @fw_die@ --- *
311 * Arguments: @int n@ = signal number
312 * @void *p@ = an uninteresting argument
316 * Use: Handles various signals and causes an abrupt shutdown.
319 static void fw_die(int n
, void *p
)
323 case SIGQUIT
: sn
= "SIGQUIT"; break;
327 fw_log(NOW
, "closing down abruptly on %s", sn
);
332 /* --- @fw_reload@ --- *
334 * Arguments: @int n@ = a signal number
335 * @void *p@ = an uninteresting argument
339 * Use: Handles a hangup signal by re-reading configuration files.
342 static void fw_reload(int n
, void *p
)
350 fw_log(NOW
, "no configuration files to reload: ignoring SIGHUP");
353 fw_log(NOW
, "reloading configuration files...");
356 for (cf
= conffiles
; cf
; cf
= cf
->next
) {
357 if ((fp
= fopen(cf
->name
, "r")) == 0)
358 fw_log(NOW
, "error loading `%s': %s", cf
->name
, strerror(errno
));
360 scan_add(&sc
, scan_file(fp
, cf
->name
, 0));
363 fw_log(NOW
, "... reload completed OK");
366 /*----- Startup and options parsing ---------------------------------------*/
368 /* --- Standard GNU help options --- */
370 static void version(FILE *fp
)
372 pquis(fp
, "$ version " VERSION
"\n");
375 static void usage(FILE *fp
)
377 pquis(fp
, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
380 static void help(FILE *fp
)
386 An excessively full-featured port-forwarder, which subsumes large chunks\n\
387 of the functionality of inetd, netcat, and normal cat.\n\
389 Configuration may be supplied in one or more configuration files, or on\n\
390 the command line (or both). If no `-f' option is present, and no\n\
391 configuration is given on the command line, the standard input stream is\n\
394 Configuration is free-form. Comments begin with a `#' character and\n\
395 continue to the end of the line. Each command line argument is considered\n\
396 to be a separate line.\n\
398 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
399 For a summary of the various options, run `$ --options'.\n\
401 Options available are:\n\
404 -h, --help Display this help message.\n\
405 -v, --version Display the program's version number.\n\
406 -u, --usage Display a terse usage summary.\n\
408 Configuration summary:\n\
409 -G, --grammar Show a summary of the configuration language.\n\
410 -O, --options Show a summary of the source and target options.\n\
413 -f, --file=FILE Read configuration from a file.\n\
414 -q, --quiet Don't emit any logging information.\n\
415 -d, --daemon Fork into background after initializing.\n\
416 -p, --pidfile=FILE Write process-id to the named FILE.\n\
417 -l, --syslog Send log output to the system logger.\n\
418 -s, --setuid=USER Change uid to USER after initializing sources.\n\
419 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
423 /* --- Other helpful options --- */
425 static void grammar(FILE *fp
)
428 fputs("\nGrammar summary\n\n", fp
);
429 fputs(grammar_text
, fp
);
432 static void options(FILE *fp
)
435 fputs("\nOption summary\n\n", fp
);
436 fputs(option_text
, fp
);
441 * Arguments: @int argc@ = number of command line arguments
442 * @char *argv[]@ = vector of argument strings
446 * Use: Simple port-forwarding server.
449 int main(int argc
, char *argv
[])
453 sig s_term
, s_quit
, s_int
, s_hup
;
457 const char *pidfile
= 0;
458 conffile
*cf
, **cff
= &conffiles
;
465 /* --- Initialize things --- */
475 fattr_init(&fattr_global
);
480 /* --- Parse command line options --- */
483 static struct option opts
[] = {
485 /* --- Standard GNU help options --- */
487 { "help", 0, 0, 'h' },
488 { "version", 0, 0, 'v' },
489 { "usage", 0, 0, 'u' },
491 /* --- Other help options --- */
493 { "grammar", 0, 0, 'G' },
494 { "options", 0, 0, 'O' },
496 /* --- Other useful arguments --- */
498 { "file", OPTF_ARGREQ
, 0, 'f' },
499 { "fork", 0, 0, 'd' },
500 { "daemon", 0, 0, 'd' },
501 { "pidfile", OPTF_ARGREQ
, 0, 'p' },
502 { "syslog", 0, 0, 'l' },
503 { "log", 0, 0, 'l' },
504 { "quiet", 0, 0, 'q' },
505 { "setuid", OPTF_ARGREQ
, 0, 's' },
506 { "uid", OPTF_ARGREQ
, 0, 's' },
507 { "setgid", OPTF_ARGREQ
, 0, 'g' },
508 { "gid", OPTF_ARGREQ
, 0, 'g' },
510 /* --- Magic terminator --- */
514 int i
= mdwopt(argc
, argv
, "+hvu" "GO" "f:dp:lqs:g:", opts
, 0, 0, 0);
540 if (strcmp(optarg
, "-") == 0)
541 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
544 if ((fp
= fopen(optarg
, "r")) == 0)
545 die(1, "couldn't open file `%s': %s", optarg
, strerror(errno
));
546 cf
= CREATE(conffile
);
550 scan_add(&sc
, scan_file(fp
, optarg
, 0));
567 if (isdigit((unsigned char )optarg
[0])) {
569 drop
= strtol(optarg
, &q
, 0);
571 die(1, "bad uid `%s'", optarg
);
573 struct passwd
*pw
= getpwnam(optarg
);
575 die(1, "unknown user `%s'", optarg
);
580 if (isdigit((unsigned char )optarg
[0])) {
582 dropg
= strtol(optarg
, &q
, 0);
584 die(1, "bad gid `%s'", optarg
);
586 struct group
*gr
= getgrnam(optarg
);
588 die(1, "unknown group `%s'", optarg
);
604 /* --- Deal with the remaining arguments --- */
607 scan_add(&sc
, scan_argv(argv
+ optind
));
610 else if (!isatty(STDIN_FILENO
))
611 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
613 moan("no configuration given and stdin is a terminal.");
614 moan("type `%s --help' for usage information.", QUIS
);
618 /* --- Parse the configuration now gathered --- */
622 /* --- Set up some signal handlers --- *
624 * Don't enable @SIGINT@ if the caller already disabled it.
630 sig_add(&s_term
, SIGTERM
, fw_tidy
, 0);
631 sig_add(&s_quit
, SIGQUIT
, fw_die
, 0);
632 sigaction(SIGINT
, 0, &sa
);
633 if (sa
.sa_handler
!= SIG_IGN
)
634 sig_add(&s_int
, SIGINT
, fw_tidy
, 0);
635 sig_add(&s_hup
, SIGHUP
, fw_reload
, 0);
638 /* --- Fork into the background --- */
645 die(1, "couldn't fork: %s", strerror(errno
));
649 close(0); close(1); close(2);
651 die(1, "couldn't change to root directory: %s", strerror(errno
));
659 FILE *fp
= fopen(pidfile
, "w");
661 die(EXIT_FAILURE
, "couldn't create pidfile `%s': %s",
662 pidfile
, strerror(errno
));
664 fprintf(fp
, "%lu\n", (unsigned long)getpid());
665 if (fflush(fp
) || ferror(fp
) || fclose(fp
)) {
666 die(EXIT_FAILURE
, "couldn't write pidfile `%s': %s",
667 pidfile
, strerror(errno
));
673 openlog(QUIS
, 0, LOG_DAEMON
);
676 /* --- Drop privileges --- */
678 if (drop
!= (uid_t
)-1)
680 #ifdef HAVE_SETGROUPS
681 if ((dropg
!= (gid_t
)-1 && (setgid(dropg
) || setgroups(1, &dropg
))) ||
682 (drop
!= (uid_t
)-1 && setuid(drop
)))
683 die(1, "couldn't drop privileges: %s", strerror(errno
));
685 if ((dropg
!= (gid_t
)-1 && setgid(dropg
)) ||
686 (drop
!= (uid_t
)-1 && setuid(drop
)))
687 die(1, "couldn't drop privileges: %s", strerror(errno
));
690 /* --- Let rip --- */
692 if (!(flags
& FW_SET
))
693 moan("nothing to do!");
694 signal(SIGPIPE
, SIG_IGN
);
699 if (!sel_select(sel
))
701 else if (errno
!= EINTR
&& errno
!= EAGAIN
) {
702 fw_log(NOW
, "error from select: %s", strerror(errno
));
705 fw_log(NOW
, "too many consecutive select errors: bailing out");
715 /*----- That's all, folks -------------------------------------------------*/