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