9391e30fd0707b04d9590e04aaf11929d932743f
[dragonfly.git] / contrib / tcp_wrappers / options.c
1  /*
2   * General skeleton for adding options to the access control language. The
3   * features offered by this module are documented in the hosts_options(5)
4   * manual page (source file: hosts_options.5, "nroff -man" format).
5   * 
6   * Notes and warnings for those who want to add features:
7   * 
8   * In case of errors, abort options processing and deny access. There are too
9   * many irreversible side effects to make error recovery feasible. For
10   * example, it makes no sense to continue after we have already changed the
11   * userid.
12   * 
13   * In case of errors, do not terminate the process: the routines might be
14   * called from a long-running daemon that should run forever. Instead, call
15   * tcpd_jump() which does a non-local goto back into the hosts_access()
16   * routine.
17   * 
18   * In case of severe errors, use clean_exit() instead of directly calling
19   * exit(), or the inetd may loop on an UDP request.
20   * 
21   * In verification mode (for example, with the "tcpdmatch" command) the
22   * "dry_run" flag is set. In this mode, an option function should just "say"
23   * what it is going to do instead of really doing it.
24   * 
25   * Some option functions do not return (for example, the twist option passes
26   * control to another program). In verification mode (dry_run flag is set)
27   * such options should clear the "dry_run" flag to inform the caller of this
28   * course of action.
29   */
30
31 /* System libraries. */
32
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/socket.h>
36 #include <sys/stat.h>
37 #include <netinet/in.h>
38 #include <netdb.h>
39 #include <stdio.h>
40 #include <unistd.h>
41 #define SYSLOG_NAMES
42 #include <syslog.h>
43 #include <pwd.h>
44 #include <grp.h>
45 #include <ctype.h>
46 #include <setjmp.h>
47 #include <string.h>
48 #include <stdlib.h>
49
50 #ifndef MAXPATHNAMELEN
51 #define MAXPATHNAMELEN  BUFSIZ
52 #endif
53
54 /* Local stuff. */
55
56 #include "tcpd.h"
57
58 /* Options runtime support. */
59
60 int     dry_run = 0;                    /* flag set in verification mode */
61 extern jmp_buf tcpd_buf;                /* tcpd_jump() support */
62
63 /* Options parser support. */
64
65 static char whitespace_eq[] = "= \t\r\n";
66 #define whitespace (whitespace_eq + 1)
67
68 static char *get_field();               /* chew :-delimited field off string */
69 static char *chop_string();             /* strip leading and trailing blanks */
70
71 /* List of functions that implement the options. Add yours here. */
72
73 static void user_option();              /* execute "user name.group" option */
74 static void group_option();             /* execute "group name" option */
75 static void umask_option();             /* execute "umask mask" option */
76 static void linger_option();            /* execute "linger time" option */
77 static void keepalive_option();         /* execute "keepalive" option */
78 static void spawn_option();             /* execute "spawn command" option */
79 static void twist_option();             /* execute "twist command" option */
80 static void rfc931_option();            /* execute "rfc931" option */
81 static void setenv_option();            /* execute "setenv name value" */
82 static void nice_option();              /* execute "nice" option */
83 static void severity_option();          /* execute "severity value" */
84 static void allow_option();             /* execute "allow" option */
85 static void deny_option();              /* execute "deny" option */
86 static void banners_option();           /* execute "banners path" option */
87
88 /* Structure of the options table. */
89
90 struct option {
91     char   *name;                       /* keyword name, case is ignored */
92     void  (*func) ();                   /* function that does the real work */
93     int     flags;                      /* see below... */
94 };
95
96 #define NEED_ARG        (1<<1)          /* option requires argument */
97 #define USE_LAST        (1<<2)          /* option must be last */
98 #define OPT_ARG         (1<<3)          /* option has optional argument */
99 #define EXPAND_ARG      (1<<4)          /* do %x expansion on argument */
100
101 #define need_arg(o)     ((o)->flags & NEED_ARG)
102 #define opt_arg(o)      ((o)->flags & OPT_ARG)
103 #define permit_arg(o)   ((o)->flags & (NEED_ARG | OPT_ARG))
104 #define use_last(o)     ((o)->flags & USE_LAST)
105 #define expand_arg(o)   ((o)->flags & EXPAND_ARG)
106
107 /* List of known keywords. Add yours here. */
108
109 static struct option option_table[] = {
110     { "user", user_option, NEED_ARG },
111     { "group", group_option, NEED_ARG },
112     { "umask", umask_option, NEED_ARG },
113     { "linger", linger_option, NEED_ARG },
114     { "keepalive", keepalive_option, 0 },
115     { "spawn", spawn_option, NEED_ARG | EXPAND_ARG },
116     { "twist", twist_option, NEED_ARG | EXPAND_ARG | USE_LAST },
117     { "rfc931", rfc931_option, OPT_ARG },
118     { "setenv", setenv_option, NEED_ARG | EXPAND_ARG },
119     { "nice", nice_option, OPT_ARG },
120     { "severity", severity_option, NEED_ARG },
121     { "allow", allow_option, USE_LAST },
122     { "deny", deny_option, USE_LAST },
123     { "banners", banners_option, NEED_ARG },
124     { .name = NULL },
125 };
126
127 /* process_options - process access control options */
128
129 void    process_options(options, request)
130 char   *options;
131 struct request_info *request;
132 {
133     char   *key;
134     char   *value;
135     char   *curr_opt;
136     char   *next_opt;
137     struct option *op;
138     char    bf[BUFSIZ];
139
140     for (curr_opt = get_field(options); curr_opt; curr_opt = next_opt) {
141         next_opt = get_field((char *) 0);
142
143         /*
144          * Separate the option into name and value parts. For backwards
145          * compatibility we ignore exactly one '=' between name and value.
146          */
147         curr_opt = chop_string(curr_opt);
148         if (*(value = curr_opt + strcspn(curr_opt, whitespace_eq))) {
149             if (*value != '=') {
150                 *value++ = 0;
151                 value += strspn(value, whitespace);
152             }
153             if (*value == '=') {
154                 *value++ = 0;
155                 value += strspn(value, whitespace);
156             }
157         }
158         if (*value == 0)
159             value = 0;
160         key = curr_opt;
161
162         /*
163          * Disallow missing option names (and empty option fields).
164          */
165         if (*key == 0)
166             tcpd_jump("missing option name");
167
168         /*
169          * Lookup the option-specific info and do some common error checks.
170          * Delegate option-specific processing to the specific functions.
171          */
172
173         for (op = option_table; op->name && STR_NE(op->name, key); op++)
174              /* VOID */ ;
175         if (op->name == 0)
176             tcpd_jump("bad option name: \"%s\"", key);
177         if (!value && need_arg(op))
178             tcpd_jump("option \"%s\" requires value", key);
179         if (value && !permit_arg(op))
180             tcpd_jump("option \"%s\" requires no value", key);
181         if (next_opt && use_last(op))
182             tcpd_jump("option \"%s\" must be at end", key);
183         if (value && expand_arg(op))
184             value = chop_string(percent_x(bf, sizeof(bf), value, request));
185         if (hosts_access_verbose)
186             syslog(LOG_DEBUG, "option:   %s %s", key, value ? value : "");
187         (*(op->func)) (value, request);
188     }
189 }
190
191 /* allow_option - grant access */
192
193 /* ARGSUSED */
194
195 static void allow_option(value, request)
196 char   *value;
197 struct request_info *request;
198 {
199     longjmp(tcpd_buf, AC_PERMIT);
200 }
201
202 /* deny_option - deny access */
203
204 /* ARGSUSED */
205
206 static void deny_option(value, request)
207 char   *value;
208 struct request_info *request;
209 {
210     longjmp(tcpd_buf, AC_DENY);
211 }
212
213 /* banners_option - expand %<char>, terminate each line with CRLF */
214
215 static void banners_option(value, request)
216 char   *value;
217 struct request_info *request;
218 {
219     char    path[MAXPATHNAMELEN];
220     char    ibuf[BUFSIZ];
221     char    obuf[2 * BUFSIZ];
222     struct stat st;
223     int     ch;
224     FILE   *fp;
225
226     sprintf(path, "%s/%s", value, eval_daemon(request));
227     if ((fp = fopen(path, "r")) != 0) {
228         while ((ch = fgetc(fp)) == 0)
229             write(request->fd, "", 1);
230         ungetc(ch, fp);
231         while (fgets(ibuf, sizeof(ibuf) - 1, fp)) {
232             if (split_at(ibuf, '\n'))
233                 strcat(ibuf, "\r\n");
234             percent_x(obuf, sizeof(obuf), ibuf, request);
235             write(request->fd, obuf, strlen(obuf));
236         }
237         fclose(fp);
238     } else if (stat(value, &st) < 0) {
239         tcpd_warn("%s: %m", value);
240     }
241 }
242
243 /* group_option - switch group id */
244
245 /* ARGSUSED */
246
247 static void group_option(value, request)
248 char   *value;
249 struct request_info *request;
250 {
251     struct group *grp;
252     struct group *getgrnam();
253
254     if ((grp = getgrnam(value)) == 0)
255         tcpd_jump("unknown group: \"%s\"", value);
256     endgrent();
257
258     if (dry_run == 0 && setgid(grp->gr_gid))
259         tcpd_jump("setgid(%s): %m", value);
260 }
261
262 /* user_option - switch user id */
263
264 /* ARGSUSED */
265
266 static void user_option(value, request)
267 char   *value;
268 struct request_info *request;
269 {
270     struct passwd *pwd;
271     struct passwd *getpwnam();
272     char   *group;
273
274     if ((group = split_at(value, '.')) != 0)
275         group_option(group, request);
276     if ((pwd = getpwnam(value)) == 0)
277         tcpd_jump("unknown user: \"%s\"", value);
278     endpwent();
279
280     if (dry_run == 0 && setuid(pwd->pw_uid))
281         tcpd_jump("setuid(%s): %m", value);
282 }
283
284 /* umask_option - set file creation mask */
285
286 /* ARGSUSED */
287
288 static void umask_option(value, request)
289 char   *value;
290 struct request_info *request;
291 {
292     unsigned mask;
293     char    junk;
294
295     if (sscanf(value, "%o%c", &mask, &junk) != 1 || (mask & 0777) != mask)
296         tcpd_jump("bad umask value: \"%s\"", value);
297     (void) umask(mask);
298 }
299
300 /* spawn_option - spawn a shell command and wait */
301
302 /* ARGSUSED */
303
304 static void spawn_option(value, request)
305 char   *value;
306 struct request_info *request;
307 {
308     if (dry_run == 0)
309         shell_cmd(value);
310 }
311
312 /* linger_option - set the socket linger time (Marc Boucher <marc@cam.org>) */
313
314 /* ARGSUSED */
315
316 static void linger_option(value, request)
317 char   *value;
318 struct request_info *request;
319 {
320     struct linger linger;
321     char    junk;
322
323     if (sscanf(value, "%d%c", &linger.l_linger, &junk) != 1
324         || linger.l_linger < 0)
325         tcpd_jump("bad linger value: \"%s\"", value);
326     if (dry_run == 0) {
327         linger.l_onoff = (linger.l_linger != 0);
328         if (setsockopt(request->fd, SOL_SOCKET, SO_LINGER, (char *) &linger,
329                        sizeof(linger)) < 0)
330             tcpd_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
331     }
332 }
333
334 /* keepalive_option - set the socket keepalive option */
335
336 /* ARGSUSED */
337
338 static void keepalive_option(value, request)
339 char   *value;
340 struct request_info *request;
341 {
342     static int on = 1;
343
344     if (dry_run == 0 && setsockopt(request->fd, SOL_SOCKET, SO_KEEPALIVE,
345                                    (char *) &on, sizeof(on)) < 0)
346         tcpd_warn("setsockopt SO_KEEPALIVE: %m");
347 }
348
349 /* nice_option - set nice value */
350
351 /* ARGSUSED */
352
353 static void nice_option(value, request)
354 char   *value;
355 struct request_info *request;
356 {
357     int     niceval = 10;
358     char    junk;
359
360     if (value != 0 && sscanf(value, "%d%c", &niceval, &junk) != 1)
361         tcpd_jump("bad nice value: \"%s\"", value);
362     if (dry_run == 0 && nice(niceval) < 0)
363         tcpd_warn("nice(%d): %m", niceval);
364 }
365
366 /* twist_option - replace process by shell command */
367
368 static void twist_option(value, request)
369 char   *value;
370 struct request_info *request;
371 {
372     char   *error;
373
374     if (dry_run != 0) {
375         dry_run = 0;
376     } else {
377         if (resident > 0)
378             tcpd_jump("twist option in resident process");
379
380         syslog(deny_severity, "twist %s to %s", eval_client(request), value);
381
382         /* Before switching to the shell, set up stdin, stdout and stderr. */
383
384 #define maybe_dup2(from, to) ((from == to) ? to : (close(to), dup(from)))
385
386         if (maybe_dup2(request->fd, 0) != 0 ||
387             maybe_dup2(request->fd, 1) != 1 ||
388             maybe_dup2(request->fd, 2) != 2) {
389             error = "twist_option: dup: %m";
390         } else {
391             if (request->fd > 2)
392                 close(request->fd);
393             (void) execl("/bin/sh", "sh", "-c", value, (char *) 0);
394             error = "twist_option: /bin/sh: %m";
395         }
396
397         /* Something went wrong: we MUST terminate the process. */
398
399         tcpd_warn(error);
400         clean_exit(request);
401     }
402 }
403
404 /* rfc931_option - look up remote user name */
405
406 static void rfc931_option(value, request)
407 char   *value;
408 struct request_info *request;
409 {
410     int     timeout;
411     char    junk;
412
413     if (value != 0) {
414         if (sscanf(value, "%d%c", &timeout, &junk) != 1 || timeout <= 0)
415             tcpd_jump("bad rfc931 timeout: \"%s\"", value);
416         rfc931_timeout = timeout;
417     }
418     (void) eval_user(request);
419 }
420
421 /* setenv_option - set environment variable */
422
423 /* ARGSUSED */
424
425 static void setenv_option(value, request)
426 char   *value;
427 struct request_info *request;
428 {
429     char   *var_value;
430
431     if (*(var_value = value + strcspn(value, whitespace)))
432         *var_value++ = 0;
433     if (setenv(chop_string(value), chop_string(var_value), 1))
434         tcpd_jump("memory allocation failure");
435 }
436
437 /* severity_map - lookup facility or severity value */
438
439 static int severity_map(table, name)
440 CODE   *table;
441 char   *name;
442 {
443     CODE *t;
444
445     for (t = table; t->c_name; t++)
446         if (STR_EQ(t->c_name, name))
447             return (t->c_val);
448     tcpd_jump("bad syslog facility or severity: \"%s\"", name);
449     /* NOTREACHED */
450 }
451
452 /* severity_option - change logging severity for this event (Dave Mitchell) */
453
454 /* ARGSUSED */
455
456 static void severity_option(value, request)
457 char   *value;
458 struct request_info *request;
459 {
460     char   *level = split_at(value, '.');
461
462     allow_severity = deny_severity = level ?
463         severity_map(facilitynames, value) | severity_map(prioritynames, level)
464         : severity_map(prioritynames, value);
465 }
466
467 /* get_field - return pointer to next field in string */
468
469 static char *get_field(string)
470 char   *string;
471 {
472     static char *last = "";
473     char   *src;
474     char   *dst;
475     char   *ret;
476     int     ch;
477
478     /*
479      * This function returns pointers to successive fields within a given
480      * string. ":" is the field separator; warn if the rule ends in one. It
481      * replaces a "\:" sequence by ":", without treating the result of
482      * substitution as field terminator. A null argument means resume search
483      * where the previous call terminated. This function destroys its
484      * argument.
485      * 
486      * Work from explicit source or from memory. While processing \: we
487      * overwrite the input. This way we do not have to maintain buffers for
488      * copies of input fields.
489      */
490
491     src = dst = ret = (string ? string : last);
492     if (src[0] == 0)
493         return (0);
494
495     while ((ch = *src) != 0) {
496         if (ch == ':') {
497             if (*++src == 0)
498                 tcpd_warn("rule ends in \":\"");
499             break;
500         }
501         if (ch == '\\' && src[1] == ':')
502             src++;
503         *dst++ = *src++;
504     }
505     last = src;
506     *dst = 0;
507     return (ret);
508 }
509
510 /* chop_string - strip leading and trailing blanks from string */
511
512 static char *chop_string(string)
513 register char *string;
514 {
515     char   *start = 0;
516     char   *end;
517     char   *cp;
518
519     for (cp = string; *cp; cp++) {
520         if (!isspace(*cp)) {
521             if (start == 0)
522                 start = cp;
523             end = cp;
524         }
525     }
526     return (start ? (end[1] = 0, start) : cp);
527 }