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