chiark / gitweb /
New resolver to initialize. Also, include options for grammar and
[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 -------------------------------------------------*/