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