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