static const char rcsid[] = "$Id: super.c,v 1.613 2007/12/20 19:14:30 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"

/*
 * Super allows users to execute other programs, particularly
 * scripts, as root (or another user/group), without unduly
 * compromising security.
 * 
 * Use:
 * 
 *     $0 -h, $0 -V
 *		...give usage info or print just the version number.
 *
 *     $0 [-H | -f] [-F superfile] [ -T hh[:mm][/day] ] [-U uid] [-G gid]
 *		...lists allowed commands.
 *
 *     $0 [-r path] [-o path] [args...]
 *		...execute command.
 *
 *     $0 -b
 *		...lists builtin variables (useful when creating super.tab).
 *
 *     $0 [-d | -D | -t] [-F superfile] [-T time] [-U uid] [-G gid] [-M mach] \
 *			[-r reqpath] [--] [commandname [args...]]
 *     $0 [-d | -D | -t] [-F superfile] [-T time] [-U uid] [-G gid] [-M mach] \
 *			[-r reqpath] -o path [--] [args...]
 *		...debug mode (-d, -D) or test mode (-t).
 *
 *     $0 -c [superfile]
 *		...just check syntax of the superfile.
 * 
 * Options:
 *	-b -- print the "builtin" variables, then exit.
 *	-c [superfile] -- check syntax of superfile, but don't execute anything.
 *	-d -- debug mode: show what would be done,
 *		but don't actually execute a command.
 *	-D -- verbose debug mode: same as -d, plus tell more about variables.
 *	-t -- test mode: test if requested command is valid, but don't
 *		execute it.  Exit code=0 if valid, else 1.
 *	-F superfile -- names the super file to use; for testing only.
 *		No command will actually be executed.
 *	-G gid -- act as if the invoking user was group gid; for testing only.
 *		No command will actually be executed.
 *	-M mach -- act as if the machine (hostname) was mach; for testing only.
 *		No command will actually be executed.
 *	-T hh[:mm][/day] -- act as if the command is executed at the specified
 *		time; for testing only.  No command will actually be executed.
 *	-h -- usage: print usage info, then exit.
 *	-H -- a long-winded listing of allowed commands for this user,
 *		then exit.
 *	-f -- (just the facts, ma'm) a version of -H that prints no extra
 *	      info, just a list of what you can execute, using the format:
 *		Cmd FullPath [initial args]
 *		Cmd FullPath [initial args]
 *		...
 *	      Useful for scripts that want a list of what you may execute.
 *	-S -- force stdin to be used for reading passwords.
 *	-U uid -- act as if the invoking user was user uid; for testing only.
 *		No command will actually be executed.
 *	-V -- print version information, then exit.
 *	-o file -- Per-user .supertab linking: if /path/to/xyz is a symlink
 *		to a user's personal .supertab file, and the .supertab file
 *		begins with #! /path/to/super -o, then the shell will invoke
 *		super with arguments like super -o /path/to/xyz [args],
 *		and super will treat this as 'super <username>:xyz [args].
 *		MUST be last argument; everything following -o file is treated
 *		as user's arguments.
 *	-r path -- error if the FullPath isn't the same file as this path.
 *		For sanity checking; a program can use
 *			test "X$SUPERCMD" = "X$prog" || \
 *			    exec /usr/local/bin/super -r $0 $prog ${1+"$@"}
 *		and it will be an error if 'super <prog>' isn't going to
 *		invoke this file, ie $0.
 * 
 * The super.tab file names each command that super will execute, and
 * says who can use it.  See super.tab.summary for an overview.

 */

/* Non-STD-C implementations do not have uniform ways of pasting strings
 * together.  So we will compose the actual superfile and super.init
 * names at runtime.
 */
char superfile_writable[MAXPATHLEN+100];/* The super.tab file -- put it
					 * into a char array so we can
					 * overwrite it later, if nec.
					 */
char *superfile = superfile_writable;
char superfile_init[MAXPATHLEN+100];	/* The super.init file */

/* Global info */
GlobalInfo globalinfo = {
	"",		/* owner (required owner of file) */
	NULL,		/* chdir_path (char *) */
	0,		/* relative_path (bool) */
	0,		/* group_slash (bool) */
	MAXENVLEN,	/* maximum length of environment variable defn */
	NULL,		/* additional permitted envvars */
	0,		/* nice_incr (int) */
	0,		/* mask (umask) */
	MAXLEN1ARG,	/* maximum length of a single argument */
	MAXLENARGS,	/* maximum length of all arguments, combined */
	{-1,-1},	/* min, max number of args (-1 = no limit) */
	{NULL,0,0,NULL},		/* argpats */

	 {		/* Global password requirements */
		0,	/* bool: is authentication required? */
		SUPER_AUTH_PASSWORD, /* authentication method, if required */
		5,	/* timeout (min) */
		0,	/* renewtime (bool) */
		1,	/* perhost (bool) */
		"",	/* authuser */
		"",	/* user (user who owns timestampuid file) */
		NULL},	/* prompt */


	{0,NULL,NULL},	/* userbefore: list of u/g/h pats before per-cmd pats */
    	{0,NULL,NULL},	/* userafter: list of u/g/h pats after per-cmd pats */
    	{0,NULL},	/* b_a_text: list of original text for above */
	1,		/* user_clear: (bool: clear if new val seen) */

	{{0,0,0,0},	/* timebefore: permitted times before per-cmd pats */
		NULL},
	{{0,0,0,0},	/* timeafter: permitted times after per-cmd pats */
		NULL},
	1,		/* time_clear: clear if new val seen */

	0,		/* use_after: set to !0 when we see <> */

	{ NULL,		/* log: FILE *fp */
	    "",		/* filename */
	    "",		/* user: value of loguid=xxx */
	    0,		/* uid: UID under which we open logfile */
	    -1,		/* pid: PID of the logger process */
	    0,		/* newfile: !0 if logfile given but not yet used */
	    0,		/* newuid: !0 if loguid given but not yet used */
	    LOG_INFO },	/* syslog() priority for success message */

	"",		/* mailcmd */
	0,		/* mail_success (bool) */
	1,		/* use gethostbyname (bool) */

    	{0},		/* groups[]: gid's of supplementary groups */
    	GROUPS_NOTSET,	/* ngroups: number of supplementary groups */
    	0,		/* groups_added */
	{NULL},		/* first element of the setenv array.  We keep this
			 * at end of global struct, so that we don't have
			 * to initialize every element in order to reach other
			 * struct elements.
			 */
	};

/* The list of currently open files */
FileList *currfile = NULL;

char authInitMsg1[1024] = "";	/* message from auth init, if any */
char authInitMsg2[1024] = "";	/* suppl message from auth init, if any */
int authInitErrno = 0;		/* set to !0, if there's an errno to go
				 * with the authInitMsg1.
				 */

/* The struct of what things we've matched */
Conditions matches;

/* The localinfo struct is filled in a bit at a time until it completely
 * describes the caller, the program to invoke, any options
 * set in the control line, etc.
 */
UserInfo userinfo;

LocalInfo localinfo = {
	{NULL, -1, 0, "", 0, 0},	/* Initialize ProgMatch empty */
					/* Other elements that need
					 * start initializers are done
					 * through explict assignment.
					 */
		};

extern char *s_re_comp P__(( char *));	/* regular-expression compiler */
int shell_compare P__(( char * ));	/* s_re_comp()-style i/f to wildmat() */
extern int s_re_exec P__(( char * ));	/* regular-expression comparison */
char *shell_compile P__(( char * ));	/* s_re_exec()-style i/f to wildmat() */


char *prog;				/* this program */
int debug=0;				/* Set by the debug options flag */
int use_stdin=0;			/* Set by the -S flag */
int it_came_from_cmdline=0;		/* Set by -F/-T/-U/-G/-M flags */
int test_mode=0;			/* Set by -t flag */
int check_syntax=0;			/* Set by the -c options flag */
int using_user_supertab = 0;		/* !0 means using a user's .supertab */
SimpleList Var_Value = {NULL, NULL};

