static const char rcsid[] = "$Id: checks.c,v 1.191 2020/08/03 17:13:04 will Exp $";
/* The code should compile with either ANSI C or K&R compilers. */

/*
 *      Copyright (c) 1993 by California Institute of Technology.
 *      Written by William Deich.  Not derived from licensed software.

 *    You may distribute under the terms of either the GNU General Public
 *    License or the Artistic License, as specified in the README file.
 */

#include "super.h"
#include "version.h"
#include "s_hsearch.h"

#define MAX_KEYLEN 1000            /* Large enough? */
static char keybuf[MAX_KEYLEN];

static char *YES = "Y";
static char *NO  = "N";

#ifdef HAVE_INNETGR
/*
#define netgrp_u_compare(pattern, user) innetgr(pattern, NULL, user, NULL)
#define netgrp_h_compare(pattern, host) innetgr(pattern, host, NULL, NULL)
 */

static int created_user_table = 0;
static int netgrp_u_compare(pattern, user)
  char *pattern;
  char *user;
{
    ENTRY item, *found_item;
    int passed, klen;

    if (!created_user_table) {
        if (!s_hcreate(HS_USER, 1000)) {
	    return Error(0, 0,
		"$$Couldn't allocate hash table for user ng processing\n");
	}
        created_user_table = 1;
    }
    /* Catenate pattern and user for the key such that
     * "abc" + "de" is not the same as "ab" + "cde".
     * Use "\n", as that will have been stripped from input before we get here.
     */
    if (snprintf(keybuf, MAX_KEYLEN, "%s\n%s", pattern, user) >= MAX_KEYLEN) {
         return Error(0, 0,
             "$$pattern and user too large for user ng processing\n");
    }

    item.key = keybuf;
    if (found_item = s_hsearch(HS_USER, item, FIND)) {
        return found_item->data == YES? 1: 0;
    }
    passed = innetgr(pattern, NULL, user, NULL);

    /* Have to make this key a permanent copy for the hash */

    klen = strlen(keybuf);
    item.key = malloc(klen + 1);
    if (!item.key) {
        return Error(0, 0,
           "$$out of memory for user ng processing\n");
    }
    strcpy(item.key, keybuf);
    item.data = passed? YES: NO;
    if (!s_hsearch(HS_USER, item, ENTER)) {
        return Error(0, 0,
           "$$hash set failure for user ng processing\n");
    }
    return passed;
}

static int created_host_table = 0;
static int netgrp_h_compare(pattern, host)
  char *pattern;
  char *host;
{
    ENTRY item, *found_item;
    int passed, klen;

    if (!created_host_table) {
        if (!s_hcreate(HS_HOST, 1000)) {
	    return Error(0, 0,
		"$$Couldn't allocate hash table for host ng processing\n");
	}
        created_host_table = 1;
    }
    /* Catenate pattern and user for the key such that
     * "abc" + "de" is not the same as "ab" + "cde".
     * Use "\n", as that will have been stripped from input before we get here.
     */
    if (snprintf(keybuf, MAX_KEYLEN, "%s\n%s", pattern, host) >= MAX_KEYLEN) {
         return Error(0, 0,
             "$$pattern and host too large for host ng processing\n");
    }

    item.key = keybuf;
    if (found_item = s_hsearch(HS_HOST, item, FIND)) {
        return found_item->data == YES? 1: 0;
    }
    passed = innetgr(pattern, host, NULL, NULL);

    /* Have to make this key a permanent copy for the hash */

    klen = strlen(keybuf);
    item.key = malloc(klen + 1);
    if (!item.key) {
        return Error(0, 0,
           "$$out of memory for host ng processing\n");
    }
    strcpy(item.key, keybuf);
    item.data = passed? YES: NO;
    if (!s_hsearch(HS_HOST, item, ENTER)) {
        return Error(0, 0,
           "$$hash set failure for host ng processing\n");
    }
    return passed;
}

#else
#define netgrp_u_compare(p, u) 0
#define netgrp_h_compare(p, h) 0
#endif

int get_setting P__ (());


#ifdef _HPUX_SOURCE

#ifdef HAVE_ISCOMSEC
    char *bigcrypt();
#else
    /* If we don't have iscomsec() on this HP system, fake it -- otherwise
     * we'll have to #ifdef the use of iscomsec(), and it's cleaner code
     * if we don't.
     */
    int iscomsec() { return 0; }
