static const char rcsid[] = "$Id: options.c,v 1.132 2020/08/03 17:13:04 will Exp $";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process a global xxx=yyy option */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "super.h"
#include "options.h"

/* KEEP SORTED IN INCREASING ORDER! (If you don't, we do sort the list
 * later, but you may as well do your bit to decrease entropy :-)
 */

Option opts[] = {
	{ "DIE",		LOCAL,		option_die },	/* OBSOLETE */
	{ "addgroups",		LIST|GLOBAL|LOCAL,	option_groups },
	{ "arg",		GLOBAL|LOCAL,	option_arg },
	{ "argv0",		LOCAL,		option_argv0 },
	{ "auth",		GLOBAL|LOCAL,	option_auth },
	{ "authprompt",		GLOBAL|LOCAL,	option_authprompt },
	{ "authtype",		GLOBAL|LOCAL,	option_authtype },
	{ "authuser",		GLOBAL|LOCAL,	option_authuser },
	{ "cd",			GLOBAL|LOCAL,	option_cd },
	{ "checkvar",		LIST|LOCAL,	option_checkvar },
	{ "die",		LOCAL,		option_die },
	{ "egid",		LOCAL,		option_egid },
	{ "env",		LIST|GLOBAL|LOCAL,	option_env },
	{ "euid",		LOCAL,		option_euid },
	{ "fd",			LIST|LOCAL,	option_fd },
	{ "gethostbyname",	GLOBAL,		option_gethostbyname },
	{ "gid",		LOCAL,		option_gid },
	{ "group_slash",	GLOBAL,		option_group_slash },
	{ "groups",		LIST|GLOBAL|LOCAL,	option_groups },
	{ "info",		LOCAL,		option_info },
	{ "lang",		GLOBAL,		option_lang },
	{ "logfile",		GLOBAL,		option_logfile },
	{ "loguid",		GLOBAL,		option_loguid },
	{ "mail",		GLOBAL|LOCAL,	option_mail },
	{ "mailany",		GLOBAL|LOCAL,	option_mailany },
	{ "maxenvlen",		GLOBAL|LOCAL,	option_maxenvlen },
	{ "maxlen",		GLOBAL|LOCAL,	option_maxlen },
	{ "nargs",		GLOBAL|LOCAL,	option_nargs },
	{ "nice",		GLOBAL|LOCAL,	option_nice },
	{ "owner",		GLOBAL|LOCAL,	option_owner },
	{ "password",		GLOBAL|LOCAL,	option_password },
	{ "patterns",		GLOBAL,		option_patterns },
	{ "print",		LOCAL,		option_print },
	{ "pwprompt",		GLOBAL|LOCAL,	option_authprompt },
	{ "relative_path",	GLOBAL,		option_relative_path },
	{ "renewtime",		GLOBAL,		option_renewtime },
	{ "rlog_host",		GLOBAL,		option_rlog_host },
	{ "setenv",		GLOBAL|LOCAL,	option_setenv },
	{ "syslog",		GLOBAL,		option_syslog },
	{ "syslog_success",	GLOBAL,		option_syslog_success },
	{ "syslog_error",	GLOBAL,		option_syslog_error },
	{ "timeout",		GLOBAL|LOCAL,	option_timeout },
	{ "timestampbyhost",	GLOBAL,		option_timestampbyhost },
	{ "timestampuid",	GLOBAL,		option_timestampuid },
	{ "u+g",		LOCAL,		option_u_g },
	{ "uid",		LOCAL,		option_uid },
	{ "umask",		GLOBAL|LOCAL,	option_umask },
	{ "",			0,		NULL },
};

/* OK, so it's a really stupid hack, but it does let me have all option_xxx()
 * functions take the same argument lists; those that are LIST types, and
 * need an extra list argument passed, can pick it up here.
 */
static char **optionlist;

void
sort_optlist()
{
    /* Sort the options list.  This is just an insertion sort, but
     * it'll work well for almost-sorted data.
     */
    int i, j;
    int N = NELEM(opts)-1;	/* use NELEM-1 because the last element is
				 * the empty end-of-list marker.
				 */

    for (i=1; i < N; i++) {
	for (j=i; j && strcmp(opts[j-1].name, opts[j].name) > 0; j--) {
	    Option o;
	    /* Swap elements; use memcpy in case struct assignment isn't
	     * allowed by this compiler.
	     */
	    memcpy((void *) &o,         (void *) &opts[j-1], sizeof(opts[0]));
	    memcpy((void *) &opts[j-1], (void *) &opts[j],   sizeof(opts[0]));
	    memcpy((void *) &opts[j],   (void *) &o,         sizeof(opts[0]));
	}
    }

}

/* binary search in options list for option word. */

