source: trunk/third/tcp_wrappers/options.c @ 11717

Revision 11717, 15.1 KB checked in by danw, 26 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r11716, which included commits to RCS files with non-trunk default branches.
Line 
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#ifndef lint
32static char sccsid[] = "@(#) options.c 1.17 96/02/11 17:01:31";
33#endif
34
35/* System libraries. */
36
37#include <sys/types.h>
38#include <sys/param.h>
39#include <sys/socket.h>
40#include <sys/stat.h>
41#include <netinet/in.h>
42#include <netdb.h>
43#include <stdio.h>
44#include <syslog.h>
45#include <pwd.h>
46#include <grp.h>
47#include <ctype.h>
48#include <setjmp.h>
49#include <string.h>
50
51#ifndef MAXPATHNAMELEN
52#define MAXPATHNAMELEN  BUFSIZ
53#endif
54
55/* Local stuff. */
56
57#include "tcpd.h"
58
59/* Options runtime support. */
60
61int     dry_run = 0;                    /* flag set in verification mode */
62extern jmp_buf tcpd_buf;                /* tcpd_jump() support */
63
64/* Options parser support. */
65
66static char whitespace_eq[] = "= \t\r\n";
67#define whitespace (whitespace_eq + 1)
68
69static char *get_field();               /* chew :-delimited field off string */
70static char *chop_string();             /* strip leading and trailing blanks */
71
72/* List of functions that implement the options. Add yours here. */
73
74static void user_option();              /* execute "user name.group" option */
75static void group_option();             /* execute "group name" option */
76static void umask_option();             /* execute "umask mask" option */
77static void linger_option();            /* execute "linger time" option */
78static void keepalive_option();         /* execute "keepalive" option */
79static void spawn_option();             /* execute "spawn command" option */
80static void twist_option();             /* execute "twist command" option */
81static void rfc931_option();            /* execute "rfc931" option */
82static void setenv_option();            /* execute "setenv name value" */
83static void nice_option();              /* execute "nice" option */
84static void severity_option();          /* execute "severity value" */
85static void allow_option();             /* execute "allow" option */
86static void deny_option();              /* execute "deny" option */
87static void banners_option();           /* execute "banners path" option */
88
89/* Structure of the options table. */
90
91struct option {
92    char   *name;                       /* keyword name, case is ignored */
93    void  (*func) ();                   /* function that does the real work */
94    int     flags;                      /* see below... */
95};
96
97#define NEED_ARG        (1<<1)          /* option requires argument */
98#define USE_LAST        (1<<2)          /* option must be last */
99#define OPT_ARG         (1<<3)          /* option has optional argument */
100#define EXPAND_ARG      (1<<4)          /* do %x expansion on argument */
101
102#define need_arg(o)     ((o)->flags & NEED_ARG)
103#define opt_arg(o)      ((o)->flags & OPT_ARG)
104#define permit_arg(o)   ((o)->flags & (NEED_ARG | OPT_ARG))
105#define use_last(o)     ((o)->flags & USE_LAST)
106#define expand_arg(o)   ((o)->flags & EXPAND_ARG)
107
108/* List of known keywords. Add yours here. */
109
110static struct option option_table[] = {
111    "user", user_option, NEED_ARG,
112    "group", group_option, NEED_ARG,
113    "umask", umask_option, NEED_ARG,
114    "linger", linger_option, NEED_ARG,
115    "keepalive", keepalive_option, 0,
116    "spawn", spawn_option, NEED_ARG | EXPAND_ARG,
117    "twist", twist_option, NEED_ARG | EXPAND_ARG | USE_LAST,
118    "rfc931", rfc931_option, OPT_ARG,
119    "setenv", setenv_option, NEED_ARG | EXPAND_ARG,
120    "nice", nice_option, OPT_ARG,
121    "severity", severity_option, NEED_ARG,
122    "allow", allow_option, USE_LAST,
123    "deny", deny_option, USE_LAST,
124    "banners", banners_option, NEED_ARG,
125    0,
126};
127
128/* process_options - process access control options */
129
130void    process_options(options, request)
131char   *options;
132struct request_info *request;
133{
134    char   *key;
135    char   *value;
136    char   *curr_opt;
137    char   *next_opt;
138    struct option *op;
139    char    bf[BUFSIZ];
140
141    for (curr_opt = get_field(options); curr_opt; curr_opt = next_opt) {
142        next_opt = get_field((char *) 0);
143
144        /*
145         * Separate the option into name and value parts. For backwards
146         * compatibility we ignore exactly one '=' between name and value.
147         */
148        curr_opt = chop_string(curr_opt);
149        if (*(value = curr_opt + strcspn(curr_opt, whitespace_eq))) {
150            if (*value != '=') {
151                *value++ = 0;
152                value += strspn(value, whitespace);
153            }
154            if (*value == '=') {
155                *value++ = 0;
156                value += strspn(value, whitespace);
157            }
158        }
159        if (*value == 0)
160            value = 0;
161        key = curr_opt;
162
163        /*
164         * Disallow missing option names (and empty option fields).
165         */
166        if (*key == 0)
167            tcpd_jump("missing option name");
168
169        /*
170         * Lookup the option-specific info and do some common error checks.
171         * Delegate option-specific processing to the specific functions.
172         */
173
174        for (op = option_table; op->name && STR_NE(op->name, key); op++)
175             /* VOID */ ;
176        if (op->name == 0)
177            tcpd_jump("bad option name: \"%s\"", key);
178        if (!value && need_arg(op))
179            tcpd_jump("option \"%s\" requires value", key);
180        if (value && !permit_arg(op))
181            tcpd_jump("option \"%s\" requires no value", key);
182        if (next_opt && use_last(op))
183            tcpd_jump("option \"%s\" must be at end", key);
184        if (value && expand_arg(op))
185            value = chop_string(percent_x(bf, sizeof(bf), value, request));
186        if (hosts_access_verbose)
187            syslog(LOG_DEBUG, "option:   %s %s", key, value ? value : "");
188        (*(op->func)) (value, request);
189    }
190}
191
192/* allow_option - grant access */
193
194/* ARGSUSED */
195
196static void allow_option(value, request)
197char   *value;
198struct request_info *request;
199{
200    longjmp(tcpd_buf, AC_PERMIT);
201}
202
203/* deny_option - deny access */
204
205/* ARGSUSED */
206
207static void deny_option(value, request)
208char   *value;
209struct request_info *request;
210{
211    longjmp(tcpd_buf, AC_DENY);
212}
213
214/* banners_option - expand %<char>, terminate each line with CRLF */
215
216static void banners_option(value, request)
217char   *value;
218struct request_info *request;
219{
220    char    path[MAXPATHNAMELEN];
221    char    ibuf[BUFSIZ];
222    char    obuf[2 * BUFSIZ];
223    struct stat st;
224    int     ch;
225    FILE   *fp;
226
227    sprintf(path, "%s/%s", value, eval_daemon(request));
228    if ((fp = fopen(path, "r")) != 0) {
229        while ((ch = fgetc(fp)) == 0)
230            write(request->fd, "", 1);
231        ungetc(ch, fp);
232        while (fgets(ibuf, sizeof(ibuf) - 1, fp)) {
233            if (split_at(ibuf, '\n'))
234                strcat(ibuf, "\r\n");
235            percent_x(obuf, sizeof(obuf), ibuf, request);
236            write(request->fd, obuf, strlen(obuf));
237        }
238        fclose(fp);
239    } else if (stat(value, &st) < 0) {
240        tcpd_warn("%s: %m", value);
241    }
242}
243
244/* group_option - switch group id */
245
246/* ARGSUSED */
247
248static void group_option(value, request)
249char   *value;
250struct request_info *request;
251{
252    struct group *grp;
253    struct group *getgrnam();
254
255    if ((grp = getgrnam(value)) == 0)
256        tcpd_jump("unknown group: \"%s\"", value);
257    endgrent();
258
259    if (dry_run == 0 && setgid(grp->gr_gid))
260        tcpd_jump("setgid(%s): %m", value);
261}
262
263/* user_option - switch user id */
264
265/* ARGSUSED */
266
267static void user_option(value, request)
268char   *value;
269struct request_info *request;
270{
271    struct passwd *pwd;
272    struct passwd *getpwnam();
273    char   *group;
274
275    if ((group = split_at(value, '.')) != 0)
276        group_option(group, request);
277    if ((pwd = getpwnam(value)) == 0)
278        tcpd_jump("unknown user: \"%s\"", value);
279    endpwent();
280
281    if (dry_run == 0 && setuid(pwd->pw_uid))
282        tcpd_jump("setuid(%s): %m", value);
283}
284
285/* umask_option - set file creation mask */
286
287/* ARGSUSED */
288
289static void umask_option(value, request)
290char   *value;
291struct request_info *request;
292{
293    unsigned mask;
294    char    junk;
295
296    if (sscanf(value, "%o%c", &mask, &junk) != 1 || (mask & 0777) != mask)
297        tcpd_jump("bad umask value: \"%s\"", value);
298    (void) umask(mask);
299}
300
301/* spawn_option - spawn a shell command and wait */
302
303/* ARGSUSED */
304
305static void spawn_option(value, request)
306char   *value;
307struct request_info *request;
308{
309    if (dry_run == 0)
310        shell_cmd(value);
311}
312
313/* linger_option - set the socket linger time (Marc Boucher <marc@cam.org>) */
314
315/* ARGSUSED */
316
317static void linger_option(value, request)
318char   *value;
319struct request_info *request;
320{
321    struct linger linger;
322    char    junk;
323
324    if (sscanf(value, "%d%c", &linger.l_linger, &junk) != 1
325        || linger.l_linger < 0)
326        tcpd_jump("bad linger value: \"%s\"", value);
327    if (dry_run == 0) {
328        linger.l_onoff = (linger.l_linger != 0);
329        if (setsockopt(request->fd, SOL_SOCKET, SO_LINGER, (char *) &linger,
330                       sizeof(linger)) < 0)
331            tcpd_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
332    }
333}
334
335/* keepalive_option - set the socket keepalive option */
336
337/* ARGSUSED */
338
339static void keepalive_option(value, request)
340char   *value;
341struct request_info *request;
342{
343    static int on = 1;
344
345    if (dry_run == 0 && setsockopt(request->fd, SOL_SOCKET, SO_KEEPALIVE,
346                                   (char *) &on, sizeof(on)) < 0)
347        tcpd_warn("setsockopt SO_KEEPALIVE: %m");
348}
349
350/* nice_option - set nice value */
351
352/* ARGSUSED */
353
354static void nice_option(value, request)
355char   *value;
356struct request_info *request;
357{
358    int     niceval = 10;
359    char    junk;
360
361    if (value != 0 && sscanf(value, "%d%c", &niceval, &junk) != 1)
362        tcpd_jump("bad nice value: \"%s\"", value);
363    if (dry_run == 0 && nice(niceval) < 0)
364        tcpd_warn("nice(%d): %m", niceval);
365}
366
367/* twist_option - replace process by shell command */
368
369static void twist_option(value, request)
370char   *value;
371struct request_info *request;
372{
373    char   *error;
374
375    if (dry_run != 0) {
376        dry_run = 0;
377    } else {
378        if (resident > 0)
379            tcpd_jump("twist option in resident process");
380
381        syslog(deny_severity, "twist %s to %s", eval_client(request), value);
382
383        /* Before switching to the shell, set up stdin, stdout and stderr. */
384
385#define maybe_dup2(from, to) ((from == to) ? to : (close(to), dup(from)))
386
387        if (maybe_dup2(request->fd, 0) != 0 ||
388            maybe_dup2(request->fd, 1) != 1 ||
389            maybe_dup2(request->fd, 2) != 2) {
390            error = "twist_option: dup: %m";
391        } else {
392            if (request->fd > 2)
393                close(request->fd);
394            (void) execl("/bin/sh", "sh", "-c", value, (char *) 0);
395            error = "twist_option: /bin/sh: %m";
396        }
397
398        /* Something went wrong: we MUST terminate the process. */
399
400        tcpd_warn(error);
401        clean_exit(request);
402    }
403}
404
405/* rfc931_option - look up remote user name */
406
407static void rfc931_option(value, request)
408char   *value;
409struct request_info *request;
410{
411    int     timeout;
412    char    junk;
413
414    if (value != 0) {
415        if (sscanf(value, "%d%c", &timeout, &junk) != 1 || timeout <= 0)
416            tcpd_jump("bad rfc931 timeout: \"%s\"", value);
417        rfc931_timeout = timeout;
418    }
419    (void) eval_user(request);
420}
421
422/* setenv_option - set environment variable */
423
424/* ARGSUSED */
425
426static void setenv_option(value, request)
427char   *value;
428struct request_info *request;
429{
430    char   *var_value;
431
432    if (*(var_value = value + strcspn(value, whitespace)))
433        *var_value++ = 0;
434    if (setenv(chop_string(value), chop_string(var_value), 1))
435        tcpd_jump("memory allocation failure");
436}
437
438 /*
439  * The severity option goes last because it comes with a huge amount of ugly
440  * #ifdefs and tables.
441  */
442
443struct syslog_names {
444    char   *name;
445    int     value;
446};
447
448static struct syslog_names log_fac[] = {
449#ifdef LOG_KERN
450    "kern", LOG_KERN,
451#endif
452#ifdef LOG_USER
453    "user", LOG_USER,
454#endif
455#ifdef LOG_MAIL
456    "mail", LOG_MAIL,
457#endif
458#ifdef LOG_DAEMON
459    "daemon", LOG_DAEMON,
460#endif
461#ifdef LOG_AUTH
462    "auth", LOG_AUTH,
463#endif
464#ifdef LOG_LPR
465    "lpr", LOG_LPR,
466#endif
467#ifdef LOG_NEWS
468    "news", LOG_NEWS,
469#endif
470#ifdef LOG_UUCP
471    "uucp", LOG_UUCP,
472#endif
473#ifdef LOG_CRON
474    "cron", LOG_CRON,
475#endif
476#ifdef LOG_LOCAL0
477    "local0", LOG_LOCAL0,
478#endif
479#ifdef LOG_LOCAL1
480    "local1", LOG_LOCAL1,
481#endif
482#ifdef LOG_LOCAL2
483    "local2", LOG_LOCAL2,
484#endif
485#ifdef LOG_LOCAL3
486    "local3", LOG_LOCAL3,
487#endif
488#ifdef LOG_LOCAL4
489    "local4", LOG_LOCAL4,
490#endif
491#ifdef LOG_LOCAL5
492    "local5", LOG_LOCAL5,
493#endif
494#ifdef LOG_LOCAL6
495    "local6", LOG_LOCAL6,
496#endif
497#ifdef LOG_LOCAL7
498    "local7", LOG_LOCAL7,
499#endif
500    0,
501};
502
503static struct syslog_names log_sev[] = {
504#ifdef LOG_EMERG
505    "emerg", LOG_EMERG,
506#endif
507#ifdef LOG_ALERT
508    "alert", LOG_ALERT,
509#endif
510#ifdef LOG_CRIT
511    "crit", LOG_CRIT,
512#endif
513#ifdef LOG_ERR
514    "err", LOG_ERR,
515#endif
516#ifdef LOG_WARNING
517    "warning", LOG_WARNING,
518#endif
519#ifdef LOG_NOTICE
520    "notice", LOG_NOTICE,
521#endif
522#ifdef LOG_INFO
523    "info", LOG_INFO,
524#endif
525#ifdef LOG_DEBUG
526    "debug", LOG_DEBUG,
527#endif
528    0,
529};
530
531/* severity_map - lookup facility or severity value */
532
533static int severity_map(table, name)
534struct syslog_names *table;
535char   *name;
536{
537    struct syslog_names *t;
538
539    for (t = table; t->name; t++)
540        if (STR_EQ(t->name, name))
541            return (t->value);
542    tcpd_jump("bad syslog facility or severity: \"%s\"", name);
543    /* NOTREACHED */
544}
545
546/* severity_option - change logging severity for this event (Dave Mitchell) */
547
548/* ARGSUSED */
549
550static void severity_option(value, request)
551char   *value;
552struct request_info *request;
553{
554    char   *level = split_at(value, '.');
555
556    allow_severity = deny_severity = level ?
557        severity_map(log_fac, value) | severity_map(log_sev, level) :
558        severity_map(log_sev, value);
559}
560
561/* get_field - return pointer to next field in string */
562
563static char *get_field(string)
564char   *string;
565{
566    static char *last = "";
567    char   *src;
568    char   *dst;
569    char   *ret;
570    int     ch;
571
572    /*
573     * This function returns pointers to successive fields within a given
574     * string. ":" is the field separator; warn if the rule ends in one. It
575     * replaces a "\:" sequence by ":", without treating the result of
576     * substitution as field terminator. A null argument means resume search
577     * where the previous call terminated. This function destroys its
578     * argument.
579     *
580     * Work from explicit source or from memory. While processing \: we
581     * overwrite the input. This way we do not have to maintain buffers for
582     * copies of input fields.
583     */
584
585    src = dst = ret = (string ? string : last);
586    if (src[0] == 0)
587        return (0);
588
589    while (ch = *src) {
590        if (ch == ':') {
591            if (*++src == 0)
592                tcpd_warn("rule ends in \":\"");
593            break;
594        }
595        if (ch == '\\' && src[1] == ':')
596            src++;
597        *dst++ = *src++;
598    }
599    last = src;
600    *dst = 0;
601    return (ret);
602}
603
604/* chop_string - strip leading and trailing blanks from string */
605
606static char *chop_string(string)
607register char *string;
608{
609    char   *start = 0;
610    char   *end;
611    char   *cp;
612
613    for (cp = string; *cp; cp++) {
614        if (!isspace(*cp)) {
615            if (start == 0)
616                start = cp;
617            end = cp;
618        }
619    }
620    return (start ? (end[1] = 0, start) : cp);
621}
Note: See TracBrowser for help on using the repository browser.