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