fwd.c (fw_log): Report the timezone in log messages.
[fwd] / fwd.c
1 /* -*-c-*-
2 *
3 * Port forwarding thingy
4 *
5 * (c) 1999 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the `fwd' port forwarder.
11 *
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.
16 *
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.
21 *
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.
25 */
26
27 #include "fwd.h"
28
29 /*----- Global variables --------------------------------------------------*/
30
31 sel_state *sel; /* Multiplexor for nonblocking I/O */
32
33 /*----- Static variables --------------------------------------------------*/
34
35 typedef struct conffile {
36 struct conffile *next;
37 char *name;
38 } conffile;
39
40 static unsigned flags = 0; /* Global state flags */
41 static unsigned active = 0; /* Number of active things */
42 static conffile *conffiles = 0; /* List of configuration files */
43
44 #define FW_SYSLOG 1u
45 #define FW_QUIET 2u
46 #define FW_SET 4u
47
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
59 static source_ops *sources[] =
60 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
61 static target_ops *targets[] =
62 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
63
64 void 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 ||
77 strcmp(sc->d.buf, "fwd") == 0 ||
78 strcmp(sc->d.buf, "from") == 0) {
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);
157 if (t->ops->confirm)
158 t->ops->confirm(t);
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 -----------------------------------------*/
223
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
235 void 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
244 if (t == NOW)
245 t = time(0);
246 tm = localtime(&t);
247 DENSURE(&d, 64);
248 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S %z ", tm);
249 va_start(ap, fmt);
250 dstr_vputf(&d, fmt, &ap);
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 *
267 * Use: Increments or decrements the active thing count. `fwd' won't
268 * quit while there are active things.
269 */
270
271 void fw_inc(void) { flags |= FW_SET; active++; }
272 void fw_dec(void) { if (active) active--; }
273
274 /* --- @fw_exit@ --- *
275 *
276 * Arguments: ---
277 *
278 * Returns: ---
279 *
280 * Use: Exits when appropriate.
281 */
282
283 static 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
299 static void fw_tidy(int n, void *p)
300 {
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
308 fw_log(NOW, "closing down gracefully on %s", sn);
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
322 static 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
330 fw_log(NOW, "closing down abruptly on %s", sn);
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
345 static 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) {
353 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
354 return;
355 }
356 fw_log(NOW, "reloading configuration files...");
357 source_killall();
358 scan_create(&sc);
359 for (cf = conffiles; cf; cf = cf->next) {
360 if ((fp = fopen(cf->name, "r")) == 0)
361 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
362 else
363 scan_add(&sc, scan_file(fp, cf->name, 0));
364 }
365 parse(&sc);
366 fw_log(NOW, "... reload completed OK");
367 }
368
369 /*----- Startup and options parsing ---------------------------------------*/
370
371 /* --- Standard GNU help options --- */
372
373 static void version(FILE *fp)
374 {
375 pquis(fp, "$ version " VERSION "\n");
376 }
377
378 static void usage(FILE *fp)
379 {
380 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
381 }
382
383 static void help(FILE *fp)
384 {
385 version(fp);
386 fputc('\n', fp);
387 usage(fp);
388 pquis(fp, "\n\
389 An excessively full-featured port-forwarder, which subsumes large chunks\n\
390 of the functionality of inetd, netcat, and normal cat.\n\
391 \n\
392 Configuration may be supplied in one or more configuration files, or on\n\
393 the command line (or both). If no `-f' option is present, and no\n\
394 configuration is given on the command line, the standard input stream is\n\
395 read.\n\
396 \n\
397 Configuration is free-form. Comments begin with a `#' character and\n\
398 continue to the end of the line. Each command line argument is considered\n\
399 to be a separate line.\n\
400 \n\
401 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
402 For a summary of the various options, run `$ --options'.\n\
403 \n\
404 Options available are:\n\
405 \n\
406 Help 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\
411 Configuration 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\
415 Other 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\
423 ");
424 }
425
426 /* --- Other helpful options --- */
427
428 static void grammar(FILE *fp)
429 {
430 version(fp);
431 fputs("\nGrammar summary\n\n", fp);
432 fputs(grammar_text, fp);
433 }
434
435 static void options(FILE *fp)
436 {
437 version(fp);
438 fputs("\nOption summary\n\n", fp);
439 fputs(option_text, fp);
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
452 int main(int argc, char *argv[])
453 {
454 unsigned f = 0;
455 sel_state sst;
456 sig s_term, s_quit, s_int, s_hup;
457 scanner sc;
458 uid_t drop = -1;
459 gid_t dropg = -1;
460 const char *pidfile = 0;
461 conffile *cf, **cff = &conffiles;
462
463 #define f_bogus 1u
464 #define f_file 2u
465 #define f_syslog 4u
466 #define f_fork 8u
467
468 /* --- Initialize things --- */
469
470 ego(argv[0]);
471 sel = &sst;
472 sel_init(sel);
473 sub_init();
474 sig_init(sel);
475 bres_init(sel);
476 bres_exec(0);
477 exec_init();
478 fattr_init(&fattr_global);
479 scan_create(&sc);
480
481 atexit(fw_exit);
482
483 /* --- Parse command line options --- */
484
485 for (;;) {
486 static struct option opts[] = {
487
488 /* --- Standard GNU help options --- */
489
490 { "help", 0, 0, 'h' },
491 { "version", 0, 0, 'v' },
492 { "usage", 0, 0, 'u' },
493
494 /* --- Other help options --- */
495
496 { "grammar", 0, 0, 'G' },
497 { "options", 0, 0, 'O' },
498
499 /* --- Other useful arguments --- */
500
501 { "file", OPTF_ARGREQ, 0, 'f' },
502 { "fork", 0, 0, 'd' },
503 { "daemon", 0, 0, 'd' },
504 { "pidfile", OPTF_ARGREQ, 0, 'p' },
505 { "syslog", 0, 0, 'l' },
506 { "log", 0, 0, 'l' },
507 { "quiet", 0, 0, 'q' },
508 { "setuid", OPTF_ARGREQ, 0, 's' },
509 { "uid", OPTF_ARGREQ, 0, 's' },
510 { "setgid", OPTF_ARGREQ, 0, 'g' },
511 { "gid", OPTF_ARGREQ, 0, 'g' },
512
513 /* --- Magic terminator --- */
514
515 { 0, 0, 0, 0 }
516 };
517 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
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;
534 case 'G':
535 grammar(stdout);
536 exit(0);
537 break;
538 case 'O':
539 options(stdout);
540 exit(0);
541 break;
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));
549 cf = CREATE(conffile);
550 cf->name = optarg;
551 *cff = cf;
552 cff = &cf->next;
553 scan_add(&sc, scan_file(fp, optarg, 0));
554 }
555 f |= f_file;
556 break;
557 case 'd':
558 f |= f_fork;
559 break;
560 case 'p':
561 pidfile = optarg;
562 break;
563 case 'l':
564 f |= f_syslog;
565 break;
566 case 'q':
567 flags |= FW_QUIET;
568 break;
569 case 's':
570 if (isdigit((unsigned char )optarg[0])) {
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':
583 if (isdigit((unsigned char )optarg[0])) {
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;
595 default:
596 f |= f_bogus;
597 break;
598 }
599 }
600
601 if (f & f_bogus) {
602 usage(stderr);
603 exit(1);
604 }
605 *cff = 0;
606
607 /* --- Deal with the remaining arguments --- */
608
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);
619 }
620
621 /* --- Parse the configuration now gathered --- */
622
623 parse(&sc);
624
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
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);
653 if (chdir("/"))
654 die(1, "couldn't change to root directory: %s", strerror(errno));
655 setsid();
656
657 kid = fork();
658 if (kid != 0)
659 _exit(0);
660 }
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 }
673
674 if (f & f_syslog) {
675 flags |= FW_SYSLOG;
676 openlog(QUIS, 0, LOG_DAEMON);
677 }
678
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
693 /* --- Let rip --- */
694
695 if (!(flags & FW_SET))
696 moan("nothing to do!");
697 signal(SIGPIPE, SIG_IGN);
698
699 {
700 int selerr = 0;
701 while (active) {
702 if (!sel_select(sel))
703 selerr = 0;
704 else if (errno != EINTR && errno != EAGAIN) {
705 fw_log(NOW, "error from select: %s", strerror(errno));
706 selerr++;
707 if (selerr > 8) {
708 fw_log(NOW, "too many consecutive select errors: bailing out");
709 exit(EXIT_FAILURE);
710 }
711 }
712 }
713 }
714
715 return (0);
716 }
717
718 /*----- That's all, folks -------------------------------------------------*/