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