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