144743Smarkm /*
244743Smarkm  * General skeleton for adding options to the access control language. The
344743Smarkm  * features offered by this module are documented in the hosts_options(5)
444743Smarkm  * manual page (source file: hosts_options.5, "nroff -man" format).
544743Smarkm  *
644743Smarkm  * Notes and warnings for those who want to add features:
744743Smarkm  *
844743Smarkm  * In case of errors, abort options processing and deny access. There are too
944743Smarkm  * many irreversible side effects to make error recovery feasible. For
1044743Smarkm  * example, it makes no sense to continue after we have already changed the
1144743Smarkm  * userid.
1244743Smarkm  *
1344743Smarkm  * In case of errors, do not terminate the process: the routines might be
1444743Smarkm  * called from a long-running daemon that should run forever. Instead, call
1544743Smarkm  * tcpd_jump() which does a non-local goto back into the hosts_access()
1644743Smarkm  * routine.
1744743Smarkm  *
1844743Smarkm  * In case of severe errors, use clean_exit() instead of directly calling
1944743Smarkm  * exit(), or the inetd may loop on an UDP request.
2044743Smarkm  *
2144743Smarkm  * In verification mode (for example, with the "tcpdmatch" command) the
2244743Smarkm  * "dry_run" flag is set. In this mode, an option function should just "say"
2344743Smarkm  * what it is going to do instead of really doing it.
2444743Smarkm  *
2544743Smarkm  * Some option functions do not return (for example, the twist option passes
2644743Smarkm  * control to another program). In verification mode (dry_run flag is set)
2744743Smarkm  * such options should clear the "dry_run" flag to inform the caller of this
2844743Smarkm  * course of action.
2971045Sdwmalone  *
3071045Sdwmalone  * $FreeBSD$
3144743Smarkm  */
3244743Smarkm
3344743Smarkm#ifndef lint
3444743Smarkmstatic char sccsid[] = "@(#) options.c 1.17 96/02/11 17:01:31";
3544743Smarkm#endif
3644743Smarkm
3744743Smarkm/* System libraries. */
3844743Smarkm
3944743Smarkm#include <sys/types.h>
4044743Smarkm#include <sys/param.h>
4144743Smarkm#include <sys/socket.h>
4244743Smarkm#include <sys/stat.h>
4344743Smarkm#include <netinet/in.h>
4444743Smarkm#include <netdb.h>
4544743Smarkm#include <stdio.h>
4671045Sdwmalone#define SYSLOG_NAMES
4744743Smarkm#include <syslog.h>
4844743Smarkm#include <pwd.h>
4944743Smarkm#include <grp.h>
5044743Smarkm#include <ctype.h>
5144743Smarkm#include <setjmp.h>
5244743Smarkm#include <string.h>
5344743Smarkm
5444743Smarkm#ifndef MAXPATHNAMELEN
5544743Smarkm#define MAXPATHNAMELEN  BUFSIZ
5644743Smarkm#endif
5744743Smarkm
5844743Smarkm/* Local stuff. */
5944743Smarkm
6044743Smarkm#include "tcpd.h"
6144743Smarkm
6244743Smarkm/* Options runtime support. */
6344743Smarkm
6444743Smarkmint     dry_run = 0;			/* flag set in verification mode */
6544743Smarkmextern jmp_buf tcpd_buf;		/* tcpd_jump() support */
6644743Smarkm
6744743Smarkm/* Options parser support. */
6844743Smarkm
6944743Smarkmstatic char whitespace_eq[] = "= \t\r\n";
7044743Smarkm#define whitespace (whitespace_eq + 1)
7144743Smarkm
7244743Smarkmstatic char *get_field();		/* chew :-delimited field off string */
7344743Smarkmstatic char *chop_string();		/* strip leading and trailing blanks */
7444743Smarkm
7544743Smarkm/* List of functions that implement the options. Add yours here. */
7644743Smarkm
7744743Smarkmstatic void user_option();		/* execute "user name.group" option */
7844743Smarkmstatic void group_option();		/* execute "group name" option */
7944743Smarkmstatic void umask_option();		/* execute "umask mask" option */
8044743Smarkmstatic void linger_option();		/* execute "linger time" option */
8144743Smarkmstatic void keepalive_option();		/* execute "keepalive" option */
8244743Smarkmstatic void spawn_option();		/* execute "spawn command" option */
8344743Smarkmstatic void twist_option();		/* execute "twist command" option */
8444743Smarkmstatic void rfc931_option();		/* execute "rfc931" option */
8544743Smarkmstatic void setenv_option();		/* execute "setenv name value" */
8644743Smarkmstatic void nice_option();		/* execute "nice" option */
8744743Smarkmstatic void severity_option();		/* execute "severity value" */
8844743Smarkmstatic void allow_option();		/* execute "allow" option */
8944743Smarkmstatic void deny_option();		/* execute "deny" option */
9044743Smarkmstatic void banners_option();		/* execute "banners path" option */
9144743Smarkm
9244743Smarkm/* Structure of the options table. */
9344743Smarkm
9444743Smarkmstruct option {
9544743Smarkm    char   *name;			/* keyword name, case is ignored */
9644743Smarkm    void  (*func) ();			/* function that does the real work */
9744743Smarkm    int     flags;			/* see below... */
9844743Smarkm};
9944743Smarkm
10044743Smarkm#define NEED_ARG	(1<<1)		/* option requires argument */
10144743Smarkm#define USE_LAST	(1<<2)		/* option must be last */
10244743Smarkm#define OPT_ARG		(1<<3)		/* option has optional argument */
10344743Smarkm#define EXPAND_ARG	(1<<4)		/* do %x expansion on argument */
10444743Smarkm
10544743Smarkm#define need_arg(o)	((o)->flags & NEED_ARG)
10644743Smarkm#define opt_arg(o)	((o)->flags & OPT_ARG)
10744743Smarkm#define permit_arg(o)	((o)->flags & (NEED_ARG | OPT_ARG))
10844743Smarkm#define use_last(o)	((o)->flags & USE_LAST)
10944743Smarkm#define expand_arg(o)	((o)->flags & EXPAND_ARG)
11044743Smarkm
11144743Smarkm/* List of known keywords. Add yours here. */
11244743Smarkm
11344743Smarkmstatic struct option option_table[] = {
11444743Smarkm    "user", user_option, NEED_ARG,
11544743Smarkm    "group", group_option, NEED_ARG,
11644743Smarkm    "umask", umask_option, NEED_ARG,
11744743Smarkm    "linger", linger_option, NEED_ARG,
11844743Smarkm    "keepalive", keepalive_option, 0,
11944743Smarkm    "spawn", spawn_option, NEED_ARG | EXPAND_ARG,
12044743Smarkm    "twist", twist_option, NEED_ARG | EXPAND_ARG | USE_LAST,
12144743Smarkm    "rfc931", rfc931_option, OPT_ARG,
12244743Smarkm    "setenv", setenv_option, NEED_ARG | EXPAND_ARG,
12344743Smarkm    "nice", nice_option, OPT_ARG,
12444743Smarkm    "severity", severity_option, NEED_ARG,
12544743Smarkm    "allow", allow_option, USE_LAST,
12644743Smarkm    "deny", deny_option, USE_LAST,
12744743Smarkm    "banners", banners_option, NEED_ARG,
12844743Smarkm    0,
12944743Smarkm};
13044743Smarkm
13144743Smarkm/* process_options - process access control options */
13244743Smarkm
13344743Smarkmvoid    process_options(options, request)
13444743Smarkmchar   *options;
13544743Smarkmstruct request_info *request;
13644743Smarkm{
13744743Smarkm    char   *key;
13844743Smarkm    char   *value;
13944743Smarkm    char   *curr_opt;
14044743Smarkm    char   *next_opt;
14144743Smarkm    struct option *op;
14244743Smarkm    char    bf[BUFSIZ];
14344743Smarkm
14444743Smarkm    for (curr_opt = get_field(options); curr_opt; curr_opt = next_opt) {
14544743Smarkm	next_opt = get_field((char *) 0);
14644743Smarkm
14744743Smarkm	/*
14844743Smarkm	 * Separate the option into name and value parts. For backwards
14944743Smarkm	 * compatibility we ignore exactly one '=' between name and value.
15044743Smarkm	 */
15144743Smarkm	curr_opt = chop_string(curr_opt);
15244743Smarkm	if (*(value = curr_opt + strcspn(curr_opt, whitespace_eq))) {
15344743Smarkm	    if (*value != '=') {
15444743Smarkm		*value++ = 0;
15544743Smarkm		value += strspn(value, whitespace);
15644743Smarkm	    }
15744743Smarkm	    if (*value == '=') {
15844743Smarkm		*value++ = 0;
15944743Smarkm		value += strspn(value, whitespace);
16044743Smarkm	    }
16144743Smarkm	}
16244743Smarkm	if (*value == 0)
16344743Smarkm	    value = 0;
16444743Smarkm	key = curr_opt;
16544743Smarkm
16644743Smarkm	/*
16744743Smarkm	 * Disallow missing option names (and empty option fields).
16844743Smarkm	 */
16944743Smarkm	if (*key == 0)
17044743Smarkm	    tcpd_jump("missing option name");
17144743Smarkm
17244743Smarkm	/*
17344743Smarkm	 * Lookup the option-specific info and do some common error checks.
17444743Smarkm	 * Delegate option-specific processing to the specific functions.
17544743Smarkm	 */
17644743Smarkm
17744743Smarkm	for (op = option_table; op->name && STR_NE(op->name, key); op++)
17844743Smarkm	     /* VOID */ ;
17944743Smarkm	if (op->name == 0)
18044743Smarkm	    tcpd_jump("bad option name: \"%s\"", key);
18144743Smarkm	if (!value && need_arg(op))
18244743Smarkm	    tcpd_jump("option \"%s\" requires value", key);
18344743Smarkm	if (value && !permit_arg(op))
18444743Smarkm	    tcpd_jump("option \"%s\" requires no value", key);
18544743Smarkm	if (next_opt && use_last(op))
18644743Smarkm	    tcpd_jump("option \"%s\" must be at end", key);
18744743Smarkm	if (value && expand_arg(op))
18844743Smarkm	    value = chop_string(percent_x(bf, sizeof(bf), value, request));
18944743Smarkm	if (hosts_access_verbose)
19044743Smarkm	    syslog(LOG_DEBUG, "option:   %s %s", key, value ? value : "");
19144743Smarkm	(*(op->func)) (value, request);
19244743Smarkm    }
19344743Smarkm}
19444743Smarkm
19544743Smarkm/* allow_option - grant access */
19644743Smarkm
19744743Smarkm/* ARGSUSED */
19844743Smarkm
19944743Smarkmstatic void allow_option(value, request)
20044743Smarkmchar   *value;
20144743Smarkmstruct request_info *request;
20244743Smarkm{
20344743Smarkm    longjmp(tcpd_buf, AC_PERMIT);
20444743Smarkm}
20544743Smarkm
20644743Smarkm/* deny_option - deny access */
20744743Smarkm
20844743Smarkm/* ARGSUSED */
20944743Smarkm
21044743Smarkmstatic void deny_option(value, request)
21144743Smarkmchar   *value;
21244743Smarkmstruct request_info *request;
21344743Smarkm{
21444743Smarkm    longjmp(tcpd_buf, AC_DENY);
21544743Smarkm}
21644743Smarkm
21744743Smarkm/* banners_option - expand %<char>, terminate each line with CRLF */
21844743Smarkm
21944743Smarkmstatic void banners_option(value, request)
22044743Smarkmchar   *value;
22144743Smarkmstruct request_info *request;
22244743Smarkm{
22344743Smarkm    char    path[MAXPATHNAMELEN];
22444743Smarkm    char    ibuf[BUFSIZ];
22544743Smarkm    char    obuf[2 * BUFSIZ];
22644743Smarkm    struct stat st;
22744743Smarkm    int     ch;
22844743Smarkm    FILE   *fp;
22944743Smarkm
23044743Smarkm    sprintf(path, "%s/%s", value, eval_daemon(request));
23144743Smarkm    if ((fp = fopen(path, "r")) != 0) {
23244743Smarkm	while ((ch = fgetc(fp)) == 0)
23344743Smarkm	    write(request->fd, "", 1);
23444743Smarkm	ungetc(ch, fp);
23544743Smarkm	while (fgets(ibuf, sizeof(ibuf) - 1, fp)) {
23644743Smarkm	    if (split_at(ibuf, '\n'))
23744743Smarkm		strcat(ibuf, "\r\n");
23844743Smarkm	    percent_x(obuf, sizeof(obuf), ibuf, request);
23944743Smarkm	    write(request->fd, obuf, strlen(obuf));
24044743Smarkm	}
24144743Smarkm	fclose(fp);
24244743Smarkm    } else if (stat(value, &st) < 0) {
24344743Smarkm	tcpd_warn("%s: %m", value);
24444743Smarkm    }
24544743Smarkm}
24644743Smarkm
24744743Smarkm/* group_option - switch group id */
24844743Smarkm
24944743Smarkm/* ARGSUSED */
25044743Smarkm
25144743Smarkmstatic void group_option(value, request)
25244743Smarkmchar   *value;
25344743Smarkmstruct request_info *request;
25444743Smarkm{
25544743Smarkm    struct group *grp;
25644743Smarkm    struct group *getgrnam();
25744743Smarkm
25844743Smarkm    if ((grp = getgrnam(value)) == 0)
25944743Smarkm	tcpd_jump("unknown group: \"%s\"", value);
26044743Smarkm    endgrent();
26144743Smarkm
26244743Smarkm    if (dry_run == 0 && setgid(grp->gr_gid))
26344743Smarkm	tcpd_jump("setgid(%s): %m", value);
26444743Smarkm}
26544743Smarkm
26644743Smarkm/* user_option - switch user id */
26744743Smarkm
26844743Smarkm/* ARGSUSED */
26944743Smarkm
27044743Smarkmstatic void user_option(value, request)
27144743Smarkmchar   *value;
27244743Smarkmstruct request_info *request;
27344743Smarkm{
27444743Smarkm    struct passwd *pwd;
27544743Smarkm    struct passwd *getpwnam();
27644743Smarkm    char   *group;
27744743Smarkm
27844743Smarkm    if ((group = split_at(value, '.')) != 0)
27944743Smarkm	group_option(group, request);
28044743Smarkm    if ((pwd = getpwnam(value)) == 0)
28144743Smarkm	tcpd_jump("unknown user: \"%s\"", value);
28244743Smarkm    endpwent();
28344743Smarkm
28444743Smarkm    if (dry_run == 0 && setuid(pwd->pw_uid))
28544743Smarkm	tcpd_jump("setuid(%s): %m", value);
28644743Smarkm}
28744743Smarkm
28844743Smarkm/* umask_option - set file creation mask */
28944743Smarkm
29044743Smarkm/* ARGSUSED */
29144743Smarkm
29244743Smarkmstatic void umask_option(value, request)
29344743Smarkmchar   *value;
29444743Smarkmstruct request_info *request;
29544743Smarkm{
29644743Smarkm    unsigned mask;
29744743Smarkm    char    junk;
29844743Smarkm
29944743Smarkm    if (sscanf(value, "%o%c", &mask, &junk) != 1 || (mask & 0777) != mask)
30044743Smarkm	tcpd_jump("bad umask value: \"%s\"", value);
30144743Smarkm    (void) umask(mask);
30244743Smarkm}
30344743Smarkm
30444743Smarkm/* spawn_option - spawn a shell command and wait */
30544743Smarkm
30644743Smarkm/* ARGSUSED */
30744743Smarkm
30844743Smarkmstatic void spawn_option(value, request)
30944743Smarkmchar   *value;
31044743Smarkmstruct request_info *request;
31144743Smarkm{
31244743Smarkm    if (dry_run == 0)
31344743Smarkm	shell_cmd(value);
31444743Smarkm}
31544743Smarkm
31644743Smarkm/* linger_option - set the socket linger time (Marc Boucher <marc@cam.org>) */
31744743Smarkm
31844743Smarkm/* ARGSUSED */
31944743Smarkm
32044743Smarkmstatic void linger_option(value, request)
32144743Smarkmchar   *value;
32244743Smarkmstruct request_info *request;
32344743Smarkm{
32444743Smarkm    struct linger linger;
32544743Smarkm    char    junk;
32644743Smarkm
32744743Smarkm    if (sscanf(value, "%d%c", &linger.l_linger, &junk) != 1
32844743Smarkm	|| linger.l_linger < 0)
32944743Smarkm	tcpd_jump("bad linger value: \"%s\"", value);
33044743Smarkm    if (dry_run == 0) {
33144743Smarkm	linger.l_onoff = (linger.l_linger != 0);
33244743Smarkm	if (setsockopt(request->fd, SOL_SOCKET, SO_LINGER, (char *) &linger,
33344743Smarkm		       sizeof(linger)) < 0)
33444743Smarkm	    tcpd_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
33544743Smarkm    }
33644743Smarkm}
33744743Smarkm
33844743Smarkm/* keepalive_option - set the socket keepalive option */
33944743Smarkm
34044743Smarkm/* ARGSUSED */
34144743Smarkm
34244743Smarkmstatic void keepalive_option(value, request)
34344743Smarkmchar   *value;
34444743Smarkmstruct request_info *request;
34544743Smarkm{
34644743Smarkm    static int on = 1;
34744743Smarkm
34844743Smarkm    if (dry_run == 0 && setsockopt(request->fd, SOL_SOCKET, SO_KEEPALIVE,
34944743Smarkm				   (char *) &on, sizeof(on)) < 0)
35044743Smarkm	tcpd_warn("setsockopt SO_KEEPALIVE: %m");
35144743Smarkm}
35244743Smarkm
35344743Smarkm/* nice_option - set nice value */
35444743Smarkm
35544743Smarkm/* ARGSUSED */
35644743Smarkm
35744743Smarkmstatic void nice_option(value, request)
35844743Smarkmchar   *value;
35944743Smarkmstruct request_info *request;
36044743Smarkm{
36144743Smarkm    int     niceval = 10;
36244743Smarkm    char    junk;
36344743Smarkm
36444743Smarkm    if (value != 0 && sscanf(value, "%d%c", &niceval, &junk) != 1)
36544743Smarkm	tcpd_jump("bad nice value: \"%s\"", value);
36644743Smarkm    if (dry_run == 0 && nice(niceval) < 0)
36744743Smarkm	tcpd_warn("nice(%d): %m", niceval);
36844743Smarkm}
36944743Smarkm
37044743Smarkm/* twist_option - replace process by shell command */
37144743Smarkm
37244743Smarkmstatic void twist_option(value, request)
37344743Smarkmchar   *value;
37444743Smarkmstruct request_info *request;
37544743Smarkm{
37644743Smarkm    char   *error;
37744743Smarkm
37844743Smarkm    if (dry_run != 0) {
37944743Smarkm	dry_run = 0;
38044743Smarkm    } else {
38144743Smarkm	if (resident > 0)
38244743Smarkm	    tcpd_jump("twist option in resident process");
38344743Smarkm
38444743Smarkm	syslog(deny_severity, "twist %s to %s", eval_client(request), value);
38544743Smarkm
38644743Smarkm	/* Before switching to the shell, set up stdin, stdout and stderr. */
38744743Smarkm
38844743Smarkm#define maybe_dup2(from, to) ((from == to) ? to : (close(to), dup(from)))
38944743Smarkm
39044743Smarkm	if (maybe_dup2(request->fd, 0) != 0 ||
39144743Smarkm	    maybe_dup2(request->fd, 1) != 1 ||
39244743Smarkm	    maybe_dup2(request->fd, 2) != 2) {
39344743Smarkm	    error = "twist_option: dup: %m";
39444743Smarkm	} else {
39544743Smarkm	    if (request->fd > 2)
39644743Smarkm		close(request->fd);
39744743Smarkm	    (void) execl("/bin/sh", "sh", "-c", value, (char *) 0);
39844743Smarkm	    error = "twist_option: /bin/sh: %m";
39944743Smarkm	}
40044743Smarkm
40144743Smarkm	/* Something went wrong: we MUST terminate the process. */
40244743Smarkm
40344743Smarkm	tcpd_warn(error);
40444743Smarkm	clean_exit(request);
40544743Smarkm    }
40644743Smarkm}
40744743Smarkm
40844743Smarkm/* rfc931_option - look up remote user name */
40944743Smarkm
41044743Smarkmstatic void rfc931_option(value, request)
41144743Smarkmchar   *value;
41244743Smarkmstruct request_info *request;
41344743Smarkm{
41444743Smarkm    int     timeout;
41544743Smarkm    char    junk;
41644743Smarkm
41744743Smarkm    if (value != 0) {
41844743Smarkm	if (sscanf(value, "%d%c", &timeout, &junk) != 1 || timeout <= 0)
41944743Smarkm	    tcpd_jump("bad rfc931 timeout: \"%s\"", value);
42044743Smarkm	rfc931_timeout = timeout;
42144743Smarkm    }
42244743Smarkm    (void) eval_user(request);
42344743Smarkm}
42444743Smarkm
42544743Smarkm/* setenv_option - set environment variable */
42644743Smarkm
42744743Smarkm/* ARGSUSED */
42844743Smarkm
42944743Smarkmstatic void setenv_option(value, request)
43044743Smarkmchar   *value;
43144743Smarkmstruct request_info *request;
43244743Smarkm{
43344743Smarkm    char   *var_value;
43444743Smarkm
43544743Smarkm    if (*(var_value = value + strcspn(value, whitespace)))
43644743Smarkm	*var_value++ = 0;
43744743Smarkm    if (setenv(chop_string(value), chop_string(var_value), 1))
43844743Smarkm	tcpd_jump("memory allocation failure");
43944743Smarkm}
44044743Smarkm
44144743Smarkm/* severity_map - lookup facility or severity value */
44244743Smarkm
44344743Smarkmstatic int severity_map(table, name)
44471045SdwmaloneCODE   *table;
44544743Smarkmchar   *name;
44644743Smarkm{
44771045Sdwmalone    CODE *t;
44844743Smarkm
44971045Sdwmalone    for (t = table; t->c_name; t++)
45071045Sdwmalone	if (STR_EQ(t->c_name, name))
45171045Sdwmalone	    return (t->c_val);
45244743Smarkm    tcpd_jump("bad syslog facility or severity: \"%s\"", name);
45344743Smarkm    /* NOTREACHED */
45444743Smarkm}
45544743Smarkm
45644743Smarkm/* severity_option - change logging severity for this event (Dave Mitchell) */
45744743Smarkm
45844743Smarkm/* ARGSUSED */
45944743Smarkm
46044743Smarkmstatic void severity_option(value, request)
46144743Smarkmchar   *value;
46244743Smarkmstruct request_info *request;
46344743Smarkm{
46444743Smarkm    char   *level = split_at(value, '.');
46544743Smarkm
46644743Smarkm    allow_severity = deny_severity = level ?
46771045Sdwmalone	severity_map(facilitynames, value) | severity_map(prioritynames, level)
46871045Sdwmalone	: severity_map(prioritynames, value);
46944743Smarkm}
47044743Smarkm
47144743Smarkm/* get_field - return pointer to next field in string */
47244743Smarkm
47344743Smarkmstatic char *get_field(string)
47444743Smarkmchar   *string;
47544743Smarkm{
47644743Smarkm    static char *last = "";
47744743Smarkm    char   *src;
47844743Smarkm    char   *dst;
47944743Smarkm    char   *ret;
48044743Smarkm    int     ch;
48144743Smarkm
48244743Smarkm    /*
48344743Smarkm     * This function returns pointers to successive fields within a given
48444743Smarkm     * string. ":" is the field separator; warn if the rule ends in one. It
48544743Smarkm     * replaces a "\:" sequence by ":", without treating the result of
48644743Smarkm     * substitution as field terminator. A null argument means resume search
48744743Smarkm     * where the previous call terminated. This function destroys its
48844743Smarkm     * argument.
48944743Smarkm     *
49044743Smarkm     * Work from explicit source or from memory. While processing \: we
49144743Smarkm     * overwrite the input. This way we do not have to maintain buffers for
49244743Smarkm     * copies of input fields.
49344743Smarkm     */
49444743Smarkm
49544743Smarkm    src = dst = ret = (string ? string : last);
49644743Smarkm    if (src[0] == 0)
49744743Smarkm	return (0);
49844743Smarkm
49944743Smarkm    while (ch = *src) {
50044743Smarkm	if (ch == ':') {
50144743Smarkm	    if (*++src == 0)
50244743Smarkm		tcpd_warn("rule ends in \":\"");
50344743Smarkm	    break;
50444743Smarkm	}
50544743Smarkm	if (ch == '\\' && src[1] == ':')
50644743Smarkm	    src++;
50744743Smarkm	*dst++ = *src++;
50844743Smarkm    }
50944743Smarkm    last = src;
51044743Smarkm    *dst = 0;
51144743Smarkm    return (ret);
51244743Smarkm}
51344743Smarkm
51444743Smarkm/* chop_string - strip leading and trailing blanks from string */
51544743Smarkm
51644743Smarkmstatic char *chop_string(string)
51744743Smarkmregister char *string;
51844743Smarkm{
51944743Smarkm    char   *start = 0;
52044743Smarkm    char   *end;
52144743Smarkm    char   *cp;
52244743Smarkm
52344743Smarkm    for (cp = string; *cp; cp++) {
52444743Smarkm	if (!isspace(*cp)) {
52544743Smarkm	    if (start == 0)
52644743Smarkm		start = cp;
52744743Smarkm	    end = cp;
52844743Smarkm	}
52944743Smarkm    }
53044743Smarkm    return (start ? (end[1] = 0, start) : cp);
53144743Smarkm}
532