Option *
find_option(word)
char *word;
{
    int i, wordlen, namelen;
    static int firsttime=1;
    Option *lower = &opts[0];
    Option *upper = &opts[NELEM(opts)-1];	/* 1 past last non-empty elt */
    Option *mid;
    char *sep;

    if (firsttime) {
	sort_optlist();
	firsttime=0;
    }

    if ((strncmp(word, "arg", 3) == 0) && strchr("0123456789", word[3])) {
	/* The argMMM[-NNN] option has to be treated as if it were "arg". */
	word = "arg";
	wordlen = 3;
    } else if (!(sep = strchr(word, OPTION_SEP))) {
	Error(0, 0,
		"$$\n\tsuper.tab option `%s' is missing separator `%c'\n",
		word, OPTION_SEP);
	return NULL;
    } else {
	/* word length up to the separator */
	wordlen = sep - word;
    }

    while (lower < upper) {
	mid = lower + (upper - lower) / 2;
	namelen = strlen(mid->name);
	if ((i=strncmp(word, mid->name, namelen)) < 0) {
	    upper = mid;
	} else if (i > 0) {
	    lower = mid + 1;
	} else if (wordlen == namelen) {
	    /* exact match */
	    return mid;
	} else {
	    /* It didn't compare right.  If wordlen < namelen, the strncmp()
	     * above would have failed, hence we know that wordlen > namelen,
	     * hence hence word > mid...
	     */
	    lower = mid + 1;
	}
    }
    return NULL;
}

int
handle_option(word, value, isglobal)
char *word;	/* The input xxx<SEP>yyy string; NULL means clear settings */
char *value;	/* pts to yyy in word; can be NULL if wd is NULL */
int isglobal;	/* bool: are we processing a global or local entry? */
{

    Option *opt;

/* We only want to print debug info at just the right points, else we
 * get too much junk output.  So define a macro to print our stuff, then
 * invoke it as necessary.  Result: messier programming but better output.
 */
#define DoDebug() \
    { if (debug>1) fprintf(stderr, \
	"\thandle_option(): word=<%s> value=<%s>; is %s\n", \
	word, value, isglobal ? "global" : "local"); }
    

    if (!word)
	return option_clear_settings(word, NULL, isglobal);

    if (*word == '!') {
	DoDebug();
	return Error(0, 0, "$$\n\tsuper.tab syntax error: \n\
options cannot be negated: <%s>\n", word);
    }

    opt = find_option(word);

    if (opt) {
	if (isglobal && !(opt->flags & GLOBAL)) {
	    DoDebug();
	    return Error(0, 0,
		    "$$\n\t`%s' may not be used as a global option\n", word);
	}
	if (!isglobal && !(opt->flags & LOCAL)) {
	    DoDebug();
	    return Error(0, 0,
		    "$$\n\t`%s' may not be used as a local option\n", word);
	}

	if (opt->flags & LIST) {
	    /* Brace-expand arg list and pass */
	    char **globlist;
	    int i;

	    if (balancedbraces(value) != 0) {
		return Error(0, 0, "$$Unbalanced braces in `%s'.\n", value);
	    }
	    if ((i=globbraces(value, 1, &globlist)) != 0) {
		/* Option argument */
		DoDebug();
		return Error(0, 0, "$$Missing `%c'.\n", i);
	    }
	    optionlist = globlist;
	}
	return opt->process(word, value, isglobal);

    } else {
	DoDebug();
	if (value-word == 5 && strncmp(word, "time", 4) == 0) {
	    return Error(0, 0, "$$\n\tUnrecognized %s option `%s'.\n\
\tPerhaps you meant to use the condition `time~%s'?\n",
			isglobal ? "global" : "local", word, value);
	} else {
	    return Error(0, 0,
			"$$\n\tInvalid %s option `%s'.\n",
			isglobal ? "global" : "local", word);
	}
    }
    /* NOTREACHED */
    return 0;	/* Unreachable statement.  Including this shuts up a warning
		 * from some compilers, but generates a warning from others.
		 */
#undef DoDebug
}

int
option_clear_settings(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (isglobal)
	return option_global_reset_settings();
    else
	return option_local_clear_settings();
    /* NOTREACHED */
}

int
option_global_reset_settings()
{
    /* reset global user/group/host patterns pointers, so that more names
     * form a new list, and are not appended to the old one.
     * Similarly for time~pattern pointers.
     * *** Note that we don't free any pattern space!
     * *** We assume that there is plenty of memory in the computer
     * *** to slurp up all _global_ patterns.
     */

    if (debug>1) fprintf(stderr, "\toption_global_reset_settings()\n");

    globalinfo.user_clear = 1;
    globalinfo.time_clear = 1;
    return 0;
}

