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