/* Routines used to compile/match user/group/host patterns */
char *(*pat_compile) P__((char *)) = s_re_comp;
int (*pat_compare) P__((char *)) = s_re_exec;
int need_re_anchor = 1;


/* For strqtokS */
unsigned char my_qm[256];		/* our quotemarks */
unsigned char my_cc[256];		/* our comment characters */

/* Core dump restrictions were shamelessly borrowed from Wietse
 * Venema's logdaemon code, to disable core dumps with cleartext
 * or shadow passwords.
 */
#ifdef RLIMIT_CORE
    struct rlimit old_core_limit;
    struct rlimit new_core_limit;
#endif

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
main(argc, argv)
int argc;
char **argv;
{
    int status, n_builtin;
    char *s, *cmd, *path, *user;
    char **arglist, **envp;
    extern char *error_prog;
    int iarg, givehelp, giveversion, verbosity, printvars;
    char *o_file;	/* The argument to -o xxx */
    char *r_path;	/* The argument to -r path */

#ifdef RLIMIT_CORE
    getrlimit(RLIMIT_CORE, &old_core_limit);
    new_core_limit.rlim_cur = 0;
    new_core_limit.rlim_max = old_core_limit.rlim_max;
    setrlimit(RLIMIT_CORE, &new_core_limit);
#endif
    check_stdio();
    sprintf(superfile_writable, "%s/%s", SUPERDIR, "super.tab");
    sprintf(superfile_init, "%s/%s", SUPERDIR, "super.init");

    s = strrchr(argv[0], '/');
    prog = (s && *(s+1)) ? s+1 : argv[0];
    error_prog = ONETRUENAME;	/* same as prog, but used by Error() */

    debug = check_syntax = giveversion = givehelp = printvars = 0;
    verbosity = HELP_BASIC;	/* only matters if *givehelp != 0 */
    o_file = NULL;
    r_path = NULL;

    error_srcfile = superfile;	/* error messages w/ line nums refer to this */
    error_line = -1;
    if (add_variable("$", "$") == -1)	/* Built-in variable $$ -> "$" */
	return 1;

    if (init_userinfo() == -1)	/* Initialize userinfo struct. */
	return 1;
    init_globalinfo();		/* Initialize globalinfo struct. */
    init_localinfo();		/* Initialize localinfo struct. */
    add_variable("PATTERNS", "regex");	/* default pattern lookup */

    add_builtin_variables();	/* Define variables that describe this system */

    if (get_encrypted_pw()!=0) { /* Get caller's encrypted password while we */
				/* definitely have privs.  This allows      */
				/* .supertab files to do password checking. */
	*userinfo.encr = '\0';
	*userinfo.salt = '\0';
	if (geteuid() != 0) {
	    sprintf(authInitMsg2, "\tWon't be able to do password-checking.  \
Note: this copy of super is not running setuid-root, \
so it can't check passwords on modern Unix systems.\n\n");
	} else {
	    sprintf(authInitMsg2, "\tWon't be able to do authentication \
(password-checking, or whatnot).\n\n");
	}
    }

    /* Decide if we were invoked as "super cmd args...", or 
     * as a link: cmd args...
     */
    if (strcmp(prog, ONETRUENAME) == 0) {
	/* Invoked as super [options] cmd [arg ...]. */

	iarg = do_options(argc, argv, &givehelp, &giveversion,
			&verbosity, &printvars, &o_file, &r_path);
	if (iarg < 0 || (argv[0] == NULL && !(givehelp || giveversion))) {
	    /* User screwed up; give minimal help */
	    fprintf(stderr, "Type   %s -h   for usage information.\n", prog);
	    exit(1);
	}
	if (o_file) {
	    /* Cmd is last component of the o_file path, e.g.
	     * -o a/b/c means the cmd is "c".
	     */
	    if ((cmd = strrchr(o_file, '/'))) {
		/* found last slash in path */
		cmd++;
		if (*cmd == '\0')
		    cmd = NULL;	/* shouldn't ever happen */
	    } else {
		/* there is no slash */
		cmd = o_file;
	    }
	    argv += iarg - 1;	/* Skip over the super options */

	} else {
	    /* Cmd is next argument on cmd line, if any */
	    cmd = argv[iarg];
	    argv += iarg;	/* Skip over the super options */

	    if (!cmd || !*cmd) {
		/* No cmd was given after options. */
		if (!giveversion)	/* If user didn't give -V, and didn't */
		    givehelp = 1;	/* ...give cmd, default is givehelp */
		cmd = NULL;
	    }
	}

	/* Test if we simply print variables, then bail out. */
	if (printvars) {
	    printf("Builtin variables:\n");
	    hprint(HS_VARS, print_variable);
	    exit(0);
	}
    } else {
	/* It's been invoked via link to super.  Therefore any options
	 * go to the command, not to super.
	 */
	s = strrchr(argv[0], '/');
	cmd = (s && *(s+1)) ? s+1 : argv[0];
    }

    if (debug)
	debug_hello();

    init_strqtokS();

    /*
     * Check if we need to switch to processing a user's super file
     */
    using_user_supertab = 0;
    if (o_file) {
	/* Have `-o file' */

	using_user_supertab = 1;
	user_supertab(o_file, 1, cmd);

    } else if (cmd && (s=strchr(cmd, ':'))) {
	/* Have user:command */

	using_user_supertab = 1;
	user = cmd;
	if (s == user)
	    Error(0, 1, "Commands may not begin with `:'\n");
	*s = '\0';	/* null-terminate the `user' part */
	cmd = s+1;
	if (!*cmd) {
	    /* No cmd given after `user:' */
	    if (!giveversion)		/* If user didn't give -V, and didn't */
		givehelp = 1;		/* ...give cmd, default is givehelp */
	    cmd = NULL;
	}
	user_supertab(user, 0, cmd);
    }
    /* Set a variable that a super.tab (or super.init) can use to determine
     * if we're processing a user's .supertab or not.
     */
    if (add_variable("IS_USERTAB", using_user_supertab ? "yes" : "no") == -1)
	exit(1);

    if (check_syntax)
	Error(0,0, "Checking syntax of superfile `%s'\n", superfile);

    /* Print version if explicitly requested or if giving help */
    if ((givehelp && verbosity != HELP_FACTS) || giveversion)
	(void) printf("%s version %s patchlevel %s\n",
					    prog, Version, Patchlevel);

    /* If giving version, and not giving help, and there's no command, stop */
    if (giveversion && !givehelp && !cmd)
	exit(0);

    /* Check for permission to execute, and change uid/gid as necessary */
    error_counter = 0;	/* incremented by Error() */
    if ((path = approve(cmd, givehelp, verbosity)) == NULL) {
	return 1;
    } else if (*path == '\0') {
	/* shouldn't try to execute anything */
	if (check_syntax) {
	    return (error_counter) ? 1 : 0;
	} else {
	    return 0;
	}
    }

    /* Get the arglist for the command, and null-terminate cmd if not
     * already done so.  Do this before things like get_owner, because
     * newargs() parses paths that look like "command args" and separates
     * out the command part.
     */
    arglist = newargs(path, argv, &n_builtin);

    /* Check argument lengths */
    if (check_arglistlen(argv) != 0)
	return 1;

    /* Determine ownership of the program */
    if (get_owner(path, &localinfo.file_uid, &localinfo.file_gid) != 0)
	return 1;

    /* Sanity check */
    if (check_syntax)
	Error(0, 2,
	    "Abort: shouldn't ever get here when check_syntax is set!\n");

    /* Check that the file to execute has proper ownership */
    if (check_owner() != 0)
	return 1;

    /* If nice increment is negative, we have to do it here, while we
     * are still root (note that the uid= option can be used to run
     * as non-root, and we can't do negative nice's as non-root).  Positive
     * nice increments are done later, just before exec'ing, so that we
     * don't have to do the rest of _this_ program at reduced priority.
     */
    if (localinfo.nice_incr < 0) {
	if (it_came_from_cmdline) {
	    Error(0, 0,
	   "Not applying nice increment = %d because of flag -F/-T/-U/-G/-M.\n",
		rcl_nice_incr());
	} else if (test_mode) {
	    /* silently don't apply nice increment */
	} else if (set_nice_incr() == -1) {
	    return 1;
	}
    }

    /* Check authorization requirements.
     * Just as for negative nice increment, this require root privileges
     * to read a shadow password file or switch to another uid for writing
     * the timestamp file.
     */
    if (!test_mode) {
	if (check_auth(cmd) != 0) {
	    return 1;
	}
    }

    /* Set uid/gid if necessary */
    if (using_user_supertab) {
	/* Already changed to user's uid/gid earlier */
	status = 0;
    } else {
	status = set_u_g();
    }

    /* Button up for security, and get a modified environment */
    envp = buttonup(cmd);
    if (!envp)
	return 1;

    if (r_path) {
	/* User supplied '-r path' check if it matches actual fullpath */
	if (check_rpath(r_path, path) != 0) {
	    return 1;
	}
    }

    /* Change directory if requested */
    if (status != -1)
	status = set_chdir();

    /* Set the umask value */
    set_umask();

    if (debug || it_came_from_cmdline) {
	debug_print(path, arglist, envp, n_builtin);
    }

    if (status == -1) {
	if (debug || it_came_from_cmdline) {
	    fprintf(stderr, "\n\t(This command would not be executed \
due to previously-reported problem)\n");
	}
	return 1;
    }

    if (debug) {
	fprintf(stderr,
	    "\n\t(Your command is ok, but isn't executed in debug mode.)\n");
	return 0;

    } else if (it_came_from_cmdline) {
	fprintf(stderr,
	    "\n\t(Your command is ok, but isn't executed because of one or \
more -F/-T/-U/-G/-M flags.)\n");
	return 0;

    } else if (test_mode) {
	/* Command is ok, but isn't executed because we're in test mode */
	return 0;
    }

    /* Do checks required by a checkvar=a,b,c... list */
    if (check_var_value() != 0)
	return 1;


    /* Log an informational message at LOG_INFO priority, not at
     * the usual error priority.
     */
    {
#ifdef HAVE_SYSLOG
	extern int error_priority;
	int old_pri = error_priority;
	error_priority = globalinfo.log.syslog_success;
#endif
	logmsg(cmd, arglist);
#ifdef HAVE_SYSLOG
	error_priority = old_pri;
#endif
    }
    /* Close the logfile writer, if any: we are done logging, and going
     * to exec the prog.
     */
    close_writer();

    /* If nice increment is positive, we do it here, just before
     * exec'ing.  Negative nice increments were done earlier, before
     * any change-uid's.
     */
    if (localinfo.nice_incr > 0)
	if (set_nice_incr() == -1)
	    return 1;

    /* If print option is set, write message before executing command */
    if (localinfo.print)
	puts(localinfo.print);

#ifdef RLIMIT_CORE
    /* Re-enable core dumps. */
    setrlimit(RLIMIT_CORE, &old_core_limit);
#endif

    if (execve(path, arglist, envp) == -1) {
#ifdef INTERPRETER_HACK
	if (errno == ENOEXEC) {
	    /* Open the file, check for "#!interpreter [argument]" */
	    FILE *fp;
	    char *interp, *argument, line1[1024];

	    if ((fp = fopen(path, "r")) &&		/* open the file */
		fgets(line1, sizeof(line1), fp) &&	/* read first line */
		strchr(line1, '\n') &&			/* not too long? */
		(strncmp(line1, "#!", 2) == 0) &&	/* begins "#!"? */
		(interp = strtok(line1+2, " \t\n"))) {	/* has interpreter? */

		argument = strtok(NULL, " \t\n");	/* get opt argument */

		/* Adjust the arglist -- recall it has room for this */
		if (argument) {
		    arglist -= 2;
		    arglist[0] = arglist[2];
		    arglist[1] = argument;
		    arglist[2] = path;
		} else {
		    arglist--;
		    arglist[0] = arglist[1];
		    arglist[1] = path;
		}
		(void) execve(interp, arglist, envp);
	    }
	}
#endif
	/* If here, we failed to exec the prog.  Re-open the logfile we
	 * closed above and write a message.
	 */
	if (*globalinfo.log.filename != '\0') {
	    int save_errno = errno;
	    opensuperlog();
	    errno = save_errno;
	}
	(void) Error(1,1, "command `%-.500s': Couldn't exec `%s': ", cmd, path);
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Set options; return index of arg that follows options, or -1 on error.
 * N.B. The flags are assumed to be initialized BY THE CALLER.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
do_options(argc, argv, givehelp, giveversion, verbosity, printvars,
			o_file, r_path)
int argc;
char **argv;
int *givehelp, *giveversion, *verbosity, *printvars;
char **o_file, **r_path;
{
    int iarg;
    char *mhost, *s, *t;
#ifndef HAVE_LOCALTIME
    char daynum;
#endif
    int takes_optional_superfile;
    long l;
    struct passwd *pw;

    /* NOTE WELL: THE CALLER IS EXPECTED TO HAVE INITIALIZED THE ARGUMENTS
     * TO THE DESIRED DEFAULT VALUES.  It's not repeated here because
     * this routine isn't called for super invoked via symlink, and
     * it's misleading to give the impression that the options are always
     * going to be init'd here.
     */
    *verbosity = HELP_BASIC;	/* only matters if *givehelp != 0 */

    for (iarg = 1; iarg < argc && argv[iarg][0] == '-'; iarg++) {
	if (strcmp(argv[iarg], "--") == 0) {
	    /* End of options */
	    iarg++;
	    break;
	}

	if (checkarg(argv[iarg]) != 0)
	    return -1;

	for (s = &argv[iarg][1]; *s; s++) {
	    takes_optional_superfile = 0;
	    switch (*s) {
	    case 'c':
		check_syntax = 1;
		takes_optional_superfile = 1;
		break;
	    case 'F':
		it_came_from_cmdline = 1;
		takes_optional_superfile = 1;
		break;
	    case 'G':
		it_came_from_cmdline = 1;
		if (*(s+1)) {
		    /* User gave -Ggid */
		    t = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave -G gid */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    t = argv[iarg];
		} else {
		    return Error(0, 0, "No gid specified after option -G\n");
		}
		l = findgid(1, localinfo.group);
		if (l == -1)
		    return Error(0,0,"Invalid gid specified after option -G\n");
		else
		    userinfo.orig_gid = l;
		    userinfo.caller.pw_gid = l;
		break;
	    case 'M':
		it_came_from_cmdline = 1;
		if (*(s+1)) {
		    /* User gave -Mmach */
		    mhost = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave -M mach */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    mhost = argv[iarg];
		} else {
		    return Error(0, 0, "No machine specified after option -M\n");
		}
		if (strlen(mhost) > sizeof(userinfo.hostname)-1)
		    Error(0, 1, "Machine name too long -- max allowed is %d\n",
				sizeof(userinfo.hostname)-1);
		strcpy(userinfo.hostname, mhost);
		if (canonicalize_hostname(userinfo.hostname,
					    sizeof(userinfo.hostname)) == -1)
		    return -1;

		/* also set lowercase version */
		strcpy(userinfo.lc_hostname, userinfo.hostname);
		strtolower(userinfo.lc_hostname);

		/* also adjust short form to match */
		if ((t = strchr(mhost, '.')))
		    *t = '\0';
		if (add_variable("HOST", mhost) == -1)
		    exit(1);

		break;
	    case 'T':
		it_came_from_cmdline = 1;
		if (*(s+1)) {
		    /* User gave -Ttime */
		    t = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave -T time */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    t = argv[iarg];
		} else {
		    return Error(0, 0, "No time specified after option -T\n");
		}
		if (readtime(t,
			&userinfo.ourtime.min, &userinfo.ourtime.day) != 0)
		    return -1;
		break;
	    case 'U':
		it_came_from_cmdline = 1;
		if (*(s+1)) {
		    /* User gave -Uuid */
		    t = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave -U uid */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    t = argv[iarg];
		} else {
		    return Error(0, 0, "No uid specified after option -U\n");
		}
		pw = getpwentry(1, t);
		if (!pw) {
		    return -1;
		} else {
		    /* Act as if we really were the named user */
		    userinfo.orig_uid = pw->pw_uid;
		    userinfo.orig_gid = pw->pw_gid;
		    userinfo.caller.pw_uid = pw->pw_uid;
		    userinfo.caller.pw_gid = pw->pw_gid;
		    if (!(userinfo.caller.pw_name = strdup(pw->pw_name)))
			(void) Error(0, 2,
			  "failed to malloc space for passwd struct field.\n");
		    if (!(userinfo.caller.pw_passwd = strdup(pw->pw_passwd)))
			(void) Error(0, 2,
			  "failed to malloc space for passwd struct field.\n");
		    if (!(userinfo.caller.pw_dir = strdup(pw->pw_dir)))
			(void) Error(0, 2,
			  "failed to malloc space passwd struct field.\n");

		    fprintf(stderr, "\n\tActing as if uid=%d (%s), gid=%d\n\n",
		    			pw->pw_uid, pw->pw_name, pw->pw_gid);
		}
		break;
	    case 'r':
		if (*(s+1)) {
		    /* User gave `-rpath' */
		    *r_path = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave `-r path' */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    *r_path = argv[iarg];
		} else {
		    return Error(0, 0, "No file specified after option -r\n");
		}
		break;
	    case 'o':
		if (*(s+1)) {
		    /* User gave `-ofile' */
		    *o_file = s+1;
		    s += strlen(s) - 1;
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave `-o file' */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    *o_file = argv[iarg];
		} else {
		    return Error(0, 0, "No file specified after option -o\n");
		}
		/* The `-o file' is always the end of options -- everything
		 * else is an argument to the user's command.
		 * Note that we have to return here (or else "goto") else
		 * we won't break out of the outer for-loop.
		 */
		iarg++;
		return iarg;
	    case 'b': *printvars = 1; break;
	    case 't':
		test_mode = 1;
		break;
	    case 'd':
		debug = 1;
		break;
	    case 'D':
		debug = 2;
		break;
	    case 'S': use_stdin = 1; break; break;
	    case 'V': *giveversion = 1; break;
	    case '?': *givehelp = 1; *verbosity = HELP_USAGE; break;
	    case 'h': *givehelp = 1; *verbosity = HELP_USAGE; break;
	    case 'f': *givehelp = 1; *verbosity = HELP_FACTS; break;
	    case 'H': *givehelp = 1; *verbosity = HELP_FULL; break;
	    default:
		    return Error(0, 0, "Unrecognized option `%c'\n", *s);
	    }
	    /* Check for optional superfile */
	    if (takes_optional_superfile) {
		if (*(s+1)) {
		    /* User gave -Xfile (where X is -c/-F) */
		    superfile = s+1;
		    s += strlen(s) - 1;
		    if (*o_file) {
			Error(0, 0, "*** Warning: requested file named \
by `-o %s' is overridden by file `%s'", *o_file, superfile);
			*o_file = NULL;
		    }
		} else if ((iarg+1 < argc) && argv[iarg+1][0] != '-') {
		    /* User gave -X file */
		    iarg++;
		    if (checkarg(argv[iarg]) != 0)
			return -1;
		    superfile = argv[iarg];
		    if (*o_file) {
			Error(0, 0, "*** Warning: requested file named \
by `-o %s' is overridden by file `%s'", *o_file, superfile);
			*o_file = NULL;
		    }
		} else {
		    /* No file was given.  Use the default super file */
		}
		/* Verify that this user has permission to access the file */
		if (access(superfile, R_OK) == -1)
		    Error(1, 1, "You can't read `%s': ", superfile);
		error_srcfile = superfile;

		if (strcmp(superfile, SUPERFILE) != 0) {
		    /* User has selected a different file.  Don't allow
		     * data-driven attacks via a user-supplied superfile:
		     * if our real uid isn't root, reset our effective uid
		     * to the real uid.
		     */
		    if (getuid() != 0) {
			setuid(getuid());
			fprintf(stderr,
    "\t** Since you have supplied a super.tab file that isn't the default,\n");
			fprintf(stderr,
    "\t** and your real uid isn't root, we're going to change back to your\n");
			fprintf(stderr,
    "\t** real uid for this test.  That protects us against attacks via\n");
			fprintf(stderr,
    "\t** nasty constructions inside user-supplied super.tab files.\n");
			fprintf(stderr,
    "\t** Not that we don't trust you...\n\n");
			fprintf(stderr,
    "\t** Now using: ruid=%d  euid=%d **\n\n", getuid(), geteuid());
		    }
		}
	    }
	}
    }
    /* Default operation: give help if no args */
    if (argc == 1)
	*givehelp = 1;
    return iarg;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Print the debug startup lines */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
debug_hello()
{
    char *s = dayname(userinfo.ourtime.day);
    fprintf(stderr, "\n\tExecuting: %s -%c:\n", prog, (debug < 2) ? 'd' : 'D');
    fprintf(stderr, "\tYou are: user=%s gid=%d hostname=%s\n\n",
	userinfo.caller.pw_name, userinfo.caller.pw_gid, userinfo.hostname);

    fprintf(stderr, "\tStart time=%d:%02d/%s (hr*60+min=%d daycode=%d)\n",
	userinfo.ourtime.min/60, userinfo.ourtime.min%60, s,
	userinfo.ourtime.min, userinfo.ourtime.day);

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Print the debug info */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
debug_print(path, arglist, envp, n_builtin)
char *path;
char **arglist;
char **envp;
int n_builtin;
{
    char *s, *t, **p, **sp;
    char *cdpath;
    int isglobal, j, iarg;
    ArgRangePat  *argpats, *arp;

    fprintf(stderr,
	  "==============================================================\n");
    fprintf(stderr, "\nSuper file is `%s'\n", superfile);
    fprintf(stderr,
	"\n\tPermitted times for execution (in reverse input order):\n");
    if (globalinfo.timebefore.next != 0 || localinfo.time.next != 0
				    || globalinfo.timeafter.next != 0) {
	for (j = 0; j<3; j++) {
	    TimeList *tl;
	    switch (j) {
	    case 0: isglobal=1; tl = globalinfo.timeafter.next; break;
	    case 1: isglobal=0; tl = localinfo.time.next; break;
	    default: isglobal=1; tl = globalinfo.timebefore.next; break;
	    }
	    for ( ; tl ; tl=tl->next) {
		fprintf(stderr, "\t\t%stime~%d:%02d-%d:%02d/%s (%s)\n",
		    tl->te.invert ? "!" : "",
		    tl->te.begin / 60, tl->te.begin % 60,
		    tl->te.end / 60, tl->te.end % 60, dayname(tl->te.day),
		    isglobal ? "global def" : "per-cmd def");
	    }
	}
    } else {
	    fputs(" (unrestricted)\n", stderr);
    }
    fputs("\n", stderr);

    (void) fprintf(stderr, "\tCommand: <%s>\n", arglist[0]);
    (void) fprintf(stderr, "\tPath: <%s>\n", path);
    for (sp=arglist; *sp; sp++)
	;
    (void) fprintf(stderr, "\tArgc: %d\n", (int) (sp - arglist));
    (void) fprintf(stderr, "\tMax len, 1 arg: %ld   Max len, all args: %ld\n",
				localinfo.maxlen1arg, localinfo.maxlenargs);
    for (sp=arglist; *sp; sp++) {
	iarg = sp - arglist;
	(void) fprintf(stderr, "\tArgv[%d]:  <%s>\n", iarg, *sp);
	if (iarg > n_builtin) {
	    argpats = (localinfo.argpats.next) ? localinfo.argpats.next :
						    globalinfo.argpats.next;
	    for (arp=ARnext(argpats, iarg-n_builtin); arp;
				    arp = ARnext(arp->next, iarg-n_builtin)) {
		fprintf(stderr, "\t\tMust match pattern: %s\n", arp->pat);
	    }
	}
    }

    if (localinfo.usr_args[0] < 0) {
	(void) fprintf(stderr,
			    "\tAny number of user-entered args allowed.\n");
    } else if (localinfo.usr_args[0] == localinfo.usr_args[1] &&
		    localinfo.usr_args[0] == 0) {
	(void) fprintf(stderr, "\tNo user-entered args are allowed.\n");
    } else if (localinfo.usr_args[0] == localinfo.usr_args[1]) {
	(void) fprintf(stderr,
		    "\t%d user-entered arg%s required.\n",
		    localinfo.usr_args[0],
		    localinfo.usr_args[0] == 1? " is" : "s are");
    } else {
	(void) fprintf(stderr,
		    "\t%d - %d user-entered args are required.\n",
		    localinfo.usr_args[0], localinfo.usr_args[1]);
    }

    (void) fprintf(stderr, "\tCommand executes with nice increment = %d.\n",
		rcl_nice_incr());

    (void) fprintf(stderr, "\tCommand executes with umask set to 0%o.\n",
		rcl_umask());

    cdpath = localinfo.chdir_path ?
			localinfo.chdir_path : globalinfo.chdir_path;
    (void) fprintf(stderr,
			"\tCommand executes with working directory = %s\n",
			cdpath && *cdpath ? cdpath : "<unchanged>");

    (void) fprintf(stderr,
	"\tMax length of user-supplied env. var's (name+value): ");
    if (localinfo.maxenvlen < 0)
	fprintf(stderr, "unrestricted\n");
    else
	fprintf(stderr, "%d chars\n", localinfo.maxenvlen);
    (void) fprintf(stderr,
	"\tAdditional permitted user's environment variables:\n");
    if (localinfo.env == NULL || localinfo.env[0] == NULL) {
	fprintf(stderr, "\t\t(none)\n");
    } else {
	for (p=localinfo.env; *p; p++)
	    (void) fprintf(stderr, "\t\t%s\n", *p);
    }

    (void) fprintf(stderr,
			"\tEnvironment variables defined with setenv=var=:\n");
    if (localinfo.setenv[0] == NULL && globalinfo.setenv[0] == NULL) {
	(void) printf("\t\t(none)\n");
    } else {
	for (p=globalinfo.setenv; *p; p++)
	    (void) printf("\t\t%s\n", *p);
	for (p=localinfo.setenv; *p; p++)
	    (void) printf("\t\t%s\n", *p);
    }

    (void) fprintf(stderr,
		    "\tComplete list of environment variables and values:\n");
    if (envp[0] == NULL) {
	(void) printf("\t\t(none)\n");
    } else {
	for (p=envp; *p; p++)
	    (void) printf("\t\t%s\n", *p);
    }
    (void) fprintf(stderr,
		    "\tFile descriptors not to be closed:\n\t\t0,1,2");
    if (localinfo.fdlist)
	(void) fprintf(stderr, ",%s", localinfo.fdlist);
    (void) fprintf(stderr, "\n\n\tID's:\treal effective\n");
    (void) fprintf(stderr, "\tuid\t%d\t%d\n", getuid(), geteuid());
    (void) fprintf(stderr, "\tgid\t%d\t%d\n", getgid(), getegid());
#ifdef HAVE_GETGROUPS
    {
	GETGROUPS_T groups[NGROUPS_MAX];
	int ng = Getgroups(NGROUPS_MAX, groups);
	(void) fprintf(stderr, "\tSupplementary groups list:\n\t\t");
	if (ng == 0) {
	    (void) fprintf(stderr, "(none)");
	} else {
	    for (j=0; j<ng; j++)
		(void) fprintf(stderr, "%d ", (int) groups[j]);
	}
	(void) fputc('\n', stderr);
    }
#endif

    (void) fprintf(stderr, "\n\tAuthentication required: %s\n", auth_name());
    if (localinfo.authinfo.required) {
	(void) fprintf(stderr, "\tAuthentication timeout: %d min\n",
				localinfo.authinfo.timeout);
	(void) fprintf(stderr,
    "\tUpdate timestamp with each use of a password-requiring command? %s\n",
				localinfo.authinfo.renewtime ? "Yes" : "No");
    }

    (void) fprintf(stderr,
	"\tVariables that must be entered in response to a prompt:\n");
    if (localinfo.checkvar == NULL || localinfo.checkvar[0] == NULL) {
	fprintf(stderr, "\t\t(none)\n");
    } else {
	for (p=localinfo.checkvar; *p; p++) {
	    t = get_variable(*p);
	    (void) fprintf(stderr, "\t\t%s\t(%s)\n", *p,
				    t ? t : "** NOT DEFINED! **");
	}
    }

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Switch to processing a user's super file, and set uid/gid/groups
 * to that user.  Die if error.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
user_supertab(file_or_user, is_file, cmd)
char *file_or_user;	/* either file from `-o file' or username */
int is_file;		/* !0 means it's filename, not username */
char *cmd;		/* for error reporting */
{
    char  *user;
    struct passwd *pass;
    int i, j;
    struct stat sto, stu;

    if (!cmd)
	cmd = "";
 
    if (!file_or_user || !*file_or_user)
	Error(0, 1, "Internal error!  \
Invoked user_supertab(NULL or \"\", ...)!\n");

    if (is_file) {

	/* Have file; verify it's a .supertab and extract user. */
	if (stat(file_or_user, &sto) == -1) {
	    Error(1, 1, "Can't stat file `%s': ", file_or_user);
	}

	/* Now validate the file: it must refer to the same file
	 * as the canonical per-user supertab file.
	 */
	if (!(pass = getpwuid(sto.st_uid))) {
	    Error(1, 1, "Can't get passwd entry for uid=%d (owner of `%s'): ",
	    		sto.st_uid, file_or_user);
	}
	/* Construct the user's .supertab filename and put into
	 * the superfile buffer.
	 */
	user = pass->pw_name;
	(void) construct_user_superfile(user);
	if (stat(superfile, &stu) == -1) {
	    Error(1, 1, "Can't stat file `%s': ", superfile);
	}

	if (stu.st_dev != sto.st_dev || stu.st_ino != sto.st_ino) {
	    Error(0, 1, "The file `%s' points to a file owned by `%s', \
but it isn't the expected file `%s'\n",
		file_or_user, pass->pw_name, superfile);
	}

    } else {
	/* Have username */
	user = file_or_user;
	pass = construct_user_superfile(user);
    } 
    error_srcfile = superfile;

    if (initgroups(user, pass->pw_gid) == -1)
	Error(1, 1,
	"Can't set groups to those of user %s, group %d:\n\t\
Command <%s:%-.500s>; uid=%d, euid=%d: ",
	    user, pass->pw_gid, user, cmd, getuid(), geteuid());

    if ((i=setgid(pass->pw_gid)) == -1) {
	Error(1, 1, "Can't set gid to %d: ", pass->pw_gid);
    } else if ((j=getgid()) != pass->pw_gid) {
	Error(1, 1, "setgid(gid=%d) returned %d, but getgid() returned %d!",
	    pass->pw_gid, i, j);
    }

    if ((i=setuid(pass->pw_uid)) == -1) {
	Error(1, 1, "Can't set uid to %d: ", pass->pw_uid);
    } else if ((j=getuid()) != pass->pw_uid) {
	Error(0, 1, "setuid(uid=%d) returned %d, but getuid() returned %d!",
	    pass->pw_uid, i, j);
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 * Construct's a user's .supertab file, and place it into the superfile.
 * Return a pointer to the passwd struct for the user.
 * Exit if there are any errors.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct passwd *
construct_user_superfile(user)
char *user;
{
    struct passwd *pass;
    char *s;

    superfile[0] = '\0';
    if (!(pass = getpwnam(user)))
	Error(0, 1, "No such user as `%s'\n", user);
    if (pass->pw_dir[0] == '\0')
	Error(0, 1, "No home directory for user `%s'?!\n", user);
    if ((strlen(pass->pw_dir) +
	    strlen(PERUSER_SUPERFILE) + 1) > sizeof(superfile_writable))
	Error(0, 1,
		"User %s's <home directory>+<%s> pathlen exceeds %d?!\n",
		user, PERUSER_SUPERFILE, sizeof(superfile_writable));
    (void) strcpy(superfile, pass->pw_dir);
    s = superfile + strlen(superfile) - 1;
    if (*s != '/')
	*(++s) = '/';
    strcpy(s+1, PERUSER_SUPERFILE);
    return pass;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Log a super call -- If "args" isn't a null ptr, it's printed inside
 *		parentheses, with whitespace separating the arguments.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

void
logmsg(cmd, args)
char *cmd;
char **args;
{
    char *ec, *logbuf, **ap;
    int e;
    int loglen = strlen(cmd) + 4;

    /* Determine buffer length needed to hold all arguments */
    if (args)
	for (ap = args; *ap; ap++)
	    loglen += strlen(*ap) + 1;

    if (!(logbuf = malloc(loglen)))
	(void) Error(0, 2, "failed to malloc space for logging command\n");

    if (args) {
	sprintf(logbuf, "%s (", cmd);
	for (ap = args; *ap; ) {
	    strcat(logbuf, *ap++);
	    strcat(logbuf, " ");
	}
	logbuf[loglen-3] = ')';
	logbuf[loglen-2] = '\n';
	logbuf[loglen-1] = '\0';
    } else {
	sprintf(logbuf, "%s\n", cmd);
    }

    /* Log the message using Error(), but
     *	- make sure msg doesn't go to stderr;
     *	- if not mail_success, don't let msg go to error_command, either.
     */
    e = error_stderr;
    ec = error_command;
    if (localinfo.mail_success == 0 ||
	    (localinfo.mail_success == -1 && globalinfo.mail_success==0))
	error_command = NULL;
    error_stderr = 0;
    Error(0, 0, "%s", logbuf);
    error_stderr = e;
    error_command = ec;
} 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get the arglist for the command, and null-terminate cmd if nec */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char **
newargs(path_plus, argv, n_builtin)
char *path_plus;	/* string = "path [options]".  Null-terminate and put
			 * options into front of arglist. */
char **argv;		/* rest of arguments for arglist (placed after
			 * the options from the path_plus string).
			 */
int *n_builtin;		/* returned w/ number of args in path_plus */
{
    int nuser, nalloc, iarg, nargs, nxtra;
    char **arglist, **ap;
    char *s;
    ArgRangePat  *argpats, *arp;

    /* Count user-entered args. */
    for (ap = argv; *ap; )
	ap++;

    /* Check number of user-entered args is ok. */
    nargs = ap - argv - 1;
    if (localinfo.usr_args[0] >= 0) {
	if (nargs < localinfo.usr_args[0] || nargs > localinfo.usr_args[1]) {
	    if (localinfo.usr_args[0] == localinfo.usr_args[1] &&
						localinfo.usr_args[0] == 0)
		    (void) Error(0, 2,
			"you may not give any arguments to `%-.500s'\n",
			argv[0]);
	    else if (localinfo.usr_args[0] == localinfo.usr_args[1])
		(void) Error(0, 2,
		    "You must give %d argument%s to `%-.500s'\n",
		    localinfo.usr_args[0],
		    localinfo.usr_args[0] == 1 ? "" : "s", argv[0]);
	    else
		(void) Error(0, 2,
		    "You must give %d - %d arguments to `%-.500s'\n",
			localinfo.usr_args[0], localinfo.usr_args[1], argv[0]);
	}
    }

    /* Check that each user-entered argument matches its pattern, if given */
    for (iarg = 1; iarg <= nargs; iarg++) {
	argpats = (localinfo.argpats.next) ? localinfo.argpats.next :
						    globalinfo.argpats.next;
	for (arp=ARnext(argpats, iarg); arp; arp = ARnext(arp->next, iarg)) {
	    if (arp->pat && match_pattern(0, 1, argv[iarg], arp->pat) != 1)
		(void) Error(0, 2,
		    "Your argument #%d <%-.500s> must match pattern <%s>\n",
		    iarg, argv[iarg], arp->pat);
	}
    }

    /* Start off with space for user-entered args + 100 args in super.tab.
     * We'll re-alloc if necessary.
     */
    nuser = (ap - argv) + 3;
    nalloc = nuser + 100;
    arglist = (char **) malloc(sizeof(char *) * nalloc);
    if (!arglist)
	(void) Error(1, 2, 
	    "failed to malloc space for %d ptrs: ", nalloc);

    /* Leave room for two extra args at front, in case we are handling
     * the "#! interpreter [opt]" file for OS's that don't support it.
     */
    arglist += 2;

    /* Copy the extra args from super.tab to the arglist,
     * re-allocing the arglist as the number of args grows.
     * First set up arglist[0]: depending on the value of localinfo.argv0,
     * either copy argv[0] to the arglist, or use the super.tab-supplied value.
     */
    s=strqtokS(path_plus, SEP, QM, "", 1); /* FullPath */
    if (localinfo.argv0 && (strcmp(localinfo.argv0, "<path>") == 0)) {
	/* Use the actual path */
	arglist[0] = s;
	argv++;
    } else if (localinfo.argv0) {
	/* Use the path named in the argv0 option in the super.tab file */
	arglist[0] = localinfo.argv0;
	argv++;
    } else {
	/* Use the name by which this command was invoked. */
	arglist[0] = *argv++;
    }

    for(nxtra=0, ap = &arglist[1], s=strqtokS(NULL, SEP, NULL, NULL, 1);  s;
				s = strqtokS(NULL, SEP, NULL, NULL, 1)) {
	nxtra++;
	if (nuser + nxtra >= nalloc) {
	    char **newarglist;
	    nalloc *= 2;
	    newarglist = (char **) realloc((void *) arglist, nalloc);
	    if (!newarglist)
		(void) Error(1, 2, 
		    "failed to realloc space for %d ptrs: ", nalloc);
	    ap = newarglist + (ap - arglist);
	    arglist = newarglist;
	}
	*ap++ = s;
    }

    /* Now add the user-supplied args at the end */
    *n_builtin = ap - arglist - 1;
    while (*argv)
	*ap++ = *argv++;
    *ap = NULL;

    return arglist;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get a safe environment for execution of the command */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char **
buttonup(cmd)
char *cmd;		/* name of command being started */
{
    /* Depending on the ability to close-on-exec, either:
     *	close all descriptors save 0,1,2 and the super.tab-specified fd list;
     *	or mark them close-on-exec.
     * Resets all signal-handling to SIG_DFL.
     * Discards all env. variables save for TERM, LINES, COLUMNS, and
     * any variables listed in the super.tab file.
     * Don't allow TERM to have any but [-/:+._a-zA-Z0-9].
     * Don't allow LINES, COLUMNS to have anything but digits.
     * To these are added reasonable values for IFS, PATH, USER, HOME.
     * USER and HOME refer to the uid under which the command is executed;
     * LOGNAME is set to the same as USER, and SUPERCMD is set to cmd.
     * ORIG_USER, ORIG_LOGNAME, and ORIG_HOME refer to the user who invoked
     * super (these are values computed by super, not passed in by the caller).
     * CALLER and CALLER_HOME are set to the same as ORIG_USER and ORIG_HOME,
     * respectively.
     * Returned:
     *  NULL on error;
     *	otherwise, a pointer to the modified environment list.
     */
    int i, j, n, fd, maxfd;
    char **p, *s;

    int fd_log;
    static char *env[200];
    static char User[100];		/* USER */
    static char Logname[100];		/* LOGNAME (same as USER) */
    static char Home[MAXPATHLEN+5];	/* HOME */
    static char OrigUser[100];		/* ORIG_USER */
    static char Caller[100];		/* CALLER (same as ORIG_USER) */
    static char OrigLogname[100];	/* ORIG_LOGNAME */
    static char OrigHome[MAXPATHLEN+9];	/* ORIG_HOME */
    static char CallerHome[MAXPATHLEN+12]; /* CALLER_HOME (same as ORIG_HOME) */
    static char SafePath[1206];		/* SAFE_PATH */
    static char Cmd[1205];		/* SUPERCMD */

#ifndef signal
    /* If signal() isn't a macro, then  declare it explicitly -- it's too
     * much of a mess to figure out whether a given operating system
     * has declared it or not.  (Any __STDC__ system should be declaring
     * it, but there are several OS's that seem to mess this up.)
     */
    SIGNAL_T (*signal())();
#endif

    /* don't close logfile yet */
    fd_log = globalinfo.log.fp ? fileno(globalinfo.log.fp) : -1;
    maxfd = MAXFD;

#ifdef HAVE_FCNTL_H
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif
    for (fd=3; fd <= maxfd; fd++)
	if (localinfo.fd[fd] == 0 && fd != fd_log)
	    (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
#else
#ifdef HAVE_IOCTL_FIOCLEX
    for (fd=3; fd <= maxfd; fd++)
	if (localinfo.fd[fd] == 0 && fd != fd_log)
	    (void) ioctl(fd, FIOCLEX, NULL);
#else
    for (fd=3; fd <= maxfd; fd++)
	if (localinfo.fd[fd] == 0 && fd != fd_log)
	    (void) close(fd);
#endif
#endif
    
    for (i=0; i<NSIG; i++)
       (void) signal(i, SIG_DFL);

    s = *localinfo.euser ? localinfo.euser :
	    *localinfo.user ? localinfo.user :
		*localinfo.u_g ? localinfo.u_g : userinfo.caller.pw_name;
    (void) sprintf(OrigUser, "ORIG_USER=%s", userinfo.caller.pw_name);
    (void) sprintf(Caller, "CALLER=%s", userinfo.caller.pw_name);
    (void) sprintf(User, "USER=%s", s);
    (void) sprintf(OrigLogname, "ORIG_LOGNAME=%s", userinfo.caller.pw_name);
    (void) sprintf(Logname, "LOGNAME=%s", s);

    if (strlen(SAFE_PATH) > sizeof(SafePath)-6) {
	Error(0, 0, "%t\n\tRidiculously long SAFE_PATH.\n");
	return NULL;
    }
    /* I've screwed up SAFE_PATH so often (and so have others) that I'm gonna
     * put this check into the code...
     */
    if (strncmp(SAFE_PATH, "PATH=", 5) == 0)
	(void) strcpy(SafePath, SAFE_PATH);
    else
	(void) sprintf(SafePath, "PATH=%s", SAFE_PATH);

    if (strlen(cmd) > sizeof(Cmd)-5) {
	Error(0, 0, "%t\n\tRidiculously long original string.\n");
	return NULL;
    }
    (void) sprintf(Cmd, "SUPERCMD=%s", cmd);

    (void) strcpy(Home, "HOME=");
    (void) getlogdir(s, Home+5);
    (void) sprintf(OrigHome, "ORIG_HOME=%s", userinfo.caller.pw_dir);
    (void) sprintf(CallerHome, "CALLER_HOME=%s", userinfo.caller.pw_dir);
    i = 0;
    env[i] = Getenv("TERM");
    if (env[i] && checkenv("TERM", env[i]+5, "^[-/:+._a-zA-Z0-9]*$") != -1) i++;
    env[i] = Getenv("LINES");
    if (env[i] && checkenv("LINES", env[i]+6, "^[0-9]*$") != -1) i++;
    env[i] = Getenv("COLUMNS");
    if (env[i] && checkenv("COLUMNS", env[i]+8, "^[0-9]*$") != -1) i++;
    env[i++] = SAFE_IFS;
    env[i++] = SafePath;
    env[i++] = User;
    env[i++] = Logname;
    env[i++] = Home;
    env[i++] = Cmd;
    env[i++] = OrigUser;
    env[i++] = Caller;
    env[i++] = OrigLogname;
    env[i++] = OrigHome;
    env[i++] = CallerHome;

    /* Now add the extra environment variables requested in the
     * super.tab file.  Make a linear search each time, so that we
     * don't define the same name twice.  Yes, this will be extraordinarily
     * inefficient if there are many variables, but in real life, there
     * aren't too many to do this...
     */
    for (p=localinfo.env; p && *p && i < NELEM(env)-1; p++) {
	n = strlen(*p);
	for (j=0; j < i; j++) {
	    if (strncmp(env[j], *p, n) == 0 && env[j][n] == '=') {
		/* name is already in use, at index j. */
		break;
	    }
	}
	s = Getenv(*p);
	if (s) {
	    env[j] = s;
	    if (strlen(env[j])+1 > localinfo.maxenvlen) {
		Error(0, 0, "%t\n\tDefinition for envvar %s exceeds \
maxenvlen=%d.\n", *p, localinfo.maxenvlen);
		return NULL;
	    }
	    if (j == i) {
		i++;
	    }
	}
    }

    for (p = globalinfo.setenv; *p && i < NELEM(env)-1 ; ) {
	n = strchr(*p, '=') + 1 - *p;
	for (j=0; j < i; j++) {
	    if (strncmp(env[j], *p, n) == 0) {
		/* name is already in use, at index j. */
		break;
	    }
	}
	env[j] = *p++;
	if (j == i) {
	    i++;
	}
    }

    for (p = localinfo.setenv; *p && i < NELEM(env)-1 ; ) {
	n = strchr(*p, '=') + 1 - *p;
	for (j=0; j < i; j++) {
	    if (strncmp(env[j], *p, n) == 0) {
		/* name is already in use, at index j. */
		break;
	    }
	}
	env[j] = *p++;
	if (j == i) {
	    i++;
	}
    }

    if (i >= NELEM(env)-1) {
	Error(0, 0, "%t\n\tAsked to save too many \
environment variables (max allowed %d).\n", NELEM(env)-1);
	return NULL;
    }

    env[i] = (char *) NULL;

    return &env[0];
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check that all arguments are within the permissible limits. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_arglistlen(argv)
char **argv;
{
    /* Check that all arguments meet the length restrictions.
     * A length restriction < 0 means unlimited size.
     * Returns 0 if all ok; else prints error message and returns 1.
     */
    long totlen;
    long l;
    char **p;

    if (localinfo.maxlen1arg < 0 && localinfo.maxlenargs < 0)
	return 0;

    for (totlen = 0, p = argv; *p; p++) {
	l = (long) strlen(*p) + 1;
	totlen += l;
	if (localinfo.maxlen1arg >= 0 && l > localinfo.maxlen1arg)
	    return Error(0, 0, "Maximum length of each argument = %d chars\n",
							localinfo.maxlen1arg);
    }
    if (localinfo.maxlenargs >= 0 && totlen > localinfo.maxlenargs)
	return Error(0, 0, "Maximum total length of arguments = %d chars\n",
							localinfo.maxlenargs);
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Take various system information (from sysinfo(), uname(), etc)
 * and turn it into variables.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
add_builtin_variables()
{
    char *s, buf[1000];

    if (add_variable("HOSTNAME", userinfo.hostname) == -1)
	exit(1);

    /* Add an unqualified version  of HOSTNAME */
    strcpy(buf, userinfo.hostname);
    if ((s=strchr(buf, '.'))) {
	*s = '\0';
    }
    if (add_variable("HOST", buf) == -1)
	exit(1);

    if (add_sysinfo_variables() == -1)	/* Makes empty def'ns if no sysinfo() */
	exit(1);

    if (add_uname_variables() == -1) 	/* Makes empty def'ns if no uname() */
	exit(1);

#ifdef HAVE_GETDOMAINNAME
    if (getdomainname(buf, sizeof(buf)) != -1) {
	if (add_variable("NIS_DOMAIN", buf) == -1)
	    exit(1);
    }
#else
	if (add_variable("NIS_DOMAIN", "") == -1)
	    exit(1);
#endif

}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Initialize the globalinfo struct.  Only call this once; thereafter, use
 * option_global_clear_settings().  Note that very little work is done in
 * this routine, because almost all fields are properly initialized in the
 * structure declaration/initialization statement.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
init_globalinfo()
{
    int i;
    init_umask(1);

    for (i=0; i<=MAXSETENV; i++)
	globalinfo.setenv[i]=NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Initialize the localinfo struct.  Only call this once; thereafter, use
 * option_local_clear_settings().
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
init_localinfo()
{
    int i;
    localinfo.info = localinfo.chdir_path =
	    localinfo.die = localinfo.print = NULL;
    localinfo.env = NULL;
    for (i=0; i<=MAXSETENV; i++)
	localinfo.setenv[i]=NULL;
    localinfo.fdlist = NULL;
    localinfo.mask = -1;
    localinfo.maxenvlen = globalinfo.maxenvlen;
    localinfo.maxlen1arg = globalinfo.maxlen1arg;
    localinfo.maxlenargs = globalinfo.maxlenargs;
    localinfo.file_uid = UID_NOTSET;
    localinfo.file_gid = GID_NOTSET;
    localinfo.nice_incr = globalinfo.nice_incr;
    localinfo.checkvar = NULL;
	       
    /* Don't init to global ngroups, because we need to be able to tell
     * later on whether local groups=xxx or global groups=xxx was used.
     */
    localinfo.ngroups = GROUPS_NOTSET;
    localinfo.groups_added = 0;
    localinfo.argpats.pat = NULL;
    localinfo.argpats.arg1 = -1;
    localinfo.argpats.arg2 = -1;
    localinfo.argpats.next = NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Look up some of the most basic user information. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
init_userinfo()
{
    struct passwd *usrpw;

    userinfo.ourtime.start = time(NULL);
#ifdef HAVE_LOCALTIME
    {
	struct tm *tm_p = localtime(&userinfo.ourtime.start);
	userinfo.ourtime.min = tm_p->tm_hour*60 + tm_p->tm_min;
	userinfo.ourtime.day = tm_p->tm_wday;
    }
#else
    userinfo.ourtime.min = (userinfo.ourtime.start/60) % (24*60);
    userinfo.ourtime.day = daynum(userinfo.ourtime.start);
#endif

    /*
     * We want the hostname (fully-qualified if possible), as well as a
     * lower-cased version (to try and deal with mixed case hostnames).
     */
    if (get_canonical_hostname(userinfo.hostname, sizeof(userinfo.hostname))
	    == -1)
	return -1;
    strcpy(userinfo.lc_hostname, userinfo.hostname);
    strtolower(userinfo.lc_hostname);

    userinfo.orig_uid = getuid();
    userinfo.orig_gid = getgid();

    usrpw = getpwuid(userinfo.orig_uid);
    if (!usrpw)
	return Error(0, 0, "Couldn't get your password entry.");
    memcpy(&userinfo.caller, (void *) usrpw, sizeof(struct passwd));

    if (add_variable("CALLER", userinfo.caller.pw_name) == -1)
	return -1;
    if (add_variable("CALLER_HOME", userinfo.caller.pw_dir) == -1)
	return -1;

    /* Since the string fields that we need are overwritten by later
     * calls to getpwxxx(), make private copies:
     */
    if (!(userinfo.caller.pw_name = strdup(usrpw->pw_name)))
	(void) Error(0, 2, "failed to malloc space for passwd struct field.\n");
    if (!(userinfo.caller.pw_passwd = strdup(usrpw->pw_passwd)))
	(void) Error(0, 2, "failed to malloc space for passwd struct field.\n");
    if (!(userinfo.caller.pw_dir = strdup(usrpw->pw_dir)))
	(void) Error(0, 2, "failed to malloc space passwd struct field.\n");
    
    error_user = userinfo.caller.pw_name;
    userinfo.orig_mask = umask(022); /* Get orig umask, and set to 022 */
    (void) umask(userinfo.orig_mask);	/* ...so restore curr val */

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Store the desired umask; set the actual umask; recall the desired umask */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
init_umask(is_global)
int is_global;
{
    if (is_global) {
	globalinfo.mask = userinfo.orig_mask;
    } else {
	localinfo.mask = -1;
    }
}

void
store_umask(mask, is_global)
int mask;
int is_global;
{
    if (is_global)
	globalinfo.mask = mask;
    else
	localinfo.mask = mask;
}

void
set_umask()
{
    umask( (localinfo.mask >= 0) ? localinfo.mask : globalinfo.mask);
}

int
rcl_umask()
{
    return (localinfo.mask >= 0) ? localinfo.mask : globalinfo.mask;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check if -r path matches actual path.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
check_rpath(r_path, xpath)
char *r_path;
char *xpath;
{
    struct stat r_stat, xstat;
    if (stat(r_path, &r_stat) == -1) {
	return Error(1, 0, "Caller used `-r %-.500s', but stat of that \
path failed: ", r_path);
    }
    if (stat(xpath, &xstat) == -1) {
	return Error(1, 0, "Caller used `-r %-.500s'; the super.tab file \
gave path `%s', and the stat of the latter file failed: ",
	    	r_path, xpath);
    }
    if (r_stat.st_dev != xstat.st_dev || r_stat.st_ino != xstat.st_ino) {
	return Error(0, 0, "File sanity-check mismatch: \
caller used `-r %-.500s' (dev=%ld,inode=%ld); the super.tab file \
gave path `%s' (dev=%ld,inode=%ld): these are not the same file.\n",
	    r_path, (long) r_stat.st_dev, (long) r_stat.st_ino,
	    xpath, (long) xstat.st_dev, (long) xstat.st_ino);
    }

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Change directory, if required */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
set_chdir()
{
    char *path = localinfo.chdir_path ?
			localinfo.chdir_path : globalinfo.chdir_path;

    if (!path || !*path)
	return 0;

    if (chdir(path) == -1)
	return Error(1, 0, "Failed to change directory to ``%s'': ", path);
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Make sure that stdin, stdout, stderr are all open; die 
 * if we cannot do so.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
check_stdio()
{
    struct stat stat;
    if (fstat(0, &stat) == -1 && !freopen("/dev/null", "r", stdin)) {
	exit(1);
    }
    if (fstat(1, &stat) == -1 && !freopen("/dev/null", "w", stdout)) {
	exit(1);
    }
    if (fstat(2, &stat) == -1 && !freopen("/dev/null", "w", stderr)) {
	exit(1);
    }
#ifdef HAVE_FILENO
    if (fileno(stdin) != 0) { exit(1); }
    if (fileno(stdout) != 1) { exit(1); }
    if (fileno(stderr) != 2) { exit(1); }
#endif
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Store/set/recall niceness */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

void
store_nice_incr(nice_incr, is_global)
int nice_incr;
int is_global;
{
    if (is_global)
	globalinfo.nice_incr = nice_incr;
    else
	localinfo.nice_incr = nice_incr;
}

int
set_nice_incr()
{
    if (localinfo.nice_incr && nice(localinfo.nice_incr) == -1)
	return Error(1, 0,
		"Failed to apply a ``nice'' increment = %d: ",
		    localinfo.nice_incr);
    return 0;
}

int
rcl_nice_incr()
{
    return localinfo.nice_incr;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Frees all elements in a SimpleList, except the one it's given.
 * The "next" field of that element is set NULL.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
free_SimpleList(sl)
SimpleList *sl;
{
    SimpleList *slp;

    if (!sl || !sl->next)
	return;
    slp = sl->next;
    sl->next = NULL;
    for (sl=sl->next ; sl; sl = slp) {
	slp = sl->next;
	free(sl->pat);
	free(sl);
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Frees all elements in a Simple2List, except the one it's given.
 * The "next" field of that element is set NULL.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
free_Simple2List(sl)
Simple2List *sl;
{
    Simple2List *slp;

    if (!sl || !sl->next)
	return;
    slp = sl->next;
    sl->next = NULL;
    for (sl=sl->next ; sl; sl = slp) {
	slp = sl->next;
	free(sl->pat);
	free(sl);
    }
}
