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