int
option_local_clear_settings()
{
    /* Clear local settings */
    int i;
    int maxfd = MAXFD;

    if (debug>1) fprintf(stderr, "\toption_local_clear_settings()\n");

    maxfd = MAXFD;
    if (localinfo.info)
	free(localinfo.info);
    localinfo.info = NULL;
    if (localinfo.die)
	free(localinfo.die);
    localinfo.die = NULL;
    if (localinfo.print)
	free(localinfo.print);
    localinfo.print = NULL;
    if (localinfo.chdir_path)
	free(localinfo.chdir_path);
    localinfo.chdir_path = NULL;
    if (localinfo.argv0)
	free(localinfo.argv0);
    localinfo.argv0 = NULL;
    *localinfo.euser = *localinfo.user = '\0';
    *localinfo.egroup = *localinfo.group = '\0';
    *localinfo.u_g = '\0';
    if (localinfo.env)
	blkfree(localinfo.env);
    if (globalinfo.env) {
	if (!(localinfo.env = blkdup(globalinfo.env)))
	    (void) Error(0, 2, "failed to alloc space for envvar list.\n");
    } else {
	localinfo.env = NULL;
    }
    localinfo.setenv[0] = NULL;
    for (i=0; i<=MAXSETENV; i++) {
	if (localinfo.setenv[i])
	    free(localinfo.setenv[i]);
	localinfo.setenv[i]=NULL;
    }
    localinfo.maxlen1arg = globalinfo.maxlen1arg;
    localinfo.maxlenargs = globalinfo.maxlenargs;
    strcpy(localinfo.owner, globalinfo.owner);
    if (localinfo.fdlist)
	free(localinfo.fdlist);
    localinfo.fdlist = NULL;
    if (!localinfo.fd) {
	localinfo.fd = (int *) malloc(sizeof(int) * (maxfd + 1));
	if (!localinfo.fd)
	    return Error(1, 0,
		"$$\n\tFailed to malloc space for file descriptor list: ");
    }
    for (i=0; i<=maxfd; i++) {
	localinfo.fd[i] = 0;
    }
    localinfo.mailcmd[0] = '\0';
    localinfo.mail_success = -1;
    /* Don't let local groups default to global groups, because then we
     * can't tell if local groups were assigned, and we want to set priority
     * to be  (1) local groups=xxx; (2) local u+g=xxx; (3) global groups=xxx.
     */
    localinfo.ngroups = GROUPS_NOTSET;
    localinfo.groups_added = 0;
    error_command = globalinfo.mailcmd[0] ? globalinfo.mailcmd : NULL;
    init_umask(0);
    localinfo.file_uid = UID_NOTSET;
    localinfo.file_gid = GID_NOTSET;
    localinfo.nice_incr = globalinfo.nice_incr;
    free_TimeList(&localinfo.time);
    free_Simple2List(&localinfo.userpats);
    free_SimpleList(&localinfo.origtext);

    localinfo.usr_args[0] = globalinfo.usr_args[0];
    localinfo.usr_args[1] = globalinfo.usr_args[1];

    /* special case: don't initialize localinfo argpats to be
     * a copy of globalinfo argpats.  That's because the
     * argpats accumulate, and we mustn't accumulate a pile
     * of local options on top of a copy of the global ones.
     * When we need to use them, we'll copy from global value
     * if the local value is still a null ptr.
     */
    ARfree(localinfo.argpats.next);
    localinfo.argpats.next = NULL;

    if (localinfo.checkvar)
	blkfree(localinfo.checkvar);
    localinfo.checkvar = NULL;
    /* should use struct assignment, but that's not portable to
     * all k&r compilers.
     */
    localinfo.authinfo.required = globalinfo.authinfo.required;
    localinfo.authinfo.method = globalinfo.authinfo.method;
    localinfo.authinfo.timeout = globalinfo.authinfo.timeout;
    localinfo.authinfo.renewtime = globalinfo.authinfo.renewtime;
    localinfo.authinfo.perhost = globalinfo.authinfo.perhost;
    if (localinfo.authinfo.prompt)
	free(localinfo.authinfo.prompt);
    localinfo.authinfo.prompt = globalinfo.authinfo.prompt;
    strcpy(localinfo.authinfo.user, globalinfo.authinfo.user);
    strcpy(localinfo.authinfo.ts_user, globalinfo.authinfo.ts_user);

    return 0;
}

/***************************************************************************/
/*                                                                         */
/***************************************************************************/
static int
option_auth(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int required;

    if (debug)
	fprintf(stderr, "\toption:auth=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "y") == 0) {
	required = 1;
    } else if (strcmp(s, "n") == 0) {
	required = 0;
    } else {
	return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }

    if (required && using_user_supertab) {
	return Error(0, 0, "$$\n\tUser-supplied .supertab files may not do \
authentication checking -- sorry.\n");
    }

    if (isglobal)
	globalinfo.authinfo.required = required;
    else
	localinfo.authinfo.required = required;

    return 0;
}

