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