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