Remove crufty CVS $Id$ tags.
[fwd] / fw.c
CommitLineData
e82f7154 1/* -*-c-*-
2 *
e82f7154 3 * Port forwarding thingy
4 *
61e3dbdf 5 * (c) 1999 Straylight/Edgeware
e82f7154 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
e82f7154 27/*----- Header files ------------------------------------------------------*/
28
29#include "config.h"
30
372a98e2 31#include <assert.h>
e82f7154 32#include <ctype.h>
33#include <errno.h>
61e3dbdf 34#include <signal.h>
35#include <stdarg.h>
e82f7154 36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
61e3dbdf 39#include <time.h>
e82f7154 40
41#include <unistd.h>
42#include <syslog.h>
43
fc170a33 44#include <grp.h>
45#include <pwd.h>
46
2d9ec601 47#include <mLib/bres.h>
61e3dbdf 48#include <mLib/dstr.h>
e82f7154 49#include <mLib/mdwopt.h>
50#include <mLib/quis.h>
51#include <mLib/report.h>
52#include <mLib/sel.h>
61e3dbdf 53#include <mLib/sig.h>
e82f7154 54#include <mLib/sub.h>
55
e82f7154 56#include "conf.h"
61e3dbdf 57#include "endpt.h"
58#include "exec.h"
59#include "fattr.h"
8c0a939e 60#include "file.h"
afd7451e 61#include "fw.h"
8cf7c7c2 62#include "mantext.h"
ee599f55 63#include "privconn.h"
e82f7154 64#include "scan.h"
8c0a939e 65#include "socket.h"
61e3dbdf 66#include "source.h"
e82f7154 67
afd7451e 68/*----- Global variables --------------------------------------------------*/
e82f7154 69
70sel_state *sel; /* Multiplexor for nonblocking I/O */
61e3dbdf 71
72/*----- Static variables --------------------------------------------------*/
73
372a98e2 74typedef struct conffile {
75 struct conffile *next;
76 char *name;
77} conffile;
78
61e3dbdf 79static unsigned flags = 0; /* Global state flags */
80static unsigned active = 0; /* Number of active things */
372a98e2 81static conffile *conffiles = 0; /* List of configuration files */
61e3dbdf 82
83#define FW_SYSLOG 1u
84#define FW_QUIET 2u
85#define FW_SET 4u
e82f7154 86
8c0a939e 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
98static source_ops *sources[] =
99 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
100static target_ops *targets[] =
101 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
102
103void 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);
ee599f55 196 if (t->ops->confirm)
197 t->ops->confirm(t);
8c0a939e 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 -----------------------------------------*/
e82f7154 262
61e3dbdf 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
274void 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);
17be1d6b 287 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
61e3dbdf 288 va_start(ap, fmt);
2f8a65a1 289 dstr_vputf(&d, fmt, &ap);
61e3dbdf 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
310void fw_inc(void) { flags |= FW_SET; active++; }
311void fw_dec(void) { if (active) active--; }
312
313/* --- @fw_exit@ --- *
314 *
315 * Arguments: ---
316 *
317 * Returns: ---
318 *
319 * Use: Exits when appropriate.
320 */
321
322static 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
338static void fw_tidy(int n, void *p)
339{
372a98e2 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
361static 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
384static 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 }
8c0a939e 404 parse(&sc);
372a98e2 405 fw_log(-1, "... reload completed OK");
61e3dbdf 406}
407
8c0a939e 408/*----- Startup and options parsing ---------------------------------------*/
409
e82f7154 410/* --- Standard GNU help options --- */
411
412static void version(FILE *fp)
413{
2d9ec601 414 pquis(fp, "$ version " VERSION "\n");
e82f7154 415}
416
417static void usage(FILE *fp)
418{
ad4499e3 419 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
e82f7154 420}
421
422static void help(FILE *fp)
423{
424 version(fp);
425 fputc('\n', fp);
426 usage(fp);
2d9ec601 427 pquis(fp, "\n\
61e3dbdf 428An excessively full-featured port-forwarder, which subsumes large chunks\n\
ad4499e3 429of the functionality of inetd, netcat, and normal cat.\n\
e82f7154 430\n\
431Configuration may be supplied in one or more configuration files, or on\n\
432the command line (or both). If no `-f' option is present, and no\n\
433configuration is given on the command line, the standard input stream is\n\
434read.\n\
435\n\
436Configuration is free-form. Comments begin with a `#' character and\n\
afd7451e 437continue to the end of the line. Each command line argument is considered\n\
61e3dbdf 438to be a separate line.\n\
afd7451e 439\n\
2d9ec601 440The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
441For a summary of the various options, run `$ --options'.\n\
ad4499e3 442\n\
443Options available are:\n\
444\n\
445Help 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\
450Configuration 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\
454Other 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\
2d9ec601 462");
463}
464
465/* --- Other helpful options --- */
466
467static void grammar(FILE *fp)
468{
469 version(fp);
8cf7c7c2
MW
470 fputs("\nGrammar summary\n\n", fp);
471 fputs(grammar_text, fp);
2d9ec601 472}
473
474static void options(FILE *fp)
475{
476 version(fp);
8cf7c7c2
MW
477 fputs("\nOption summary\n\n", fp);
478 fputs(option_text, fp);
e82f7154 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
491int main(int argc, char *argv[])
492{
493 unsigned f = 0;
494 sel_state sst;
372a98e2 495 sig s_term, s_quit, s_int, s_hup;
61e3dbdf 496 scanner sc;
fc170a33 497 uid_t drop = -1;
498 gid_t dropg = -1;
4166ea7c 499 const char *pidfile = 0;
372a98e2 500 conffile *cf, **cff = &conffiles;
e82f7154 501
372a98e2 502#define f_bogus 1u
a8b9c5eb 503#define f_file 2u
504#define f_syslog 4u
505#define f_fork 8u
e82f7154 506
507 /* --- Initialize things --- */
508
509 ego(argv[0]);
510 sel = &sst;
511 sel_init(sel);
512 sub_init();
61e3dbdf 513 sig_init(sel);
e82f7154 514 bres_init(sel);
2d9ec601 515 bres_exec(0);
61e3dbdf 516 exec_init();
517 fattr_init(&fattr_global);
518 scan_create(&sc);
519
61e3dbdf 520 atexit(fw_exit);
e82f7154 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
2d9ec601 533 /* --- Other help options --- */
534
1e8a64e8 535 { "grammar", 0, 0, 'G' },
536 { "options", 0, 0, 'O' },
2d9ec601 537
e82f7154 538 /* --- Other useful arguments --- */
539
540 { "file", OPTF_ARGREQ, 0, 'f' },
61e3dbdf 541 { "fork", 0, 0, 'd' },
542 { "daemon", 0, 0, 'd' },
4166ea7c 543 { "pidfile", OPTF_ARGREQ, 0, 'p' },
17be1d6b 544 { "syslog", 0, 0, 'l' },
545 { "log", 0, 0, 'l' },
61e3dbdf 546 { "quiet", 0, 0, 'q' },
fc170a33 547 { "setuid", OPTF_ARGREQ, 0, 's' },
548 { "uid", OPTF_ARGREQ, 0, 's' },
549 { "setgid", OPTF_ARGREQ, 0, 'g' },
550 { "gid", OPTF_ARGREQ, 0, 'g' },
e82f7154 551
552 /* --- Magic terminator --- */
553
554 { 0, 0, 0, 0 }
555 };
4166ea7c 556 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
e82f7154 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;
fc170a33 573 case 'G':
2d9ec601 574 grammar(stdout);
575 exit(0);
576 break;
fc170a33 577 case 'O':
2d9ec601 578 options(stdout);
579 exit(0);
580 break;
61e3dbdf 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));
372a98e2 588 cf = CREATE(conffile);
589 cf->name = optarg;
590 *cff = cf;
591 cff = &cf->next;
61e3dbdf 592 scan_add(&sc, scan_file(fp, optarg, 0));
593 }
e82f7154 594 f |= f_file;
e82f7154 595 break;
61e3dbdf 596 case 'd':
e82f7154 597 f |= f_fork;
598 break;
4166ea7c 599 case 'p':
600 pidfile = optarg;
601 break;
17be1d6b 602 case 'l':
603 f |= f_syslog;
604 break;
61e3dbdf 605 case 'q':
606 flags |= FW_QUIET;
607 break;
fc170a33 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;
e82f7154 634 default:
635 f |= f_bogus;
636 break;
637 }
638 }
639
640 if (f & f_bogus) {
641 usage(stderr);
642 exit(1);
643 }
372a98e2 644 *cff = 0;
e82f7154 645
646 /* --- Deal with the remaining arguments --- */
647
61e3dbdf 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);
e82f7154 658 }
659
61e3dbdf 660 /* --- Parse the configuration now gathered --- */
e82f7154 661
8c0a939e 662 parse(&sc);
e82f7154 663
372a98e2 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
e82f7154 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);
afd7451e 692 chdir("/");
e82f7154 693 setsid();
694
695 kid = fork();
696 if (kid != 0)
697 _exit(0);
17be1d6b 698 }
4166ea7c 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 }
afd7451e 711
17be1d6b 712 if (f & f_syslog) {
afd7451e 713 flags |= FW_SYSLOG;
714 openlog(QUIS, 0, LOG_DAEMON);
e82f7154 715 }
716
8938f77b
MW
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
e82f7154 731 /* --- Let rip --- */
732
61e3dbdf 733 if (!(flags & FW_SET))
734 moan("nothing to do!");
735 signal(SIGPIPE, SIG_IGN);
f9d40245 736
737 {
738 int selerr = 0;
739 while (active) {
740 if (!sel_select(sel))
741 selerr = 0;
372a98e2 742 else if (errno != EINTR && errno != EAGAIN) {
f9d40245 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
e82f7154 753 return (0);
754}
755
756/*----- That's all, folks -------------------------------------------------*/