chiark / gitweb /
Fix changed options in parser table.
[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 -------------------------------------------------*/