Home | History | Annotate | Line # | Download | only in libwrap
options.c revision 1.1
      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
     32 static 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 
     61 int     dry_run = 0;			/* flag set in verification mode */
     62 extern jmp_buf tcpd_buf;		/* tcpd_jump() support */
     63 
     64 /* Options parser support. */
     65 
     66 static char whitespace_eq[] = "= \t\r\n";
     67 #define whitespace (whitespace_eq + 1)
     68 
     69 static char *get_field();		/* chew :-delimited field off string */
     70 static char *chop_string();		/* strip leading and trailing blanks */
     71 
     72 /* List of functions that implement the options. Add yours here. */
     73 
     74 static void user_option();		/* execute "user name.group" option */
     75 static void group_option();		/* execute "group name" option */
     76 static void umask_option();		/* execute "umask mask" option */
     77 static void linger_option();		/* execute "linger time" option */
     78 static void keepalive_option();		/* execute "keepalive" option */
     79 static void spawn_option();		/* execute "spawn command" option */
     80 static void twist_option();		/* execute "twist command" option */
     81 static void rfc931_option();		/* execute "rfc931" option */
     82 static void setenv_option();		/* execute "setenv name value" */
     83 static void nice_option();		/* execute "nice" option */
     84 static void severity_option();		/* execute "severity value" */
     85 static void allow_option();		/* execute "allow" option */
     86 static void deny_option();		/* execute "deny" option */
     87 static void banners_option();		/* execute "banners path" option */
     88 
     89 /* Structure of the options table. */
     90 
     91 struct 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 
    110 static 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 
    130 void    process_options(options, request)
    131 char   *options;
    132 struct 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 
    196 static void allow_option(value, request)
    197 char   *value;
    198 struct request_info *request;
    199 {
    200     longjmp(tcpd_buf, AC_PERMIT);
    201 }
    202 
    203 /* deny_option - deny access */
    204 
    205 /* ARGSUSED */
    206 
    207 static void deny_option(value, request)
    208 char   *value;
    209 struct request_info *request;
    210 {
    211     longjmp(tcpd_buf, AC_DENY);
    212 }
    213 
    214 /* banners_option - expand %<char>, terminate each line with CRLF */
    215 
    216 static void banners_option(value, request)
    217 char   *value;
    218 struct 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 
    248 static void group_option(value, request)
    249 char   *value;
    250 struct 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 
    267 static void user_option(value, request)
    268 char   *value;
    269 struct 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 
    289 static void umask_option(value, request)
    290 char   *value;
    291 struct 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 
    305 static void spawn_option(value, request)
    306 char   *value;
    307 struct 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 (at) cam.org>) */
    314 
    315 /* ARGSUSED */
    316 
    317 static void linger_option(value, request)
    318 char   *value;
    319 struct 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 
    339 static void keepalive_option(value, request)
    340 char   *value;
    341 struct 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 
    354 static void nice_option(value, request)
    355 char   *value;
    356 struct 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 
    369 static void twist_option(value, request)
    370 char   *value;
    371 struct 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 
    407 static void rfc931_option(value, request)
    408 char   *value;
    409 struct 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 
    426 static void setenv_option(value, request)
    427 char   *value;
    428 struct 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 
    443 struct syslog_names {
    444     char   *name;
    445     int     value;
    446 };
    447 
    448 static 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 
    503 static 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 
    533 static int severity_map(table, name)
    534 struct syslog_names *table;
    535 char   *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 
    550 static void severity_option(value, request)
    551 char   *value;
    552 struct 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 
    563 static char *get_field(string)
    564 char   *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 
    606 static char *chop_string(string)
    607 register 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 }
    622