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>
69 /*----- Global variables --------------------------------------------------*/
71 sel_state
*sel
; /* Multiplexor for nonblocking I/O */
73 /*----- Static variables --------------------------------------------------*/
75 typedef struct conffile
{
76 struct conffile
*next
;
80 static unsigned flags
= 0; /* Global state flags */
81 static unsigned active
= 0; /* Number of active things */
82 static conffile
*conffiles
= 0; /* List of configuration files */
88 /*----- Configuration parsing ---------------------------------------------*/
92 * Arguments: @scanner *sc@ = pointer to scanner definition
96 * Use: Parses a configuration file from the scanner.
99 static source_ops
*sources
[] =
100 { &xsource_ops
, &fsource_ops
, &ssource_ops
, 0 };
101 static target_ops
*targets
[] =
102 { &xtarget_ops
, &ftarget_ops
, &starget_ops
, 0 };
104 void parse(scanner
*sc
)
109 if (sc
->t
== CTOK_EOF
)
111 if (sc
->t
!= CTOK_WORD
)
112 error(sc
, "parse error, keyword expected");
114 /* --- Handle a forwarding request --- */
116 if (strcmp(sc
->d
.buf
, "forward") == 0 ||
117 strcmp(sc
->d
.buf
, "fw") == 0 ||
118 strcmp(sc
->d
.buf
, "from") == 0) {
124 /* --- Read a source description --- */
129 /* --- Try to find a source type which understands --- */
132 for (sops
= sources
; *sops
; sops
++) {
133 if ((s
= (*sops
)->read(sc
)) != 0)
136 error(sc
, "unknown source name `%s'", sc
->d
.buf
);
138 /* --- Read any source-specific options --- */
143 while (sc
->t
== CTOK_WORD
) {
144 if (!s
->ops
->option
|| !s
->ops
->option(s
, sc
)) {
145 error(sc
, "unknown %s source option `%s'",
146 s
->ops
->name
, sc
->d
.buf
);
152 error(sc
, "parse error, missing `}'");
157 /* --- Read a destination description --- */
159 if (sc
->t
== CTOK_WORD
&& (strcmp(sc
->d
.buf
, "to") == 0 ||
160 strcmp(sc
->d
.buf
, "->") == 0))
166 /* --- Try to find a target which understands --- */
169 for (tops
= targets
; *tops
; tops
++) {
170 if ((t
= (*tops
)->read(sc
)) != 0)
173 error(sc
, "unknown target name `%s'", sc
->d
.buf
);
175 /* --- Read any target-specific options --- */
180 while (sc
->t
== CTOK_WORD
) {
181 if (!t
->ops
->option
|| !t
->ops
->option(t
, sc
)) {
182 error(sc
, "unknown %s target option `%s'",
183 t
->ops
->name
, sc
->d
.buf
);
189 error(sc
, "parse error, `}' expected");
194 /* --- Combine the source and target --- */
196 s
->ops
->attach(s
, sc
, t
);
201 /* --- Include configuration from a file --- *
203 * Slightly tricky. Scan the optional semicolon from the including
204 * stream, not the included one.
207 else if (strcmp(sc
->d
.buf
, "include") == 0) {
212 conf_name(sc
, '/', &d
);
213 if ((fp
= fopen(d
.buf
, "r")) == 0)
214 error(sc
, "can't include `%s': %s", d
.buf
, strerror(errno
));
218 scan_push(sc
, scan_file(fp
, d
.buf
, 0));
221 continue; /* Don't parse a trailing `;' */
224 /* --- Other configuration is handled elsewhere --- */
228 /* --- First try among the sources --- */
233 for (sops
= sources
; *sops
; sops
++) {
234 if ((*sops
)->option
&& (*sops
)->option(0, sc
))
239 /* --- Then try among the targets --- */
244 for (tops
= targets
; *tops
; tops
++) {
245 if ((*tops
)->option
&& (*tops
)->option(0, sc
))
250 /* --- Nobody wants the option --- */
252 error(sc
, "unknown global option or prefix `%s'", sc
->d
.buf
);
262 /*----- General utility functions -----------------------------------------*/
264 /* --- @fw_log@ --- *
266 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
267 * @const char *fmt@ = format string to fill in
268 * @...@ = other arguments
272 * Use: Logs a connection.
275 void fw_log(time_t t
, const char *fmt
, ...)
281 if (flags
& FW_QUIET
)
288 d
.len
+= strftime(d
.buf
, d
.sz
, "%Y-%m-%d %H:%M:%S ", tm
);
290 dstr_vputf(&d
, fmt
, &ap
);
292 if (flags
& FW_SYSLOG
)
293 syslog(LOG_NOTICE
, "%s", d
.buf
);
296 dstr_write(&d
, stderr
);
301 /* --- @fw_inc@, @fw_dec@ --- *
307 * Use: Increments or decrements the active thing count. `fw' won't
308 * quit while there are active things.
311 void fw_inc(void) { flags
|= FW_SET
; active
++; }
312 void fw_dec(void) { if (active
) active
--; }
314 /* --- @fw_exit@ --- *
320 * Use: Exits when appropriate.
323 static void fw_exit(void)
329 /* --- @fw_tidy@ --- *
331 * Arguments: @int n@ = signal number
332 * @void *p@ = an uninteresting argument
336 * Use: Handles various signals and causes a clean tidy-up.
339 static void fw_tidy(int n
, void *p
)
343 case SIGTERM
: sn
= "SIGTERM"; break;
344 case SIGINT
: sn
= "SIGINT"; break;
348 fw_log(-1, "closing down gracefully on %s", sn
);
352 /* --- @fw_die@ --- *
354 * Arguments: @int n@ = signal number
355 * @void *p@ = an uninteresting argument
359 * Use: Handles various signals and causes an abrupt shutdown.
362 static void fw_die(int n
, void *p
)
366 case SIGQUIT
: sn
= "SIGQUIT"; break;
370 fw_log(-1, "closing down abruptly on %s", sn
);
375 /* --- @fw_reload@ --- *
377 * Arguments: @int n@ = a signal number
378 * @void *p@ = an uninteresting argument
382 * Use: Handles a hangup signal by re-reading configuration files.
385 static void fw_reload(int n
, void *p
)
393 fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
396 fw_log(-1, "reloading configuration files...");
399 for (cf
= conffiles
; cf
; cf
= cf
->next
) {
400 if ((fp
= fopen(cf
->name
, "r")) == 0)
401 fw_log(-1, "error loading `%s': %s", cf
->name
, strerror(errno
));
403 scan_add(&sc
, scan_file(fp
, cf
->name
, 0));
406 fw_log(-1, "... reload completed OK");
409 /*----- Startup and options parsing ---------------------------------------*/
411 /* --- Standard GNU help options --- */
413 static void version(FILE *fp
)
415 pquis(fp
, "$ version " VERSION
"\n");
418 static void usage(FILE *fp
)
420 pquis(fp
, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
423 static void help(FILE *fp
)
429 An excessively full-featured port-forwarder, which subsumes large chunks\n\
430 of the functionality of inetd, netcat, and normal cat.\n\
432 Configuration may be supplied in one or more configuration files, or on\n\
433 the command line (or both). If no `-f' option is present, and no\n\
434 configuration is given on the command line, the standard input stream is\n\
437 Configuration is free-form. Comments begin with a `#' character and\n\
438 continue to the end of the line. Each command line argument is considered\n\
439 to be a separate line.\n\
441 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
442 For a summary of the various options, run `$ --options'.\n\
444 Options available are:\n\
447 -h, --help Display this help message.\n\
448 -v, --version Display the program's version number.\n\
449 -u, --usage Display a terse usage summary.\n\
451 Configuration summary:\n\
452 -G, --grammar Show a summary of the configuration language.\n\
453 -O, --options Show a summary of the source and target options.\n\
456 -f, --file=FILE Read configuration from a file.\n\
457 -q, --quiet Don't emit any logging information.\n\
458 -d, --daemon Fork into background after initializing.\n\
459 -p, --pidfile=FILE Write process-id to the named FILE.\n\
460 -l, --syslog Send log output to the system logger.\n\
461 -s, --setuid=USER Change uid to USER after initializing sources.\n\
462 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
466 /* --- Other helpful options --- */
468 static void grammar(FILE *fp
)
475 FILE ::= EMPTY | FILE STMT [`;']\n\
476 STMT ::= OPTION-STMT | FW-STMT\n\
477 FW-STMT ::= `fw' SOURCE OPTIONS [`to'|`->'] TARGET OPTIONS\n\
478 OPTIONS ::= `{' OPTION-SEQ `}'\n\
479 OPTION-SEQ ::= EMPTY | OPTION-STMT [`;'] OPTION-SEQ\n\
482 OPTION-STMT ::= Q-OPTION\n\
483 Q-OPTION ::= OPTION\n\
484 | PREFIX `.' Q-OPTION\n\
485 | PREFIX `{' OPTION-SEQ `}'\n\
488 File source and target\n\
491 FILE ::= `file' [`.'] FSPEC [`,' FSPEC]\n\
492 FSPEC ::= FD-SPEC | NAME-SPEC | NULL-SPEC\n\
493 FD-SPEC ::= [[`:']`fd'[`:']] NUMBER|`stdin'|`stdout'\n\
494 NAME-SPEC ::= [[`:']`name'[`:']] FILE-NAME\n\
495 FILE-NAME ::= PATH-SEQ | [ PATH-SEQ ]\n\
496 PATH-SEQ ::= PATH-ELT | PATH-SEQ PATH-ELT\n\
497 PATH-ELT ::= `/' | WORD\n\
498 NULL-SPEC ::= [`:']`null'[`:']\n\
500 Exec source and target\n\
503 EXEC ::= `exec' [`.'] CMD-SPEC\n\
504 CMD-SPEC ::= SHELL-CMD | [PROG-NAME] `[' ARGV0 ARG-SEQ `]'\n\
505 ARG-SEQ ::= WORD | ARG-SEQ WORD\n\
506 SHELL-CMD ::= WORD\n\
509 Socket source and target\n\
510 SOURCE ::= SOCKET-SOURCE\n\
511 TARGET ::= SOCKET-TARGET\n\
512 SOCKET-SOURCE ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] SOURCE-ADDR\n\
513 SOCKET-TARGET ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] TARGET-ADDR\n\
515 INET-SOURCE-ADDR ::= [`port'] PORT\n\
516 INET-TARGET-ADDR ::= ADDRESS [`:'] PORT\n\
517 ADDRESS ::= ADDR-ELT | ADDRESS ADDR-ELT\n\
518 ADDR-ELT ::= `.' | WORD\n\
520 UNIX-SOURCE-ADDR ::= FILE-NAME\n\
521 UNIX-TARGET-ADDR ::= FILE-NAME\n\
525 static void options(FILE *fp
)
531 File attributes (`fattr')\n\
532 prefix.FATTR.MODE [=] MODE\n\
533 prefix.FATTR.OWNER [=] USER\n\
534 prefix.FATTR.GROUP [=] GROUP\n\
537 file.create [=] yes|no\n\
538 file.open [=] no|truncate|append\n\
542 exec.logging [=] yes|no\n\
543 exec.dir [=] FILE-NAME\n\
544 exec.root [=] FILE-NAME\n\
545 exec.user [=] USER\n\
546 exec.group [=] GROUP\n\
547 exec.rlimit.LIMIT[.hard|.soft] [=] VALUE\n\
549 exec.env.unset VAR\n\
550 exec.env.[set] VAR [=] VALUE\n\
553 socket.conn [=] NUMBER|unlimited|one-shot\n\
554 socket.listen [=] NUMBER\n\
555 socket.logging [=] yes|no\n\
557 socket.inet.source.[allow|deny] [host] ADDR [/ ADDR]\n\
558 socket.inet.source.[allow|deny] priv-port\n\
559 socket.inet.source.addr [=] any|ADDR\n\
560 socket.inet.dest.addr [=] any|ADDR\n\
561 socket.inet.dest.priv-port [=] yes|no\n\
563 socket.unix.fattr.*\n\
569 * Arguments: @int argc@ = number of command line arguments
570 * @char *argv[]@ = vector of argument strings
574 * Use: Simple port-forwarding server.
577 int main(int argc
, char *argv
[])
581 sig s_term
, s_quit
, s_int
, s_hup
;
585 const char *pidfile
= 0;
586 conffile
*cf
, **cff
= &conffiles
;
593 /* --- Initialize things --- */
603 fattr_init(&fattr_global
);
608 /* --- Parse command line options --- */
611 static struct option opts
[] = {
613 /* --- Standard GNU help options --- */
615 { "help", 0, 0, 'h' },
616 { "version", 0, 0, 'v' },
617 { "usage", 0, 0, 'u' },
619 /* --- Other help options --- */
621 { "grammar", 0, 0, 'G' },
622 { "options", 0, 0, 'O' },
624 /* --- Other useful arguments --- */
626 { "file", OPTF_ARGREQ
, 0, 'f' },
627 { "fork", 0, 0, 'd' },
628 { "daemon", 0, 0, 'd' },
629 { "pidfile", OPTF_ARGREQ
, 0, 'p' },
630 { "syslog", 0, 0, 'l' },
631 { "log", 0, 0, 'l' },
632 { "quiet", 0, 0, 'q' },
633 { "setuid", OPTF_ARGREQ
, 0, 's' },
634 { "uid", OPTF_ARGREQ
, 0, 's' },
635 { "setgid", OPTF_ARGREQ
, 0, 'g' },
636 { "gid", OPTF_ARGREQ
, 0, 'g' },
638 /* --- Magic terminator --- */
642 int i
= mdwopt(argc
, argv
, "+hvu" "GO" "f:dp:ls:g:", opts
, 0, 0, 0);
668 if (strcmp(optarg
, "-") == 0)
669 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
672 if ((fp
= fopen(optarg
, "r")) == 0)
673 die(1, "couldn't open file `%s': %s", optarg
, strerror(errno
));
674 cf
= CREATE(conffile
);
678 scan_add(&sc
, scan_file(fp
, optarg
, 0));
695 if (isdigit((unsigned char )optarg
[0])) {
697 drop
= strtol(optarg
, &q
, 0);
699 die(1, "bad uid `%s'", optarg
);
701 struct passwd
*pw
= getpwnam(optarg
);
703 die(1, "unknown user `%s'", optarg
);
708 if (isdigit((unsigned char )optarg
[0])) {
710 dropg
= strtol(optarg
, &q
, 0);
712 die(1, "bad gid `%s'", optarg
);
714 struct group
*gr
= getgrnam(optarg
);
716 die(1, "unknown group `%s'", optarg
);
732 /* --- Deal with the remaining arguments --- */
735 scan_add(&sc
, scan_argv(argv
+ optind
));
738 else if (!isatty(STDIN_FILENO
))
739 scan_add(&sc
, scan_file(stdin
, "<stdin>", SCF_NOCLOSE
));
741 moan("no configuration given and stdin is a terminal.");
742 moan("type `%s --help' for usage information.", QUIS
);
746 /* --- Parse the configuration now gathered --- */
750 /* --- Set up some signal handlers --- *
752 * Don't enable @SIGINT@ if the caller already disabled it.
758 sig_add(&s_term
, SIGTERM
, fw_tidy
, 0);
759 sig_add(&s_quit
, SIGQUIT
, fw_die
, 0);
760 sigaction(SIGINT
, 0, &sa
);
761 if (sa
.sa_handler
!= SIG_IGN
)
762 sig_add(&s_int
, SIGINT
, fw_tidy
, 0);
763 sig_add(&s_hup
, SIGHUP
, fw_reload
, 0);
766 /* --- Drop privileges --- */
768 if (drop
!= (uid_t
)-1)
770 #ifdef HAVE_SETGROUPS
771 if ((dropg
!= (gid_t
)-1 && (setgid(dropg
) || setgroups(1, &dropg
))) ||
772 (drop
!= (uid_t
)-1 && setuid(drop
)))
773 die(1, "couldn't drop privileges: %s", strerror(errno
));
775 if ((dropg
!= (gid_t
)-1 && setgid(dropg
)) ||
776 (drop
!= (uid_t
)-1 && setuid(drop
)))
777 die(1, "couldn't drop privileges: %s", strerror(errno
));
780 /* --- Fork into the background --- */
787 die(1, "couldn't fork: %s", strerror(errno
));
791 close(0); close(1); close(2);
800 FILE *fp
= fopen(pidfile
, "w");
802 die(EXIT_FAILURE
, "couldn't create pidfile `%s': %s",
803 pidfile
, strerror(errno
));
805 fprintf(fp
, "%lu\n", (unsigned long)getpid());
806 if (fflush(fp
) || ferror(fp
) || fclose(fp
)) {
807 die(EXIT_FAILURE
, "couldn't write pidfile `%s': %s",
808 pidfile
, strerror(errno
));
814 openlog(QUIS
, 0, LOG_DAEMON
);
817 /* --- Let rip --- */
819 if (!(flags
& FW_SET
))
820 moan("nothing to do!");
821 signal(SIGPIPE
, SIG_IGN
);
826 if (!sel_select(sel
))
828 else if (errno
!= EINTR
&& errno
!= EAGAIN
) {
829 fw_log(-1, "error from select: %s", strerror(errno
));
832 fw_log(-1, "too many consecutive select errors: bailing out");
842 /*----- That's all, folks -------------------------------------------------*/