chiark / gitweb /
Provide a `--pidfile' option in `fw'.
[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   const char *pidfile = 0;
581   conffile *cf, **cff = &conffiles;
582
583 #define f_bogus 1u
584 #define f_file 2u
585 #define f_syslog 4u
586 #define f_fork 8u
587
588   /* --- Initialize things --- */
589
590   ego(argv[0]);
591   sel = &sst;
592   sel_init(sel);
593   sub_init();
594   sig_init(sel);
595   bres_init(sel);
596   bres_exec(0);
597   exec_init();
598   fattr_init(&fattr_global);
599   scan_create(&sc);
600
601   atexit(fw_exit);
602
603   /* --- Parse command line options --- */
604
605   for (;;) {
606     static struct option opts[] = {
607
608       /* --- Standard GNU help options --- */
609
610       { "help",         0,              0,      'h' },
611       { "version",      0,              0,      'v' },
612       { "usage",        0,              0,      'u' },
613
614       /* --- Other help options --- */
615
616       { "grammar",      0,              0,      'G' },
617       { "options",      0,              0,      'O' },
618
619       /* --- Other useful arguments --- */
620
621       { "file",         OPTF_ARGREQ,    0,      'f' },
622       { "fork",         0,              0,      'd' },
623       { "daemon",       0,              0,      'd' },
624       { "pidfile",      OPTF_ARGREQ,    0,      'p' },
625       { "syslog",       0,              0,      'l' },
626       { "log",          0,              0,      'l' },
627       { "quiet",        0,              0,      'q' },
628       { "setuid",       OPTF_ARGREQ,    0,      's' },
629       { "uid",          OPTF_ARGREQ,    0,      's' },
630       { "setgid",       OPTF_ARGREQ,    0,      'g' },
631       { "gid",          OPTF_ARGREQ,    0,      'g' },
632
633       /* --- Magic terminator --- */
634
635       { 0,              0,              0,      0 }
636     };
637     int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
638
639     if (i < 0)
640       break;
641     switch (i) {
642       case 'h':
643         help(stdout);
644         exit(0);
645         break;
646       case 'v':
647         version(stdout);
648         exit(0);
649         break;
650       case 'u':
651         usage(stdout);
652         exit(0);
653         break;
654       case 'G':
655         grammar(stdout);
656         exit(0);
657         break;
658       case 'O':
659         options(stdout);
660         exit(0);
661         break;
662       case 'f':
663         if (strcmp(optarg, "-") == 0)
664           scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
665         else {
666           FILE *fp;
667           if ((fp = fopen(optarg, "r")) == 0)
668             die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
669           cf = CREATE(conffile);
670           cf->name = optarg;
671           *cff = cf;
672           cff = &cf->next;
673           scan_add(&sc, scan_file(fp, optarg, 0));
674         }
675         f |= f_file;
676         break;
677       case 'd':
678         f |= f_fork;
679         break;
680       case 'p':
681         pidfile = optarg;
682         break;
683       case 'l':
684         f |= f_syslog;
685         break;
686       case 'q':
687         flags |= FW_QUIET;
688         break;
689       case 's':
690         if (isdigit((unsigned char )optarg[0])) {
691           char *q;
692           drop = strtol(optarg, &q, 0);
693           if (*q)
694             die(1, "bad uid `%s'", optarg);
695         } else {
696           struct passwd *pw = getpwnam(optarg);
697           if (!pw)
698             die(1, "unknown user `%s'", optarg);
699           drop = pw->pw_uid;
700         }
701         break;
702       case 'g':
703         if (isdigit((unsigned char )optarg[0])) {
704           char *q;
705           dropg = strtol(optarg, &q, 0);
706           if (*q)
707             die(1, "bad gid `%s'", optarg);
708         } else {
709           struct group *gr = getgrnam(optarg);
710           if (!gr)
711             die(1, "unknown group `%s'", optarg);
712           dropg = gr->gr_gid;
713         }
714         break;
715       default:
716         f |= f_bogus;
717         break;
718     }
719   }
720
721   if (f & f_bogus) {
722     usage(stderr);
723     exit(1);
724   }
725   *cff = 0;
726
727   /* --- Deal with the remaining arguments --- */
728
729   if (optind < argc)
730     scan_add(&sc, scan_argv(argv + optind));
731   else if (f & f_file)
732     /* Cool */;
733   else if (!isatty(STDIN_FILENO))
734     scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
735   else {
736     moan("no configuration given and stdin is a terminal.");
737     moan("type `%s --help' for usage information.", QUIS);
738     exit(1);
739   }
740
741   /* --- Parse the configuration now gathered --- */
742
743   parse(&sc);
744
745   /* --- Set up some signal handlers --- *
746    *
747    * Don't enable @SIGINT@ if the caller already disabled it.
748    */
749
750   {
751     struct sigaction sa;
752
753     sig_add(&s_term, SIGTERM, fw_tidy, 0);
754     sig_add(&s_quit, SIGQUIT, fw_die, 0);
755     sigaction(SIGINT, 0, &sa);
756     if (sa.sa_handler != SIG_IGN)
757       sig_add(&s_int, SIGINT, fw_tidy, 0);
758     sig_add(&s_hup, SIGHUP, fw_reload, 0);
759   }
760
761   /* --- Drop privileges --- */
762
763   if (drop != (uid_t)-1)
764     privconn_split(sel);
765 #ifdef HAVE_SETGROUPS
766   if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
767       (drop != (uid_t)-1 && setuid(drop)))
768     die(1, "couldn't drop privileges: %s", strerror(errno));
769 #else
770   if ((dropg != (gid_t)-1 && setgid(dropg)) ||
771       (drop != (uid_t)-1 && setuid(drop)))
772     die(1, "couldn't drop privileges: %s", strerror(errno));
773 #endif
774
775   /* --- Fork into the background --- */
776
777   if (f & f_fork) {
778     pid_t kid;
779
780     kid = fork();
781     if (kid == -1)
782       die(1, "couldn't fork: %s", strerror(errno));
783     if (kid != 0)
784       _exit(0);
785
786     close(0); close(1); close(2);
787     chdir("/");
788     setsid();
789
790     kid = fork();
791     if (kid != 0)
792       _exit(0);
793   }
794   if (pidfile) {
795     FILE *fp = fopen(pidfile, "w");
796     if (!fp) {
797       die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
798           pidfile, strerror(errno));
799     }
800     fprintf(fp, "%lu\n", (unsigned long)getpid());
801     if (fflush(fp) || ferror(fp) || fclose(fp)) {
802       die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
803           pidfile, strerror(errno));
804     }
805   }
806
807   if (f & f_syslog) {
808     flags |= FW_SYSLOG;
809     openlog(QUIS, 0, LOG_DAEMON);
810   }
811
812   /* --- Let rip --- */
813
814   if (!(flags & FW_SET))
815     moan("nothing to do!");
816   signal(SIGPIPE, SIG_IGN);
817
818   {
819     int selerr = 0;
820     while (active) {
821       if (!sel_select(sel))
822         selerr = 0;
823       else if (errno != EINTR && errno != EAGAIN) {
824         fw_log(-1, "error from select: %s", strerror(errno));
825         selerr++;
826         if (selerr > 8) {
827           fw_log(-1, "too many consecutive select errors: bailing out");
828           exit(EXIT_FAILURE);
829         }
830       }
831     }
832   }
833     
834   return (0);
835 }
836
837 /*----- That's all, folks -------------------------------------------------*/