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