identify.c: Stash a copy of the caller's description string.
[fwd] / fwd.c
1 /* -*-c-*-
2 *
3 * Port forwarding thingy
4 *
5 * (c) 1999 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the `fwd' port forwarder.
11 *
12 * `fwd' is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * `fwd' is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with `fwd'; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27 #include "fwd.h"
28
29 /*----- Global variables --------------------------------------------------*/
30
31 sel_state *sel; /* Multiplexor for nonblocking I/O */
32 unsigned flags = 0; /* Global state flags */
33
34 /*----- Static variables --------------------------------------------------*/
35
36 typedef struct conffile {
37 struct conffile *next;
38 char *name;
39 } conffile;
40
41 static unsigned active = 0; /* Number of active things */
42 static conffile *conffiles = 0; /* List of configuration files */
43
44 /*----- Configuration parsing ---------------------------------------------*/
45
46 /* --- @parse@ --- *
47 *
48 * Arguments: @scanner *sc@ = pointer to scanner definition
49 *
50 * Returns: ---
51 *
52 * Use: Parses a configuration file from the scanner.
53 */
54
55 static source_ops *sources[] =
56 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
57 static target_ops *targets[] =
58 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
59
60 void parse(scanner *sc)
61 {
62 token(sc);
63
64 for (;;) {
65 if (sc->t == CTOK_EOF)
66 break;
67 if (sc->t != CTOK_WORD)
68 error(sc, "parse error, keyword expected");
69
70 /* --- Handle a forwarding request --- */
71
72 if (strcmp(sc->d.buf, "forward") == 0 ||
73 strcmp(sc->d.buf, "fwd") == 0 ||
74 strcmp(sc->d.buf, "from") == 0) {
75 source *s;
76 target *t;
77
78 token(sc);
79
80 /* --- Read a source description --- */
81
82 {
83 source_ops **sops;
84
85 /* --- Try to find a source type which understands --- */
86
87 s = 0;
88 for (sops = sources; *sops; sops++) {
89 if ((s = (*sops)->read(sc)) != 0)
90 goto found_source;
91 }
92 error(sc, "unknown source name `%s'", sc->d.buf);
93
94 /* --- Read any source-specific options --- */
95
96 found_source:
97 if (sc->t == '{') {
98 token(sc);
99 while (sc->t == CTOK_WORD) {
100 if (!s->ops->option || !s->ops->option(s, sc)) {
101 error(sc, "unknown %s source option `%s'",
102 s->ops->name, sc->d.buf);
103 }
104 if (sc->t == ';')
105 token(sc);
106 }
107 if (sc->t != '}')
108 error(sc, "parse error, missing `}'");
109 token(sc);
110 }
111 }
112
113 /* --- Read a destination description --- */
114
115 if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
116 strcmp(sc->d.buf, "->") == 0))
117 token(sc);
118
119 {
120 target_ops **tops;
121
122 /* --- Try to find a target which understands --- */
123
124 t = 0;
125 for (tops = targets; *tops; tops++) {
126 if ((t = (*tops)->read(sc)) != 0)
127 goto found_target;
128 }
129 error(sc, "unknown target name `%s'", sc->d.buf);
130
131 /* --- Read any target-specific options --- */
132
133 found_target:
134 if (sc->t == '{') {
135 token(sc);
136 while (sc->t == CTOK_WORD) {
137 if (!t->ops->option || !t->ops->option(t, sc)) {
138 error(sc, "unknown %s target option `%s'",
139 t->ops->name, sc->d.buf);
140 }
141 if (sc->t == ';')
142 token(sc);
143 }
144 if (sc->t != '}')
145 error(sc, "parse error, `}' expected");
146 token(sc);
147 }
148 }
149
150 /* --- Combine the source and target --- */
151
152 s->ops->attach(s, sc, t);
153 if (t->ops->confirm)
154 t->ops->confirm(t);
155 }
156
157 /* --- Include configuration from a file --- *
158 *
159 * Slightly tricky. Scan the optional semicolon from the including
160 * stream, not the included one.
161 */
162
163 else if (strcmp(sc->d.buf, "include") == 0) {
164 FILE *fp;
165 dstr d = DSTR_INIT;
166
167 token(sc);
168 conf_name(sc, '/', &d);
169 if ((fp = fopen(d.buf, "r")) == 0)
170 error(sc, "can't include `%s': %s", d.buf, strerror(errno));
171 if (sc->t == ';')
172 token(sc);
173 pushback(sc);
174 scan_push(sc, scan_file(fp, d.buf, 0));
175 token(sc);
176 dstr_destroy(&d);
177 continue; /* Don't parse a trailing `;' */
178 }
179
180 /* --- Other configuration is handled elsewhere --- */
181
182 else {
183
184 /* --- First try among the sources --- */
185
186 {
187 source_ops **sops;
188
189 for (sops = sources; *sops; sops++) {
190 if ((*sops)->option && (*sops)->option(0, sc))
191 goto found_option;
192 }
193 }
194
195 /* --- Then try among the targets --- */
196
197 {
198 target_ops **tops;
199
200 for (tops = targets; *tops; tops++) {
201 if ((*tops)->option && (*tops)->option(0, sc))
202 goto found_option;
203 }
204 }
205
206 /* --- Nobody wants the option --- */
207
208 error(sc, "unknown global option or prefix `%s'", sc->d.buf);
209
210 found_option:;
211 }
212
213 if (sc->t == ';')
214 token(sc);
215 }
216 }
217
218 /*----- General utility functions -----------------------------------------*/
219
220 /* --- @fw_log@ --- *
221 *
222 * Arguments: @time_t t@ = when the connection occurred or (@-1@)
223 * @const char *fmt@ = format string to fill in
224 * @...@ = other arguments
225 *
226 * Returns: ---
227 *
228 * Use: Logs a connection.
229 */
230
231 void fw_log(time_t t, const char *fmt, ...)
232 {
233 struct tm *tm;
234 dstr d = DSTR_INIT;
235 va_list ap;
236
237 if (flags & FW_QUIET)
238 return;
239
240 if (t == NOW)
241 t = time(0);
242 tm = localtime(&t);
243 DENSURE(&d, 64);
244 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S %z ", tm);
245 va_start(ap, fmt);
246 dstr_vputf(&d, fmt, &ap);
247 va_end(ap);
248 if (flags & FW_SYSLOG)
249 syslog(LOG_NOTICE, "%s", d.buf);
250 else {
251 DPUTC(&d, '\n');
252 dstr_write(&d, stderr);
253 }
254 DDESTROY(&d);
255 }
256
257 /* --- @fw_inc@, @fw_dec@ --- *
258 *
259 * Arguments: ---
260 *
261 * Returns: ---
262 *
263 * Use: Increments or decrements the active thing count. `fwd' won't
264 * quit while there are active things.
265 */
266
267 void fw_inc(void) { flags |= FW_SET; active++; }
268 void fw_dec(void) { if (active) active--; }
269
270 /* --- @fw_exit@ --- *
271 *
272 * Arguments: ---
273 *
274 * Returns: ---
275 *
276 * Use: Exits when appropriate.
277 */
278
279 static void fw_exit(void)
280 {
281 endpt_killall();
282 source_killall();
283 }
284
285 /* --- @fw_tidy@ --- *
286 *
287 * Arguments: @int n@ = signal number
288 * @void *p@ = an uninteresting argument
289 *
290 * Returns: ---
291 *
292 * Use: Handles various signals and causes a clean tidy-up.
293 */
294
295 static void fw_tidy(int n, void *p)
296 {
297 const char *sn = 0;
298 switch (n) {
299 case SIGTERM: sn = "SIGTERM"; break;
300 case SIGINT: sn = "SIGINT"; break;
301 default: abort();
302 }
303
304 fw_log(NOW, "closing down gracefully on %s", sn);
305 source_killall();
306 }
307
308 /* --- @fw_die@ --- *
309 *
310 * Arguments: @int n@ = signal number
311 * @void *p@ = an uninteresting argument
312 *
313 * Returns: ---
314 *
315 * Use: Handles various signals and causes an abrupt shutdown.
316 */
317
318 static void fw_die(int n, void *p)
319 {
320 const char *sn = 0;
321 switch (n) {
322 case SIGQUIT: sn = "SIGQUIT"; break;
323 default: abort();
324 }
325
326 fw_log(NOW, "closing down abruptly on %s", sn);
327 source_killall();
328 endpt_killall();
329 }
330
331 /* --- @fw_reload@ --- *
332 *
333 * Arguments: @int n@ = a signal number
334 * @void *p@ = an uninteresting argument
335 *
336 * Returns: ---
337 *
338 * Use: Handles a hangup signal by re-reading configuration files.
339 */
340
341 static void fw_reload(int n, void *p)
342 {
343 FILE *fp;
344 scanner sc;
345 conffile *cf;
346
347 assert(n == SIGHUP);
348 if (!conffiles) {
349 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
350 return;
351 }
352 fw_log(NOW, "reloading configuration files...");
353 source_killall();
354 scan_create(&sc);
355 for (cf = conffiles; cf; cf = cf->next) {
356 if ((fp = fopen(cf->name, "r")) == 0)
357 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
358 else
359 scan_add(&sc, scan_file(fp, cf->name, 0));
360 }
361 parse(&sc);
362 fw_log(NOW, "... reload completed OK");
363 }
364
365 /*----- Startup and options parsing ---------------------------------------*/
366
367 /* --- Standard GNU help options --- */
368
369 static void version(FILE *fp)
370 {
371 pquis(fp, "$ version " VERSION "\n");
372 }
373
374 static void usage(FILE *fp)
375 {
376 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
377 }
378
379 static void help(FILE *fp)
380 {
381 version(fp);
382 fputc('\n', fp);
383 usage(fp);
384 pquis(fp, "\n\
385 An excessively full-featured port-forwarder, which subsumes large chunks\n\
386 of the functionality of inetd, netcat, and normal cat.\n\
387 \n\
388 Configuration may be supplied in one or more configuration files, or on\n\
389 the command line (or both). If no `-f' option is present, and no\n\
390 configuration is given on the command line, the standard input stream is\n\
391 read.\n\
392 \n\
393 Configuration is free-form. Comments begin with a `#' character and\n\
394 continue to the end of the line. Each command line argument is considered\n\
395 to be a separate line.\n\
396 \n\
397 The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
398 For a summary of the various options, run `$ --options'.\n\
399 \n\
400 Options available are:\n\
401 \n\
402 Help options:\n\
403 -h, --help Display this help message.\n\
404 -v, --version Display the program's version number.\n\
405 -u, --usage Display a terse usage summary.\n\
406 \n\
407 Configuration summary:\n\
408 -G, --grammar Show a summary of the configuration language.\n\
409 -O, --options Show a summary of the source and target options.\n\
410 \n\
411 Other options:\n\
412 -f, --file=FILE Read configuration from a file.\n\
413 -q, --quiet Don't emit any logging information.\n\
414 -d, --daemon Fork into background after initializing.\n\
415 -p, --pidfile=FILE Write process-id to the named FILE.\n\
416 -l, --syslog Send log output to the system logger.\n\
417 -s, --setuid=USER Change uid to USER after initializing sources.\n\
418 -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
419 ");
420 }
421
422 /* --- Other helpful options --- */
423
424 static void grammar(FILE *fp)
425 {
426 version(fp);
427 fputs("\nGrammar summary\n\n", fp);
428 fputs(grammar_text, fp);
429 }
430
431 static void options(FILE *fp)
432 {
433 version(fp);
434 fputs("\nOption summary\n\n", fp);
435 fputs(option_text, fp);
436 }
437
438 /* --- @main@ --- *
439 *
440 * Arguments: @int argc@ = number of command line arguments
441 * @char *argv[]@ = vector of argument strings
442 *
443 * Returns: ---
444 *
445 * Use: Simple port-forwarding server.
446 */
447
448 int main(int argc, char *argv[])
449 {
450 unsigned f = 0;
451 sel_state sst;
452 sig s_term, s_quit, s_int, s_hup;
453 scanner sc;
454 uid_t drop = -1;
455 gid_t dropg = -1;
456 const char *pidfile = 0;
457 conffile *cf, **cff = &conffiles;
458
459 #define f_bogus 1u
460 #define f_file 2u
461 #define f_syslog 4u
462 #define f_fork 8u
463
464 /* --- Initialize things --- */
465
466 ego(argv[0]);
467 sel = &sst;
468 sel_init(sel);
469 sub_init();
470 sig_init(sel);
471 bres_init(sel);
472 bres_exec(0);
473 exec_init();
474 fattr_init(&fattr_global);
475 scan_create(&sc);
476
477 atexit(fw_exit);
478
479 /* --- Parse command line options --- */
480
481 for (;;) {
482 static struct option opts[] = {
483
484 /* --- Standard GNU help options --- */
485
486 { "help", 0, 0, 'h' },
487 { "version", 0, 0, 'v' },
488 { "usage", 0, 0, 'u' },
489
490 /* --- Other help options --- */
491
492 { "grammar", 0, 0, 'G' },
493 { "options", 0, 0, 'O' },
494
495 /* --- Other useful arguments --- */
496
497 { "file", OPTF_ARGREQ, 0, 'f' },
498 { "fork", 0, 0, 'd' },
499 { "daemon", 0, 0, 'd' },
500 { "pidfile", OPTF_ARGREQ, 0, 'p' },
501 { "syslog", 0, 0, 'l' },
502 { "log", 0, 0, 'l' },
503 { "quiet", 0, 0, 'q' },
504 { "setuid", OPTF_ARGREQ, 0, 's' },
505 { "uid", OPTF_ARGREQ, 0, 's' },
506 { "setgid", OPTF_ARGREQ, 0, 'g' },
507 { "gid", OPTF_ARGREQ, 0, 'g' },
508
509 /* --- Magic terminator --- */
510
511 { 0, 0, 0, 0 }
512 };
513 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
514
515 if (i < 0)
516 break;
517 switch (i) {
518 case 'h':
519 help(stdout);
520 exit(0);
521 break;
522 case 'v':
523 version(stdout);
524 exit(0);
525 break;
526 case 'u':
527 usage(stdout);
528 exit(0);
529 break;
530 case 'G':
531 grammar(stdout);
532 exit(0);
533 break;
534 case 'O':
535 options(stdout);
536 exit(0);
537 break;
538 case 'f':
539 if (strcmp(optarg, "-") == 0)
540 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
541 else {
542 FILE *fp;
543 if ((fp = fopen(optarg, "r")) == 0)
544 die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
545 cf = CREATE(conffile);
546 cf->name = optarg;
547 *cff = cf;
548 cff = &cf->next;
549 scan_add(&sc, scan_file(fp, optarg, 0));
550 }
551 f |= f_file;
552 break;
553 case 'd':
554 f |= f_fork;
555 break;
556 case 'p':
557 pidfile = optarg;
558 break;
559 case 'l':
560 f |= f_syslog;
561 break;
562 case 'q':
563 flags |= FW_QUIET;
564 break;
565 case 's':
566 if (isdigit((unsigned char )optarg[0])) {
567 char *q;
568 drop = strtol(optarg, &q, 0);
569 if (*q)
570 die(1, "bad uid `%s'", optarg);
571 } else {
572 struct passwd *pw = getpwnam(optarg);
573 if (!pw)
574 die(1, "unknown user `%s'", optarg);
575 drop = pw->pw_uid;
576 }
577 break;
578 case 'g':
579 if (isdigit((unsigned char )optarg[0])) {
580 char *q;
581 dropg = strtol(optarg, &q, 0);
582 if (*q)
583 die(1, "bad gid `%s'", optarg);
584 } else {
585 struct group *gr = getgrnam(optarg);
586 if (!gr)
587 die(1, "unknown group `%s'", optarg);
588 dropg = gr->gr_gid;
589 }
590 break;
591 default:
592 f |= f_bogus;
593 break;
594 }
595 }
596
597 if (f & f_bogus) {
598 usage(stderr);
599 exit(1);
600 }
601 *cff = 0;
602
603 /* --- Deal with the remaining arguments --- */
604
605 if (optind < argc)
606 scan_add(&sc, scan_argv(argv + optind));
607 else if (f & f_file)
608 /* Cool */;
609 else if (!isatty(STDIN_FILENO))
610 scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
611 else {
612 moan("no configuration given and stdin is a terminal.");
613 moan("type `%s --help' for usage information.", QUIS);
614 exit(1);
615 }
616
617 /* --- Parse the configuration now gathered --- */
618
619 parse(&sc);
620
621 /* --- Set up some signal handlers --- *
622 *
623 * Don't enable @SIGINT@ if the caller already disabled it.
624 */
625
626 {
627 struct sigaction sa;
628
629 sig_add(&s_term, SIGTERM, fw_tidy, 0);
630 sig_add(&s_quit, SIGQUIT, fw_die, 0);
631 sigaction(SIGINT, 0, &sa);
632 if (sa.sa_handler != SIG_IGN)
633 sig_add(&s_int, SIGINT, fw_tidy, 0);
634 sig_add(&s_hup, SIGHUP, fw_reload, 0);
635 }
636
637 /* --- Fork into the background --- */
638
639 if (f & f_fork) {
640 pid_t kid;
641
642 kid = fork();
643 if (kid == -1)
644 die(1, "couldn't fork: %s", strerror(errno));
645 if (kid != 0)
646 _exit(0);
647
648 close(0); close(1); close(2);
649 if (chdir("/"))
650 die(1, "couldn't change to root directory: %s", strerror(errno));
651 setsid();
652
653 kid = fork();
654 if (kid != 0)
655 _exit(0);
656 }
657 if (pidfile) {
658 FILE *fp = fopen(pidfile, "w");
659 if (!fp) {
660 die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
661 pidfile, strerror(errno));
662 }
663 fprintf(fp, "%lu\n", (unsigned long)getpid());
664 if (fflush(fp) || ferror(fp) || fclose(fp)) {
665 die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
666 pidfile, strerror(errno));
667 }
668 }
669
670 if (f & f_syslog) {
671 flags |= FW_SYSLOG;
672 openlog(QUIS, 0, LOG_DAEMON);
673 }
674
675 /* --- Drop privileges --- */
676
677 if (drop != (uid_t)-1)
678 privconn_split(sel);
679 #ifdef HAVE_SETGROUPS
680 if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
681 (drop != (uid_t)-1 && setuid(drop)))
682 die(1, "couldn't drop privileges: %s", strerror(errno));
683 #else
684 if ((dropg != (gid_t)-1 && setgid(dropg)) ||
685 (drop != (uid_t)-1 && setuid(drop)))
686 die(1, "couldn't drop privileges: %s", strerror(errno));
687 #endif
688
689 /* --- Let rip --- */
690
691 if (!(flags & FW_SET))
692 moan("nothing to do!");
693 signal(SIGPIPE, SIG_IGN);
694
695 {
696 int selerr = 0;
697 while (active) {
698 if (!sel_select(sel))
699 selerr = 0;
700 else if (errno != EINTR && errno != EAGAIN) {
701 fw_log(NOW, "error from select: %s", strerror(errno));
702 selerr++;
703 if (selerr > 8) {
704 fw_log(NOW, "too many consecutive select errors: bailing out");
705 exit(EXIT_FAILURE);
706 }
707 }
708 }
709 }
710
711 return (0);
712 }
713
714 /*----- That's all, folks -------------------------------------------------*/