#endif
#endif

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check that an environment variable only includes allowed characters */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns 0 if pat matched; -1 otherwise.  */
int
checkenv(name, value, pat)
char *name;	/* variable name to check (e.g. "TERM") */
char *value;	/* contents of variable (e.g. "vt100") */
char *pat;	/* pattern that value must match */
{
    int len;

    if (!value)
	return -1;

    if (debug)
	(void) fprintf(stderr,
	    "\tcheckenv args: name=\"%s\"; value=\"%s\"; pat=\"%s\"\n",
	    name, value, pat);
    
    /* Environment variables are always checked with s_re_comp/s_re_exec:
     * the patterns are fixed internally, not supplied by the user.
     */
    if (s_re_comp(pat)) {
	return Error(0, 0,
	    "$$\n\tcheckenv(): couldn't compile pattern `%-.500s'.\n", pat);
    }

    if (s_re_exec(value) != 1) {
	return Error(0, 0,
	    "checkenv(): $%.100s (=%.100s) doesn't match pattern %-.500s.\n",
			name, value, pat);
    }

    /* Limit the value to a reasonable length (MAXENVLEN chars) */
    len = strlen(value);
    if (len > MAXENVLEN) {
	return Error(0, 0, "Imported environment variables may not \
exceed %d chars; you passed a string of length %d!\n", MAXENVLEN, len);
    }

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check the checkvar=name,... list.
 * If the list isn't empty, use /dev/tty for input.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns 0 if list ok; -1 otherwise.  */
int
check_var_value()
{
    int l, ntry, match;
    char buf[500];
    char *varname, *truevalue;
    int ivar;
    FILE *prompt_fp;
    char *prompt_dev="/dev/tty";

    /* This is implemented as an inefficient operation, but it doesn't
     * matter because we _assume_ the number of variables to be small
     * and it's only invoked once.
     */

    if (!localinfo.checkvar || !localinfo.checkvar[0])
	return 0;	/* no checkvar list */

    if (!(prompt_fp = fopen(prompt_dev, "r+"))) {
	return Error(0, 0,
	    "Can't open %s to test checkvar=name,...\n", prompt_dev);
    }
    if (localinfo.checkvar[1]) {
	/* There must be more than one variable */
	fprintf(prompt_fp, "%c%s needs you enter some variables \
before proceeding...\n", toupper(prog[0]), prog+1);
    } else {
	fprintf(prompt_fp, "%c%s needs you to enter the %s variable \
before proceeding.\n", toupper(prog[0]), prog+1, localinfo.checkvar[0]);
    }

    for (ivar=0; localinfo.checkvar[ivar]; ivar++) {
	varname = localinfo.checkvar[ivar];
	truevalue = get_variable(varname);

	if (!truevalue) {
	    /* Ouch, checkvar=xxx is used, but xxx isn't defined! */
	    fclose(prompt_fp);
	    return Error(0, 0,
		"$$\n\tError in super.tab file: \
`checkvar=%s' is used, but variable %s isn't defined!\n", varname, varname);
	}

	for (ntry=0, match=0; ntry < MAXTRY && !match; ntry++) {
	    if (ntry == 0) {
		fprintf(prompt_fp, "Enter %s (<Return> for reminder): ",
								    varname);
	    } else {
		fprintf(prompt_fp, "Enter %s (expecting `%s'): ",
	    						varname, truevalue);
	    }
	    fflush(prompt_fp);
	    if (!fgets(buf, sizeof(buf), prompt_fp)) {
		if (feof(prompt_fp)) {
		    fclose(prompt_fp);
		    return Error(0, 0, "can't read %s -- stream was closed!\n",
				prompt_dev);
		} else if (ferror(prompt_fp)) {
		    fclose(prompt_fp);
		    return Error(0, 0, "error reading %s\n", prompt_dev);
		} else {
		    fclose(prompt_fp);
		    return Error(0, 0, "??? feof() and ferror() return 0, \
but I can't read %s\n", prompt_dev);
		}
	    }

	    if (strlen(buf) == sizeof(buf)-1) {
		fclose(prompt_fp);
		return Error(0, 0, "Ridiculously long value <%.300s...> \
returned for variable %s\n", buf, varname);
	    }
	    l = strlen(buf);
	    if (buf[l-1] == '\n')
		buf[l-1] = '\0';
	    match = (strcmp(buf, truevalue) == 0);
	    if (!match) {
		fprintf(prompt_fp, "Variable %s incorrect\n", varname);
	    }
	}
	if (!match) {
	    fclose(prompt_fp);
	    return Error(0, 0, "Never got variable %s entered correctly.\n",
	    							varname);
	}
    }
    fclose(prompt_fp);

    /* all variables are ok. */
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Compare a value to a pattern */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns 0 if pat matched; -1 otherwise.  */
int
check_value(value, pat)
char *value;	/* value to check */
char *pat;	/* pattern that value must match */
{

    if (!value)
	return -1;

    if (debug)
	(void) fprintf(stderr,
	    "\tcheck_value args: value=\"%s\"; pat=\"%s\"\n",
	    value, pat);
    
    if (s_re_comp(pat)) {
	return Error(0, 0,
	    "$$\n\tcheck_value(): couldn't compile pattern `%-.500s'.\n", pat);
    }

    if (s_re_exec(value) != 1) {
	return Error(0, 0,
	    "check_value(): value doesn't match pattern %-.500s.\n", pat);
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Option checking -- ensure that options to super are reasonable strings. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns 0 if reasonable; -1 otherwise.  */
int
checkarg(str)
char *str;
{
    int len;
    static int optlen_tot = 0;	/* running length of all options to super */

    /* Limit each option to a reasonable length (MAXOPTLEN chars) */
    len = strlen(str);
    if (len > MAXOPTLEN) {
	return Error(0, 0, "Command-line options may not \
exceed %d chars; you passed a string of length %d!\n", MAXOPTLEN, len);
    }

    /* Limit the combined option to length MAXOPTLEN_TOT chars */
    optlen_tot += len;
    if (optlen_tot > MAXOPTLEN_TOT) {
	return Error(0, 0, "The total length of command-line options \
may not exceed %d chars!\n", MAXOPTLEN_TOT);
    }

    /* Ensure the option pattern matches OPT_PATTERN */
    if (check_value(str, OPT_PATTERN) != 0)
	return -1;
    
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Look up owner of a file, return uid and gid of owner */
/* Return 0 on success, -1 & print message on failure */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
get_owner(file, uid_p, gid_p)
char *file;
uid_t *uid_p;
gid_t *gid_p;
{
    /* Return 0 if file ownership ok; -1 if not ok */
    struct stat st;

    if (!file || *file == '\0')
	return Error(0, 0, "get_owner(): passed null ptr or empty string\n");

    if (stat(file, &st) == -1)
	return Error(1, 0, "stat() failed on file `%s': ", file);

    *uid_p = st.st_uid;
    *gid_p = st.st_gid;

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check ownership of the file */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_owner()
{
    /* Return 0 if file ownership ok; -1 if not ok */
    struct passwd *owner_pw;

    if (*localinfo.owner == '\0')
	return 0;	/* no checks required */

    /* Convert desired-owner string to a uid */
    owner_pw = getpwentry(1, localinfo.owner);
    if (!owner_pw)
	return -1;

    if (localinfo.file_uid != owner_pw->pw_uid)
	return Error(0, 0,
	    "Actual owner of `%s' is uid %d, but superfile \
requires owner to be %d (%s).\n",
	    localinfo.progs.cmd_file[localinfo.progs.match].File,
	    localinfo.file_uid, owner_pw->pw_uid, owner_pw->pw_name);
    
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Gets an entry from the password file.  Optionally accepts special bracketed
 * name (e.g. <owner> or <caller>).  Accepts names or text uid's.
 * Returns ptr to password entry.  The password entry may be that returned
 * by getpwnam(), or it may be one already stored in a super-owned struct;
 * you can't make any assumptions about it -- therefore don't modify it.
 *
 * The returned pointer points to an area that may be overwritten by later
 * calls to the getpwxxx() routines; therefore if the caller wants to save
 * the data, the data must be copied.
 *
 * On error, print message and return NULL.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct passwd *
getpwentry(allow_brackets, username)
int allow_brackets;	/* allow special names like <caller> */
char *username;		/* name to translate */
{
    struct passwd *pw;
    int l = strlen(username);

    if (allow_brackets && username[0] == '<' && username[l-1] == '>') {
	/* Translate special name */
	if (strcmp(username, "<owner>") == 0) {
	    if (localinfo.file_uid != UID_NOTSET) {
		pw = getpwuid(localinfo.file_uid);
	    } else {
		Error(0, 0,
	    "$$: getpwentry() Internal Error!\n\tFile owner not yet known!\n");
		return NULL;
	    }
	} else if (strcmp(username, "<caller>") == 0) {
	    pw = &userinfo.caller;
	} else {
	    Error(0, 0, "$$\n\t\tUnknown special name %s\n", username);
	    return NULL;
	}
    } else {
	/* Regular name or number */
	pw = getpwnam(username);
	if (!pw) {
	    char c;
	    int i, numeric;
	    numeric = (sscanf(username, "%d%c", &i, &c) == 1);
	    if (numeric)
		pw = getpwuid(i);
	}
    }
    if (!pw) {
	Error(0, 0, "$$\n\tNo such user or uid as `%s' in password file.\n",
	    username);
	return NULL;
    }
    return pw;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Gets a group entry.
 * Accepts text gid's or names.
 * Returns ptr to group entry.
 *
 * The returned pointer points to an area that may be overwritten by later
 * calls to the getgrxxx() routines; therefore if the caller wants to save
 * the data, the data must be copied.
 *
 * On error, print message and return NULL.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct group *
getgrentry(name)
char *name;		/* name to translate */
{
    struct group *gr;
    char c;
    int i, numeric;

    gr = getgrnam(name);
    if (!gr) {
	numeric = (sscanf(name, "%d%c", &i, &c) == 1);
	if (numeric)
	    gr = getgrgid(i);
    }
    if (!gr) {
	Error(0, 0, "$$\n\tNo such group or gid as `%s'.\n", name);
	return NULL;
    }
    return gr;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Set supplementary groups according to the specified args                */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HAVE_GETGROUPS
int
set_suppl_groups() { return 0; }
#else
int
set_suppl_groups()
{
    GETGROUPS_T *addgroups_p, *groups_p, groups[NGROUPS_MAX];
    GETGROUPS_T gotten_groups[NGROUPS_MAX];
    int naddgroups, ngroups;
    int i;

    /* Set the supplementary groups */
    if (geteuid() != 0) {
	/* Can't set supplementary groups if we've changed to some other uid */
	return 0;
    }
    if (*localinfo.u_g) {
	/* Default is to take from that user.  (Don't worry if there is no
	 * such user; it'll get noticed later.)  Conflicts with local
	 * option groups=xxx, which is disallowed.
	 */
	if (localinfo.ngroups != GROUPS_NOTSET && !localinfo.groups_added)
	    return Error(0, 0, "$$\n\t\tu+g=xxx conflicts with groups=yyy \
and may not be used in the same entry\n");

	initgroups(localinfo.user, userinfo.new_gid);
	/* Check for local or global addgroups */
	if (localinfo.ngroups != GROUPS_NOTSET) {
	    addgroups_p = localinfo.groups;
	    naddgroups = localinfo.ngroups;
	} else if (globalinfo.ngroups != GROUPS_NOTSET &&
					globalinfo.groups_added) {
	    addgroups_p = globalinfo.groups;
	    naddgroups = globalinfo.ngroups;
	} else {
	    addgroups_p = NULL;
	    naddgroups = 0;
	}
	ngroups = Getgroups(NGROUPS_MAX, gotten_groups);
	if (ngroups == -1)
	    return Error(1, 0, "$$ Getgroups() failed: ");
	if (ngroups + naddgroups > NGROUPS_MAX)
	    return Error(1, 0,
			    "$$\n\t\taddgroups=xxx adds too many groups.");
	for (i = 0; i < ngroups; i++)
	    groups[i] = gotten_groups[i];
	for (groups_p = &groups[ngroups], i=0; i < naddgroups; i++)
	    *groups_p++ = *addgroups_p++;
	ngroups += naddgroups;
	    
    }  else if (localinfo.ngroups != GROUPS_NOTSET) {
	/* There are some explicit local groups=xxx */
	if (Setgroups(localinfo.ngroups, localinfo.groups) == -1)
	    return Error(1, 0, "Failed to set supplementary groups list: ");

    } else if (globalinfo.ngroups != GROUPS_NOTSET) {
	/* There is an explicit global groups=xxx or addgroups=xxx */
	if (Setgroups(globalinfo.ngroups, globalinfo.groups) == -1)
	    return Error(1, 0, "Failed to set supplementary groups list: ");
    } else {
	/* Default is no supplementary groups */
	if (Setgroups(0, localinfo.groups) == -1)
	    return Error(1, 0,
			    "Failed to clear supplementary groups list: ");
    }
    return 0;
}
#endif

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Set user, group, and supplementary groups according to the specified args */
/* Side effect: if localinfo.user is numeric, change it to a name */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
set_u_g()
{
    /* Return 0 on success, -1 on failure */
    SETGRENT_T setgrent();
    void endgrent();
    int i, j, k = -1;
    int found_gid = -1, found_egid = -1;
    struct passwd *pw = NULL;
    int use_setreuid;	/* true iff we need to use setreuid() */
    int use_setregid;	/* true iff we need to use setregid() */
    uid_t uid_pw_uid = -1, euid_pw_uid = -1;
    gid_t u_g_pw_gid = -1;
    char *uid_pw_name = NULL, *euid_pw_name = NULL;

    /*
     * Start by looking up user id's and group id's.  We'll assign
     * actual values after we've looked it all up.
     */

    /* First, get the group id's specified by {e,}gid=xxx */
    if (*localinfo.group) {
	found_gid = findgid(1, localinfo.group);

	if (found_gid == -1) {
	    return Error(0, 0,
	    "$$\n\tCan't set gid: no such group as `%s' in group file.\n",
		localinfo.group);
	}
    }
    if (*localinfo.egroup) {
	found_egid = findgid(1, localinfo.egroup);

	if (found_egid == -1) {
	    return Error(0, 0,
	    "$$\n\tCan't set egid: no such group as `%s' in group file.\n",
		localinfo.egroup);
	}
    }

    /* Second, get the password entries specified by {e,}uid=xxx, u+g=xxx */
    if (*localinfo.u_g && !*localinfo.user) {
	/* u+g=zzz was used, but uid=xxx was not.  So, set user from u+g. */
	strcpy(localinfo.user, localinfo.u_g);
    }

    if (*localinfo.user) {
	pw = getpwentry(1, localinfo.user);
	if (!pw)
	    return -1;
	uid_pw_uid  = pw->pw_uid;
	uid_pw_name = strdup(pw->pw_name);
	if (!uid_pw_name) 
		return Error(0, 0,
		    "$$\n\tfailed to malloc space for uid_pw_name=<%s>\n", pw->pw_name);
	
    }
    if (*localinfo.euser) {
	pw = getpwentry(1, localinfo.euser);
	if (!pw)
	    return -1;
	euid_pw_uid  = pw->pw_uid;
	euid_pw_name = strdup(pw->pw_name);
	if (!euid_pw_name)
		return Error(0, 0,
		    "$$\n\tfailed to malloc space for euid_pw_name=<%s>\n", pw->pw_name);
    }
    if (*localinfo.u_g) {
	pw = getpwentry(1, localinfo.u_g);
	if (!pw)
	    return -1;
	u_g_pw_gid  = pw->pw_gid;
    }

    pw = NULL;
    
    if (*localinfo.group && *localinfo.u_g) {
	return Error(0, 0,
		"$$\n\tCan't mix options gid=xxx and u+g=yyy in one entry.");
    }

    /*
     * OK, now we have all the uid/gid info.  Select uid's and gid's,
     * and select the appropriate set of functions to do the assignments.
     */

    /* UID defaults: */
    userinfo.new_uid = userinfo.caller.pw_uid;
    use_setreuid = 0;		/* don't have to use setreuid() */

    if (*localinfo.euser) {
	/* euid=xxx was used */
	userinfo.new_euid = euid_pw_uid;
	if (strcmp(euid_pw_name, localinfo.euser) != 0) {
	    /* localinfo.euser must be numeric; convert it to string */
	    strcpy(localinfo.euser, euid_pw_name);
	}
	use_setreuid = 1;	/* have to use setreuid() */
    }

    if (*localinfo.user) {
	/* uid=xxx or u+g=xxx was used */
	userinfo.new_uid = uid_pw_uid;
	if (strcmp(uid_pw_name, localinfo.user) != 0) {
	    /* localinfo.user must be numeric; convert it to string */
	    strcpy(localinfo.user, uid_pw_name);
	}
    }

    /* GID defaults: */
    userinfo.new_gid = userinfo.caller.pw_gid;
    use_setregid = 0;		/* don't have to use setreuid() */

    if (*localinfo.egroup) {
	/* egid=xxx was used */
	userinfo.new_egid = found_egid;
	use_setregid = 1;	/* have to use setreuid() */
    }

    if (*localinfo.group) {
	/* gid=xxx was used */
	userinfo.new_gid = found_gid;
    } else if (*localinfo.u_g) {
	userinfo.new_gid = u_g_pw_gid;
    }

    /* Set supplementary groups */
    if (set_suppl_groups() == -1)
	return -1;


    /* Now set uid & gid */
    if (use_setregid) {
#ifdef HAVE_SETREGID
	if ((i=setregid(userinfo.new_gid, userinfo.new_egid)) == -1) {
	    return Error(1, 0, "setregid(gid=%d, egid=%d) failed: ",
		    	userinfo.new_gid, userinfo.new_egid);
	} else if ((j=getgid()) != userinfo.new_gid ||
				    (k=getegid()) != userinfo.new_egid) {
	    return Error(0, 0,
	"setregid(gid=%d,egid=%d) returned 0, but getgid()=%d,getegid()=%d!",
		userinfo.new_gid, userinfo.new_egid, j, k);
	}
#else
	return Error(0, 0, "Can't use egid=xxx option, because this host doesn't offer the setregid() function");
#endif
    } else if (*localinfo.group || *localinfo.u_g) {
	if ((i=setgid(userinfo.new_gid)) == -1) {
	    return Error(1, 0, "setgid(gid=%d) failed: ", userinfo.new_gid);
	} else if ((j=getgid()) != userinfo.new_gid) {
	    return Error(0, 0,
		"setgid(gid=%d) returned %d, but getgid() returned %d!",
		userinfo.new_gid, i, j);
	}
    }


    if (use_setreuid) {
#ifdef HAVE_SETREUID
	if ((i=setreuid(userinfo.new_uid, userinfo.new_euid)) == -1) {
	    return Error(1, 0, "setreuid(uid=%d, euid=%d) failed: ",
		    	userinfo.new_uid, userinfo.new_euid);
	} else if ((j=getuid()) != userinfo.new_uid ||
				    (k=geteuid()) != userinfo.new_euid) {
	    return Error(1, 0,
	"setreuid(uid=%d,euid=%d) returned 0, but getuid()=%d,geteuid()=%d!",
		userinfo.new_uid, userinfo.new_euid, j, k);
	}
#else
	return Error(1, 0, "Can't use euid=xxx option, because this host doesn't offer the setreuid() function");
#endif
    } else if (*localinfo.user || *localinfo.u_g) {
	if ((i=setuid(userinfo.new_uid)) == -1) {
	    return Error(1, 0, "setuid(uid=%d) failed: ", userinfo.new_uid);
	} else if ((j=getuid()) != userinfo.new_uid) {
	    return Error(1, 0,
		"setuid(uid=%d) returned %d, but getuid() returned %d!",
		userinfo.new_uid, i, j);
	}
    }

    if (uid_pw_name) 
	    free(uid_pw_name);
    if (euid_pw_name)
	    free(euid_pw_name);
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Puts the encrypted password in userinfo.encr, and the salt in userinfo.salt.
 * Returns 0 on success, -1 on failure to obtain the password.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifdef SUNOS5 
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    /* Shadow passwords are always used on Sunos 5.x */
    struct spwd *caller_pw;
    if (!(caller_pw = getspnam(userinfo.caller.pw_name))) {
	authInitErrno = errno;
	sprintf(authInitMsg1,
		"Failed to obtain shadow password entry for user %s",
		userinfo.caller.pw_name);
	return -1;
    }
    strcpy(userinfo.encr, caller_pw->sp_pwdp);
    return get_setting();
}
#endif

#ifdef _HPUX_SOURCE
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    if(iscomsec()) {

	struct pr_passwd *caller_pw;

	if (!(caller_pw = getprpwnam(userinfo.caller.pw_name))) {
	    authInitErrno = errno;
	    sprintf(authInitMsg1,
	      "Failed to obtain Trusted Computing Base entry for user %s",
		userinfo.caller.pw_name);
	    return -1;
	}
	strcpy(userinfo.encr, caller_pw->ufld.fd_encrypt);

    } else {
	struct passwd *caller_pw;

	/* See if we can do shadow password lookup for HPUX 9.x.
	 * The rule is that if /.secure/etc/passwd exists, we have to use it;
	 * otherwise, fall through to regular password file lookup.
	 */
	static struct stat st;
	if (stat("/.secure/etc/passwd", &st) == 0) {
	    /* Shadow password file exists; use it */
	    struct s_passwd *caller_pw;
	    if (!(caller_pw = getspwnam(userinfo.caller.pw_name))) {
		authInitErrno = errno;
		sprintf(authInitMsg1,
		    "Failed to obtain shadow password entry for user %s",
		    userinfo.caller.pw_name);
		return -1;
	    }
	    strcpy(userinfo.encr, caller_pw->pw_passwd);
	} else {

	    /* Fall through to regular password file lookup. */
	    strcpy(userinfo.encr, userinfo.caller.pw_passwd);
	}
    }
    return get_setting();
}
#endif

#ifdef SCO
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    struct passwd *caller_pw;
    struct spwd *caller_ps;

    if (!(caller_pw = getpwnam(userinfo.caller.pw_name))) {
	sprintf(authInitMsg1, "No password entry for user %s.\n",
		userinfo.caller.pw_name);
	return -1;
    }

    /* SCO 3.2v4 has "x" in password field to indicate shadow password
     * file has to be consulted.
     */
    if (strcmp(caller_pw->pw_passwd, "x") == 0) {
	/* Shadow password in use... */
	if (!(caller_ps = getspnam(userinfo.caller.pw_name))) {
	    authInitErrno = errno;
	    sprintf(authInitMsg1,
		"Failed to obtain shadow password entry for user %s",
		userinfo.caller.pw_name);
	    return -1;
	}
	strcpy(userinfo.encr, caller_ps->sp_pwdp);

    } else {
	/* Fall through to regular password file lookup. */
	strcpy(userinfo.encr, caller_pw->pw_passwd);
    }
    return get_setting();
}
#endif

#ifdef Digital_UNIX
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    struct passwd *caller_pw;
    struct pr_passwd *caller_prpass;

    if (!(caller_pw = getpwnam(userinfo.caller.pw_name))) {
	sprintf(authInitMsg1, "No password entry for user %s.\n",
		userinfo.caller.pw_name);
	return -1;
    }

    /* Digital Unix has "x" in password field to indicate shadow password
     * file has to be consulted.
     */
    if (strcmp(caller_pw->pw_passwd, "x") == 0) {
	/* Protected (shadow) password in use... */
	if (!(caller_prpass = getprpwnam(userinfo.caller.pw_name))) {
	    authInitErrno = errno;
	    sprintf(authInitMsg1,
		"Failed to obtain shadow password entry for user %s",
	    userinfo.caller.pw_name);
	    return -1;
	}
	strcpy(userinfo.encr, caller_prpass->ufld.fd_encrypt);

    } else {
	/* Fall through to regular password file lookup. */
	strcpy(userinfo.encr, caller_pw->pw_passwd);
    }
    return get_setting();
}
#endif

#ifdef __linux__
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    /* Use /etc/shadow if it exists; else fall back on std */
    static struct stat st;
    if (stat("/etc/shadow", &st) == 0) {
	struct spwd *spwd = 0L;
	if (!(spwd = getspnam(userinfo.caller.pw_name))) {
	    /* Gordon Lack notes: Linux doesn't use the shadow file for NIS
	     * passwords, so if you get ENOENT or no error, then try the
	     * local password file.
	     */
	    if (errno == ENOENT || errno == 0) {
		strcpy(userinfo.encr, userinfo.caller.pw_passwd);
	    } else {
		authInitErrno = errno;
		sprintf(authInitMsg1,
		    "Failed to obtain shadow password entry for user %s",
		    userinfo.caller.pw_name);
		return -1;
	    }
        } else {
	    /* got shadow entry */
	    strcpy(userinfo.encr, spwd->sp_pwdp);
	}
    } else {
	/* Fall through to regular password file lookup. */
	strcpy(userinfo.encr, userinfo.caller.pw_passwd);
    }

    return get_setting();
}
#endif

#ifdef __FreeBSD__
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    /* getpwnam() and getpwuid() will have read the shadow passwd
     * if our effective uid == root; no need to use getspnam() or the like.
     * But if the encrypted pw is "*", then we didn't get the real pw.
     */
    strcpy(userinfo.encr, userinfo.caller.pw_passwd);
    if (strcmp(userinfo.encr, "*") == 0) {
	sprintf(authInitMsg1, "Can't get encrypted password, \
or there is no password, for user %s", userinfo.caller.pw_name);
	return -1;
    }

    return get_setting();
}
#endif

#ifdef __OpenBSD__
#define HAVE_G_E_PW

int
get_encrypted_pw()
{
    /* getpwnam() and getpwuid() will have read the shadow passwd
     * if our effective uid == root; no need to use getspnam() or the like.
     * But if the encrypted pw is "*", then we didn't get the real pw.
     */
    strcpy(userinfo.encr, userinfo.caller.pw_passwd);
    if (strcmp(userinfo.encr, "*") == 0) {
	sprintf(authInitMsg1, "Can't get encrypted password, \
or there is no password, for user %s", userinfo.caller.pw_name);
	return -1;
    }

    return get_setting();
}
#endif

#ifndef HAVE_G_E_PW

int
get_encrypted_pw()
{
    /* Vanilla password file lookup */

    strcpy(userinfo.encr, userinfo.caller.pw_passwd);
    strncpy(userinfo.salt, userinfo.caller.pw_passwd, 2);
    return get_setting();
}
#endif

/*
 * Read the userinfo.encr (encrypted passwd field) and pull out the data
 * needed for the "salt" or "setting" argument to crypt().
 * This version implements the following rule:
 * o  if encr begins with "$", the entire encr field should be used
 * 	(crypt() will be responsible for extracting the data it needs);
 * o  otherwise, if encr begins with "_", use the first 9 chars
 * 	(crypt() will discard the leading "_" and use the next 8 characters);
 * o  otherwise, the salt is the first two chars.
 *
 * Returns:
 * -1 (plus Error()) on failure
 * 0 on success.
 */
int
get_setting()
{
    if (sizeof(userinfo.salt) <= strlen(userinfo.encr)) {
	return Error(0, 0, "Compilation error: the userinfo.salt field is too short (%d chars) to hold a copy of the userinfo.encr field (%d chars) for user %s",
	    (int) sizeof(userinfo.salt), (int) strlen(userinfo.encr),
	    userinfo.caller.pw_name);
    }

    if (userinfo.encr[0] == '$') {
	strcpy(userinfo.salt, userinfo.encr);

    } else if (userinfo.encr[0] == '_') {
	/* crypt() _might_ parse the encr field to pull off the necessary
	 * salt, but we don't make that assumption.
	 */
	strncpy(userinfo.salt, userinfo.encr, 9);
	userinfo.salt[9] = '\0';

    } else {
	/* Salt is first two chars. */
	strncpy(userinfo.salt, userinfo.encr, 2);
	userinfo.salt[2] = '\0';
    }

    return 0;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns ptr to string with name of desired authentication method. */
char *
auth_name()
{
    if (!localinfo.authinfo.required) {
	return "None";
    } else if (localinfo.authinfo.method == SUPER_AUTH_PASSWORD) {
	return "Password";
    } else if (localinfo.authinfo.method == SUPER_AUTH_PAM) {
	return "PAM";
    } else {
	Error(0, 1, "auth_name: don't recognize auth method %d!\n",
						localinfo.authinfo.method);
    }
    /* NOTREACHED */
    return "Unknown!"; /* to make gcc -Wall happy... */
}

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


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Checks if authentication is needed, and does so if needed.
 * Expects that the encrypted password is already in userinfo.{encr,salt},
 * so that this can be run after switching to unpriviledged uid.
 * Returns 0 on success, -1 on error.

 * The timestamp directory faces the same problem as the logfile: if the
 * administrator wants to share an NFS-mounted directory across hosts
 * on which root is translated to nobody for NFS access, we have to be
 * able to create the timestamp file under a special uid.  This is done
 * just as in open_writer(): we fork, setuid(), and do the file
 * manipulation in the child.  This allows us to implement a special uid
 * for the timestamp file, without needing the operating system to
 * offer saved uid's or interprocess file-descriptor passing, etc.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_auth(cmd)
char *cmd;
{
    char file[MAXPATHLEN];
    struct stat st;
    int l, istat, file_exists, got_auth, err, timestamp_creatok;
    int timed_out = 0;
    int status;
    pid_t pid, child;
    char mkdirMsg[1000];
    char *authuser;

    if (!localinfo.authinfo.required)
	return 0;			/* don't need authentication */

    if (*authInitMsg1 || *authInitMsg2) {
	/* A message was generated during authentication initialization,
	 * way back before the super.tab file was read.  It was only
	 * relevant if the command required authorization... well, this
	 * command requires user authentication, so print the message now.
	 */
	if (*authInitMsg1) {
	    if (authInitErrno) {
		errno = authInitErrno;
		Error(1, 0, "%s: ", authInitMsg1);
	    } else {
		Error(0, 0, "%s", authInitMsg1);
	    }
	}
	if (*authInitMsg2) {
	    Error(0, 0, "%s", authInitMsg2);
	}
    }

    /* Create or update the timestamp file even if the lifetime is 0
     * (always ask for password).  We do this because the user may
     * execute _another_ command which has a password expiry > 0,
     * and which will be happy to use the password that was already
     * entered with the 0-lifetime command.
     */
    child = fork();
    if (child == -1) {
	Error(1, 0, "Failed to create child for timestamp processing: ");
	return -1;

    } else if (child > 0) {
	/* In parent -- wait to see if the child succeeded */
	while ((pid = wait(&status)) > 0 && pid != child) {
	    if (pid == globalinfo.log.pid) {
		Error(0, 0,
	    "Warning: logging process died -- logging to file has stopped.");
		globalinfo.log.pid = -1;
	    } else {
		Error(0, 0,
		    "Wait() surprised! check_pass() received pid %d;\n\t\
expected child pid = %d; waiting for correct pid...\n", pid, child);
	    }
	}
	if (pid == -1) {
	    /* wait() failed */
	    Error(1, 0, "Waiting for timestamp creation process: ");
	    return -1;
	} else if (status == 0) {
	    /* child succeeded */
	    return 0;
	} else if (status != 0) {
	    /* child failed to authenticate user */
	    Error(0, 0, "Authentication failed\n");
	    return -1;
	}
	/* NOTREACHED */
    }
	
    /*
     * If here, must be in child.
     * Unless this command specifies a timeout > 0, don't generate
     * error messages if we fail to store timestamp info.
     * We'll use exit code 1 for failure to authenticate;
     * otherwise we'll exit 0 (success).
     */
    if (child != 0) {
	Error(0, 1, "Internal error: child=%d; should be 0!\n", child);
    }

    /*
     * setuid, then make and/or test the directory
     */
    if (*localinfo.authinfo.ts_user != '\0') {
	stringcopy(localinfo.user, localinfo.authinfo.ts_user,
					    sizeof(localinfo.user));
	*localinfo.group = '\0';
	*localinfo.u_g = '\0';
	if (set_u_g() == -1) {
	    if (localinfo.authinfo.timeout > 0) {
		Error(1, 0,
		"failed to setuid to user=%s before setting timestamp file: ",
		    localinfo.user);
	    }
	    exit(2);
	}
    }
    /* Make the timestamp directory name */
    timestamp_creatok = 1;
    mkdirMsg[0] = '\0';
    if (!makedirname(TIMESTAMP_DIR,
		    globalinfo.authinfo.perhost ? userinfo.hostname : "",
		    file, &err, mkdirMsg)) {
	timestamp_creatok = 0;
	if (localinfo.authinfo.timeout > 0 && mkdirMsg[0]) {
	    if (err) {
		errno = err;
		Error(1, 0, "Warning: can't record timestamp: %s: ",
			mkdirMsg);
	    } else {
		Error(0, 0, "Warning: can't record timestamp: %s",
			mkdirMsg);
	    }
	}
    }

    /* Make the timestamp directory */
    if (timestamp_creatok) {
	mkdirMsg[0] = '\0';
	if (makedir(file, &err, mkdirMsg) == -1) {
	    timestamp_creatok = 0;
	    if (localinfo.authinfo.timeout > 0 && mkdirMsg[0]) {
		if (err) {
		    errno = err;
		    Error(1, 0,
			"Warning: can't record timestamp: %s: ",
			mkdirMsg);
		} else {
		    Error(0, 0,
			"Warning: can't record timestamp: %s",
			mkdirMsg);
		}
	    }
	}
    }

    /* Make the file in the timestamp directory */
    if (timestamp_creatok) {
	l = strlen(file) + 1 + strlen(userinfo.caller.pw_name);
	if (l >= MAXPATHLEN) {
	    if (localinfo.authinfo.timeout > 0) {
		Error(1, 0,
		"Can't create timestamp file: would exceed MAXPATHLEN = %d\n",
		MAXPATHLEN);
	    }
	    timestamp_creatok = 0;
	}
    }

    if (timestamp_creatok) {
	strcat(file, "/");
	strcat(file, userinfo.caller.pw_name);

	istat = stat(file, &st);
	if (istat != 0 && errno != ENOENT) {
	    if (localinfo.authinfo.timeout > 0) {
		Error(1, 0, "Failed to stat timestamp file `%s': ", file);
	    }
	    timestamp_creatok = 0;
	}
    }

    if (timestamp_creatok) {
	file_exists = (istat == 0);
	if (file_exists) {
	    timed_out = (localinfo.authinfo.timeout < 1) ||
			((time(NULL)-st.st_mtime) >
			localinfo.authinfo.timeout*60);
	}
    }

    got_auth=0;
    if (localinfo.authinfo.user[0]) {
	authuser = localinfo.authinfo.user;	/* authuser=xxx local opt */
    } else if (globalinfo.authinfo.user[0]) {
	authuser = globalinfo.authinfo.user;	/* authuser=xxx global opt */
    } else {
	authuser = userinfo.caller.pw_name;	/* default: caller's pw */
    }
    if (!timestamp_creatok || !file_exists || timed_out) {
	switch (localinfo.authinfo.method) {
	case SUPER_AUTH_PASSWORD:
	    got_auth = (get_password(cmd, userinfo.caller.pw_name, authuser,
				    userinfo.salt, userinfo.encr) == 1);
	    break;
	case SUPER_AUTH_PAM:
#if WITH_PAM
	    got_auth = (get_pam(cmd, userinfo.caller.pw_name, authuser) == 1);
#else
	    Error(0, 1, "Auth method is PAM, but this copy of super \
was compiled without PAM support!");
#endif
	    break;
	default:
	    Error(0, 1, "Internal error in check_auth: \
unknown auth method %d!\n", localinfo.authinfo.method);
	}
	if (!got_auth)
	    return -1;
    }

    /* NOTE: A race condition is possible between two super's, with the
     * worst-case effect of an error message and failure to run the
     * requested command.
     */

    /* If file exists, and we haven't (a) gotten the password again, or
     * (b) supposed to automatically refresh the timestamp, do nothing to
     * the file except ensure that we own it.

     * Otherwise create the file (unlink it first if it exists).
     */
    if (!timestamp_creatok) {
	/* do nothing: we failed in setting up the timestamp file,
	 * so don't try to update it.
	 */

    } else if (file_exists && !(got_auth || localinfo.authinfo.renewtime)) {
	if (st.st_uid != geteuid())
	    return Error(0, 0,
	"Timestamp file `%s' is owned by uid=%d, but expected owner=%d.\n\
\tN.B. If you recently changed the value of timestampuid=xxx, all existing\n\
\tfiles in the timestamp directory _may_ have the wrong owner; delete them.\n\
\t(No security hole appears when you delete a timestamp file.)\n",
	    file, st.st_uid, geteuid());

    } else {
	if (file_exists) {
	    if (unlink(file) != 0)
		return Error(1, 0,
		    "Failed to unlink() timestamp file `%s': ", file);
	}
	if (open(file, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0200) == -1)
	    return Error(1, 0,
		"Failed to open() timestamp file `%s': ", file);
    }
    exit(0);
    /* UNREACHABLE */
    Error(0, 1, "Unreachable code!\n");
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Encrypt a password.  Returns value from crypt() or similar. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
docrypt(prompt, salt)
char *prompt;
char *salt;
{
    char *crypt();
    char *getpass();
    char buf[300];
    int n;

    n = s_getpass(prompt, use_stdin, buf, sizeof(buf));
    if (n < 0) {
	Error(0, 1, "Failed to get password.\n");
    } else if (n >= sizeof(buf)) {
	Error(0, 1, "Buffer too small (%d chars) to hold input password!\n",
							(int) sizeof(buf)-1);
    }

#ifdef _HPUX_SOURCE

    if (iscomsec()) {
	#if (HPUX_MAJOR == 10)
	    return bigcrypt(buf, salt);
	#endif
	#if (HPUX_MAJOR == 11)
	    return bigcrypt(buf, salt);
	#endif
	return crypt(buf, salt);
    } else {
	return crypt(buf, salt);
    }

#else

    return crypt(buf, salt);

#endif
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Gets a user's encrypted password. Returns -1 on failure, +1 on success */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
get_password(cmd, caller, user, salt, encr)
char *cmd;
char *caller;	/* the person who invoked super */
char *user;	/* the person whose authentication is required */
char *salt;
char *encr;
{
    /* No such file or password timed out -- get password */
    int ntry, match;
    char msg[500];
    char *encrypted = NULL;

    if (strcmp(encr, "") == 0) {
	return Error(0, 0,
	"Command requires a password, but user `%s' has no password\n", user);
    }
    for (ntry=0, match=0; ntry < MAXTRY && !match; ntry++) {
	if (ntry == 0) {
	    if (localinfo.authinfo.prompt && localinfo.authinfo.prompt[0]) {
		stringcopy(msg,
			do_variables(localinfo.authinfo.prompt), sizeof(msg));
	    } else if (strcmp(caller, user) == 0) {
		(void) sprintf(msg,
	"Your password is required for super command `%.400s'...\nPassword: ",
		cmd);
	    } else {
		(void) sprintf(msg,
	"%c%s's password is required for super command `%.400s'...\nPassword: ",
		toupper(*user), user+1, cmd);
	    }
	} else {
	    strcpy(msg, "Password incorrect\nPassword: ");
	}
        encrypted = docrypt(msg, salt);

	if (encr && encrypted) {
	    match = (strcmp(encr, encrypted) == 0);
	} else {
	    match = 0;
	}
    }
    if (!match)
	return Error(0, 0, "Password incorrect\n");
    return 1;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Looks up a group name or number (as a text string), returns gid.
 * Accepts special names (e.g. <caller> or <owner>).
 * Returns -1 if no such group.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
findgid(allowbrackets, grouplabel)
int allowbrackets;	/* accept names like <caller> */
char *grouplabel;	/* name or numeric form */
{
    struct group *gp;
    SETGRENT_T setgrent();
    void endgrent();
    int numeric_gid;
    int found_gid, is_numeric=0;
    int l=strlen(grouplabel);

    char c;

    if (allowbrackets && grouplabel[0] == '<' && grouplabel[l-1] == '>') {
	/* Translate special name */
	struct passwd *pw = getpwentry(1, grouplabel);
	if (!pw)
	    return -1;
	else
	    return pw->pw_gid;
    }

    is_numeric = (sscanf(grouplabel, "%d%c", &numeric_gid, &c) == 1);

    if (is_numeric)
	return numeric_gid;

    /* Grouplabel didn't look like a number (according to sscanf),
     * so look up its name.
     */
    setgrent();
    for (found_gid = -1, gp = getgrent(); gp; gp = getgrent()) {
	if (strcmp(grouplabel, gp->gr_name) == 0) {
	    /* Found the gid in the group file */
	    found_gid = gp->gr_gid;
	    break;
	}
    }
    endgrent();

    return found_gid;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Adds condition to condition list.
 *	returns -1 on syntax error, malloc error, etc;
 *	returns 0 otherwise.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
InsertCondition(condition, s, isglobal)
char *condition;	/* condition to insert: xxx~yyy */
char *s;		/* pts to yyy in condition */
int isglobal;		/* Is this a per-command or global condition? */
{
    char **globlist;
    TimeList *tl;
    int i;
    int invert = (*condition == '!');

    if ( invert )
	condition++;

    /* All conditions accept {a,b,c}  or a,b,c  as lists.
     * Form the globlist and pass along...
     */
    /* Do brace globbing */
    if ((i=globbraces(s, 1, &globlist)) != 0) {
	/* Local Condition */
	return Error(0, 0, "$$Missing `%c'.\n", i);
    }
    if (balancedbraces(s) != 0) {
	return Error(0, 0, "$$Unbalanced braces in %s.\n", s);
    }

    if (STRMATCH3("time", condition, s-1)) {
	tl = isglobal ? &globalinfo.timeafter : &localinfo.time;
	if (InsertTimeList(s, globlist,
			tl, isglobal ? "global" : "local", invert) == -1)
	    return -1;

    } else if (STRMATCH3("user", condition, s-1)) {
	if (InsertUserList(s, globlist,
		    &localinfo.userpats, &localinfo.origtext, invert) == -1)
	    return -1;

    } else {
	return Error(0, 0,
		"$$\n\tInternal error: unrecognized condition <%s>.\n",
		condition);
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Add a user/group/host pattern to a list.
 *	returns -1 on syntax error, malloc error, etc;
 *	returns 0 otherwise.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
InsertUserList(wd, wdlist, sl, otl, invert)
char *wd;	/* Pattern to match; must NOT have leading '!'; braces ok;
		 * disallow '<', '>'.
		 */
char **wdlist;	/* brace-expanded u/g/h list */
Simple2List *sl;/* Insert user list elements (i.e. argument wdlist)
		 * at sl->next.
		 */
SimpleList *otl;/* Insert original text (i.e. argument wd) * at otl->next.  */
int invert;	/* Inverts the test */
{
    int iwd;
    char *tok, *s;
    SimpleList *new;
    Simple2List *new2;

    /* Check for illegal characters */
    if ((s=strchr(wd, '>')) || (s=strchr(wd, '<'))) {
	if (s-wd == 4 && strncmp(wd, "time", 4) == 0) {
	    return Error(0, 0,
	    "$$\n\tPermittedUser patterns may not use '>' or '<';\n\
\tyou used '%s'; perhaps you meant to write 'time~%s'\n", wd, s);
	} else {
	    return Error(0, 0,
	    "$$\n\tPermittedUser patterns may not use '>' or '<';\n\
\tyou used '%s'.\n", wd);
	}
    }
    new = (SimpleList *) malloc(sizeof(SimpleList));
    if (!new)
	return Error(0, 0, "$$\n\tFailed to malloc space for PermittedUser\n");
    new->next = otl->next;
    new->pat = (char *) malloc(strlen(wd) + 1);
    if (!new->pat)
	return Error(0, 0,
		"$$\n\tFailed to malloc space for PermittedUser pat\n");
    strcpy(new->pat, wd);
    otl->next = new;

    for (iwd=0; (tok=wdlist[iwd]); iwd++) {
	new2 = (Simple2List *) malloc(sizeof(Simple2List));
	if (!new2)
	    return Error(0, 0,
		"$$\n\tFailed to malloc space for PermittedUser\n");
	new2->next = sl->next;
	new2->other = otl->next;
	new2->pat = (char *) malloc(strlen(tok) + (invert ? 2 : 1));
	if (!new2->pat)
	    return Error(0, 0,
		"$$\n\tFailed to malloc space for PermittedUser pat\n");
	if (invert) {
	    *new2->pat = '!';
	    strcpy(new2->pat+1, tok);
	} else {
	    strcpy(new2->pat, tok);
	}
	sl->next = new2;
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Match a list of user/group/host pattern against the present user. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
match_ugh_user(sl, isglobal)
Simple2List *sl;/* A list of user pats to match against, starting at sl->next */
int isglobal;	/* !0 means its from a global def */
{
    /* Checks if user is matched against each elt in sl.
     * Sets matches.match_user if user matches; sets it to 1 if the last
     * match is non-inverting, 0 otherwise.

     * BUT! the list created by the InsertUserList function
     * is in reverse order, so we only need to find the first
     * entry in the list that is a match (+ or -) and stop there!
     */

    int invert, match;
    int check_ugh P__((char *, char *));

    for (match=0, sl=sl->next; sl && !match; sl=sl->next) {
	invert = *sl->pat == '!';
	if (check_ugh(sl->other->pat, invert ? sl->pat+1 : sl->pat) == 0) {
	    match = 1;
	    matches.user = invert ? 0 : 1;
	    if (debug || it_came_from_cmdline)
		(void) fprintf(stderr,
			"\tPermission %s: %s pattern %suser~%s\n",
			invert ? "denied" : "allowed",
			isglobal ? "global" : "per-cmd",
			invert ? "!" : "", sl->other->pat);
	} else if (debug || it_came_from_cmdline) {
	    (void) fprintf(stderr, "\tNot applicable: %s pattern %suser~%s\n",
			isglobal ? "global" : "per-cmd",
			invert ? "!" : "", sl->other->pat);
	}
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check pattern against a hostname.  If the hostname is fully-qualified,
 * then try stripping off each of the domains to find a match.
 * Return -1 on failure to match; 0 on success.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_host(pat, host)
char *pat, *host;
{
    int is_netgroup = 0;
    int match;
    char *p, *dotp;

    if (*pat == '+') {
       is_netgroup = 1;
       ++pat;
    }

    match = (is_netgroup ? netgrp_h_compare(pat, host)
						: (*pat_compare)(host));

    dotp = strrchr (host, '.');
    while (dotp && !match) {
       *dotp = 0;
       match = (is_netgroup ? netgrp_h_compare(pat, host)
						: (*pat_compare)(host));
       p = strrchr (host, '.');
       *dotp = '.';
       dotp = p;
    }
    return (match ? 0 : -1);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Try to match a string to a pattern. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
match_pattern(match, do_glob, str, pattern)
int match;	/* Return input value of match on failure; 1 on success */
int do_glob;	/* 0: no brace globbing;
		 * 1: brace glob;
		 * >1: wrap in braces, then brace-glob.
		 */
char *str;
char *pattern;
{
    int i, ipat, wrap;
    char *tok, tokbuf[1000];
    char **patlist, *pat1list[2];
    char chkbuf[1024];


    if (do_glob != 0) {
	/* Do brace globbing on the pattern */
	wrap = (do_glob > 1) ? 1 : 0;
	if ((i=globbraces(pattern, wrap, &patlist)) != 0) {
	    Error(0, 0, "$$Missing `%c'.\n", i);
	    return match;
	}
    } else {
	pat1list[0] = pattern;
	pat1list[1] = NULL;
	patlist = pat1list;
    }
    if (balancedbraces(pattern) != 0) {
	Error(0, 0, "$$Unbalanced braces in %s.\n", pattern);
	return match;
    }

    for (ipat=0; (tok=patlist[ipat]); ipat++) {
	strcpy(tokbuf, tok);
	anchor(tok, chkbuf);			/* Anchor all matches */
	if ((*pat_compile)(chkbuf) != NULL) {
	    Error(0, 0, "$$\n\tBad command pattern: `%s'.\n", pattern);
	    return match;

	} else if ((*pat_compare)(str) == 1) {
		if (debug)
		    (void) fprintf(stderr,
			"\tMatched user's command=%s to CmdPattern=%s\n",
			str, pattern);
		return 1;

	} else if (debug) {
	    (void) fprintf(stderr,
		    "\tNo match user's command=%s to CmdPattern=%s\n",
			str, pattern);
	}
    }
    return match;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check a single user/group/host string
 * Return -1 on failure to match; 0 on success.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_ugh(origtext, token)
char *origtext;		/* original text -- for error messages */
char *token;		/* user/group/host pattern */
{
    char chkbuf[1024];
    char *userpat, *grouppat, *hostpat;
    char *colon;
    int match, i;

    if (strlen(token) > sizeof(chkbuf)-4) {
	return Error(0, 1, "$$Can't handle patterns larger than %d chars.\n",
		(int) sizeof(chkbuf)-4);
    }

    /* Split into user:group@host; check host part first (if it exists) */
    if ((hostpat = strchr(token, '@'))) {
	if (hostpat[1] == 0) {
	    return Error(0, 0,
	    	"$$Missing hostname in pattern `%s'.\n", origtext);
	}
	*hostpat++ = 0;
 
	match = -1;
	if (hostpat[0] == '+') {

#ifdef HAVE_INNETGR
	    if (hostpat[1] == 0) {
		return Error(0, 0,
			"$$Missing netgroupname in pattern `%s'.\n", origtext);
	    }
	    match = check_host(hostpat, userinfo.hostname);
#else
	    return Error(0, 0, 
	"$$hostnames may not begin with `+' since this super() was compiled\n\
without -DHAVE_INNETGR.\n");
#endif
	} else {
	    strtolower(hostpat);
	    anchor(hostpat, chkbuf);    /* Force all matches to be anchored */
	    if ((*pat_compile)(chkbuf) != NULL) {
		return Error(0, 0, "$$bad host pattern: `%s'.\n", origtext);
	    }
	}
 
	if (match == -1)
	    match = check_host(hostpat, userinfo.lc_hostname);

	if (debug > 1)
	    fprintf(stderr,
		"\thost pattern <%s> %s user's host <%s>\n",
		hostpat, (match == -1) ? "did not match" : "matched",
		userinfo.lc_hostname);

	if (match == -1)
	    return -1;
    }

    colon = grouppat = strchr(token, ':');
    userpat = token;
    if (*token == '\0' && !hostpat) {
	/* Nothing in pattern?! */
	return Error(0, 0, "$$\n\tUnacceptable pattern `%s'.\n", origtext);

    } else if (*token == '\0') {
	userpat = grouppat = "^.*$";		/* only hostname given */

    } else if (grouppat && *(grouppat+1)) {	/* pat is "uuu:ggg or ":ggg" */
	if (token == grouppat)
	    userpat = "^.*$";			/* pat is ":ggg" */
	*grouppat++ = '\0';

    } else {					/* pat is "uuu" or "uuu:" */
	if (grouppat)
	    *grouppat = '\0';			/* pat is "uuu:" */
	grouppat = "^.*$";
    }
    if (strchr(grouppat, ':')) {
	return Error(0, 0, "$$\n\tGroup pattern `%s' contains a colon!\n",
			grouppat);
    }

    if (globalinfo.group_slash == 0 && strchr(grouppat, '/')) {
	return Error(0, 0, "$$\n\tFormat error in super.tab file: \
group pattern `%s' contains a slash.\n\
\tPerhaps you meant to use Cmd::Filename, but forgot one colon,\n\
\tso it looks like User:Group?  If you really need to allow\n\
\tslashes in group patterns, use global option group_slash=y.\n", grouppat);
    }


#ifdef HAVE_INNETGR
    if (userpat[0] == '+') {
	if (userpat[1] == 0) {
	    return Error(0, 0,
			"$$Missing netgroupname in pattern `%s'.\n", origtext);
	}
	match = netgrp_u_compare(&userpat[1], userinfo.caller.pw_name);
    } else
#endif
    {
	anchor(userpat, chkbuf);		/* Anchor all matches */
	if ((*pat_compile)(chkbuf) != NULL) {
	    return Error(0, 0, "$$\n\tbad user pattern: `%s'.\n", origtext);
	}
	match = (*pat_compare)(userinfo.caller.pw_name);
#ifdef MATCH_DECIMAL_UID
	if (match != 1) {
	    /* Enabling MATCH_DECIMAL_UID allows the userpat to be
	     * numeric, as an alternative to being interpreted as a
	     * user name: after checking the username, we check if the
	     * user's uid, as a decimal text value, matches the user
	     * pattern userpat.
	     */
	    char buf[20];
	    (void) sprintf(buf, "%d", userinfo.caller.pw_uid);
	    match = (*pat_compare)(buf);
	}
#endif
    }
    if (debug > 1)
	fprintf(stderr,
	    "\tuser pattern <%s> %s username <%s>\n",
	    userpat, (match != 1) ? "did not match" : "matched",
	    userinfo.caller.pw_name);

    if (match != 1)
	return -1;

    anchor(grouppat, chkbuf);
    i = ingroup(userinfo.caller.pw_name, userinfo.caller.pw_gid, chkbuf);
    if (i == -1)
	return Error(0, 0, "$$\n\tbad group pattern <%s>\n", origtext);

    if (debug > 1)
	fprintf(stderr,
	    "\tuser <%s> is %sa member of group <%s>\n",
	    userinfo.caller.pw_name, (i != 1) ? "not " : "", grouppat);

    if (i != 1)
	return -1;
    
    return 0;				/* Success! */
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Determines if user's group matches a group pattern. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static int created_group_table = 0;

int
ingroup(user, gid, gp_pat)
char *user;
gid_t gid;
char *gp_pat;	/* pattern to match */
{
    /* Use:
     *	ingroup(user, gid, gp_pat)
     * Returns:
     *	1 if the user is in a group matching the regex pattern gp_pat.
     *	0 if the user isn't in a group matching the pattern.
     *	-1 if pattern failed to compile.

     * SIDE-EFFECT: uses pat_compile/pat_compare!
     *			-- messes up caller's use of same!

     * Examples:
     *	ingroup("joe", joes_gid, "xyz")
     * returns !0 if user joe is in group "xyz".
     *	ingroup("joe", joes_gid, "xy.*")
     * returns !0 if user joe is in any group matching "xy.*".

     */

    struct group *gp;
    char **mem;
    char buf[20];
    SETGRENT_T setgrent();
    void endgrent();

    ENTRY item, *found_item;
    int passed, klen;

    if ((*pat_compile)(gp_pat) != (char *)0 )
	return -1;

    if (!created_group_table) {
        if (!s_hcreate(HS_GROUP, 1000)) {
	    return Error(0, 0,
		"$$Couldn't allocate hash table for group processing\n");
	}
        created_group_table = 1;
    }
    /* Catenate user, gid and gp_pat for the key such that
     * "abc" + 10 + "de" is not the same as "ab" + 1 + "0cde".
     * Use "\n", as that will have been stripped from input before we get here.
     */
    if (snprintf(keybuf, MAX_KEYLEN, "%s\n%d\n%s", user, gid, gp_pat)
             >= MAX_KEYLEN) {
         return Error(0, 0,
             "$$user/gid/gp_pat too large for user ng processing\n");
    }

    item.key = keybuf;
    if (found_item = s_hsearch(HS_GROUP, item, FIND)) {
        return found_item->data == YES? 1: 0;
    }

    passed = 0;     /* Assume the worst */

    /* Search group file for groups user is in.  For each group of which
     * the user is a member, test a match to the pattern.
     */
    setgrent();
    for (gp = getgrent(); gp; gp = getgrent()) {
	/* The gr_mem list only shows usernames added in the /etc/group file,
	 * and not any users assigned to the group in the passwd file.
	 * Thus discover group membership by first checking the user's
	 * group from the password file (gp->gr_gid) against this group's
	 * gid, then check to see if this user is in the gp->gr_mem list.
	 */
	if (gid != gp->gr_gid) {
	    for (mem = gp->gr_mem; *mem ; mem++)
		if (strcmp(*mem, user) == 0)
		    break;
	    if (!*mem)
		continue;			/* not in group */
	}
	/* if here, the user is in group gp; now check if group
	 * name gp->gr_name matches group pattern gp_pat.
	 */
	if ((*pat_compare)(gp->gr_name) == 1) {
	    /* successful match -- user is in a group that matches gp_pat */
	    endgrent();
	    /* return 1; */
            passed = 1;
            goto found_it;
	}
#ifdef MATCH_DECIMAL_GID
	else {
	    /* Enabling MATCH_DECIMAL_GID allows the gp_pat to be
	     * numeric, as an alternative to being interpreted as a
	     * group name: we check if the group id gp->gr_gid, as a
	     * decimal text value, matches the group pattern gp_pat.
	     */
	    (void) sprintf(buf, "%d", gp->gr_gid);
	    if ((*pat_compare)(buf) == 1){
		/* successful match -- user is in a group that matches gp_pat */
		endgrent();
		/* return 1; */
                passed = 1;
                goto found_it;
	    }
	}
#endif
    }

#ifdef MATCH_DECIMAL_GID
    /* We haven't found any group from /etc/group to which we belong that
     * matches the pattern.  It is possible that the user's group id from the
     * password file isn't in the /etc/group file at all, in which case the
     * user's group won't have matched the pattern since we've only checked
     * /etc/group entries so far.  Now check the numeric id from the
     * /etc/passwd file against the pattern.
     */
    (void) sprintf(buf, "%d", gid);
    if ((*pat_compare)(buf) == 1){
	endgrent();
	/* return 1; */
        passed = 1;
        goto found_it;
    }
#endif

    endgrent();
    /* return 0; */

found_it:

    /* Have to make this key a permanent copy for the hash */

    klen = strlen(keybuf);
    item.key = malloc(klen + 1);
    if (!item.key) {
        return Error(0, 0,
           "$$out of memory for group processing\n");
    }
    strcpy(item.key, keybuf);
    item.data = passed? YES: NO;
    if (!s_hsearch(HS_GROUP, item, ENTER)) {
        return Error(0, 0,
           "$$hash set failure for group processing\n");
    }
    return passed;
}

