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