chiark / gitweb /
docs: Generate grammar and option summaries from manpage.
[fwd] / fw.c
1 /* -*-c-*-
2  *
3  * $Id$
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 /*----- Header files ------------------------------------------------------*/
30
31 #include "config.h"
32
33 #include <assert.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <signal.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42
43 #include <unistd.h>
44 #include <syslog.h>
45
46 #include <grp.h>
47 #include <pwd.h>
48
49 #include <mLib/bres.h>
50 #include <mLib/dstr.h>
51 #include <mLib/mdwopt.h>
52 #include <mLib/quis.h>
53 #include <mLib/report.h>
54 #include <mLib/sel.h>
55 #include <mLib/sig.h>
56 #include <mLib/sub.h>
57
58 #include "conf.h"
59 #include "endpt.h"
60 #include "exec.h"
61 #include "fattr.h"
62 #include "file.h"
63 #include "fw.h"
64 #include "mantext.h"
65 #include "privconn.h"
66 #include "scan.h"
67 #include "socket.h"
68 #include "source.h"
69
70 /*----- Global variables --------------------------------------------------*/
71
72 sel_state *sel;                         /* Multiplexor for nonblocking I/O */
73
74 /*----- Static variables --------------------------------------------------*/
75
76 typedef struct conffile {
77   struct conffile *next;
78   char *name;
79 } conffile;
80
81 static unsigned flags = 0;              /* Global state flags */
82 static unsigned active = 0;             /* Number of active things */
83 static conffile *conffiles = 0;         /* List of configuration files */
84
85 #define FW_SYSLOG 1u
86 #define FW_QUIET 2u
87 #define FW_SET 4u
88
89 /*----- Configuration parsing ---------------------------------------------*/
90
91 /* --- @parse@ --- *
92  *
93  * Arguments:   @scanner *sc@ = pointer to scanner definition
94  *
95  * Returns:     ---
96  *
97  * Use:         Parses a configuration file from the scanner.
98  */
99
100 static source_ops *sources[] =
101   { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
102 static target_ops *targets[] =
103   { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
104
105 void parse(scanner *sc)
106 {
107   token(sc);
108
109   for (;;) {
110     if (sc->t == CTOK_EOF)
111       break;
112     if (sc->t != CTOK_WORD)
113       error(sc, "parse error, keyword expected");
114
115     /* --- Handle a forwarding request --- */
116
117     if (strcmp(sc->d.buf, "forward") == 0 ||
118         strcmp(sc->d.buf, "fw") == 0 ||
119         strcmp(sc->d.buf, "from") == 0) {
120       source *s;
121       target *t;
122
123       token(sc);
124
125       /* --- Read a source description --- */
126
127       {
128         source_ops **sops;
129
130         /* --- Try to find a source type which understands --- */
131
132         s = 0;
133         for (sops = sources; *sops; sops++) {
134           if ((s = (*sops)->read(sc)) != 0)
135             goto found_source;
136         }
137         error(sc, "unknown source name `%s'", sc->d.buf);
138
139         /* --- Read any source-specific options --- */
140
141       found_source:
142         if (sc->t == '{') {
143           token(sc);
144           while (sc->t == CTOK_WORD) {
145             if (!s->ops->option || !s->ops->option(s, sc)) {
146               error(sc, "unknown %s source option `%s'",
147                     s->ops->name, sc->d.buf);
148             }
149             if (sc->t == ';')
150               token(sc);
151           }
152           if (sc->t != '}')
153             error(sc, "parse error, missing `}'");
154           token(sc);
155         }
156       }
157
158       /* --- Read a destination description --- */
159
160       if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
161                                  strcmp(sc->d.buf, "->") == 0))
162         token(sc);
163
164       {
165         target_ops **tops;
166
167         /* --- Try to find a target which understands --- */
168
169         t = 0;
170         for (tops = targets; *tops; tops++) {
171           if ((t = (*tops)->read(sc)) != 0)
172             goto found_target;
173         }
174         error(sc, "unknown target name `%s'", sc->d.buf);
175
176         /* --- Read any target-specific options --- */
177
178       found_target:
179         if (sc->t == '{') {
180           token(sc);
181           while (sc->t == CTOK_WORD) {
182             if (!t->ops->option || !t->ops->option(t, sc)) {
183               error(sc, "unknown %s target option `%s'",
184                     t->ops->name, sc->d.buf);
185             }
186             if (sc->t == ';')
187               token(sc);
188           }
189           if (sc->t != '}')
190             error(sc, "parse error, `}' expected");
191           token(sc);
192         }
193       }
194
195       /* --- Combine the source and target --- */
196
197       s->ops->attach(s, sc, t);
198       if (t->ops->confirm)
199         t->ops->confirm(t);
200     }
201
202     /* --- Include configuration from a file --- *
203      *
204      * Slightly tricky.  Scan the optional semicolon from the including
205      * stream, not the included one.
206      */
207
208     else if (strcmp(sc->d.buf, "include") == 0) {
209       FILE *fp;
210       dstr d = DSTR_INIT;
211
212       token(sc);
213       conf_name(sc, '/', &d);
214       if ((fp = fopen(d.buf, "r")) == 0)
215         error(sc, "can't include `%s': %s", d.buf, strerror(errno));
216       if (sc->t == ';')
217         token(sc);
218       pushback(sc);
219       scan_push(sc, scan_file(fp, d.buf, 0));
220       token(sc);
221       dstr_destroy(&d);
222       continue;                         /* Don't parse a trailing `;' */
223     }
224
225     /* --- Other configuration is handled elsewhere --- */
226
227     else {
228
229       /* --- First try among the sources --- */
230
231       {
232         source_ops **sops;
233
234         for (sops = sources; *sops; sops++) {
235           if ((*sops)->option && (*sops)->option(0, sc))
236             goto found_option;
237         }
238       }
239
240       /* --- Then try among the targets --- */
241
242       {
243         target_ops **tops;
244
245         for (tops = targets; *tops; tops++) {
246           if ((*tops)->option && (*tops)->option(0, sc))
247             goto found_option;
248         }
249       }
250
251       /* --- Nobody wants the option --- */
252
253       error(sc, "unknown global option or prefix `%s'", sc->d.buf);
254
255     found_option:;
256     }
257
258     if (sc->t == ';')
259       token(sc);
260   }
261 }
262
263 /*----- General utility functions -----------------------------------------*/
264
265 /* --- @fw_log@ --- *
266  *
267  * Arguments:   @time_t t@ = when the connection occurred or (@-1@)
268  *              @const char *fmt@ = format string to fill in
269  *              @...@ = other arguments
270  *
271  * Returns:     ---
272  *
273  * Use:         Logs a connection.
274  */
275
276 void fw_log(time_t t, const char *fmt, ...)
277 {
278   struct tm *tm;
279   dstr d = DSTR_INIT;
280   va_list ap;
281
282   if (flags & FW_QUIET)
283     return;
284
285   if (t == -1)
286     t = time(0);
287   tm = localtime(&t);
288   DENSURE(&d, 64);
289   d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
290   va_start(ap, fmt);
291   dstr_vputf(&d, fmt, &ap);
292   va_end(ap);
293   if (flags & FW_SYSLOG)
294     syslog(LOG_NOTICE, "%s", d.buf);
295   else {
296     DPUTC(&d, '\n');
297     dstr_write(&d, stderr);
298   }
299   DDESTROY(&d);
300 }
301
302 /* --- @fw_inc@, @fw_dec@ --- *
303  *
304  * Arguments:   ---
305  *
306  * Returns:     ---
307  *
308  * Use:         Increments or decrements the active thing count.  `fw' won't
309  *              quit while there are active things.
310  */
311
312 void fw_inc(void) { flags |= FW_SET; active++; }
313 void fw_dec(void) { if (active) active--; }
314
315 /* --- @fw_exit@ --- *
316  *
317  * Arguments:   ---
318  *
319  * Returns:     ---
320  *
321  * Use:         Exits when appropriate.
322  */
323
324 static void fw_exit(void)
325 {
326   endpt_killall();
327   source_killall();
328 }
329
330 /* --- @fw_tidy@ --- *
331  *
332  * Arguments:   @int n@ = signal number
333  *              @void *p@ = an uninteresting argument
334  *
335  * Returns:     ---
336  *
337  * Use:         Handles various signals and causes a clean tidy-up.
338  */
339
340 static void fw_tidy(int n, void *p)
341 {
342   const char *sn = 0;
343   switch (n) {
344     case SIGTERM: sn = "SIGTERM"; break;
345     case SIGINT: sn = "SIGINT"; break;
346     default: abort();
347   }
348
349   fw_log(-1, "closing down gracefully on %s", sn);
350   source_killall();
351 }
352
353 /* --- @fw_die@ --- *
354  *
355  * Arguments:   @int n@ = signal number
356  *              @void *p@ = an uninteresting argument
357  *
358  * Returns:     ---
359  *
360  * Use:         Handles various signals and causes an abrupt shutdown.
361  */
362
363 static void fw_die(int n, void *p)
364 {
365   const char *sn = 0;
366   switch (n) {
367     case SIGQUIT: sn = "SIGQUIT"; break;
368     default: abort();
369   }
370
371   fw_log(-1, "closing down abruptly on %s", sn);
372   source_killall();
373   endpt_killall();
374 }
375
376 /* --- @fw_reload@ --- *
377  *
378  * Arguments:   @int n@ = a signal number
379  *              @void *p@ = an uninteresting argument
380  *
381  * Returns:     ---
382  *
383  * Use:         Handles a hangup signal by re-reading configuration files.
384  */
385
386 static void fw_reload(int n, void *p)
387 {
388   FILE *fp;
389   scanner sc;
390   conffile *cf;
391
392   assert(n == SIGHUP);
393   if (!conffiles) {
394     fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
395     return;
396   }
397   fw_log(-1, "reloading configuration files...");
398   source_killall();
399   scan_create(&sc);
400   for (cf = conffiles; cf; cf = cf->next) {
401     if ((fp = fopen(cf->name, "r")) == 0)
402       fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
403     else
404       scan_add(&sc, scan_file(fp, cf->name, 0));
405   }
406   parse(&sc);
407   fw_log(-1, "... reload completed OK");
408 }
409
410 /*----- Startup and options parsing ---------------------------------------*/
411
412 /* --- Standard GNU help options --- */
413
414 static void version(FILE *fp)
415 {
416   pquis(fp, "$ version " VERSION "\n");
417 }
418
419 static void usage(FILE *fp)
420 {
421   pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
422 }
423
424 static void help(FILE *fp)
425 {
426   version(fp);
427   fputc('\n', fp);
428   usage(fp);
429   pquis(fp, "\n\
430 An excessively full-featured port-forwarder, which subsumes large chunks\n\
431 of the functionality of inetd, netcat, and normal cat.\n\
432 \n\
433 Configuration may be supplied in one or more configuration files, or on\n\
434 the command line (or both).  If no `-f' option is present, and no\n\
435 configuration is given on the command line, the standard input stream is\n\
436 read.\n\
437 \n\
438 Configuration is free-form.  Comments begin with a `#' character and\n\
439 continue to the end of the line.  Each command line argument is considered\n\
440 to be a separate line.\n\
441 \n\
442 The grammar is fairly complicated.  For a summary, run `$ --grammar'.\n\
443 For a summary of the various options, run `$ --options'.\n\
444 \n\
445 Options available are:\n\
446 \n\
447 Help options:\n\
448   -h, --help           Display this help message.\n\
449   -v, --version        Display the program's version number.\n\
450   -u, --usage          Display a terse usage summary.\n\
451 \n\
452 Configuration summary:\n\
453   -G, --grammar        Show a summary of the configuration language.\n\
454   -O, --options        Show a summary of the source and target options.\n\
455 \n\
456 Other options:\n\
457   -f, --file=FILE      Read configuration from a file.\n\
458   -q, --quiet          Don't emit any logging information.\n\
459   -d, --daemon         Fork into background after initializing.\n\
460   -p, --pidfile=FILE   Write process-id to the named FILE.\n\
461   -l, --syslog         Send log output to the system logger.\n\
462   -s, --setuid=USER    Change uid to USER after initializing sources.\n\
463   -g, --setgid=GRP     Change gid to GRP after initializing sources.\n\
464 ");
465 }
466
467 /* --- Other helpful options --- */
468
469 static void grammar(FILE *fp)
470 {
471   version(fp);
472   fputs("\nGrammar summary\n\n", fp);
473   fputs(grammar_text, fp);
474 }
475
476 static void options(FILE *fp)
477 {
478   version(fp);
479   fputs("\nOption summary\n\n", fp);
480   fputs(option_text, fp);
481 }
482
483 /* --- @main@ --- *
484  *
485  * Arguments:   @int argc@ = number of command line arguments
486  *              @char *argv[]@ = vector of argument strings
487  *
488  * Returns:     ---
489  *
490  * Use:         Simple port-forwarding server.
491  */
492
493 int main(int argc, char *argv[])
494 {
495   unsigned f = 0;
496   sel_state sst;
497   sig s_term, s_quit, s_int, s_hup;
498   scanner sc;
499   uid_t drop = -1;
500   gid_t dropg = -1;
501   const char *pidfile = 0;
502   conffile *cf, **cff = &conffiles;
503
504 #define f_bogus 1u
505 #define f_file 2u
506 #define f_syslog 4u
507 #define f_fork 8u
508
509   /* --- Initialize things --- */
510
511   ego(argv[0]);
512   sel = &sst;
513   sel_init(sel);
514   sub_init();
515   sig_init(sel);
516   bres_init(sel);
517   bres_exec(0);
518   exec_init();
519   fattr_init(&fattr_global);
520   scan_create(&sc);
521
522   atexit(fw_exit);
523
524   /* --- Parse command line options --- */
525
526   for (;;) {
527     static struct option opts[] = {
528
529       /* --- Standard GNU help options --- */
530
531       { "help",         0,              0,      'h' },
532       { "version",      0,              0,      'v' },
533       { "usage",        0,              0,      'u' },
534
535       /* --- Other help options --- */
536
537       { "grammar",      0,              0,      'G' },
538       { "options",      0,              0,      'O' },
539
540       /* --- Other useful arguments --- */
541
542       { "file",         OPTF_ARGREQ,    0,      'f' },
543       { "fork",         0,              0,      'd' },
544       { "daemon",       0,              0,      'd' },
545       { "pidfile",      OPTF_ARGREQ,    0,      'p' },
546       { "syslog",       0,              0,      'l' },
547       { "log",          0,              0,      'l' },
548       { "quiet",        0,              0,      'q' },
549       { "setuid",       OPTF_ARGREQ,    0,      's' },
550       { "uid",          OPTF_ARGREQ,    0,      's' },
551       { "setgid",       OPTF_ARGREQ,    0,      'g' },
552       { "gid",          OPTF_ARGREQ,    0,      'g' },
553
554       /* --- Magic terminator --- */
555
556       { 0,              0,              0,      0 }
557     };
558     int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
559
560     if (i < 0)
561       break;
562     switch (i) {
563       case 'h':
564         help(stdout);
565         exit(0);
566         break;
567       case 'v':
568         version(stdout);
569         exit(0);
570         break;
571       case 'u':
572         usage(stdout);
573         exit(0);
574         break;
575       case 'G':
576         grammar(stdout);
577         exit(0);
578         break;
579       case 'O':
580         options(stdout);
581         exit(0);
582         break;
583       case 'f':
584         if (strcmp(optarg, "-") == 0)
585           scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
586         else {
587           FILE *fp;
588           if ((fp = fopen(optarg, "r")) == 0)
589             die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
590           cf = CREATE(conffile);
591           cf->name = optarg;
592           *cff = cf;
593           cff = &cf->next;
594           scan_add(&sc, scan_file(fp, optarg, 0));
595         }
596         f |= f_file;
597         break;
598       case 'd':
599         f |= f_fork;
600         break;
601       case 'p':
602         pidfile = optarg;
603         break;
604       case 'l':
605         f |= f_syslog;
606         break;
607       case 'q':
608         flags |= FW_QUIET;
609         break;
610       case 's':
611         if (isdigit((unsigned char )optarg[0])) {
612           char *q;
613           drop = strtol(optarg, &q, 0);
614           if (*q)
615             die(1, "bad uid `%s'", optarg);
616         } else {
617           struct passwd *pw = getpwnam(optarg);
618           if (!pw)
619             die(1, "unknown user `%s'", optarg);
620           drop = pw->pw_uid;
621         }
622         break;
623       case 'g':
624         if (isdigit((unsigned char )optarg[0])) {
625           char *q;
626           dropg = strtol(optarg, &q, 0);
627           if (*q)
628             die(1, "bad gid `%s'", optarg);
629         } else {
630           struct group *gr = getgrnam(optarg);
631           if (!gr)
632             die(1, "unknown group `%s'", optarg);
633           dropg = gr->gr_gid;
634         }
635         break;
636       default:
637         f |= f_bogus;
638         break;
639     }
640   }
641
642   if (f & f_bogus) {
643     usage(stderr);
644     exit(1);
645   }
646   *cff = 0;
647
648   /* --- Deal with the remaining arguments --- */
649
650   if (optind < argc)
651     scan_add(&sc, scan_argv(argv + optind));
652   else if (f & f_file)
653     /* Cool */;
654   else if (!isatty(STDIN_FILENO))
655     scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
656   else {
657     moan("no configuration given and stdin is a terminal.");
658     moan("type `%s --help' for usage information.", QUIS);
659     exit(1);
660   }
661
662   /* --- Parse the configuration now gathered --- */
663
664   parse(&sc);
665
666   /* --- Set up some signal handlers --- *
667    *
668    * Don't enable @SIGINT@ if the caller already disabled it.
669    */
670
671   {
672     struct sigaction sa;
673
674     sig_add(&s_term, SIGTERM, fw_tidy, 0);
675     sig_add(&s_quit, SIGQUIT, fw_die, 0);
676     sigaction(SIGINT, 0, &sa);
677     if (sa.sa_handler != SIG_IGN)
678       sig_add(&s_int, SIGINT, fw_tidy, 0);
679     sig_add(&s_hup, SIGHUP, fw_reload, 0);
680   }
681
682   /* --- Drop privileges --- */
683
684   if (drop != (uid_t)-1)
685     privconn_split(sel);
686 #ifdef HAVE_SETGROUPS
687   if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
688       (drop != (uid_t)-1 && setuid(drop)))
689     die(1, "couldn't drop privileges: %s", strerror(errno));
690 #else
691   if ((dropg != (gid_t)-1 && setgid(dropg)) ||
692       (drop != (uid_t)-1 && setuid(drop)))
693     die(1, "couldn't drop privileges: %s", strerror(errno));
694 #endif
695
696   /* --- Fork into the background --- */
697
698   if (f & f_fork) {
699     pid_t kid;
700
701     kid = fork();
702     if (kid == -1)
703       die(1, "couldn't fork: %s", strerror(errno));
704     if (kid != 0)
705       _exit(0);
706
707     close(0); close(1); close(2);
708     chdir("/");
709     setsid();
710
711     kid = fork();
712     if (kid != 0)
713       _exit(0);
714   }
715   if (pidfile) {
716     FILE *fp = fopen(pidfile, "w");
717     if (!fp) {
718       die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
719           pidfile, strerror(errno));
720     }
721     fprintf(fp, "%lu\n", (unsigned long)getpid());
722     if (fflush(fp) || ferror(fp) || fclose(fp)) {
723       die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
724           pidfile, strerror(errno));
725     }
726   }
727
728   if (f & f_syslog) {
729     flags |= FW_SYSLOG;
730     openlog(QUIS, 0, LOG_DAEMON);
731   }
732
733   /* --- Let rip --- */
734
735   if (!(flags & FW_SET))
736     moan("nothing to do!");
737   signal(SIGPIPE, SIG_IGN);
738
739   {
740     int selerr = 0;
741     while (active) {
742       if (!sel_select(sel))
743         selerr = 0;
744       else if (errno != EINTR && errno != EAGAIN) {
745         fw_log(-1, "error from select: %s", strerror(errno));
746         selerr++;
747         if (selerr > 8) {
748           fw_log(-1, "too many consecutive select errors: bailing out");
749           exit(EXIT_FAILURE);
750         }
751       }
752     }
753   }
754     
755   return (0);
756 }
757
758 /*----- That's all, folks -------------------------------------------------*/