chiark / gitweb /
fwd: Improve `source' and `target' lifecycle management.
[fwd] / fwd.c
CommitLineData
e82f7154 1/* -*-c-*-
e82f7154 2 *
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 */
45f51d2f 32unsigned flags = 0; /* Global state flags */
61e3dbdf 33
34/*----- Static variables --------------------------------------------------*/
35
372a98e2 36typedef struct conffile {
37 struct conffile *next;
38 char *name;
39} conffile;
40
61e3dbdf 41static unsigned active = 0; /* Number of active things */
372a98e2 42static conffile *conffiles = 0; /* List of configuration files */
61e3dbdf 43
8c0a939e 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
55static source_ops *sources[] =
56 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
57static target_ops *targets[] =
58 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
59
60void 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 ||
9155ea97 73 strcmp(sc->d.buf, "fwd") == 0 ||
206212ca 74 strcmp(sc->d.buf, "from") == 0) {
8c0a939e 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);
ee599f55 153 if (t->ops->confirm)
154 t->ops->confirm(t);
57ceb980 155 source_dec(s); target_dec(t);
8c0a939e 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 -----------------------------------------*/
e82f7154 220
61e3dbdf 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
232void 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
d935f68b 241 if (t == NOW)
61e3dbdf 242 t = time(0);
243 tm = localtime(&t);
244 DENSURE(&d, 64);
367d81af 245 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S %z ", tm);
61e3dbdf 246 va_start(ap, fmt);
2f8a65a1 247 dstr_vputf(&d, fmt, &ap);
61e3dbdf 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 *
9155ea97 264 * Use: Increments or decrements the active thing count. `fwd' won't
61e3dbdf 265 * quit while there are active things.
266 */
267
268void fw_inc(void) { flags |= FW_SET; active++; }
269void fw_dec(void) { if (active) active--; }
270
271/* --- @fw_exit@ --- *
272 *
273 * Arguments: ---
274 *
275 * Returns: ---
276 *
277 * Use: Exits when appropriate.
278 */
279
280static 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
296static void fw_tidy(int n, void *p)
297{
372a98e2 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
d935f68b 305 fw_log(NOW, "closing down gracefully on %s", sn);
372a98e2 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
319static 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
d935f68b 327 fw_log(NOW, "closing down abruptly on %s", sn);
372a98e2 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
342static 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) {
d935f68b 350 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
372a98e2 351 return;
352 }
d935f68b 353 fw_log(NOW, "reloading configuration files...");
372a98e2 354 source_killall();
355 scan_create(&sc);
356 for (cf = conffiles; cf; cf = cf->next) {
357 if ((fp = fopen(cf->name, "r")) == 0)
d935f68b 358 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
372a98e2 359 else
360 scan_add(&sc, scan_file(fp, cf->name, 0));
361 }
8c0a939e 362 parse(&sc);
d935f68b 363 fw_log(NOW, "... reload completed OK");
61e3dbdf 364}
365
8c0a939e 366/*----- Startup and options parsing ---------------------------------------*/
367
e82f7154 368/* --- Standard GNU help options --- */
369
370static void version(FILE *fp)
371{
2d9ec601 372 pquis(fp, "$ version " VERSION "\n");
e82f7154 373}
374
375static void usage(FILE *fp)
376{
ad4499e3 377 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
e82f7154 378}
379
380static void help(FILE *fp)
381{
382 version(fp);
383 fputc('\n', fp);
384 usage(fp);
2d9ec601 385 pquis(fp, "\n\
61e3dbdf 386An excessively full-featured port-forwarder, which subsumes large chunks\n\
ad4499e3 387of the functionality of inetd, netcat, and normal cat.\n\
e82f7154 388\n\
389Configuration may be supplied in one or more configuration files, or on\n\
390the command line (or both). If no `-f' option is present, and no\n\
391configuration is given on the command line, the standard input stream is\n\
392read.\n\
393\n\
394Configuration is free-form. Comments begin with a `#' character and\n\
afd7451e 395continue to the end of the line. Each command line argument is considered\n\
61e3dbdf 396to be a separate line.\n\
afd7451e 397\n\
2d9ec601 398The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
399For a summary of the various options, run `$ --options'.\n\
ad4499e3 400\n\
401Options available are:\n\
402\n\
403Help 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\
408Configuration 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\
412Other 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\
2d9ec601 420");
421}
422
423/* --- Other helpful options --- */
424
425static void grammar(FILE *fp)
426{
427 version(fp);
8cf7c7c2
MW
428 fputs("\nGrammar summary\n\n", fp);
429 fputs(grammar_text, fp);
2d9ec601 430}
431
432static void options(FILE *fp)
433{
434 version(fp);
8cf7c7c2
MW
435 fputs("\nOption summary\n\n", fp);
436 fputs(option_text, fp);
e82f7154 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
449int main(int argc, char *argv[])
450{
451 unsigned f = 0;
452 sel_state sst;
372a98e2 453 sig s_term, s_quit, s_int, s_hup;
61e3dbdf 454 scanner sc;
fc170a33 455 uid_t drop = -1;
456 gid_t dropg = -1;
4166ea7c 457 const char *pidfile = 0;
372a98e2 458 conffile *cf, **cff = &conffiles;
e82f7154 459
372a98e2 460#define f_bogus 1u
a8b9c5eb 461#define f_file 2u
462#define f_syslog 4u
463#define f_fork 8u
e82f7154 464
465 /* --- Initialize things --- */
466
467 ego(argv[0]);
468 sel = &sst;
469 sel_init(sel);
470 sub_init();
61e3dbdf 471 sig_init(sel);
e82f7154 472 bres_init(sel);
2d9ec601 473 bres_exec(0);
61e3dbdf 474 exec_init();
475 fattr_init(&fattr_global);
476 scan_create(&sc);
477
61e3dbdf 478 atexit(fw_exit);
e82f7154 479
480 /* --- Parse command line options --- */
481
482 for (;;) {
483 static struct option opts[] = {
484
485 /* --- Standard GNU help options --- */
486
096c89c3 487 { "help", 0, 0, 'h' },
e82f7154 488 { "version", 0, 0, 'v' },
489 { "usage", 0, 0, 'u' },
490
2d9ec601 491 /* --- Other help options --- */
492
1e8a64e8 493 { "grammar", 0, 0, 'G' },
494 { "options", 0, 0, 'O' },
2d9ec601 495
e82f7154 496 /* --- Other useful arguments --- */
497
498 { "file", OPTF_ARGREQ, 0, 'f' },
61e3dbdf 499 { "fork", 0, 0, 'd' },
500 { "daemon", 0, 0, 'd' },
4166ea7c 501 { "pidfile", OPTF_ARGREQ, 0, 'p' },
17be1d6b 502 { "syslog", 0, 0, 'l' },
503 { "log", 0, 0, 'l' },
61e3dbdf 504 { "quiet", 0, 0, 'q' },
fc170a33 505 { "setuid", OPTF_ARGREQ, 0, 's' },
506 { "uid", OPTF_ARGREQ, 0, 's' },
507 { "setgid", OPTF_ARGREQ, 0, 'g' },
508 { "gid", OPTF_ARGREQ, 0, 'g' },
e82f7154 509
510 /* --- Magic terminator --- */
511
512 { 0, 0, 0, 0 }
513 };
c1e028be 514 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
e82f7154 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;
fc170a33 531 case 'G':
2d9ec601 532 grammar(stdout);
533 exit(0);
534 break;
fc170a33 535 case 'O':
2d9ec601 536 options(stdout);
537 exit(0);
538 break;
61e3dbdf 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));
372a98e2 546 cf = CREATE(conffile);
547 cf->name = optarg;
548 *cff = cf;
549 cff = &cf->next;
61e3dbdf 550 scan_add(&sc, scan_file(fp, optarg, 0));
551 }
e82f7154 552 f |= f_file;
e82f7154 553 break;
61e3dbdf 554 case 'd':
e82f7154 555 f |= f_fork;
556 break;
4166ea7c 557 case 'p':
558 pidfile = optarg;
559 break;
17be1d6b 560 case 'l':
561 f |= f_syslog;
562 break;
61e3dbdf 563 case 'q':
564 flags |= FW_QUIET;
565 break;
fc170a33 566 case 's':
206212ca 567 if (isdigit((unsigned char )optarg[0])) {
fc170a33 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':
206212ca 580 if (isdigit((unsigned char )optarg[0])) {
fc170a33 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;
e82f7154 592 default:
593 f |= f_bogus;
594 break;
595 }
596 }
597
598 if (f & f_bogus) {
599 usage(stderr);
600 exit(1);
601 }
372a98e2 602 *cff = 0;
e82f7154 603
604 /* --- Deal with the remaining arguments --- */
605
61e3dbdf 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);
e82f7154 616 }
617
61e3dbdf 618 /* --- Parse the configuration now gathered --- */
e82f7154 619
8c0a939e 620 parse(&sc);
e82f7154 621
372a98e2 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
e82f7154 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);
f8f2bc04
MW
650 if (chdir("/"))
651 die(1, "couldn't change to root directory: %s", strerror(errno));
e82f7154 652 setsid();
653
654 kid = fork();
655 if (kid != 0)
656 _exit(0);
17be1d6b 657 }
4166ea7c 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 }
afd7451e 670
17be1d6b 671 if (f & f_syslog) {
afd7451e 672 flags |= FW_SYSLOG;
673 openlog(QUIS, 0, LOG_DAEMON);
e82f7154 674 }
675
8938f77b
MW
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
e82f7154 690 /* --- Let rip --- */
691
61e3dbdf 692 if (!(flags & FW_SET))
693 moan("nothing to do!");
694 signal(SIGPIPE, SIG_IGN);
f9d40245 695
696 {
697 int selerr = 0;
698 while (active) {
699 if (!sel_select(sel))
206212ca 700 selerr = 0;
372a98e2 701 else if (errno != EINTR && errno != EAGAIN) {
d935f68b 702 fw_log(NOW, "error from select: %s", strerror(errno));
f9d40245 703 selerr++;
704 if (selerr > 8) {
d935f68b 705 fw_log(NOW, "too many consecutive select errors: bailing out");
f9d40245 706 exit(EXIT_FAILURE);
707 }
708 }
709 }
710 }
206212ca 711
e82f7154 712 return (0);
713}
714
715/*----- That's all, folks -------------------------------------------------*/