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