static int
option_authprompt(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    char *str;

    if (debug)
	fprintf(stderr, "\toption:authprompt=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (using_user_supertab) {
	return Error(0, 0, "$$\n\tUser-supplied .supertab files may not do \
password checking -- sorry.\n");
    }

    str = strdup(s);
    if (!str)
	return Error(0, 0,
		    "$$\n\tfailed to malloc space for authprompt=<%s>\n", s);
    if (isglobal)
	globalinfo.authinfo.prompt = str;
    else
	localinfo.authinfo.prompt = str;

    return 0;
}

static int
option_authtype(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int method;

    if (debug)
	fprintf(stderr, "\toption:authtype=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "pam") == 0) {
	method = SUPER_AUTH_PAM;
    } else if (strcmp(s, "password") == 0) {
	method = SUPER_AUTH_PASSWORD;
    } else {
	return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `pam' or `password'\n", word);
    }

    if (isglobal)
	globalinfo.authinfo.method = method;
    else
	localinfo.authinfo.method = method;

    return 0;
}

static int
option_authuser(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:authuser=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if debugging or just checking syntax! */
    if (check_syntax || it_came_from_cmdline) {
	Error(0, 0, "$$\n\tIgnoring authuser option, due to debug mode\n");
	return 0;
    }

    if (isglobal)
	strcpy(globalinfo.authinfo.user, s);
    else
	strcpy(localinfo.authinfo.user, s);

    return 0;
}

/***************************************************************************/
/*                                                                         */
/***************************************************************************/

static int
option_arg(word, pat, isglobal)
char *word;
char *pat;
int isglobal;
{
    /* Must be argMMM-NNN<SEP>SSS or argNNN<SEP>SSS */
    int i, iarg1, iarg2;
    char eq;
    ArgRangePat *argpats;

    if (debug)
	fprintf(stderr, "\toption:looks like argMMM-NNN=%s (%s)...\n",
				pat, isglobal ? "global" : "local");

    if (isglobal) {
	argpats = &globalinfo.argpats;
    } else {
	argpats = &localinfo.argpats;
    }

    /* Check that word matches argNNN=SSS */
    i = sscanf(word, "arg%d-%d%c", &iarg1, &iarg2, &eq);
    if (i != 3 || eq != OPTION_SEP) {
	i = sscanf(word, "arg%d%c", &iarg1, &eq);
	if (i == 2)
	    i = 3;
	iarg2 = iarg1;
    }
    if (debug)
	fprintf(stderr, "\toption:is arg%d-%d=%s (%s)\n",
			iarg1, iarg2, pat, isglobal ? "global" : "local");

    if (i != 3 || eq != OPTION_SEP || iarg1 <= 0 || iarg2 < iarg1)
	return Error(0, 0,
		"$$\n\tImproper use of argNNN%cXXX option\n", OPTION_SEP);

    /* Put pattern into the argpat list. */
    {
	char buf[500];
	if (strlen(pat) > sizeof(buf)-3)
	    return Error(0, 0,
			    "$$\n\tArgument pattern <%s> too long (max %d)\n",
			    pat, (int) sizeof(buf)-3);
	anchor(pat, buf);
	if (ARinsert(argpats, iarg1, iarg2, buf) == -1) {
	    return Error(0, 0,
		"Failed to malloc space for argument pattern <%s>\n", buf);
	}
    }
    return 0; 
}

static int
option_argv0(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:argv0=%s (%s)\n",
				s, isglobal ? "global" : "local");

    localinfo.argv0 = strdup(s);
    if (!localinfo.argv0)
	return Error(0, 0, "Failed to malloc space for argv0=<%s>\n", s);
    return 0;
}

static int
option_cd(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:cd=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal) {
	globalinfo.chdir_path = strdup(s);
	if (!globalinfo.chdir_path)
	    return Error(0, 0,
		"Failed to malloc space for cd=<%s>\n", s);
    } else {
	localinfo.chdir_path = strdup(s);
	if (!localinfo.chdir_path)
	    return Error(0, 0,
		"Failed to malloc space for cd=<%s>\n", s);
    }
    return 0;
}

static int
option_checkvar(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:checkvar=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal) {
	return Error(0, 0,
		    "internal error -- option_checkvar called as global!\n");
    } else {
	localinfo.checkvar = optionlist;
    }
    return 0;
}


static int
option_die(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:die=%s (%s)\n",
				s, isglobal ? "global" : "local");

    localinfo.die = strdup(s);
    if (!localinfo.die)
	return Error(0, 0, "$$\n\tfailed to malloc space for die=<%s>\n", s);
    return 0;
}

static int
option_egid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:egid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    stringcopy(localinfo.egroup, s, sizeof(localinfo.egroup));
    return 0;
}

static int
option_euid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:euid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    stringcopy(localinfo.euser, s, sizeof(localinfo.euser));
    return 0;
}

static int
option_env(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:env=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal) {
	globalinfo.env = optionlist;
    } else {
	localinfo.env = optionlist;
    }
    return 0;
}

