identify.c: Stash a copy of the caller's description string.
[fwd] / fwd.c
CommitLineData
e82f7154 1/* -*-c-*-
2 *
e82f7154 3 * Port forwarding thingy
4 *
61e3dbdf 5 * (c) 1999 Straylight/Edgeware
e82f7154 6 */
7
206212ca 8/*----- Licensing notice --------------------------------------------------*
e82f7154 9 *
9155ea97 10 * This file is part of the `fwd' port forwarder.
e82f7154 11 *
9155ea97 12 * `fwd' is free software; you can redistribute it and/or modify
e82f7154 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.
206212ca 16 *
9155ea97 17 * `fwd' is distributed in the hope that it will be useful,
e82f7154 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.
206212ca 21 *
e82f7154 22 * You should have received a copy of the GNU General Public License
9155ea97 23 * along with `fwd'; if not, write to the Free Software Foundation,
e82f7154 24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
9155ea97 27#include "fwd.h"
e82f7154 28
afd7451e 29/*----- Global variables --------------------------------------------------*/
e82f7154 30
31sel_state *sel; /* Multiplexor for nonblocking I/O */
45f51d2f 32unsigned flags = 0; /* Global state flags */
61e3dbdf 33
34/*----- Static variables --------------------------------------------------*/
35
372a98e2 36typedef struct conffile {
37 struct conffile *next;
38 char *name;
39} conffile;
40
61e3dbdf 41static unsigned active = 0; /* Number of active things */
372a98e2 42static conffile *conffiles = 0; /* List of configuration files */
61e3dbdf 43
8c0a939e 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
55static source_ops *sources[] =
56 { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
57static target_ops *targets[] =
58 { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
59
60void 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 ||
9155ea97 73 strcmp(sc->d.buf, "fwd") == 0 ||
206212ca 74 strcmp(sc->d.buf, "from") == 0) {
8c0a939e 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);
ee599f55 153 if (t->ops->confirm)
154 t->ops->confirm(t);
8c0a939e 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 -----------------------------------------*/
e82f7154 219
61e3dbdf 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
231void 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
d935f68b 240 if (t == NOW)
61e3dbdf 241 t = time(0);
242 tm = localtime(&t);
243 DENSURE(&d, 64);
367d81af 244 d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S %z ", tm);
61e3dbdf 245 va_start(ap, fmt);
2f8a65a1 246 dstr_vputf(&d, fmt, &ap);
61e3dbdf 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 *
9155ea97 263 * Use: Increments or decrements the active thing count. `fwd' won't
61e3dbdf 264 * quit while there are active things.
265 */
266
267void fw_inc(void) { flags |= FW_SET; active++; }
268void fw_dec(void) { if (active) active--; }
269
270/* --- @fw_exit@ --- *
271 *
272 * Arguments: ---
273 *
274 * Returns: ---
275 *
276 * Use: Exits when appropriate.
277 */
278
279static 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
295static void fw_tidy(int n, void *p)
296{
372a98e2 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
d935f68b 304 fw_log(NOW, "closing down gracefully on %s", sn);
372a98e2 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
318static 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
d935f68b 326 fw_log(NOW, "closing down abruptly on %s", sn);
372a98e2 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
341static 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) {
d935f68b 349 fw_log(NOW, "no configuration files to reload: ignoring SIGHUP");
372a98e2 350 return;
351 }
d935f68b 352 fw_log(NOW, "reloading configuration files...");
372a98e2 353 source_killall();
354 scan_create(&sc);
355 for (cf = conffiles; cf; cf = cf->next) {
356 if ((fp = fopen(cf->name, "r")) == 0)
d935f68b 357 fw_log(NOW, "error loading `%s': %s", cf->name, strerror(errno));
372a98e2 358 else
359 scan_add(&sc, scan_file(fp, cf->name, 0));
360 }
8c0a939e 361 parse(&sc);
d935f68b 362 fw_log(NOW, "... reload completed OK");
61e3dbdf 363}
364
8c0a939e 365/*----- Startup and options parsing ---------------------------------------*/
366
e82f7154 367/* --- Standard GNU help options --- */
368
369static void version(FILE *fp)
370{
2d9ec601 371 pquis(fp, "$ version " VERSION "\n");
e82f7154 372}
373
374static void usage(FILE *fp)
375{
ad4499e3 376 pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
e82f7154 377}
378
379static void help(FILE *fp)
380{
381 version(fp);
382 fputc('\n', fp);
383 usage(fp);
2d9ec601 384 pquis(fp, "\n\
61e3dbdf 385An excessively full-featured port-forwarder, which subsumes large chunks\n\
ad4499e3 386of the functionality of inetd, netcat, and normal cat.\n\
e82f7154 387\n\
388Configuration may be supplied in one or more configuration files, or on\n\
389the command line (or both). If no `-f' option is present, and no\n\
390configuration is given on the command line, the standard input stream is\n\
391read.\n\
392\n\
393Configuration is free-form. Comments begin with a `#' character and\n\
afd7451e 394continue to the end of the line. Each command line argument is considered\n\
61e3dbdf 395to be a separate line.\n\
afd7451e 396\n\
2d9ec601 397The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
398For a summary of the various options, run `$ --options'.\n\
ad4499e3 399\n\
400Options available are:\n\
401\n\
402Help 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\
407Configuration 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\
411Other 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\
2d9ec601 419");
420}
421
422/* --- Other helpful options --- */
423
424static void grammar(FILE *fp)
425{
426 version(fp);
8cf7c7c2
MW
427 fputs("\nGrammar summary\n\n", fp);
428 fputs(grammar_text, fp);
2d9ec601 429}
430
431static void options(FILE *fp)
432{
433 version(fp);
8cf7c7c2
MW
434 fputs("\nOption summary\n\n", fp);
435 fputs(option_text, fp);
e82f7154 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
448int main(int argc, char *argv[])
449{
450 unsigned f = 0;
451 sel_state sst;
372a98e2 452 sig s_term, s_quit, s_int, s_hup;
61e3dbdf 453 scanner sc;
fc170a33 454 uid_t drop = -1;
455 gid_t dropg = -1;
4166ea7c 456 const char *pidfile = 0;
372a98e2 457 conffile *cf, **cff = &conffiles;
e82f7154 458
372a98e2 459#define f_bogus 1u
a8b9c5eb 460#define f_file 2u
461#define f_syslog 4u
462#define f_fork 8u
e82f7154 463
464 /* --- Initialize things --- */
465
466 ego(argv[0]);
467 sel = &sst;
468 sel_init(sel);
469 sub_init();
61e3dbdf 470 sig_init(sel);
e82f7154 471 bres_init(sel);
2d9ec601 472 bres_exec(0);
61e3dbdf 473 exec_init();
474 fattr_init(&fattr_global);
475 scan_create(&sc);
476
61e3dbdf 477 atexit(fw_exit);
e82f7154 478
479 /* --- Parse command line options --- */
480
481 for (;;) {
482 static struct option opts[] = {
483
484 /* --- Standard GNU help options --- */
485
096c89c3 486 { "help", 0, 0, 'h' },
e82f7154 487 { "version", 0, 0, 'v' },
488 { "usage", 0, 0, 'u' },
489
2d9ec601 490 /* --- Other help options --- */
491
1e8a64e8 492 { "grammar", 0, 0, 'G' },
493 { "options", 0, 0, 'O' },
2d9ec601 494
e82f7154 495 /* --- Other useful arguments --- */
496
497 { "file", OPTF_ARGREQ, 0, 'f' },
61e3dbdf 498 { "fork", 0, 0, 'd' },
499 { "daemon", 0, 0, 'd' },
4166ea7c 500 { "pidfile", OPTF_ARGREQ, 0, 'p' },
17be1d6b 501 { "syslog", 0, 0, 'l' },
502 { "log", 0, 0, 'l' },
61e3dbdf 503 { "quiet", 0, 0, 'q' },
fc170a33 504 { "setuid", OPTF_ARGREQ, 0, 's' },
505 { "uid", OPTF_ARGREQ, 0, 's' },
506 { "setgid", OPTF_ARGREQ, 0, 'g' },
507 { "gid", OPTF_ARGREQ, 0, 'g' },
e82f7154 508
509 /* --- Magic terminator --- */
510
511 { 0, 0, 0, 0 }
512 };
c1e028be 513 int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:lqs:g:", opts, 0, 0, 0);
e82f7154 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;
fc170a33 530 case 'G':
2d9ec601 531 grammar(stdout);
532 exit(0);
533 break;
fc170a33 534 case 'O':
2d9ec601 535 options(stdout);
536 exit(0);
537 break;
61e3dbdf 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));
372a98e2 545 cf = CREATE(conffile);
546 cf->name = optarg;
547 *cff = cf;
548 cff = &cf->next;
61e3dbdf 549 scan_add(&sc, scan_file(fp, optarg, 0));
550 }
e82f7154 551 f |= f_file;
e82f7154 552 break;
61e3dbdf 553 case 'd':
e82f7154 554 f |= f_fork;
555 break;
4166ea7c 556 case 'p':
557 pidfile = optarg;
558 break;
17be1d6b 559 case 'l':
560 f |= f_syslog;
561 break;
61e3dbdf 562 case 'q':
563 flags |= FW_QUIET;
564 break;
fc170a33 565 case 's':
206212ca 566 if (isdigit((unsigned char )optarg[0])) {
fc170a33 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':
206212ca 579 if (isdigit((unsigned char )optarg[0])) {
fc170a33 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;
e82f7154 591 default:
592 f |= f_bogus;
593 break;
594 }
595 }
596
597 if (f & f_bogus) {
598 usage(stderr);
599 exit(1);
600 }
372a98e2 601 *cff = 0;
e82f7154 602
603 /* --- Deal with the remaining arguments --- */
604
61e3dbdf 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);
e82f7154 615 }
616
61e3dbdf 617 /* --- Parse the configuration now gathered --- */
e82f7154 618
8c0a939e 619 parse(&sc);
e82f7154 620
372a98e2 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
e82f7154 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);
f8f2bc04
MW
649 if (chdir("/"))
650 die(1, "couldn't change to root directory: %s", strerror(errno));
e82f7154 651 setsid();
652
653 kid = fork();
654 if (kid != 0)
655 _exit(0);
17be1d6b 656 }
4166ea7c 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 }
afd7451e 669
17be1d6b 670 if (f & f_syslog) {
afd7451e 671 flags |= FW_SYSLOG;
672 openlog(QUIS, 0, LOG_DAEMON);
e82f7154 673 }
674
8938f77b
MW
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
e82f7154 689 /* --- Let rip --- */
690
61e3dbdf 691 if (!(flags & FW_SET))
692 moan("nothing to do!");
693 signal(SIGPIPE, SIG_IGN);
f9d40245 694
695 {
696 int selerr = 0;
697 while (active) {
698 if (!sel_select(sel))
206212ca 699 selerr = 0;
372a98e2 700 else if (errno != EINTR && errno != EAGAIN) {
d935f68b 701 fw_log(NOW, "error from select: %s", strerror(errno));
f9d40245 702 selerr++;
703 if (selerr > 8) {
d935f68b 704 fw_log(NOW, "too many consecutive select errors: bailing out");
f9d40245 705 exit(EXIT_FAILURE);
706 }
707 }
708 }
709 }
206212ca 710
e82f7154 711 return (0);
712}
713
714/*----- That's all, folks -------------------------------------------------*/