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