static int
option_maxenvlen(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int i, m;
    if (debug)
	fprintf(stderr, "\toption:env=%s (%s)\n",
				s, isglobal ? "global" : "local");

    i = sscanf(s, "%d", &m);
    if (i != 1) {
	return Error(0, 0, "$$\n\tmaxenvlen must be maxenvlen=nnn; you used <%s>.\n", word);
    }
    if (isglobal) {
	globalinfo.maxenvlen = m;
    } else {
	localinfo.maxenvlen = m;
    }
    return 0;
}

static int
option_fd(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int n;
    int iwd;
    char *p, *wd;
    int maxfd = MAXFD;

    if (debug)
	fprintf(stderr, "\toption:fd=%s (%s)\n",
				s, isglobal ? "global" : "local");

    localinfo.fdlist = strdup(s);
    if (!localinfo.fdlist)
	return Error(0, 0,
	    "Failed to malloc space for copy of fd=<%s>\n", s);

    for (iwd=0; (wd=optionlist[iwd]); iwd++) {
	if (((n=strtol(wd, &p, 0)) >= 0) && n <= maxfd && p != wd)
	    localinfo.fd[n] = 1;
	else
	    return Error(0, 0,
		"$$\n\tRidiculous value in file descriptor list: `%s'\n",
		word);
    }
    blkfree(optionlist);
    return 0;
}

static int
option_gid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:gid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    stringcopy(localinfo.group, s, sizeof(localinfo.group));
    return 0;
}

static int
option_group_slash(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:group_slash=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "n") == 0) {
	globalinfo.group_slash = 0;
    } else {
	globalinfo.group_slash = 1;
	if (strcmp(s, "y") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }
    return 0;
}

#ifndef HAVE_GETGROUPS
static int
option_groups(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:%s=%s (%s)\n",
				word, s, isglobal ? "global" : "local");

    return Error(0, 0, "Option %s=  not supported on this machine\n", word);
}

#else

static int
option_groups(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int i, is_addgroups, *ngroups_p;
    int iwd;
    char *wd;
    GETGROUPS_T *group_p;

    if (debug)
	fprintf(stderr, "\toption:%s=%s (%s)\n",
				word, s, isglobal ? "global" : "local");

    if (strcmp(word, "groups") == 0) {
	is_addgroups = 0;
    } else if (strcmp(word, "addgroups") != 0) {
	is_addgroups = 1;
    } else {
	return Error(0, 0, "$$Internal error:\n\
\tDon't know what to do when option isn't groups or addgroups!\n");
    }

    if (isglobal) {
	group_p = globalinfo.groups;
	ngroups_p = &globalinfo.ngroups;
	globalinfo.groups_added = is_addgroups;
    } else {
	group_p = localinfo.groups;
	ngroups_p = &localinfo.ngroups;
	localinfo.groups_added = is_addgroups;
    }
    if (*ngroups_p != GROUPS_NOTSET)
	return Error(0, 0, "$$\n\t\tCan't have multiple addgroups=xxx and/or \
groups=xxx options in one entry\n");

    for (*ngroups_p = 0, iwd=0; (wd=optionlist[iwd]); iwd++) {
	i = findgid(1, wd);
	if (i == -1) {
	    *ngroups_p = 0;
	    return Error(0, 0,
	    "$$\n\tCan't set gid: no such group as `%s' in group file.\n", wd);

	} else {
	    *group_p++ = (GETGROUPS_T) i;
	    (*ngroups_p)++;
	}
    }
    blkfree(optionlist);
    return (wd) ? Error(0, 0, "$$\n\ttoo many supplementary \
groups (max=?) in option %s=...", word) : 0;
}
#endif

static int
option_info(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:info=%s (%s)\n",
				s, isglobal ? "global" : "local");

    localinfo.info = strdup(s);
    if (!localinfo.info)
	return Error(0, 0,
	    "Failed to malloc space for info=<%s>\n", s);
    return 0;
}

static int
option_lang(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:lang=%s (%s)\n",
				s, isglobal ? "global" : "local");

#ifndef HAVE_LOCALE_H
    return Error(0, 0, "$$\n\tOption `lang' isn't supported on this host, \
because the locale functions aren't available\n");
#endif

    if (!setlocale(LC_TIME, s))
	return Error(0, 0, "$$\n\tCan't set time locale to `%s'.  \
No such locale?\n", s);

    /* Update the list of day names */
    readtime_init();

    return 0;
}

static int
option_logfile(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:logfile=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if debugging or just checking syntax! */
    if (check_syntax || it_came_from_cmdline) {
	Error(0, 0, "$$\n\tIgnoring logfile option, due to debug mode\n");
	return 0;
    }

    strcpy(globalinfo.log.filename, s);
    globalinfo.log.newfile = 1;
    return 0;
}

static int
option_loguid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:loguid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if debugging or just checking syntax! */
    if (check_syntax || it_came_from_cmdline) {
	Error(0, 0, "$$\n\tIgnoring loguid option, due to debug mode\n");
	return 0;
    }

    strcpy(globalinfo.log.user, s);
    globalinfo.log.newuid = 1;
    return 0;
}

