chiark / gitweb /
Check for @getnetbyname@, since it appears not to be available under
[fwd] / fw.c
1 /* -*-c-*-
2  *
3  * $Id: fw.c,v 1.11 2001/02/03 20:33:26 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.11  2001/02/03 20:33:26  mdw
33  * Fix flags to be unsigned.
34  *
35  * Revision 1.10  2001/02/03 20:30:03  mdw
36  * Support re-reading config files on SIGHUP.
37  *
38  * Revision 1.9  2001/01/20 11:55:17  mdw
39  * Handle select errors more robustly.
40  *
41  * Revision 1.8  2000/03/23 23:19:19  mdw
42  * Fix changed options in parser table.
43  *
44  * Revision 1.7  2000/03/23 00:37:33  mdw
45  * Add option to change user and group after initialization.  Naughtily
46  * reassign short equivalents of --grammar and --options.
47  *
48  * Revision 1.6  1999/12/22 15:44:10  mdw
49  * Make syslog a separate option, and do it better.
50  *
51  * Revision 1.5  1999/10/22 22:47:50  mdw
52  * Grammar changes.  Also, don't enable SIGINT if it's currently ignored.
53  *
54  * Revision 1.4  1999/10/10 16:46:12  mdw
55  * New resolver to initialize.  Also, include options for grammar and
56  * options references.
57  *
58  * Revision 1.3  1999/07/26 23:30:42  mdw
59  * Major reconstruction work for new design.
60  *
61  * Revision 1.2  1999/07/03 13:55:17  mdw
62  * Various changes.  Add configuration grammar to help text.  Change to
63  * root directory and open syslog when forking into background.
64  *
65  * Revision 1.1.1.1  1999/07/01 08:56:23  mdw
66  * Initial revision.
67  *
68  */
69
70 /*----- Header files ------------------------------------------------------*/
71
72 #include "config.h"
73
74 #include <assert.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <signal.h>
78 #include <stdarg.h>
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <time.h>
83
84 #include <unistd.h>
85 #include <syslog.h>
86
87 #include <grp.h>
88 #include <pwd.h>
89
90 #include <mLib/bres.h>
91 #include <mLib/dstr.h>
92 #include <mLib/mdwopt.h>
93 #include <mLib/quis.h>
94 #include <mLib/report.h>
95 #include <mLib/sel.h>
96 #include <mLib/sig.h>
97 #include <mLib/sub.h>
98
99 #include "conf.h"
100 #include "endpt.h"
101 #include "exec.h"
102 #include "fattr.h"
103 #include "fw.h"
104 #include "scan.h"
105 #include "source.h"
106
107 /*----- Global variables --------------------------------------------------*/
108
109 sel_state *sel;                         /* Multiplexor for nonblocking I/O */
110
111 /*----- Static variables --------------------------------------------------*/
112
113 typedef struct conffile {
114   struct conffile *next;
115   char *name;
116 } conffile;
117
118 static unsigned flags = 0;              /* Global state flags */
119 static unsigned active = 0;             /* Number of active things */
120 static conffile *conffiles = 0;         /* List of configuration files */
121
122 #define FW_SYSLOG 1u
123 #define FW_QUIET 2u
124 #define FW_SET 4u
125
126 /*----- Main code ---------------------------------------------------------*/
127
128 /* --- @fw_log@ --- *
129  *
130  * Arguments:   @time_t t@ = when the connection occurred or (@-1@)
131  *              @const char *fmt@ = format string to fill in
132  *              @...@ = other arguments
133  *
134  * Returns:     ---
135  *
136  * Use:         Logs a connection.
137  */
138
139 void fw_log(time_t t, const char *fmt, ...)
140 {
141   struct tm *tm;
142   dstr d = DSTR_INIT;
143   va_list ap;
144
145   if (flags & FW_QUIET)
146     return;
147
148   if (t == -1)
149     t = time(0);
150   tm = localtime(&t);
151   DENSURE(&d, 64);
152   d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
153   va_start(ap, fmt);
154   dstr_vputf(&d, fmt, ap);
155   va_end(ap);
156   if (flags & FW_SYSLOG)
157     syslog(LOG_NOTICE, "%s", d.buf);
158   else {
159     DPUTC(&d, '\n');
160     dstr_write(&d, stderr);
161   }
162   DDESTROY(&d);
163 }
164
165 /* --- @fw_inc@, @fw_dec@ --- *
166  *
167  * Arguments:   ---
168  *
169  * Returns:     ---
170  *
171  * Use:         Increments or decrements the active thing count.  `fw' won't
172  *              quit while there are active things.
173  */
174
175 void fw_inc(void) { flags |= FW_SET; active++; }
176 void fw_dec(void) { if (active) active--; }
177
178 /* --- @fw_exit@ --- *
179  *
180  * Arguments:   ---
181  *
182  * Returns:     ---
183  *
184  * Use:         Exits when appropriate.
185  */
186
187 static void fw_exit(void)
188 {
189   endpt_killall();
190   source_killall();
191 }
192
193 /* --- @fw_tidy@ --- *
194  *
195  * Arguments:   @int n@ = signal number
196  *              @void *p@ = an uninteresting argument
197  *
198  * Returns:     ---
199  *
200  * Use:         Handles various signals and causes a clean tidy-up.
201  */
202
203 static void fw_tidy(int n, void *p)
204 {
205   const char *sn = 0;
206   switch (n) {
207     case SIGTERM: sn = "SIGTERM"; break;
208     case SIGINT: sn = "SIGINT"; break;
209     default: abort();
210   }
211
212   fw_log(-1, "closing down gracefully on %s", sn);
213   source_killall();
214 }
215
216 /* --- @fw_die@ --- *
217  *
218  * Arguments:   @int n@ = signal number
219  *              @void *p@ = an uninteresting argument
220  *
221  * Returns:     ---
222  *
223  * Use:         Handles various signals and causes an abrupt shutdown.
224  */
225
226 static void fw_die(int n, void *p)
227 {
228   const char *sn = 0;
229   switch (n) {
230     case SIGQUIT: sn = "SIGQUIT"; break;
231     default: abort();
232   }
233
234   fw_log(-1, "closing down abruptly on %s", sn);
235   source_killall();
236   endpt_killall();
237 }
238
239 /* --- @fw_reload@ --- *
240  *
241  * Arguments:   @int n@ = a signal number
242  *              @void *p@ = an uninteresting argument
243  *
244  * Returns:     ---
245  *
246  * Use:         Handles a hangup signal by re-reading configuration files.
247  */
248
249 static void fw_reload(int n, void *p)
250 {
251   FILE *fp;
252   scanner sc;
253   conffile *cf;
254
255   assert(n == SIGHUP);
256   if (!conffiles) {
257     fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
258     return;
259   }
260   fw_log(-1, "reloading configuration files...");
261   source_killall();
262   scan_create(&sc);
263   for (cf = conffiles; cf; cf = cf->next) {
264     if ((fp = fopen(cf->name, "r")) == 0)
265       fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
266     else
267       scan_add(&sc, scan_file(fp, cf->name, 0));
268   }
269   conf_parse(&sc);
270   fw_log(-1, "... reload completed OK");
271 }
272
273 /* --- Standard GNU help options --- */
274
275 static void version(FILE *fp)
276 {
277   pquis(fp, "$ version " VERSION "\n");
278 }
279
280 static void usage(FILE *fp)
281 {
282   pquis(fp, "Usage: $ [-dql] [-f file] [config statements...]\n");
283 }
284
285 static void help(FILE *fp)
286 {
287   version(fp);
288   fputc('\n', fp);
289   usage(fp);
290   pquis(fp, "\n\
291 An excessively full-featured port-forwarder, which subsumes large chunks\n\
292 of the functionality of inetd, netcat, and normal cat.  Options available\n\
293 are:\n\
294 \n\
295 -h, --help        Display this help message.\n\
296 -v, --version     Display the program's version number.\n\
297 -u, --usage       Display a terse usage summary.\n\
298 \n\
299 -G, --grammar     Show a summary of the configuration language.\n\
300 -O, --options     Show a summary of the source and target options.\n\
301 \n\
302 -f, --file=FILE   Read configuration from a file.\n\
303 -q, --quiet       Don't emit any logging information.\n\
304 -d, --daemon      Fork into background after initializing.\n\
305 -l, --syslog      Send log output to the system logger.\n\
306 -s, --setuid=USER Change uid to USER after initializing sources.\n\
307 -g, --setgid=GRP  Change gid to GRP after initializing sources.\n\
308 \n\
309 Configuration may be supplied in one or more configuration files, or on\n\
310 the command line (or both).  If no `-f' option is present, and no\n\
311 configuration is given on the command line, the standard input stream is\n\
312 read.\n\
313 \n\
314 Configuration is free-form.  Comments begin with a `#' character and\n\
315 continue to the end of the line.  Each command line argument is considered\n\
316 to be a separate line.\n\
317 \n\
318 The grammar is fairly complicated.  For a summary, run `$ --grammar'.\n\
319 For a summary of the various options, run `$ --options'.\n\
320 ");
321 }
322
323 /* --- Other helpful options --- */
324
325 static void grammar(FILE *fp)
326 {
327   version(fp);
328   pquis(fp, "\n\
329 Grammar summary\n\
330 \n\
331 Basic syntax\n\
332         file ::= empty | file stmt [`;']\n\
333         stmt ::= option-stmt | fw-stmt\n\
334         fw-stmt ::= `fw' source options [`to'|`->'] target options\n\
335         options ::= `{' option-seq `}'\n\
336         option-seq ::= empty | option-stmt [`;'] option-seq\n\
337 \n\
338 Option syntax\n\
339         option-stmt ::= q-option\n\
340         q-option ::= option\n\
341              | prefix `.' q-option\n\
342              | prefix `{' option-seq `}'\n\
343         prefix ::= word\n\
344 \n\
345 File source and target\n\
346         source ::= file\n\
347         target ::= file\n\
348         file ::= `file' [`.'] fspec [`,' fspec]\n\
349         fspec ::= fd-spec | name-spec | null-spec\n\
350         fd-spec ::= [[`:']`fd'[`:']] number|`stdin'|`stdout'\n\
351         name-spec ::= [[`:']`file'[`:']] file-name\n\
352         file-name ::= path-seq | [ path-seq ]\n\
353         path-seq ::= path-elt | path-seq path-elt\n\
354         path-elt ::= `/' | word\n\
355         null-spec ::= [`:']`null'[`:']\n\
356 \n\
357 Exec source and target\n\
358         source ::= exec\n\
359         target ::= exec\n\
360         exec ::= `exec' [`.'] cmd-spec\n\
361         cmd-spec ::= shell-cmd | [prog-name] `[' argv0 arg-seq `]'\n\
362         arg-seq ::= word | arg-seq word\n\
363         shell-cmd ::= word\n\
364         argv0 ::= word\n\
365 \n\
366 Socket source and target\n\
367         source ::= socket-source\n\
368         target ::= socket-target\n\
369         socket-source ::= [`socket'[`.']] [[`:']addr-type[`:']] source-addr\n\
370         socket-target ::= [`socket'[`.']] [[`:']addr-type[`:']] target-addr\n\
371 \n\
372         inet-source-addr ::= [port] port\n\
373         inet-target-addr ::= address [`:'] port\n\
374         address ::= addr-elt | address addr-elt\n\
375         addr-elt ::= `.' | word\n\
376 \n\
377         unix-source-addr ::= file-name\n\
378         unix-target-addr ::= file-name\n\
379 ");
380 }
381
382 static void options(FILE *fp)
383 {
384   version(fp);
385   pquis(fp, "\n\
386 Options summary\n\
387 \n\
388 File attributes (`fattr')\n\
389         prefix.fattr.mode [=] mode\n\
390         prefix.fattr.owner [=] user\n\
391         prefix.fattr.group [=] group\n\
392 \n\
393 File options\n\
394         file.create [=] yes|no\n\
395         file.open [=] no|truncate|append\n\
396         file.fattr.*\n\
397 \n\
398 Exec options\n\
399         exec.logging [=] yes|no\n\
400         exec.dir [=] file-name\n\
401         exec.root [=] file-name\n\
402         exec.user [=] user\n\
403         exec.group [=] group\n\
404         exec.rlimit.limit[.hard|.soft] [=] value\n\
405         exec.env.clear\n\
406         exec.env.unset var\n\
407         exec.env.[set] var [=] value\n\
408 \n\
409 Socket options\n\
410         socket.conn [=] number|unlimited|one-shot\n\
411         socket.logging [=] yes|no\n\
412         socket.inet.[allow|deny] [from] address [/ address]\n\
413         socket.unix.fattr.*\n\
414 ");
415 }
416
417 /* --- @main@ --- *
418  *
419  * Arguments:   @int argc@ = number of command line arguments
420  *              @char *argv[]@ = vector of argument strings
421  *
422  * Returns:     ---
423  *
424  * Use:         Simple port-forwarding server.
425  */
426
427 int main(int argc, char *argv[])
428 {
429   unsigned f = 0;
430   sel_state sst;
431   sig s_term, s_quit, s_int, s_hup;
432   scanner sc;
433   uid_t drop = -1;
434   gid_t dropg = -1;
435   conffile *cf, **cff = &conffiles;
436
437 #define f_bogus 1u
438 #define f_file 2u
439 #define f_syslog 4u
440 #define f_fork 8u
441
442   /* --- Initialize things --- */
443
444   ego(argv[0]);
445   sel = &sst;
446   sel_init(sel);
447   sub_init();
448   sig_init(sel);
449   bres_init(sel);
450   bres_exec(0);
451   exec_init();
452   fattr_init(&fattr_global);
453   scan_create(&sc);
454
455   atexit(fw_exit);
456
457   /* --- Parse command line options --- */
458
459   for (;;) {
460     static struct option opts[] = {
461
462       /* --- Standard GNU help options --- */
463
464       { "help",         0,              0,      'h' },
465       { "version",      0,              0,      'v' },
466       { "usage",        0,              0,      'u' },
467
468       /* --- Other help options --- */
469
470       { "grammar",      0,              0,      'G' },
471       { "options",      0,              0,      'O' },
472
473       /* --- Other useful arguments --- */
474
475       { "file",         OPTF_ARGREQ,    0,      'f' },
476       { "fork",         0,              0,      'd' },
477       { "daemon",       0,              0,      'd' },
478       { "syslog",       0,              0,      'l' },
479       { "log",          0,              0,      'l' },
480       { "quiet",        0,              0,      'q' },
481       { "setuid",       OPTF_ARGREQ,    0,      's' },
482       { "uid",          OPTF_ARGREQ,    0,      's' },
483       { "setgid",       OPTF_ARGREQ,    0,      'g' },
484       { "gid",          OPTF_ARGREQ,    0,      'g' },
485
486       /* --- Magic terminator --- */
487
488       { 0,              0,              0,      0 }
489     };
490     int i = mdwopt(argc, argv, "+hvu GO f:dls:g:", opts, 0, 0, 0);
491
492     if (i < 0)
493       break;
494     switch (i) {
495       case 'h':
496         help(stdout);
497         exit(0);
498         break;
499       case 'v':
500         version(stdout);
501         exit(0);
502         break;
503       case 'u':
504         usage(stdout);
505         exit(0);
506         break;
507       case 'G':
508         grammar(stdout);
509         exit(0);
510         break;
511       case 'O':
512         options(stdout);
513         exit(0);
514         break;
515       case 'f':
516         if (strcmp(optarg, "-") == 0)
517           scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
518         else {
519           FILE *fp;
520           if ((fp = fopen(optarg, "r")) == 0)
521             die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
522           cf = CREATE(conffile);
523           cf->name = optarg;
524           *cff = cf;
525           cff = &cf->next;
526           scan_add(&sc, scan_file(fp, optarg, 0));
527         }
528         f |= f_file;
529         break;
530       case 'd':
531         f |= f_fork;
532         break;
533       case 'l':
534         f |= f_syslog;
535         break;
536       case 'q':
537         flags |= FW_QUIET;
538         break;
539       case 's':
540         if (isdigit((unsigned char )optarg[0])) {
541           char *q;
542           drop = strtol(optarg, &q, 0);
543           if (*q)
544             die(1, "bad uid `%s'", optarg);
545         } else {
546           struct passwd *pw = getpwnam(optarg);
547           if (!pw)
548             die(1, "unknown user `%s'", optarg);
549           drop = pw->pw_uid;
550         }
551         break;
552       case 'g':
553         if (isdigit((unsigned char )optarg[0])) {
554           char *q;
555           dropg = strtol(optarg, &q, 0);
556           if (*q)
557             die(1, "bad gid `%s'", optarg);
558         } else {
559           struct group *gr = getgrnam(optarg);
560           if (!gr)
561             die(1, "unknown group `%s'", optarg);
562           dropg = gr->gr_gid;
563         }
564         break;
565       default:
566         f |= f_bogus;
567         break;
568     }
569   }
570
571   if (f & f_bogus) {
572     usage(stderr);
573     exit(1);
574   }
575   *cff = 0;
576
577   /* --- Deal with the remaining arguments --- */
578
579   if (optind < argc)
580     scan_add(&sc, scan_argv(argv + optind));
581   else if (f & f_file)
582     /* Cool */;
583   else if (!isatty(STDIN_FILENO))
584     scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
585   else {
586     moan("no configuration given and stdin is a terminal.");
587     moan("type `%s --help' for usage information.", QUIS);
588     exit(1);
589   }
590
591   /* --- Parse the configuration now gathered --- */
592
593   conf_parse(&sc);
594
595   /* --- Set up some signal handlers --- *
596    *
597    * Don't enable @SIGINT@ if the caller already disabled it.
598    */
599
600   {
601     struct sigaction sa;
602
603     sig_add(&s_term, SIGTERM, fw_tidy, 0);
604     sig_add(&s_quit, SIGQUIT, fw_die, 0);
605     sigaction(SIGINT, 0, &sa);
606     if (sa.sa_handler != SIG_IGN)
607       sig_add(&s_int, SIGINT, fw_tidy, 0);
608     sig_add(&s_hup, SIGHUP, fw_reload, 0);
609   }
610
611   /* --- Drop privileges --- */
612
613 #ifdef HAVE_SETGROUPS
614   if ((dropg != -1 && (setgid(dropg) || setgroups(1, &dropg))) ||
615       (drop != -1 && setuid(drop)))
616     die(1, "couldn't drop privileges: %s", strerror(errno));
617 #else
618   if ((dropg != -1 && setgid(dropg)) ||
619       (drop != -1 && setuid(drop)))
620     die(1, "couldn't drop privileges: %s", strerror(errno));
621 #endif
622
623   /* --- Fork into the background --- */
624
625   if (f & f_fork) {
626     pid_t kid;
627
628     kid = fork();
629     if (kid == -1)
630       die(1, "couldn't fork: %s", strerror(errno));
631     if (kid != 0)
632       _exit(0);
633
634     close(0); close(1); close(2);
635     chdir("/");
636     setsid();
637
638     kid = fork();
639     if (kid != 0)
640       _exit(0);
641   }
642
643   if (f & f_syslog) {
644     flags |= FW_SYSLOG;
645     openlog(QUIS, 0, LOG_DAEMON);
646   }
647
648   /* --- Let rip --- */
649
650   if (!(flags & FW_SET))
651     moan("nothing to do!");
652   signal(SIGPIPE, SIG_IGN);
653
654   {
655     int selerr = 0;
656     while (active) {
657       if (!sel_select(sel))
658         selerr = 0;
659       else if (errno != EINTR && errno != EAGAIN) {
660         fw_log(-1, "error from select: %s", strerror(errno));
661         selerr++;
662         if (selerr > 8) {
663           fw_log(-1, "too many consecutive select errors: bailing out");
664           exit(EXIT_FAILURE);
665         }
666       }
667     }
668   }
669     
670   return (0);
671 }
672
673 /*----- That's all, folks -------------------------------------------------*/