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 -------------------------------------------------*/