static int
option_mail(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:mail=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if just checking syntax! */
    if (check_syntax)
	return 0;

    if (isglobal) {
	if (strlen(s) > sizeof(globalinfo.mailcmd)-1)
	    return Error(0, 0, "$$\n\tRidiculous length of string: `%s'.\n\
\tMaximum permitted length is %d; the entry used %d\n",
		word, (int) sizeof(globalinfo.mailcmd)-1, (int) strlen(s));

	strcpy(globalinfo.mailcmd, s);
	error_command = globalinfo.mailcmd;
	globalinfo.mail_success = 0;
    } else {
	if (strlen(s) > sizeof(localinfo.mailcmd)-1)
	    return Error(0, 0, "$$\n\tRidiculous length of string: `%s'.\n\
\tMaximum permitted length is %d; the entry used %d\n",
		word, (int) sizeof(localinfo.mailcmd)-1, (int) strlen(s));

	strcpy(localinfo.mailcmd, s);
	error_command = localinfo.mailcmd;
	localinfo.mail_success = 0;
    }
    return 0;
}

static int
option_mailany(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:mailany=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if just checking syntax! */
    if (check_syntax)
	return 0;

    if (isglobal) {
	if (strlen(s) > sizeof(globalinfo.mailcmd)-1)
	    return Error(0, 0, "$$\n\tRidiculous length of string: `%s'.\n\
\tMaximum permitted length is %d; the entry used %d\n",
		word, (int) sizeof(globalinfo.mailcmd)-1, (int) strlen(s));

	strcpy(globalinfo.mailcmd, s);
	error_command = globalinfo.mailcmd;
	globalinfo.mail_success = 1;
    } else {
	if (strlen(s) > sizeof(localinfo.mailcmd)-1)
	    return Error(0, 0, "$$\n\tRidiculous length of string: `%s'.\n\
\tMaximum permitted length is %d; the entry used %d\n",
		word, (int) sizeof(localinfo.mailcmd)-1, (int) strlen(s));

	strcpy(localinfo.mailcmd, s);
	error_command = localinfo.mailcmd;
	localinfo.mail_success = 1;
    }
    return 0;
}

static int
option_nargs(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int i, m, n;
    int *nargsp;

    if (debug)
	fprintf(stderr, "\toption:nargs=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal) {
	nargsp = globalinfo.usr_args;
    } else {
	nargsp = localinfo.usr_args;
    }
    i = sscanf(s, "%u-%u", &m, &n);
    switch(i) {
    case 1:     nargsp[0] = nargsp[1] = m;
		break;
    case 2:     nargsp[0] = m;
		nargsp[1] = n;
		break;
    default:
	return Error(0, 0,
		"$$\n\tnargs must be nargs=nnn or  nargs=mmm-nnn; you used <%s>\n", word);
    }
    return 0; 
}

static int
option_maxlen(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int i;
    long m, n;

    if (debug)
	fprintf(stderr, "\toption:maxlen=%s (%s)\n",
				s, isglobal ? "global" : "local");

    i = sscanf(s, "%ld,%ld", &m, &n);
    switch(i) {
    case 1:
	n = m;
	break;
    case 2:
	break;
    default:
	return Error(0, 0,
	    "$$\n\tmaxlen must be maxlen=nnn or  maxlen=mmm,nnn; you used <%s>\n", word);
    }
    if (isglobal) {
	globalinfo.maxlen1arg = m;
	globalinfo.maxlenargs = n;
    } else {
	localinfo.maxlen1arg = m;
	localinfo.maxlenargs = n;
    }
    return 0; 
}

static int
option_nice(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int nice_incr;
    char *p;
    
    if (debug)
	fprintf(stderr, "\toption:nice=%s (%s)\n",
				s, isglobal ? "global" : "local");

    nice_incr = strtol(s, &p, 0);
    if (p == word)
	return Error(0, 0, "$$\n\tillegal value in `nice=%s'\n", word);
    store_nice_incr(nice_incr, isglobal);
    return 0;
}

static int
option_owner(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:owner=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal)
	stringcopy(globalinfo.owner, s, sizeof(globalinfo.owner));
    else
        stringcopy(localinfo.owner, s, sizeof(localinfo.owner));
    return 0; 
}

