chiark / gitweb /
Fix the manpage too.
[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] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\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.\n\
431 \n\
432 Configuration may be supplied in one or more configuration files, or on\n\
433 the command line (or both).  If no `-f' option is present, and no\n\
434 configuration is given on the command line, the standard input stream is\n\
435 read.\n\
436 \n\
437 Configuration is free-form.  Comments begin with a `#' character and\n\
438 continue to the end of the line.  Each command line argument is considered\n\
439 to be a separate line.\n\
440 \n\
441 The grammar is fairly complicated.  For a summary, run `$ --grammar'.\n\
442 For a summary of the various options, run `$ --options'.\n\
443 \n\
444 Options available are:\n\
445 \n\
446 Help options:\n\
447   -h, --help           Display this help message.\n\
448   -v, --version        Display the program's version number.\n\
449   -u, --usage          Display a terse usage summary.\n\
450 \n\
451 Configuration summary:\n\
452   -G, --grammar        Show a summary of the configuration language.\n\
453   -O, --options        Show a summary of the source and target options.\n\
454 \n\
455 Other options:\n\
456   -f, --file=FILE      Read configuration from a file.\n\
457   -q, --quiet          Don't emit any logging information.\n\
458   -d, --daemon         Fork into background after initializing.\n\
459   -p, --pidfile=FILE   Write process-id to the named FILE.\n\
460   -l, --syslog         Send log output to the system logger.\n\
461   -s, --setuid=USER    Change uid to USER after initializing sources.\n\
462   -g, --setgid=GRP     Change gid to GRP after initializing sources.\n\
463 ");
464 }
465
466 /* --- Other helpful options --- */
467
468 static void grammar(FILE *fp)
469 {
470   version(fp);
471   pquis(fp, "\n\
472 Grammar summary\n\
473 \n\
474 Basic syntax\n\
475         FILE ::= EMPTY | FILE STMT [`;']\n\
476         STMT ::= OPTION-STMT | FW-STMT\n\
477         FW-STMT ::= `fw' SOURCE OPTIONS [`to'|`->'] TARGET OPTIONS\n\
478         OPTIONS ::= `{' OPTION-SEQ `}'\n\
479         OPTION-SEQ ::= EMPTY | OPTION-STMT [`;'] OPTION-SEQ\n\
480 \n\
481 Option syntax\n\
482         OPTION-STMT ::= Q-OPTION\n\
483         Q-OPTION ::= OPTION\n\
484              | PREFIX `.' Q-OPTION\n\
485              | PREFIX `{' OPTION-SEQ `}'\n\
486         PREFIX ::= WORD\n\
487 \n\
488 File source and target\n\
489         SOURCE ::= FILE\n\
490         TARGET ::= FILE\n\
491         FILE ::= `file' [`.'] FSPEC [`,' FSPEC]\n\
492         FSPEC ::= FD-SPEC | NAME-SPEC | NULL-SPEC\n\
493         FD-SPEC ::= [[`:']`fd'[`:']] NUMBER|`stdin'|`stdout'\n\
494         NAME-SPEC ::= [[`:']`name'[`:']] FILE-NAME\n\
495         FILE-NAME ::= PATH-SEQ | [ PATH-SEQ ]\n\
496         PATH-SEQ ::= PATH-ELT | PATH-SEQ PATH-ELT\n\
497         PATH-ELT ::= `/' | WORD\n\
498         NULL-SPEC ::= [`:']`null'[`:']\n\
499 \n\
500 Exec source and target\n\
501         SOURCE ::= EXEC\n\
502         TARGET ::= EXEC\n\
503         EXEC ::= `exec' [`.'] CMD-SPEC\n\
504         CMD-SPEC ::= SHELL-CMD | [PROG-NAME] `[' ARGV0 ARG-SEQ `]'\n\
505         ARG-SEQ ::= WORD | ARG-SEQ WORD\n\
506         SHELL-CMD ::= WORD\n\
507         ARGV0 ::= WORD\n\
508 \n\
509 Socket source and target\n\
510         SOURCE ::= SOCKET-SOURCE\n\
511         TARGET ::= SOCKET-TARGET\n\
512         SOCKET-SOURCE ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] SOURCE-ADDR\n\
513         SOCKET-TARGET ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] TARGET-ADDR\n\
514 \n\
515         INET-SOURCE-ADDR ::= [`port'] PORT\n\
516         INET-TARGET-ADDR ::= ADDRESS [`:'] PORT\n\
517         ADDRESS ::= ADDR-ELT | ADDRESS ADDR-ELT\n\
518         ADDR-ELT ::= `.' | WORD\n\
519 \n\
520         UNIX-SOURCE-ADDR ::= FILE-NAME\n\
521         UNIX-TARGET-ADDR ::= FILE-NAME\n\
522 ");
523 }
524
525 static void options(FILE *fp)
526 {
527   version(fp);
528   pquis(fp, "\n\
529 Options summary\n\
530 \n\
531 File attributes (`fattr')\n\
532         prefix.FATTR.MODE [=] MODE\n\
533         prefix.FATTR.OWNER [=] USER\n\
534         prefix.FATTR.GROUP [=] GROUP\n\
535 \n\
536 File options\n\
537         file.create [=] yes|no\n\
538         file.open [=] no|truncate|append\n\
539         file.fattr.*\n\
540 \n\
541 Exec options\n\
542         exec.logging [=] yes|no\n\
543         exec.dir [=] FILE-NAME\n\
544         exec.root [=] FILE-NAME\n\
545         exec.user [=] USER\n\
546         exec.group [=] GROUP\n\
547         exec.rlimit.LIMIT[.hard|.soft] [=] VALUE\n\
548         exec.env.clear\n\
549         exec.env.unset VAR\n\
550         exec.env.[set] VAR [=] VALUE\n\
551 \n\
552 Socket options\n\
553         socket.conn [=] NUMBER|unlimited|one-shot\n\
554         socket.listen [=] NUMBER\n\
555         socket.logging [=] yes|no\n\
556 \n\
557         socket.inet.source.[allow|deny] [host] ADDR [/ ADDR]\n\
558         socket.inet.source.[allow|deny] priv-port\n\
559         socket.inet.source.addr [=] any|ADDR\n\
560         socket.inet.dest.addr [=] any|ADDR\n\
561         socket.inet.dest.priv-port [=] yes|no\n\
562 \n\
563         socket.unix.fattr.*\n\
564 ");
565 }
566
567 /* --- @main@ --- *
568  *
569  * Arguments:   @int argc@ = number of command line arguments
570  *              @char *argv[]@ = vector of argument strings
571  *
572  * Returns:     ---
573  *
574  * Use:         Simple port-forwarding server.
575  */
576
577 int main(int argc, char *argv[])
578 {
579   unsigned f = 0;
580   sel_state sst;
581   sig s_term, s_quit, s_int, s_hup;
582   scanner sc;
583   uid_t drop = -1;
584   gid_t dropg = -1;
585   const char *pidfile = 0;
586   conffile *cf, **cff = &conffiles;
587
588 #define f_bogus 1u
589 #define f_file 2u
590 #define f_syslog 4u
591 #define f_fork 8u
592
593   /* --- Initialize things --- */
594
595   ego(argv[0]);
596   sel = &sst;
597   sel_init(sel);
598   sub_init();
599   sig_init(sel);
600   bres_init(sel);
601   bres_exec(0);
602   exec_init();
603   fattr_init(&fattr_global);
604   scan_create(&sc);
605
606   atexit(fw_exit);
607
608   /* --- Parse command line options --- */
609
610   for (;;) {
611     static struct option opts[] = {
612
613       /* --- Standard GNU help options --- */
614
615       { "help",         0,              0,      'h' },
616       { "version",      0,              0,      'v' },
617       { "usage",        0,              0,      'u' },
618
619       /* --- Other help options --- */
620
621       { "grammar",      0,              0,      'G' },
622       { "options",      0,              0,      'O' },
623
624       /* --- Other useful arguments --- */
625
626       { "file",         OPTF_ARGREQ,    0,      'f' },
627       { "fork",         0,              0,      'd' },
628       { "daemon",       0,              0,      'd' },
629       { "pidfile",      OPTF_ARGREQ,    0,      'p' },
630       { "syslog",       0,              0,      'l' },
631       { "log",          0,              0,      'l' },
632       { "quiet",        0,              0,      'q' },
633       { "setuid",       OPTF_ARGREQ,    0,      's' },
634       { "uid",          OPTF_ARGREQ,    0,      's' },
635       { "setgid",       OPTF_ARGREQ,    0,      'g' },
636       { "gid",          OPTF_ARGREQ,    0,      'g' },
637
638       /* --- Magic terminator --- */
639
640       { 0,              0,              0,      0 }
641     };
642     int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
643
644     if (i < 0)
645       break;
646     switch (i) {
647       case 'h':
648         help(stdout);
649         exit(0);
650         break;
651       case 'v':
652         version(stdout);
653         exit(0);
654         break;
655       case 'u':
656         usage(stdout);
657         exit(0);
658         break;
659       case 'G':
660         grammar(stdout);
661         exit(0);
662         break;
663       case 'O':
664         options(stdout);
665         exit(0);
666         break;
667       case 'f':
668         if (strcmp(optarg, "-") == 0)
669           scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
670         else {
671           FILE *fp;
672           if ((fp = fopen(optarg, "r")) == 0)
673             die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
674           cf = CREATE(conffile);
675           cf->name = optarg;
676           *cff = cf;
677           cff = &cf->next;
678           scan_add(&sc, scan_file(fp, optarg, 0));
679         }
680         f |= f_file;
681         break;
682       case 'd':
683         f |= f_fork;
684         break;
685       case 'p':
686         pidfile = optarg;
687         break;
688       case 'l':
689         f |= f_syslog;
690         break;
691       case 'q':
692         flags |= FW_QUIET;
693         break;
694       case 's':
695         if (isdigit((unsigned char )optarg[0])) {
696           char *q;
697           drop = strtol(optarg, &q, 0);
698           if (*q)
699             die(1, "bad uid `%s'", optarg);
700         } else {
701           struct passwd *pw = getpwnam(optarg);
702           if (!pw)
703             die(1, "unknown user `%s'", optarg);
704           drop = pw->pw_uid;
705         }
706         break;
707       case 'g':
708         if (isdigit((unsigned char )optarg[0])) {
709           char *q;
710           dropg = strtol(optarg, &q, 0);
711           if (*q)
712             die(1, "bad gid `%s'", optarg);
713         } else {
714           struct group *gr = getgrnam(optarg);
715           if (!gr)
716             die(1, "unknown group `%s'", optarg);
717           dropg = gr->gr_gid;
718         }
719         break;
720       default:
721         f |= f_bogus;
722         break;
723     }
724   }
725
726   if (f & f_bogus) {
727     usage(stderr);
728     exit(1);
729   }
730   *cff = 0;
731
732   /* --- Deal with the remaining arguments --- */
733
734   if (optind < argc)
735     scan_add(&sc, scan_argv(argv + optind));
736   else if (f & f_file)
737     /* Cool */;
738   else if (!isatty(STDIN_FILENO))
739     scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
740   else {
741     moan("no configuration given and stdin is a terminal.");
742     moan("type `%s --help' for usage information.", QUIS);
743     exit(1);
744   }
745
746   /* --- Parse the configuration now gathered --- */
747
748   parse(&sc);
749
750   /* --- Set up some signal handlers --- *
751    *
752    * Don't enable @SIGINT@ if the caller already disabled it.
753    */
754
755   {
756     struct sigaction sa;
757
758     sig_add(&s_term, SIGTERM, fw_tidy, 0);
759     sig_add(&s_quit, SIGQUIT, fw_die, 0);
760     sigaction(SIGINT, 0, &sa);
761     if (sa.sa_handler != SIG_IGN)
762       sig_add(&s_int, SIGINT, fw_tidy, 0);
763     sig_add(&s_hup, SIGHUP, fw_reload, 0);
764   }
765
766   /* --- Drop privileges --- */
767
768   if (drop != (uid_t)-1)
769     privconn_split(sel);
770 #ifdef HAVE_SETGROUPS
771   if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
772       (drop != (uid_t)-1 && setuid(drop)))
773     die(1, "couldn't drop privileges: %s", strerror(errno));
774 #else
775   if ((dropg != (gid_t)-1 && setgid(dropg)) ||
776       (drop != (uid_t)-1 && setuid(drop)))
777     die(1, "couldn't drop privileges: %s", strerror(errno));
778 #endif
779
780   /* --- Fork into the background --- */
781
782   if (f & f_fork) {
783     pid_t kid;
784
785     kid = fork();
786     if (kid == -1)
787       die(1, "couldn't fork: %s", strerror(errno));
788     if (kid != 0)
789       _exit(0);
790
791     close(0); close(1); close(2);
792     chdir("/");
793     setsid();
794
795     kid = fork();
796     if (kid != 0)
797       _exit(0);
798   }
799   if (pidfile) {
800     FILE *fp = fopen(pidfile, "w");
801     if (!fp) {
802       die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
803           pidfile, strerror(errno));
804     }
805     fprintf(fp, "%lu\n", (unsigned long)getpid());
806     if (fflush(fp) || ferror(fp) || fclose(fp)) {
807       die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
808           pidfile, strerror(errno));
809     }
810   }
811
812   if (f & f_syslog) {
813     flags |= FW_SYSLOG;
814     openlog(QUIS, 0, LOG_DAEMON);
815   }
816
817   /* --- Let rip --- */
818
819   if (!(flags & FW_SET))
820     moan("nothing to do!");
821   signal(SIGPIPE, SIG_IGN);
822
823   {
824     int selerr = 0;
825     while (active) {
826       if (!sel_select(sel))
827         selerr = 0;
828       else if (errno != EINTR && errno != EAGAIN) {
829         fw_log(-1, "error from select: %s", strerror(errno));
830         selerr++;
831         if (selerr > 8) {
832           fw_log(-1, "too many consecutive select errors: bailing out");
833           exit(EXIT_FAILURE);
834         }
835       }
836     }
837   }
838     
839   return (0);
840 }
841
842 /*----- That's all, folks -------------------------------------------------*/