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