static int
option_password(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int required;

    if (debug)
	fprintf(stderr, "\toption:password=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "n") == 0) {
	required = 0;
    } else {
	required = 1;
	if (strcmp(s, "y") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }

    if (required && using_user_supertab) {
	return Error(0, 0, "$$\n\tUser-supplied .supertab files may not do \
password checking -- sorry.\n");
    }

    if (isglobal) {
	globalinfo.authinfo.required = required;
	if (required)
	    globalinfo.authinfo.method = SUPER_AUTH_PASSWORD;
    } else {
	localinfo.authinfo.required = required;
	if (required)
	    localinfo.authinfo.method = SUPER_AUTH_PASSWORD;
    }

    return 0;
}

static int
option_patterns(word, pat_name, isglobal)
char *word;
char *pat_name;
int isglobal;
{
    char *p;
    if (debug)
	fprintf(stderr, "\toption:patterns=%s (%s)\n",
				pat_name, isglobal ? "global" : "local");

    if (strcmp(pat_name, "shell") == 0) {
	pat_compile = shell_compile;
	pat_compare = shell_compare;
	need_re_anchor = 0;
    } else if (strcmp(pat_name, "regex") == 0) {
	pat_compile = s_re_comp;
	pat_compare = s_re_exec;
	need_re_anchor = 1;
#ifdef HAVE_POSIX_REGEX
    } else if (strncmp(pat_name, "posix", 5) == 0 &&
		(pat_name[5] == '/' || strlen(pat_name) == 5)) {
	char *buf = strdup(pat_name);
	if (!buf) {
	    return Error(0, 0,
		"$$\n\tCan't malloc space for copy of <%s>\n", pat_name);
	}
	/* Check the options: we allow:
	 *	posix[/extended][/icase]
	 */
	p = strtok(buf, "/");
	if (strcmp(p, "posix") != 0) {
	    return Error(0, 0, "$$\n\tInternal error: got pat type <%s> when altready checked it's simply `posix'!\n", p);
	}
	for (p=strtok(NULL, "/"); p; p=strtok(NULL, "/")) {
	    if (strcmp(p, "extended") == 0) {
		p_regcomp_flags(REG_EXTENDED);
	    } else if (strcmp(p, "icase") == 0) {
		p_regcomp_flags(REG_ICASE);
	    } else {
		return Error(0, 0,
			    "$$\n\tUnknown posix pattern variant `%s'.\n", p);
	    }
	}
	free(buf);
	pat_compile = p_compile;
	pat_compare = p_compare;
	need_re_anchor = 1;
#endif
    } else {
#ifdef HAVE_POSIX_REGEX
	return Error(0, 0, "$$\n\tInvalid pattern type `%s'.  \
Valid: \"shell\", \"regex\", and \"posix[/extended][/icase]\"\n", pat_name);
#else
	return Error(0, 0, "$$\n\tInvalid pattern type `%s'.  \
Valid: \"shell\" and \"regex\"\n", pat_name);
#endif
    }
    add_variable("PATTERNS", pat_name);
    return 0;
}

static int
option_print(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:print=%s (%s)\n",
				s, isglobal ? "global" : "local");

    localinfo.print = strdup(s);
    if (!localinfo.print)
	return Error(0, 0, "$$\n\tfailed to malloc space for print=<%s>\n", s);
    return 0;
}

static int
option_relative_path(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:relative_path=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "n") == 0) {
	globalinfo.relative_path = 0;
    } else {
	globalinfo.relative_path = 1;
	if (strcmp(s, "y") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }
    return 0;
}

static int
option_renewtime(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:renewtime=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "y") == 0) {
	globalinfo.authinfo.renewtime = 1;
    } else {
	globalinfo.authinfo.renewtime = 0;
	if (strcmp(s, "n") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }
    return 0; 
}

static int
option_rlog_host(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:rlog_host=%s (%s)\n",
				s, isglobal ? "global" : "local");

#ifndef USE_RSYSLOG
    return Error(0, 0, "$$\n\tOption rlog_host is not allowed, because \
this copy of super was compiled with rsyslog disabled.\n");
#endif

    if (*s == '\0')
	return Error(0, 0, "$$\n\tInvalid option value in `%s' \
-- empty value not allowed.\n", word);

    error_rlog_host = strdup(s);
    return 0;
}

