fwd.c (fw_log): Report the timezone in log messages.
[fwd] / fwd.c
CommitLineData
e82f7154 1/* -*-c-*-
2 *
e82f7154 3 * Port forwarding thingy
4 *
61e3dbdf 5 * (c) 1999 Straylight/Edgeware
e82f7154 6 */
7
206212ca 8/*----- Licensing notice --------------------------------------------------*
e82f7154 9 *
9155ea97 10 * This file is part of the `fwd' port forwarder.
e82f7154 11 *
9155ea97 12 * `fwd' is free software; you can redistribute it and/or modify
e82f7154 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.
206212ca 16 *
9155ea97 17 * `fwd' is distributed in the hope that it will be useful,
e82f7154 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.
206212ca 21 *
e82f7154 22 * You should have received a copy of the GNU General Public License
9155ea97 23 * along with `fwd'; if not, write to the Free Software Foundation,
e82f7154 24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
9155ea97 27#include "fwd.h"
e82f7154 28
afd7451e 29/*----- Global variables --------------------------------------------------*/
e82f7154 30
31sel_state *sel; /* Multiplexor for nonblocking I/O */
61e3dbdf 32
33/*----- Static variables --------------------------------------------------*/
34
372a98e2 35typedef struct conffile {
36 struct conffile *next;
37 char *name;
38} conffile;
39
61e3dbdf 40static unsigned flags = 0; /* Global state flags */
41static unsigned active = 0; /* Number of active things */
372a98e2 42static conffile *conffiles = 0; /* List of configuration files */
61e3dbdf 43
44#define FW_SYSLOG 1u
45#define FW_QUIET 2u
46#define FW_SET 4u
e82f7154 47
8c0a939e 48/*----- Configuration parsing ---------------------------------------------*/
49
50/* --- @parse@ --- *
51 *
52 * Arguments: @scanner *sc@ = pointer to scanner definition
53 *
54 * Returns: ---
55 *
56 * Use: Parses a configuration file from the scanner.
57 */
58
59static source_ops *sources[] =
60 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
61static target_ops *targets[] =
62 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
63
64void parse(scanner *sc)
65{
66 token(sc);
67
68 for (;;) {
69 if (sc->t == CTOK_EOF)
70 break;
71 if (sc->t != CTOK_WORD)
72 error(sc, "parse error, keyword expected");
73
74 /* --- Handle a forwarding request --- */
75
76 if (strcmp(sc->d.buf, "forward") == 0 ||
9155ea97 77 strcmp(sc->d.buf, "fwd") == 0 ||
206212ca 78 strcmp(sc->d.buf, "from") == 0) {
8c0a939e 79 source *s;
80 target *t;
81
82 token(sc);
83
84 /* --- Read a source description --- */
85
86 {
87 source_ops **sops;
88
89 /* --- Try to find a source type which understands --- */
90
91 s = 0;
92 for (sops = sources; *sops; sops++) {
93 if ((s = (*sops)->read(sc)) != 0)
94 goto found_source;
95 }
96 error(sc, "unknown source name `%s'", sc->d.buf);
97
98 /* --- Read any source-specific options --- */
99
100 found_source:
101 if (sc->t == '{') {
102 token(sc);
103 while (sc->t == CTOK_WORD) {
104 if (!s->ops->option || !s->ops->option(s, sc)) {
105 error(sc, "unknown %s source option `%s'",
106 s->ops->name, sc->d.buf);
107 }
108 if (sc->t == ';')
109 token(sc);
110 }
111 if (sc->t != '}')
112 error(sc, "parse error, missing `}'");
113 token(sc);
114 }
115 }
116
117 /* --- Read a destination description --- */
118
119 if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
120 strcmp(sc->d.buf, "->") == 0))
121 token(sc);
122
123 {
124 target_ops **tops;
125
126 /* --- Try to find a target which understands --- */
127
128 t = 0;
129 for (tops = targets; *tops; tops++) {
130 if ((t = (*tops)->read(sc)) != 0)
131 goto found_target;
132 }
133 error(sc, "unknown target name `%s'", sc->d.buf);
134
135 /* --- Read any target-specific options --- */
136
137 found_target:
138 if (sc->t == '{') {
139 token(sc);
140 while (sc->t == CTOK_WORD) {
141 if (!t->ops->option || !t->ops->option(t, sc)) {
142 error(sc, "unknown %s target option `%s'",
143 t->ops->name, sc->d.buf);
144 }
145 if (sc->t == ';')
146 token(sc);
147 }
148 if (sc->t != '}')
149 error(sc, "parse error, `}' expected");
150 token(sc);
151 }
152 }
153
154 /* --- Combine the source and target --- */
155
156 s->ops->attach(s, sc, t);
ee599f55 157 if (t->ops->confirm)
158 t->ops->confirm(t);
8c0a939e 159 }
160
161 /* --- Include configuration from a file --- *
162 *
163 * Slightly tricky. Scan the optional semicolon from the including
164 * stream, not the included one.
165 */
166
167 else if (strcmp(sc->d.buf, "include") == 0) {
168 FILE *fp;
169 dstr d = DSTR_INIT;
170
171 token(sc);
172 conf_name(sc, '/', &d);
173 if ((fp = fopen(d.buf, "r")) == 0)
174 error(sc, "can't include `%s': %s", d.buf, strerror(errno));
175 if (sc->t == ';')
176 token(sc);
177 pushback(sc);
178 scan_push(sc, scan_file(fp, d.buf, 0));
179 token(sc);
180 dstr_destroy(&d);
181 continue; /* Don't parse a trailing `;' */
182 }
183
184 /* --- Other configuration is handled elsewhere --- */
185
186 else {
187
188 /* --- First try among the sources --- */
189
190 {
191 source_ops **sops;
192
193 for (sops = sources; *sops; sops++) {
194 if ((*sops)->option && (*sops)->option(0, sc))
195 goto found_option;
196 }
197 }
198
199 /* --- Then try among the targets --- */
200
201 {
202 target_ops **tops;
203
204 for (tops = targets; *tops; tops++) {
205 if ((*tops)->option && (*tops)->option(0, sc))
206 goto found_option;
207 }
208 }
209
210 /* --- Nobody wants the option --- */
211
212 error(sc, "unknown global option or prefix `%s'", sc->d.buf);
213
214 found_option:;
215 }
216
217 if (sc->t == ';')
218 token(sc);
219 }
220}
221
222/*----- General utility functions -----------------------------------------*/
e82f7154 223
61e3dbdf 224/* --- @fw_log@ --- *
225 *
226 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
227 * @const char *fmt@ = format string to fill in
228 * @...@ = other arguments
229 *
230 * Returns: ---
231 *
232 * Use: Logs a connection.
233 */
234
235void fw_log(time_t t, const char *fmt, ...)
236{
237 struct tm *tm;
238 dstr d = DSTR_INIT;
239 va_list ap;
240
241 if (flags & FW_QUIET)
242 return;
243
d935f68b 244 if (t == NOW)
61e3dbdf 245 t = time(0);
246 tm = localtime(&t);
247 DENSURE(&d, 64);
367d81af 248 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S %z ", tm);
61e3dbdf 249 va_start(ap, fmt);
2f8a65a1 250 dstr_vputf(&d, fmt, &ap);
61e3dbdf 251 va_end(ap);
252 if (flags & FW_SYSLOG)
253 syslog(LOG_NOTICE, "%s", d.buf);
254 else {
255 DPUTC(&d, '\n');
256 dstr_write(&d, stderr);
257 }
258 DDESTROY(&d);
259}
260
261/* --- @fw_inc@, @fw_dec@ --- *
262 *
263 * Arguments: ---
264 *
265 * Returns: ---
266 *
9155ea97 267 * Use: Increments or decrements the active thing count. `fwd' won't
61e3dbdf 268 * quit while there are active things.
269 */
270
271void fw_inc(void) { flags |= FW_SET; active++; }
272void fw_dec(void) { if (active) active--; }
273
274/* --- @fw_exit@ --- *
275 *
276 * Arguments: ---
277 *
278 * Returns: ---
279 *
280 * Use: Exits when appropriate.
281 */
282
283static void fw_exit(void)
284{
285 endpt_killall();
286 source_killall();
287}
288
289/* --- @fw_tidy@ --- *
290 *
291 * Arguments: @int n@ = signal number
292 * @void *p@ = an uninteresting argument
293 *
294 * Returns: ---
295 *
296 * Use: Handles various signals and causes a clean tidy-up.
297 */
298
299static void fw_tidy(int n, void *p)
300{
372a98e2 301 const char *sn = 0;
302 switch (n) {
303 case SIGTERM: sn = "SIGTERM"; break;
304 case SIGINT: sn = "SIGINT"; break;
305 default: abort();
306 }
307
d935f68b 308 fw_log(NOW, "closing down gracefully on %s", sn);
372a98e2 309 source_killall();
310}
311
312/* --- @fw_die@ --- *
313 *
314 * Arguments: @int n@ = signal number
315 * @void *p@ = an uninteresting argument
316 *
317 * Returns: ---
318 *
319 * Use: Handles various signals and causes an abrupt shutdown.
320 */
321
322static void fw_die(int n, void *p)
323{
324 const char *sn = 0;
325 switch (n) {
326 case SIGQUIT: sn = "SIGQUIT"; break;
327 default: abort();
328 }
329
d935f68b 330 fw_log(NOW, "closing down abruptly on %s", sn);
372a98e2 331 source_killall();
332 endpt_killall();
333}
334
335/* --- @fw_reload@ --- *
336 *
337 * Arguments: @int n@ = a signal number
338 * @void *p@ = an uninteresting argument
339 *
340 * Returns: ---
341 *
342 * Use: Handles a hangup signal by re-reading configuration files.
343 */
344
345static void fw_reload(int n, void *p)
346{
347 FILE *fp;
348 scanner sc;
349 conffile *cf;
350
351 assert(n == SIGHUP);
352 if (!conffiles) {
d935f68b 353 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
372a98e2 354 return;
355 }
d935f68b 356 fw_log(NOW, "reloading configuration files...");
372a98e2 357 source_killall();
358 scan_create(&sc);
359 for (cf = conffiles; cf; cf = cf->next) {
360 if ((fp = fopen(cf->name, "r")) == 0)
d935f68b 361 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
372a98e2 362 else
363 scan_add(&sc, scan_file(fp, cf->name, 0));
364 }
8c0a939e 365 parse(&sc);
d935f68b 366 fw_log(NOW, "... reload completed OK");
61e3dbdf 367}
368
8c0a939e 369/*----- Startup and options parsing ---------------------------------------*/
370
e82f7154 371/* --- Standard GNU help options --- */
372
373static void version(FILE *fp)
374{
2d9ec601 375 pquis(fp, "$ version " VERSION "\n");
e82f7154 376}
377
378static void usage(FILE *fp)
379{
ad4499e3 380 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
e82f7154 381}
382
383static void help(FILE *fp)
384{
385 version(fp);
386 fputc('\n', fp);
387 usage(fp);
2d9ec601 388 pquis(fp, "\n\
61e3dbdf 389An excessively full-featured port-forwarder, which subsumes large chunks\n\
ad4499e3 390of the functionality of inetd, netcat, and normal cat.\n\
e82f7154 391\n\
392Configuration may be supplied in one or more configuration files, or on\n\
393the command line (or both). If no `-f' option is present, and no\n\
394configuration is given on the command line, the standard input stream is\n\
395read.\n\
396\n\
397Configuration is free-form. Comments begin with a `#' character and\n\
afd7451e 398continue to the end of the line. Each command line argument is considered\n\
61e3dbdf 399to be a separate line.\n\
afd7451e 400\n\
2d9ec601 401The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
402For a summary of the various options, run `$ --options'.\n\
ad4499e3 403\n\
404Options available are:\n\
405\n\
406Help options:\n\
407 -h, --help Display this help message.\n\
408 -v, --version Display the program's version number.\n\
409 -u, --usage Display a terse usage summary.\n\
410\n\
411Configuration summary:\n\
412 -G, --grammar Show a summary of the configuration language.\n\
413 -O, --options Show a summary of the source and target options.\n\
414\n\
415Other options:\n\
416 -f, --file=FILE Read configuration from a file.\n\
417 -q, --quiet Don't emit any logging information.\n\
418 -d, --daemon Fork into background after initializing.\n\
419 -p, --pidfile=FILE Write process-id to the named FILE.\n\
420 -l, --syslog Send log output to the system logger.\n\
421 -s, --setuid=USER Change uid to USER after initializing sources.\n\
422 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
2d9ec601 423");
424}
425
426/* --- Other helpful options --- */
427
428static void grammar(FILE *fp)
429{
430 version(fp);
8cf7c7c2
MW
431 fputs("\nGrammar summary\n\n", fp);
432 fputs(grammar_text, fp);
2d9ec601 433}
434
435static void options(FILE *fp)
436{
437 version(fp);
8cf7c7c2
MW
438 fputs("\nOption summary\n\n", fp);
439 fputs(option_text, fp);
e82f7154 440}
441
442/* --- @main@ --- *
443 *
444 * Arguments: @int argc@ = number of command line arguments
445 * @char *argv[]@ = vector of argument strings
446 *
447 * Returns: ---
448 *
449 * Use: Simple port-forwarding server.
450 */
451
452int main(int argc, char *argv[])
453{
454 unsigned f = 0;
455 sel_state sst;
372a98e2 456 sig s_term, s_quit, s_int, s_hup;
61e3dbdf 457 scanner sc;
fc170a33 458 uid_t drop = -1;
459 gid_t dropg = -1;
4166ea7c 460 const char *pidfile = 0;
372a98e2 461 conffile *cf, **cff = &conffiles;
e82f7154 462
372a98e2 463#define f_bogus 1u
a8b9c5eb 464#define f_file 2u
465#define f_syslog 4u
466#define f_fork 8u
e82f7154 467
468 /* --- Initialize things --- */
469
470 ego(argv[0]);
471 sel = &sst;
472 sel_init(sel);
473 sub_init();
61e3dbdf 474 sig_init(sel);
e82f7154 475 bres_init(sel);
2d9ec601 476 bres_exec(0);
61e3dbdf 477 exec_init();
478 fattr_init(&fattr_global);
479 scan_create(&sc);
480
61e3dbdf 481 atexit(fw_exit);
e82f7154 482
483 /* --- Parse command line options --- */
484
485 for (;;) {
486 static struct option opts[] = {
487
488 /* --- Standard GNU help options --- */
489
096c89c3 490 { "help", 0, 0, 'h' },
e82f7154 491 { "version", 0, 0, 'v' },
492 { "usage", 0, 0, 'u' },
493
2d9ec601 494 /* --- Other help options --- */
495
1e8a64e8 496 { "grammar", 0, 0, 'G' },
497 { "options", 0, 0, 'O' },
2d9ec601 498
e82f7154 499 /* --- Other useful arguments --- */
500
501 { "file", OPTF_ARGREQ, 0, 'f' },
61e3dbdf 502 { "fork", 0, 0, 'd' },
503 { "daemon", 0, 0, 'd' },
4166ea7c 504 { "pidfile", OPTF_ARGREQ, 0, 'p' },
17be1d6b 505 { "syslog", 0, 0, 'l' },
506 { "log", 0, 0, 'l' },
61e3dbdf 507 { "quiet", 0, 0, 'q' },
fc170a33 508 { "setuid", OPTF_ARGREQ, 0, 's' },
509 { "uid", OPTF_ARGREQ, 0, 's' },
510 { "setgid", OPTF_ARGREQ, 0, 'g' },
511 { "gid", OPTF_ARGREQ, 0, 'g' },
e82f7154 512
513 /* --- Magic terminator --- */
514
515 { 0, 0, 0, 0 }
516 };
c1e028be 517 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
e82f7154 518
519 if (i < 0)
520 break;
521 switch (i) {
522 case 'h':
523 help(stdout);
524 exit(0);
525 break;
526 case 'v':
527 version(stdout);
528 exit(0);
529 break;
530 case 'u':
531 usage(stdout);
532 exit(0);
533 break;
fc170a33 534 case 'G':
2d9ec601 535 grammar(stdout);
536 exit(0);
537 break;
fc170a33 538 case 'O':
2d9ec601 539 options(stdout);
540 exit(0);
541 break;
61e3dbdf 542 case 'f':
543 if (strcmp(optarg, "-") == 0)
544 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
545 else {
546 FILE *fp;
547 if ((fp = fopen(optarg, "r")) == 0)
548 die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
372a98e2 549 cf = CREATE(conffile);
550 cf->name = optarg;
551 *cff = cf;
552 cff = &cf->next;
61e3dbdf 553 scan_add(&sc, scan_file(fp, optarg, 0));
554 }
e82f7154 555 f |= f_file;
e82f7154 556 break;
61e3dbdf 557 case 'd':
e82f7154 558 f |= f_fork;
559 break;
4166ea7c 560 case 'p':
561 pidfile = optarg;
562 break;
17be1d6b 563 case 'l':
564 f |= f_syslog;
565 break;
61e3dbdf 566 case 'q':
567 flags |= FW_QUIET;
568 break;
fc170a33 569 case 's':
206212ca 570 if (isdigit((unsigned char )optarg[0])) {
fc170a33 571 char *q;
572 drop = strtol(optarg, &q, 0);
573 if (*q)
574 die(1, "bad uid `%s'", optarg);
575 } else {
576 struct passwd *pw = getpwnam(optarg);
577 if (!pw)
578 die(1, "unknown user `%s'", optarg);
579 drop = pw->pw_uid;
580 }
581 break;
582 case 'g':
206212ca 583 if (isdigit((unsigned char )optarg[0])) {
fc170a33 584 char *q;
585 dropg = strtol(optarg, &q, 0);
586 if (*q)
587 die(1, "bad gid `%s'", optarg);
588 } else {
589 struct group *gr = getgrnam(optarg);
590 if (!gr)
591 die(1, "unknown group `%s'", optarg);
592 dropg = gr->gr_gid;
593 }
594 break;
e82f7154 595 default:
596 f |= f_bogus;
597 break;
598 }
599 }
600
601 if (f & f_bogus) {
602 usage(stderr);
603 exit(1);
604 }
372a98e2 605 *cff = 0;
e82f7154 606
607 /* --- Deal with the remaining arguments --- */
608
61e3dbdf 609 if (optind < argc)
610 scan_add(&sc, scan_argv(argv + optind));
611 else if (f & f_file)
612 /* Cool */;
613 else if (!isatty(STDIN_FILENO))
614 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
615 else {
616 moan("no configuration given and stdin is a terminal.");
617 moan("type `%s --help' for usage information.", QUIS);
618 exit(1);
e82f7154 619 }
620
61e3dbdf 621 /* --- Parse the configuration now gathered --- */
e82f7154 622
8c0a939e 623 parse(&sc);
e82f7154 624
372a98e2 625 /* --- Set up some signal handlers --- *
626 *
627 * Don't enable @SIGINT@ if the caller already disabled it.
628 */
629
630 {
631 struct sigaction sa;
632
633 sig_add(&s_term, SIGTERM, fw_tidy, 0);
634 sig_add(&s_quit, SIGQUIT, fw_die, 0);
635 sigaction(SIGINT, 0, &sa);
636 if (sa.sa_handler != SIG_IGN)
637 sig_add(&s_int, SIGINT, fw_tidy, 0);
638 sig_add(&s_hup, SIGHUP, fw_reload, 0);
639 }
640
e82f7154 641 /* --- Fork into the background --- */
642
643 if (f & f_fork) {
644 pid_t kid;
645
646 kid = fork();
647 if (kid == -1)
648 die(1, "couldn't fork: %s", strerror(errno));
649 if (kid != 0)
650 _exit(0);
651
652 close(0); close(1); close(2);
f8f2bc04
MW
653 if (chdir("/"))
654 die(1, "couldn't change to root directory: %s", strerror(errno));
e82f7154 655 setsid();
656
657 kid = fork();
658 if (kid != 0)
659 _exit(0);
17be1d6b 660 }
4166ea7c 661 if (pidfile) {
662 FILE *fp = fopen(pidfile, "w");
663 if (!fp) {
664 die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
665 pidfile, strerror(errno));
666 }
667 fprintf(fp, "%lu\n", (unsigned long)getpid());
668 if (fflush(fp) || ferror(fp) || fclose(fp)) {
669 die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
670 pidfile, strerror(errno));
671 }
672 }
afd7451e 673
17be1d6b 674 if (f & f_syslog) {
afd7451e 675 flags |= FW_SYSLOG;
676 openlog(QUIS, 0, LOG_DAEMON);
e82f7154 677 }
678
8938f77b
MW
679 /* --- Drop privileges --- */
680
681 if (drop != (uid_t)-1)
682 privconn_split(sel);
683#ifdef HAVE_SETGROUPS
684 if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
685 (drop != (uid_t)-1 && setuid(drop)))
686 die(1, "couldn't drop privileges: %s", strerror(errno));
687#else
688 if ((dropg != (gid_t)-1 && setgid(dropg)) ||
689 (drop != (uid_t)-1 && setuid(drop)))
690 die(1, "couldn't drop privileges: %s", strerror(errno));
691#endif
692
e82f7154 693 /* --- Let rip --- */
694
61e3dbdf 695 if (!(flags & FW_SET))
696 moan("nothing to do!");
697 signal(SIGPIPE, SIG_IGN);
f9d40245 698
699 {
700 int selerr = 0;
701 while (active) {
702 if (!sel_select(sel))
206212ca 703 selerr = 0;
372a98e2 704 else if (errno != EINTR && errno != EAGAIN) {
d935f68b 705 fw_log(NOW, "error from select: %s", strerror(errno));
f9d40245 706 selerr++;
707 if (selerr > 8) {
d935f68b 708 fw_log(NOW, "too many consecutive select errors: bailing out");
f9d40245 709 exit(EXIT_FAILURE);
710 }
711 }
712 }
713 }
206212ca 714
e82f7154 715 return (0);
716}
717
718/*----- That's all, folks -------------------------------------------------*/