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