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 -------------------------------------------------*/