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