static int
option_setenv(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
   char **p;
    int i;

    if (debug)
	fprintf(stderr, "\toption:setenv=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* s must have form v=xxx, where v is any string not including `='. */
    if (*s == '=' || !strchr(s+1, '='))
	return Error(0, 0,
	"$$\n\tbad syntax for setenv%cvar=xxx; you used `setenv%c%s'.\n",
	OPTION_SEP, OPTION_SEP, s);

    /* Append this variable: first skip past already-set variables... */
    if (isglobal) {
	for (i=0, p=globalinfo.setenv; p[i] && i < MAXSETENV; i++)
	    ;
	/* ...then add this definition */
	if (i == MAXSETENV) {
	    return Error(0, 0,
		"$$\n\ttoo many global setenv%cvar=xxx entries; max is %d.\n",
		OPTION_SEP, MAXSETENV);
	} else {
	    char *t = strdup(s);
	    if (!t)
		return Error(0, 0,
		"Failed to malloc space for setenv using <%s>\n", s);
	    globalinfo.setenv[i++] = t;
	    globalinfo.setenv[i] = NULL;
	}
    } else {
	/* islocal */
	for (i=0, p=localinfo.setenv; p[i] && i < MAXSETENV; i++)
	    ;
	/* ...then add this definition */
	if (i == MAXSETENV) {
	    return Error(0, 0,
		"$$\n\ttoo many local setenv%cvar=xxx entries; max is %d.\n",
		OPTION_SEP, MAXSETENV);
	} else {
	    char *t = strdup(s);
	    if (!t)
		return Error(0, 0,
		"Failed to malloc space for setenv using <%s>\n", s);
	    localinfo.setenv[i++] = t;
	    localinfo.setenv[i] = NULL;
	}
    }
    return 0;
}

static int
option_syslog(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:syslog=%s (%s)\n",
				s, isglobal ? "global" : "local");

    /* Don't do any logging if just checking syntax! */
    if (check_syntax)
	return 0;

    if (strcmp(s, "n") == 0) {
	error_syslog = 0;
    } else {
	error_syslog = 1;
	if (strcmp(s, "y") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value in `%s' \
-- value must be `y' or `n'.\n", word);
#ifndef HAVE_SYSLOG
	return Error(0, 0, "$$\n\tCan't use option syslog -- \
not compiled with HAVE_SYSLOG defined.\n");
#endif
    }
    return 0;
}

static int
option_syslog_error(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int syslogcode;

    if (debug)
	fprintf(stderr, "\toption:syslog_error=%s (%s)\n",
				s, isglobal ? "global" : "local");

#ifndef HAVE_SYSLOG
	return Error(0, 0, "$$\n\tCan't use option syslog_error -- \
not compiled with HAVE_SYSLOG defined.\n");
#endif

    if (read_syslogcode(s, &syslogcode) == -1)
	return -1;
    error_priority = syslogcode;
    if (debug)
	fprintf(stderr, "\t\t\t=%#x\n", syslogcode);
    return 0;
}

static int
option_syslog_success(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    int syslogcode;

    if (debug)
	fprintf(stderr, "\toption:syslog_success=%s (%s)\n",
				s, isglobal ? "global" : "local");

#ifndef HAVE_SYSLOG
	return Error(0, 0, "$$\n\tCan't use option syslog_success -- \
not compiled with HAVE_SYSLOG defined.\n");
#endif

    if (read_syslogcode(s, &syslogcode) == -1)
	return -1;
    globalinfo.log.syslog_success = syslogcode;
    if (debug)
	fprintf(stderr, "\t\t\t=%#x\n", syslogcode);
    return 0;
}

static int
option_timeout(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:timeout=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (isglobal)
	globalinfo.authinfo.timeout = atoi(s);
    else
	localinfo.authinfo.timeout = atoi(s);

    return 0;
}

static int
option_timestampbyhost(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:timestampbyhost=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "n") == 0) {
	globalinfo.authinfo.perhost = 0;
    } else if (strcmp(s, "y") == 0) {
	globalinfo.authinfo.perhost = 1;
    } else {
	return Error(0, 0,
		"$$\n\tInvalid option value in `%s' -- \
value must be `y' or `n'.\n", word);
    }
    return 0;
}

static int
option_timestampuid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:timestampuid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    strcpy(globalinfo.authinfo.ts_user, s);
    return 0;
}

static int
option_u_g(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:u+g=%s (%s)\n",
				s, isglobal ? "global" : "local");

    stringcopy(localinfo.u_g, s, sizeof(localinfo.u_g));
    return 0;
}

static int
option_uid(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:uid=%s (%s)\n",
				s, isglobal ? "global" : "local");

    stringcopy(localinfo.user, s, sizeof(localinfo.user));
    return 0;
}

static int
option_umask(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    char *p;
    int mask;
    
    if (debug)
	fprintf(stderr, "\toption:umask=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "<caller>") == 0) {
	mask = userinfo.orig_mask;
    } else if (strcmp(s, "<owner>") == 0) {
	return Error(0, 0,
	    "$$\n\tThe <owner> value isn't allowed in the umask=xxx option\n");
    } else {
	mask = strtol(s, &p, 0);
	if (mask < 0 || p == word)
	    return Error(0, 0, "$$\n\tillegal value in `%s'\n", word);
    }

    store_umask(mask, isglobal);
    return 0;
}

static int
option_gethostbyname(word, s, isglobal)
char *word;
char *s;
int isglobal;
{
    if (debug)
	fprintf(stderr, "\toption:gethostbyname=%s (%s)\n",
				s, isglobal ? "global" : "local");

    if (strcmp(s, "n") == 0) {
	globalinfo.gethostbyname = 0;
    } else {
	globalinfo.gethostbyname = 1;
	if (strcmp(s, "y") != 0)
	    return Error(0, 0, "$$\n\tInvalid option value \
in `%s' -- value must be `y' or `n'\n", word);
    }
    return 0;
}

