chiark / gitweb /
Update docco for new options.
[fwd] / fw.c
1 /* -*-c-*-
2  *
3  * $Id: fw.c,v 1.15 2003/11/25 14:46:50 mdw Exp $
4  *
5  * Port forwarding thingy
6  *
7  * (c) 1999 Straylight/Edgeware
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
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: fw.c,v $
32  * Revision 1.15  2003/11/25 14:46:50  mdw
33  * Update docco for new options.
34  *
35  * Revision 1.14  2003/01/24 20:12:40  mdw
36  * Correctly cast uid and gid sentinel values.
37  *
38  * Revision 1.13  2002/02/22 23:45:20  mdw
39  * Add option to change the listen(2) parameter.  Receive `fw'-specific
40  * code from `conf.c'.
41  *
42  * Revision 1.12  2002/01/13 14:49:17  mdw
43  * Track @dstr_vputf@ change.
44  *
45  * Revision 1.11  2001/02/03 20:33:26  mdw
46  * Fix flags to be unsigned.
47  *
48  * Revision 1.10  2001/02/03 20:30:03  mdw
49  * Support re-reading config files on SIGHUP.
50  *
51  * Revision 1.9  2001/01/20 11:55:17  mdw
52  * Handle select errors more robustly.
53  *
54  * Revision 1.8  2000/03/23 23:19:19  mdw
55  * Fix changed options in parser table.
56  *
57  * Revision 1.7  2000/03/23 00:37:33  mdw
58  * Add option to change user and group after initialization.  Naughtily
59  * reassign short equivalents of --grammar and --options.
60  *
61  * Revision 1.6  1999/12/22 15:44:10  mdw
62  * Make syslog a separate option, and do it better.
63  *
64  * Revision 1.5  1999/10/22 22:47:50  mdw
65  * Grammar changes.  Also, don't enable SIGINT if it's currently ignored.
66  *
67  * Revision 1.4  1999/10/10 16:46:12  mdw
68  * New resolver to initialize.  Also, include options for grammar and
69  * options references.
70  *
71  * Revision 1.3  1999/07/26 23:30:42  mdw
72  * Major reconstruction work for new design.
73  *
74  * Revision 1.2  1999/07/03 13:55:17  mdw
75  * Various changes.  Add configuration grammar to help text.  Change to
76  * root directory and open syslog when forking into background.
77  *
78  * Revision 1.1.1.1  1999/07/01 08:56:23  mdw
79  * Initial revision.
80  *
81  */
82
83 /*----- Header files ------------------------------------------------------*/
84
85 #include "config.h"
86
87 #include <assert.h>
88 #include <ctype.h>
89 #include <errno.h>
90 #include <signal.h>
91 #include <stdarg.h>
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <string.h>
95 #include <time.h>
96
97 #include <unistd.h>
98 #include <syslog.h>
99
100 #include <grp.h>
101 #include <pwd.h>
102
103 #include <mLib/bres.h>
104 #include <mLib/dstr.h>
105 #include <mLib/mdwopt.h>
106 #include <mLib/quis.h>
107 #include <mLib/report.h>
108 #include <mLib/sel.h>
109 #include <mLib/sig.h>
110 #include <mLib/sub.h>
111
112 #include "conf.h"
113 #include "endpt.h"
114 #include "exec.h"
115 #include "fattr.h"
116 #include "file.h"
117 #include "fw.h"
118 #include "scan.h"
119 #include "socket.h"
120 #include "source.h"
121
122 /*----- Global variables --------------------------------------------------*/
123
124 sel_state *sel;                         /* Multiplexor for nonblocking I/O */
125
126 /*----- Static variables --------------------------------------------------*/
127
128 typedef struct conffile {
129   struct conffile *next;
130   char *name;
131 } conffile;
132
133 static unsigned flags = 0;              /* Global state flags */
134 static unsigned active = 0;             /* Number of active things */
135 static conffile *conffiles = 0;         /* List of configuration files */
136
137 #define FW_SYSLOG 1u
138 #define FW_QUIET 2u
139 #define FW_SET 4u
140
141 /*----- Configuration parsing ---------------------------------------------*/
142
143 /* --- @parse@ --- *
144  *
145  * Arguments:   @scanner *sc@ = pointer to scanner definition
146  *
147  * Returns:     ---
148  *
149  * Use:         Parses a configuration file from the scanner.
150  */
151
152 static source_ops *sources[] =
153   { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
154 static target_ops *targets[] =
155   { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
156
157 void parse(scanner *sc)
158 {
159   token(sc);
160
161   for (;;) {
162     if (sc->t == CTOK_EOF)
163       break;
164     if (sc->t != CTOK_WORD)
165       error(sc, "parse error, keyword expected");
166
167     /* --- Handle a forwarding request --- */
168
169     if (strcmp(sc->d.buf, "forward") == 0 ||
170         strcmp(sc->d.buf, "fw") == 0 ||
171         strcmp(sc->d.buf, "from") == 0) {
172       source *s;
173       target *t;
174
175       token(sc);
176
177       /* --- Read a source description --- */
178
179       {
180         source_ops **sops;
181
182         /* --- Try to find a source type which understands --- */
183
184         s = 0;
185         for (sops = sources; *sops; sops++) {
186           if ((s = (*sops)->read(sc)) != 0)
187             goto found_source;
188         }
189         error(sc, "unknown source name `%s'", sc->d.buf);
190
191         /* --- Read any source-specific options --- */
192
193       found_source:
194         if (sc->t == '{') {
195           token(sc);
196           while (sc->t == CTOK_WORD) {
197             if (!s->ops->option || !s->ops->option(s, sc)) {
198               error(sc, "unknown %s source option `%s'",
199                     s->ops->name, sc->d.buf);
200             }
201             if (sc->t == ';')
202               token(sc);
203           }
204           if (sc->t != '}')
205             error(sc, "parse error, missing `}'");
206           token(sc);
207         }
208       }
209
210       /* --- Read a destination description --- */
211
212       if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
213                                  strcmp(sc->d.buf, "->") == 0))
214         token(sc);
215
216       {
217         target_ops **tops;
218
219         /* --- Try to find a target which understands --- */
220
221         t = 0;
222         for (tops = targets; *tops; tops++) {
223           if ((t = (*tops)->read(sc)) != 0)
224             goto found_target;
225         }
226         error(sc, "unknown target name `%s'", sc->d.buf);
227
228         /* --- Read any target-specific options --- */
229
230       found_target:
231         if (sc->t == '{') {
232           token(sc);
233           while (sc->t == CTOK_WORD) {
234             if (!t->ops->option || !t->ops->option(t, sc)) {
235               error(sc, "unknown %s target option `%s'",
236                     t->ops->name, sc->d.buf);
237             }
238             if (sc->t == ';')
239               token(sc);
240           }
241           if (sc->t != '}')
242             error(sc, "parse error, `}' expected");
243           token(sc);
244         }
245       }
246
247       /* --- Combine the source and target --- */
248
249       s->ops->attach(s, sc, t);
250     }
251
252     /* --- Include configuration from a file --- *
253      *
254      * Slightly tricky.  Scan the optional semicolon from the including
255      * stream, not the included one.
256      */
257
258     else if (strcmp(sc->d.buf, "include") == 0) {
259       FILE *fp;
260       dstr d = DSTR_INIT;
261
262       token(sc);
263       conf_name(sc, '/', &d);
264       if ((fp = fopen(d.buf, "r")) == 0)
265         error(sc, "can't include `%s': %s", d.buf, strerror(errno));
266       if (sc->t == ';')
267         token(sc);
268       pushback(sc);
269       scan_push(sc, scan_file(fp, d.buf, 0));
270       token(sc);
271       dstr_destroy(&d);
272       continue;                         /* Don't parse a trailing `;' */
273     }
274
275     /* --- Other configuration is handled elsewhere --- */
276
277     else {
278
279       /* --- First try among the sources --- */
280
281       {
282         source_ops **sops;
283
284         for (sops = sources; *sops; sops++) {
285           if ((*sops)->option && (*sops)->option(0, sc))
286             goto found_option;
287         }
288       }
289
290       /* --- Then try among the targets --- */
291
292       {
293         target_ops **tops;
294
295         for (tops = targets; *tops; tops++) {
296           if ((*tops)->option && (*tops)->option(0, sc))
297             goto found_option;
298         }
299       }
300
301       /* --- Nobody wants the option --- */
302
303       error(sc, "unknown global option or prefix `%s'", sc->d.buf);
304
305     found_option:;
306     }
307
308     if (sc->t == ';')
309       token(sc);
310   }
311 }
312
313 /*----- General utility functions -----------------------------------------*/
314
315 /* --- @fw_log@ --- *
316  *
317  * Arguments:   @time_t t@ = when the connection occurred or (@-1@)
318  *              @const char *fmt@ = format string to fill in
319  *              @...@ = other arguments
320  *
321  * Returns:     ---
322  *
323  * Use:         Logs a connection.
324  */
325
326 void fw_log(time_t t, const char *fmt, ...)
327 {
328   struct tm *tm;
329   dstr d = DSTR_INIT;
330   va_list ap;
331
332   if (flags & FW_QUIET)
333     return;
334
335   if (t == -1)
336     t = time(0);
337   tm = localtime(&t);
338   DENSURE(&d, 64);
339   d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
340   va_start(ap, fmt);
341   dstr_vputf(&d, fmt, &ap);
342   va_end(ap);
343   if (flags & FW_SYSLOG)
344     syslog(LOG_NOTICE, "%s", d.buf);
345   else {
346     DPUTC(&d, '\n');
347     dstr_write(&d, stderr);
348   }
349   DDESTROY(&d);
350 }
351
352 /* --- @fw_inc@, @fw_dec@ --- *
353  *
354  * Arguments:   ---
355  *
356  * Returns:     ---
357  *
358  * Use:         Increments or decrements the active thing count.  `fw' won't
359  *              quit while there are active things.
360  */
361
362 void fw_inc(void) { flags |= FW_SET; active++; }
363 void fw_dec(void) { if (active) active--; }
364
365 /* --- @fw_exit@ --- *
366  *
367  * Arguments:   ---
368  *
369  * Returns:     ---
370  *
371  * Use:         Exits when appropriate.
372  */
373
374 static void fw_exit(void)
375 {
376   endpt_killall();
377   source_killall();
378 }
379
380 /* --- @fw_tidy@ --- *
381  *
382  * Arguments:   @int n@ = signal number
383  *              @void *p@ = an uninteresting argument
384  *
385  * Returns:     ---
386  *
387  * Use:         Handles various signals and causes a clean tidy-up.
388  */
389
390 static void fw_tidy(int n, void *p)
391 {
392   const char *sn = 0;
393   switch (n) {
394     case SIGTERM: sn = "SIGTERM"; break;
395     case SIGINT: sn = "SIGINT"; break;
396     default: abort();
397   }
398
399   fw_log(-1, "closing down gracefully on %s", sn);
400   source_killall();
401 }
402
403 /* --- @fw_die@ --- *
404  *
405  * Arguments:   @int n@ = signal number
406  *              @void *p@ = an uninteresting argument
407  *
408  * Returns:     ---
409  *
410  * Use:         Handles various signals and causes an abrupt shutdown.
411  */
412
413 static void fw_die(int n, void *p)
414 {
415   const char *sn = 0;
416   switch (n) {
417     case SIGQUIT: sn = "SIGQUIT"; break;
418     default: abort();
419   }
420
421   fw_log(-1, "closing down abruptly on %s", sn);
422   source_killall();
423   endpt_killall();
424 }
425
426 /* --- @fw_reload@ --- *
427  *
428  * Arguments:   @int n@ = a signal number
429  *              @void *p@ = an uninteresting argument
430  *
431  * Returns:     ---
432  *
433  * Use:         Handles a hangup signal by re-reading configuration files.
434  */
435
436 static void fw_reload(int n, void *p)
437 {
438   FILE *fp;
439   scanner sc;
440   conffile *cf;
441
442   assert(n == SIGHUP);
443   if (!conffiles) {
444     fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
445     return;
446   }
447   fw_log(-1, "reloading configuration files...");
448   source_killall();
449   scan_create(&sc);
450   for (cf = conffiles; cf; cf = cf->next) {
451     if ((fp = fopen(cf->name, "r")) == 0)
452       fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
453     else
454       scan_add(&sc, scan_file(fp, cf->name, 0));
455   }
456   parse(&sc);
457   fw_log(-1, "... reload completed OK");
458 }
459
460 /*----- Startup and options parsing ---------------------------------------*/
461
462 /* --- Standard GNU help options --- */
463
464 static void version(FILE *fp)
465 {
466   pquis(fp, "$ version " VERSION "\n");
467 }
468
469 static void usage(FILE *fp)
470 {
471   pquis(fp, "Usage: $ [-dql] [-f file] [config statements...]\n");
472 }
473
474 static void help(FILE *fp)
475 {
476   version(fp);
477   fputc('\n', fp);
478   usage(fp);
479   pquis(fp, "\n\
480 An excessively full-featured port-forwarder, which subsumes large chunks\n\
481 of the functionality of inetd, netcat, and normal cat.  Options available\n\
482 are:\n\
483 \n\
484 -h, --help        Display this help message.\n\
485 -v, --version     Display the program's version number.\n\
486 -u, --usage       Display a terse usage summary.\n\
487 \n\
488 -G, --grammar     Show a summary of the configuration language.\n\
489 -O, --options     Show a summary of the source and target options.\n\
490 \n\
491 -f, --file=FILE   Read configuration from a file.\n\
492 -q, --quiet       Don't emit any logging information.\n\
493 -d, --daemon      Fork into background after initializing.\n\
494 -l, --syslog      Send log output to the system logger.\n\
495 -s, --setuid=USER Change uid to USER after initializing sources.\n\
496 -g, --setgid=GRP  Change gid to GRP after initializing sources.\n\
497 \n\
498 Configuration may be supplied in one or more configuration files, or on\n\
499 the command line (or both).  If no `-f' option is present, and no\n\
500 configuration is given on the command line, the standard input stream is\n\
501 read.\n\
502 \n\
503 Configuration is free-form.  Comments begin with a `#' character and\n\
504 continue to the end of the line.  Each command line argument is considered\n\
505 to be a separate line.\n\
506 \n\
507 The grammar is fairly complicated.  For a summary, run `$ --grammar'.\n\
508 For a summary of the various options, run `$ --options'.\n\
509 ");
510 }
511
512 /* --- Other helpful options --- */
513
514 static void grammar(FILE *fp)
515 {
516   version(fp);
517   pquis(fp, "\n\
518 Grammar summary\n\
519 \n\
520 Basic syntax\n\
521         FILE ::= EMPTY | FILE STMT [`;']\n\
522         STMT ::= OPTION-STMT | FW-STMT\n\
523         FW-STMT ::= `fw' SOURCE OPTIONS [`to'|`->'] TARGET OPTIONS\n\
524         OPTIONS ::= `{' OPTION-SEQ `}'\n\
525         OPTION-SEQ ::= EMPTY | OPTION-STMT [`;'] OPTION-SEQ\n\
526 \n\
527 Option syntax\n\
528         OPTION-STMT ::= Q-OPTION\n\
529         Q-OPTION ::= OPTION\n\
530              | PREFIX `.' Q-OPTION\n\
531              | PREFIX `{' OPTION-SEQ `}'\n\
532         PREFIX ::= WORD\n\
533 \n\
534 File source and target\n\
535         SOURCE ::= FILE\n\
536         TARGET ::= FILE\n\
537         FILE ::= `file' [`.'] FSPEC [`,' FSPEC]\n\
538         FSPEC ::= FD-SPEC | NAME-SPEC | NULL-SPEC\n\
539         FD-SPEC ::= [[`:']`fd'[`:']] NUMBER|`stdin'|`stdout'\n\
540         NAME-SPEC ::= [[`:']`file'[`:']] FILE-NAME\n\
541         FILE-NAME ::= PATH-SEQ | [ PATH-SEQ ]\n\
542         PATH-SEQ ::= PATH-ELT | PATH-SEQ PATH-ELT\n\
543         PATH-ELT ::= `/' | WORD\n\
544         NULL-SPEC ::= [`:']`null'[`:']\n\
545 \n\
546 Exec source and target\n\
547         SOURCE ::= EXEC\n\
548         TARGET ::= EXEC\n\
549         EXEC ::= `exec' [`.'] CMD-SPEC\n\
550         CMD-SPEC ::= SHELL-CMD | [PROG-NAME] `[' ARGV0 ARG-SEQ `]'\n\
551         ARG-SEQ ::= WORD | ARG-SEQ WORD\n\
552         SHELL-CMD ::= WORD\n\
553         ARGV0 ::= WORD\n\
554 \n\
555 Socket source and target\n\
556         SOURCE ::= SOCKET-SOURCE\n\
557         TARGET ::= SOCKET-TARGET\n\
558         SOCKET-SOURCE ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] SOURCE-ADDR\n\
559         SOCKET-TARGET ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] TARGET-ADDR\n\
560 \n\
561         INET-SOURCE-ADDR ::= [`port'] PORT\n\
562         INET-TARGET-ADDR ::= ADDRESS [`:'] PORT\n\
563         ADDRESS ::= ADDR-ELT | ADDRESS ADDR-ELT\n\
564         ADDR-ELT ::= `.' | WORD\n\
565 \n\
566         UNIX-SOURCE-ADDR ::= FILE-NAME\n\
567         UNIX-TARGET-ADDR ::= FILE-NAME\n\
568 ");
569 }
570
571 static void options(FILE *fp)
572 {
573   version(fp);
574   pquis(fp, "\n\
575 Options summary\n\
576 \n\
577 File attributes (`fattr')\n\
578         prefix.FATTR.MODE [=] MODE\n\
579         prefix.FATTR.OWNER [=] USER\n\
580         prefix.FATTR.GROUP [=] GROUP\n\
581 \n\
582 File options\n\
583         file.create [=] yes|no\n\
584         file.open [=] no|truncate|append\n\
585         file.fattr.*\n\
586 \n\
587 Exec options\n\
588         exec.logging [=] yes|no\n\
589         exec.dir [=] FILE-NAME\n\
590         exec.root [=] FILE-NAME\n\
591         exec.user [=] USER\n\
592         exec.group [=] GROUP\n\
593         exec.rlimit.LIMIT[.hard|.soft] [=] VALUE\n\
594         exec.env.clear\n\
595         exec.env.unset VAR\n\
596         exec.env.[set] VAR [=] VALUE\n\
597 \n\
598 Socket options\n\
599         socket.conn [=] NUMBER|unlimited|one-shot\n\
600         socket.listen [=] NUMBER\n\
601         socket.logging [=] yes|no\n\
602 \n\
603         socket.inet.source.[allow|deny] [host] ADDR [/ ADDR]\n\
604         socket.inet.source.[allow|deny] priv-port\n\
605         socket.inet.source.addr [=] any|ADDR\n\
606         socket.inet.dest.addr [=] any|ADDR\n\
607 \n\
608         socket.unix.fattr.*\n\
609 ");
610 }
611
612 /* --- @main@ --- *
613  *
614  * Arguments:   @int argc@ = number of command line arguments
615  *              @char *argv[]@ = vector of argument strings
616  *
617  * Returns:     ---
618  *
619  * Use:         Simple port-forwarding server.
620  */
621
622 int main(int argc, char *argv[])
623 {
624   unsigned f = 0;
625   sel_state sst;
626   sig s_term, s_quit, s_int, s_hup;
627   scanner sc;
628   uid_t drop = -1;
629   gid_t dropg = -1;
630   conffile *cf, **cff = &conffiles;
631
632 #define f_bogus 1u
633 #define f_file 2u
634 #define f_syslog 4u
635 #define f_fork 8u
636
637   /* --- Initialize things --- */
638
639   ego(argv[0]);
640   sel = &sst;
641   sel_init(sel);
642   sub_init();
643   sig_init(sel);
644   bres_init(sel);
645   bres_exec(0);
646   exec_init();
647   fattr_init(&fattr_global);
648   scan_create(&sc);
649
650   atexit(fw_exit);
651
652   /* --- Parse command line options --- */
653
654   for (;;) {
655     static struct option opts[] = {
656
657       /* --- Standard GNU help options --- */
658
659       { "help",         0,              0,      'h' },
660       { "version",      0,              0,      'v' },
661       { "usage",        0,              0,      'u' },
662
663       /* --- Other help options --- */
664
665       { "grammar",      0,              0,      'G' },
666       { "options",      0,              0,      'O' },
667
668       /* --- Other useful arguments --- */
669
670       { "file",         OPTF_ARGREQ,    0,      'f' },
671       { "fork",         0,              0,      'd' },
672       { "daemon",       0,              0,      'd' },
673       { "syslog",       0,              0,      'l' },
674       { "log",          0,              0,      'l' },
675       { "quiet",        0,              0,      'q' },
676       { "setuid",       OPTF_ARGREQ,    0,      's' },
677       { "uid",          OPTF_ARGREQ,    0,      's' },
678       { "setgid",       OPTF_ARGREQ,    0,      'g' },
679       { "gid",          OPTF_ARGREQ,    0,      'g' },
680
681       /* --- Magic terminator --- */
682
683       { 0,              0,              0,      0 }
684     };
685     int i = mdwopt(argc, argv, "+hvu GO f:dls:g:", opts, 0, 0, 0);
686
687     if (i < 0)
688       break;
689     switch (i) {
690       case 'h':
691         help(stdout);
692         exit(0);
693         break;
694       case 'v':
695         version(stdout);
696         exit(0);
697         break;
698       case 'u':
699         usage(stdout);
700         exit(0);
701         break;
702       case 'G':
703         grammar(stdout);
704         exit(0);
705         break;
706       case 'O':
707         options(stdout);
708         exit(0);
709         break;
710       case 'f':
711         if (strcmp(optarg, "-") == 0)
712           scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
713         else {
714           FILE *fp;
715           if ((fp = fopen(optarg, "r")) == 0)
716             die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
717           cf = CREATE(conffile);
718           cf->name = optarg;
719           *cff = cf;
720           cff = &cf->next;
721           scan_add(&sc, scan_file(fp, optarg, 0));
722         }
723         f |= f_file;
724         break;
725       case 'd':
726         f |= f_fork;
727         break;
728       case 'l':
729         f |= f_syslog;
730         break;
731       case 'q':
732         flags |= FW_QUIET;
733         break;
734       case 's':
735         if (isdigit((unsigned char )optarg[0])) {
736           char *q;
737           drop = strtol(optarg, &q, 0);
738           if (*q)
739             die(1, "bad uid `%s'", optarg);
740         } else {
741           struct passwd *pw = getpwnam(optarg);
742           if (!pw)
743             die(1, "unknown user `%s'", optarg);
744           drop = pw->pw_uid;
745         }
746         break;
747       case 'g':
748         if (isdigit((unsigned char )optarg[0])) {
749           char *q;
750           dropg = strtol(optarg, &q, 0);
751           if (*q)
752             die(1, "bad gid `%s'", optarg);
753         } else {
754           struct group *gr = getgrnam(optarg);
755           if (!gr)
756             die(1, "unknown group `%s'", optarg);
757           dropg = gr->gr_gid;
758         }
759         break;
760       default:
761         f |= f_bogus;
762         break;
763     }
764   }
765
766   if (f & f_bogus) {
767     usage(stderr);
768     exit(1);
769   }
770   *cff = 0;
771
772   /* --- Deal with the remaining arguments --- */
773
774   if (optind < argc)
775     scan_add(&sc, scan_argv(argv + optind));
776   else if (f & f_file)
777     /* Cool */;
778   else if (!isatty(STDIN_FILENO))
779     scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
780   else {
781     moan("no configuration given and stdin is a terminal.");
782     moan("type `%s --help' for usage information.", QUIS);
783     exit(1);
784   }
785
786   /* --- Parse the configuration now gathered --- */
787
788   parse(&sc);
789
790   /* --- Set up some signal handlers --- *
791    *
792    * Don't enable @SIGINT@ if the caller already disabled it.
793    */
794
795   {
796     struct sigaction sa;
797
798     sig_add(&s_term, SIGTERM, fw_tidy, 0);
799     sig_add(&s_quit, SIGQUIT, fw_die, 0);
800     sigaction(SIGINT, 0, &sa);
801     if (sa.sa_handler != SIG_IGN)
802       sig_add(&s_int, SIGINT, fw_tidy, 0);
803     sig_add(&s_hup, SIGHUP, fw_reload, 0);
804   }
805
806   /* --- Drop privileges --- */
807
808 #ifdef HAVE_SETGROUPS
809   if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
810       (drop != (uid_t)-1 && setuid(drop)))
811     die(1, "couldn't drop privileges: %s", strerror(errno));
812 #else
813   if ((dropg != (gid_t)-1 && setgid(dropg)) ||
814       (drop != (uid_t)-1 && setuid(drop)))
815     die(1, "couldn't drop privileges: %s", strerror(errno));
816 #endif
817
818   /* --- Fork into the background --- */
819
820   if (f & f_fork) {
821     pid_t kid;
822
823     kid = fork();
824     if (kid == -1)
825       die(1, "couldn't fork: %s", strerror(errno));
826     if (kid != 0)
827       _exit(0);
828
829     close(0); close(1); close(2);
830     chdir("/");
831     setsid();
832
833     kid = fork();
834     if (kid != 0)
835       _exit(0);
836   }
837
838   if (f & f_syslog) {
839     flags |= FW_SYSLOG;
840     openlog(QUIS, 0, LOG_DAEMON);
841   }
842
843   /* --- Let rip --- */
844
845   if (!(flags & FW_SET))
846     moan("nothing to do!");
847   signal(SIGPIPE, SIG_IGN);
848
849   {
850     int selerr = 0;
851     while (active) {
852       if (!sel_select(sel))
853         selerr = 0;
854       else if (errno != EINTR && errno != EAGAIN) {
855         fw_log(-1, "error from select: %s", strerror(errno));
856         selerr++;
857         if (selerr > 8) {
858           fw_log(-1, "too many consecutive select errors: bailing out");
859           exit(EXIT_FAILURE);
860         }
861       }
862     }
863   }
864     
865   return (0);
866 }
867
868 /*----- That's all, folks -------------------------------------------------*/