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