static const char rcsid[] = "$Id: approve.c,v 1.154 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"

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Look up command "usrcmd" in file "superfile".  Return path to execute
 * if approved, empty string if no action should be taken, NULL ptr
 * if error.  As a ``side effect'': Sets the fields in the superinfo struct.
 * approve() returns:
 *	- NULL ptr if error:
 *		a) username not found;
 *		b) superfile can't be opened for reading;
 *		c) no such command as usrcmd in superfile;
 *		d) user not allowed to execute this command;
 *		e) invalid superfile contents.
 *	- ptr to empty string if all ok, but no program should be executed.
 *	- ptr to path of file to exec, if user allowed to do so.
 *	Any error also generates a message to stderr.

 * New calls to approve() overwrite the buffer containing the returned path.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
approve(usrcmd, givehelp, verbosity)
char *usrcmd;		/* command we're to check on. Should NOT be a
			 * command-line option!  Can be a NULL pointer
			 * or empty string, in which case we give help,
			 * print version info, or whatever.
			 */
int givehelp;		/* Don't execute command; give help instead. */
int verbosity;		/* HELP_BASIC, HELP_FULL, or HELP_FACTS */
{
    char *buf, *bbuf;
    char *do_variables();
    int indentok;

    /* Do we (1) give help, or (2) match a command with this user? */
    if (givehelp && !check_syntax)
	printhelp_hello(verbosity, usrcmd);
    if (givehelp && verbosity == HELP_USAGE)
	return "";

    /* Open the super.tab file we are supposed to process, and then... */
    if (!(currfile = file_open(NULL, superfile, 0, NULL, NULL))) {
	return NULL;
    }
    /* ...immediately chain-open the common super file (if it exists), so
     * that it is always invoked by all commands (super.tab or per-user),
     * and require it to be root-owned.
     */
    if (!(currfile = file_open(currfile, superfile_init, 1, 0, NULL))) {
	return NULL;
    }

    for (localinfo.progs.commandfound=NULL, currfile->line = 1;
	(buf=fillbuffer(currfile->fp, &indentok, &currfile->nl)) ||
	    ( (check_syntax || indentok) && currfile->prev != NULL);
					     currfile->line += currfile->nl) {
	if (!buf) {
	    /* Reached EOF on this file -- close it, continue with prev file */
	    currfile = file_close(currfile);
	    continue;
	}
	error_srcfile = currfile->fullname;
	error_line = currfile->line;
	error_nl = currfile->nl;

	if (!indentok) {
	    Error(0, 0, "$$\n\tformat error in super.tab file:\n\
\tContinued line not indented.\n");
	    if (check_syntax) {
		continue;
	    } else {
		return NULL;
	    }
	}


	/* Discard empty lines and pure-comment lines */
	bbuf = buf + strspn(buf, SEP);	/* skip leading whitespace */
	if (!*bbuf || *bbuf == '#')
	    continue;

	if (bbuf != buf) {
	    Error(0, 0, "$$\n\tformat error in super(1) file: \n\
\tNon-comments must begin in column 1.\n");
	    if (check_syntax) {
		continue;
	    } else {
		return NULL;
	    }
	}

	/* Clean out embedded comments, whitespace-newline-whitespace. */
	buf = clean_buf(buf, NULL);
	if (!buf) {
	    return NULL;
	}
	if (debug) {
	    fprintf(stderr,
	    "==============================================================\n");
	    fprintf(stderr, "Input text: %s", buf);
	}
    
	/* Do variable expansion */
	buf = do_variables(buf);
	if (!buf) {
	    if (check_syntax) {
		continue;
	    } else {
		return NULL;
	    }
	}

	/* Initialize localinfo */
	if (handle_option(NULL, NULL, 0) == -1) {
	    if (check_syntax) {
		continue;
	    } else {
		return NULL;
	    }
	}

	if (check_syntax) {
	    if (parseline(0, 1, buf, usrcmd) == -1) {
	    }

	} else if (givehelp) {
	    switch (parseline(1, 0, buf, usrcmd)) {
	    case -1:
		return NULL;
		break;
	    
	    case 0:
		break;
	    
	    default:
		printhelp(verbosity);
	    }

	} else {
	    switch(parseline(0, 0, buf, usrcmd)) {
	    case -1:
		return NULL;
		break;

	    case 0:
		break;

	    default:
		/* Have an acceptable line, unless die=xxx was used */
		return localinfo.progs.cmd_file[localinfo.progs.match].File;
	    }
	}
    }
    file_close(currfile);

    if (!check_syntax && !indentok) {
	return NULL;
    } else if (givehelp || check_syntax) {
	return "";
    } else if (!localinfo.progs.evermatched) {
	Error(0, 0, "No such super command as `%-.500s'.\n", usrcmd);
	return NULL;
    } else {
	Error(0, 0, "%-.500s - Permission denied to user %s\n",
					    usrcmd, userinfo.caller.pw_name);
	return NULL;
    }
    /* NOTREACHED */
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Parse one control line (may contain several text lines).
 * Side effect: prints help information if givehelp is set.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*	Return -1 if caller should give up parsing the file;
 *	return 0 if caller should loop to next chunk (because we
 *		are doing help processing, because of nomatch, because
 *		of ":"-command, etc.);
 *	return 1 if success.
 *	The cmd/file list and which one matched (if any) are returned
 *	through the localinfo.progs struct.
 */
int
parseline(givehelp, checksyntax, buf, usrcmd)
int givehelp;		/* request is to give help, not check valid command.  */
int checksyntax;	/* request is to check syntax of super.tab file,
			 * not check valid command.
			 */
char *buf;		/* Input chunk.  Gets modified by strqtokS(). */
char *usrcmd;		/* Command user wants to execute -- can be
			 * NULL if givehelp_or_checksyntax!=0.
			 */
{
    char *word1;
    char *cond_or_opt_wd;
    char *bbuf;
    int i, continue_after_if;
    int condition_match;
    /* FullPath is twice MAXPATHLEN, because this allows room for
     * MAXPATHLEN characters of arguments as well as MAXPATHLEN
     * characters for the filename itself.
     */
    static char FullPath[2*MAXPATHLEN];

    localinfo.progs.commandfound = NULL;

    bbuf = buf;
    buf += strspn(buf, SEP);	/* skip leading whitespace */
    if (!*buf || *buf == '#')
	return 0;		/* Discard empty lines and pure-comment lines */
    
    if (bbuf != buf) {
	return Error(0, 0, "$$\n\tformat error in super(1) file: \n\
\tCommandPatterns must begin in column 1.\n");
    }

    /* Get the list of command/file pairs */
    strqS_qm = my_qm;
    strqS_cc = my_cc;

    if (localinfo.progs.nalloc == 0) {
	int nalloc = 10;
	if ( !(localinfo.progs.cmd_file = (ProgList *)
				malloc(nalloc * sizeof(ProgList))) )
	    return Error(1, 0, "$$Failed to malloc space for Cmd+File list\n");
	localinfo.progs.nalloc = 10;
    }
    localinfo.progs.n = 0;	/* Initialize to 0 cmd/file pairs */

    /* Start by inspecting the first word.  If it begins ":", it is
     * a special builtin command.  Otherwise, if it contains "::", it
     * must be a command/file pair.  Otherwise it's a single command.
     */
    word1 = strqtokS(buf, SEP, NULL, NULL, 1);
    if (!word1)
	return Error(0, 0, "$$\n\tformat error in super.tab file: \
CmdPattern or FullPathName missing.\n");

    do {
	continue_after_if = 0;
	if (*word1 == ':' || strcmp(word1, "/") == 0) {
	    /* It's a special builtin command beginning ":" (or the obsolete
	     * special command name "/".)
	     */
	    int i;

	    i = process_colon_cmds(word1, &continue_after_if);
	    if (!continue_after_if) {
		return (i < 0) ? -1 : 0;
	    } else {
		/* A :if  evaluated to true; replace "word1" with the next
		 * word from this line, and process the rest of the line.
		 */
		word1 = strqtokS(NULL, SEP, NULL, NULL, 1);
		if (!word1)
		    return 0;	/* no body for conditional */
	    }
	}
    } while (continue_after_if);

    /* Build lists of cmd::file entries */
    if ((i=build_cmd_file(usrcmd, givehelp || checksyntax,
					word1, &cond_or_opt_wd)) <= 0) {
	return i;
    }


    if (!(givehelp || checksyntax)) {
	int indx = (localinfo.progs.match >= 0) ? localinfo.progs.match : 0;

	/* Check if we are supposed to generate a modified FullPath */
	char *path = localinfo.progs.cmd_file[indx].File;

	/* Adjust fullpath as necessary */
	if (fixup_fullpath(indx, usrcmd, path,
					FullPath, sizeof(FullPath)) == -1) {
	    return -1;
	}

    } else {
	/* givehelp or check syntax */
	int indx = (localinfo.progs.match >= 0) ? localinfo.progs.match : 0;
	localinfo.progs.commandfound = localinfo.progs.cmd_file[indx].Cmd;
    }

    if (!debug && it_came_from_cmdline) {
	/* Not debug, but -F / -T / -U / -G / -M flag was given -- so, let's
	 * print a separator line to help out the reader.
	 */
	fprintf(stderr,
	"==============================================================\n");
    }
    if (!givehelp && (debug || it_came_from_cmdline)) {
	fprintf(stderr, "\tCommandfound = `%s' path = `%s'\n",
	    localinfo.progs.commandfound, localinfo.progs.match >= 0 ?
		localinfo.progs.cmd_file[localinfo.progs.match].File : "-");
    }

    /* Now process command-line conditions and options */
    if ((condition_match=conditions_and_options(cond_or_opt_wd)) == -1) {
	return condition_match;
    }


    if (condition_match && localinfo.die) {
	if (checksyntax || givehelp) {
	    return 0;
	} else {
	    extern char *error_prog;
	    char *save_prog = error_prog;
	    error_prog = NULL;
	    Error(0, 0, "%s\n", strdup(do_variables(localinfo.die)));
	    error_prog = save_prog;
	    return -1;
	}
    }
    return condition_match;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Builds list of cmd::file pairs for a control line.
 *	Return -1 if caller should give up parsing the file;
 *	return 0 if caller should loop to next chunk (because we
 *		are doing help processing, because of nomatch, etc.
 *	return 1 if success.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
build_cmd_file(usrcmd, givehelp_or_checksyntax, word1, cond_or_opt_wd)
char *usrcmd;			/* User's typed command */
int givehelp_or_checksyntax;	/* Just giving help or checking syntax
				 * of super.tab file?
				 */
char *word1;			/* First wd to process; get rest w/ strqtokS */
char **cond_or_opt_wd;
{
    /* Expect to see either
     *		cmd::file ...
     *	or
     *		cmd   file
     */

    char *s, *coloncolon, *pair;
    extern char *taglines();

    if ((s = strchr(word1, ':')) && *(s+1) == ':') {
	/* It's a Cmd::Filename pair.  Go through the list of
	 * command/filenames, until they are all used up, and record
	 * the matching filename.
	 */
	pair = word1;
	localinfo.progs.match = -1;	/* initialize to no match */
	do {
	    s = strchr(pair, ':');
	    coloncolon = (s && *(s+1) == ':') ? s : NULL;
	    if (!coloncolon) {
		/* Reached end of cmd::file list. */
		*cond_or_opt_wd = pair;
		break;
	    }
	    if (debug)
		fprintf(stderr, "\tCommand::file = `%s'\n", pair);

	    if (coloncolon == pair)
		return Error(0, 0, "$$\n\tFormat error in super.tab file:\n\
\tCmd::File used, but the Cmd part is missing.\n");
	    *coloncolon = '\0';
	    localinfo.progs.n++;	/* Have a new cmd/file pair */
	    if (localinfo.progs.n == localinfo.progs.nalloc) {
		localinfo.progs.nalloc *= 2;
		localinfo.progs.cmd_file =
			    (ProgList *) realloc(localinfo.progs.cmd_file,
				localinfo.progs.nalloc * sizeof(ProgList));
		if (!localinfo.progs.cmd_file)
		    return Error(1, 0,
			"$$Failed to malloc space for Cmd+File list\n");
	    }

	    localinfo.progs.cmd_file[localinfo.progs.n - 1].Cmd = pair;
	    localinfo.progs.cmd_file[localinfo.progs.n - 1].File =
							coloncolon + 2;

	    if ((givehelp_or_checksyntax && !usrcmd) ||
				    match_pattern(0, 2, usrcmd, pair) == 1) {
		/* Matched the command */
		localinfo.progs.match = localinfo.progs.n - 1;
		localinfo.progs.evermatched = 1;
		if (localinfo.progs.cmd_file[localinfo.progs.match].File[0]
				== '\0')
		    return Error(0, 0, "$$\n\tFormat error in super.tab file:\n\
\tCmd::File used, but the File part is missing.\n");

		if (globalinfo.relative_path == 0 &&
		    localinfo.progs.cmd_file[localinfo.progs.match].File[0]
				!= '/')
		    return Error(0, 0, "$$\n\tFormat error in super.tab file:\n\
\tfilename `%s' is not an absolute path.\n\
\tUse global option relative_path=y if you really want such foolishness!\n",
		    localinfo.progs.cmd_file[localinfo.progs.match].File);
	    }

	} while ( (pair = strqtokS(NULL, SEP, NULL, NULL, 1)) );
	if (!pair)
	    *cond_or_opt_wd = NULL;

	if (!(givehelp_or_checksyntax && !usrcmd) &&
				localinfo.progs.match == -1)
	    return 0;		/* no match */

    } else if (s && *s == ':') {
	/* Embedded `:' in command name! */

	return Error(0, 0, "$$\n\tFormat error in super.tab file: \
The CmdPattern (%s) may not contain an embedded colon\n", word1);

    } else {
	/* It was a plain command name.  Get the full path. */
	localinfo.progs.n = 1;
	localinfo.progs.match = -1;	/* initialize to no match */
	localinfo.progs.cmd_file[0].Cmd = word1;
	localinfo.progs.cmd_file[0].File = strqtokS(NULL, SEP, NULL, NULL, 1);
	if (localinfo.progs.cmd_file[0].File == NULL ||
				localinfo.progs.cmd_file[0].File[0] == '\0')
	    return Error(0, 0, "$$\n\tformat error in super.tab file: \
CmdPattern or FullPathName missing.\n");

	    if (globalinfo.relative_path == 0 &&
			localinfo.progs.cmd_file[0].File[0] != '/')
		return Error(0, 0, "$$\n\tFormat error in super.tab file: \
filename `%s' is not an absolute path.\n\
\tUse global option relative_path=y if you really want such foolishness!\n",
		localinfo.progs.cmd_file[0].File);

	if (debug)
	    fprintf(stderr, "\tCmdPattern = `%s' %s\n",
		localinfo.progs.cmd_file[0].Cmd, taglines(1));

	if ((givehelp_or_checksyntax && !usrcmd)  ||
	    match_pattern(0, 2, usrcmd, localinfo.progs.cmd_file[0].Cmd) == 1) {
	    localinfo.progs.match = 0;	/* success */
	    localinfo.progs.evermatched = 1;
	}

	if (!(givehelp_or_checksyntax && !usrcmd) &&
					localinfo.progs.match == -1)
	    return 0;		/* Skip non-matching commands */

	/* The next word has to be ready in advance of the next loop: */
	*cond_or_opt_wd = strqtokS(NULL, SEP, NULL, NULL, 1);
    }
    return 1;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Take user's command, and the path from the control line, and generate
 * the full path that should be executed.
 * Returns -1 on error, indicating file should be parsed no further.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
fixup_fullpath(indx, usrcmd, path, FullPath, l_FullPath)
int indx;		/* Index into cmd_file array whence comes the path */
char *usrcmd;		/* User's entered command */
char *path;		/* Matched path from control line */
char *FullPath;		/* output: the generated full path */
int l_FullPath;		/* length of the FullPath buffer */
{
    register char *p, *q;
    int lpath, n;

    /* Watch out for sneaky users */
    if (strcspn(usrcmd, SEP) != strlen(usrcmd) || strchr(usrcmd, '\\'))
	return Error(0, 0,
	    "You entered `%s',\nbut you may NOT include whitespace or \
backslashes in the cmd!\n", usrcmd);

    /* Determine the length of the filename part of the path -- remember
     * that it may contain whitespace-separated arguments following the
     * filename.
     */
    lpath = strcspn(path, SEP);
    p = strchr(path, '*');	/* Check for asterisk in filename part */
    n = p ? p - path : 0;	/* number of characters before asterisk */
    if (p && n < lpath) {
	/* Wildcard command -- replace asterisk with the usrcmd */

	if ((strlen(path) + strlen(usrcmd)) > l_FullPath)
	    return Error(0, 0,
	    "$$\n\tRidiculously long path would be formed from Cmd \
and FullPathName:\n<%s> + <%s>", path, usrcmd);
	if ((q=strchr(p+1, '*')) && q < (path + lpath))
	    return Error(0, 0,
		"$$\n\tFullPathName can only have 1 asterisk, \
but the superfile entry\nreads `%s'\n", path);
	if (n > 0)
	    strncpy(FullPath, path, n);
	strcpy(FullPath + n, usrcmd);
	strcat(FullPath, p+1);
	localinfo.progs.commandfound = usrcmd;
	localinfo.progs.cmd_file[indx].File = FullPath;
    } else {
	/* Regular command */
	localinfo.progs.commandfound = usrcmd;
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Process command-line conditions and options.
 * Returns -1 on syntax and other errors;
 * otherwise returns 0 if conditions fail to match;
 * otherwise conditions match, and returns 1;
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
conditions_and_options(cond_or_opt_wd)
char *cond_or_opt_wd;	/* First word to process; rest we get with strqtokS */
{
    int condition_match;
    int invert;
    register char *p;

    strqS_qm = my_qm;
    strqS_cc = my_cc;
    if (cond_or_opt_wd) {
	do {

	    p = cond_or_opt_wd;
	    if (*p == CONDITION_SEP || *p == OPTION_SEP)
		return Error(0, 0, "$$\n\tBad syntax: <%s>\n", cond_or_opt_wd);

	    for (p++; *p && ((*p != CONDITION_SEP && *p != OPTION_SEP) ||
						    (*(p-1) == '\\')); p++)
		    ;

	    if (*p == OPTION_SEP) {
		/* ************** */
		/* It's an option */
		/* ************** */
		if (handle_option(cond_or_opt_wd, p+1, 0) == -1)
		    return -1;
		continue;

	    } else if (*p == CONDITION_SEP) {
		/* **************** */
		/* It's a condition */
		/* **************** */
		if (InsertCondition(cond_or_opt_wd, p+1, 0) == -1)
		    return -1;

	    } else {
		/* Default is to treat it as user~ pattern.
		 * Insert in user list.
		 */
		int i;
		char *s, **glob;

		invert = *cond_or_opt_wd == '!';
		s = invert ? cond_or_opt_wd+1 : cond_or_opt_wd;

		/* Do brace globbing */
		if (balancedbraces(s) != 0) {
		    return Error(0, 0, "$$Unbalanced braces in `%s'.\n", s);
		}
		if ((i=globbraces(s, 1, &glob)) != 0) {
		    return Error(0, 0, "$$Missing `%c'.\n", i);
		}

		if (InsertUserList(s, glob,
		    &localinfo.userpats, &localinfo.origtext, invert) == -1)
		    return -1;
	    }
	} while ( (cond_or_opt_wd = strqtokS(NULL, SEP, NULL, NULL, 1)) );
    }

    condition_match = 1;

    /* Look at condition lists in reverse order, and stop as soon
     * as there is a result.
     */
    if (globalinfo.userafter.next ||
	    localinfo.userpats.next ||
	    globalinfo.userbefore.next) {
	/* We have user fields to match */

	matches.user = -1;	/* no yes/no has yet been made for this user */

	/* Start with the global pats that get put after the per-line pats */
	match_ugh_user(&globalinfo.userafter, 1);
	if (matches.user == -1) {
	    /* no yes or no yet for this user; try the per-line pats */
	    match_ugh_user(&localinfo.userpats, 0);
	    if (matches.user == -1) {
		/* no yes or no yet; try the global before-line pats */
		match_ugh_user(&globalinfo.userbefore, 1);
	    }
	}
	if (matches.user == -1) {
	    /* No yes or no for this user; use a default.
	     * Default for root is to allow;
	     * default for all others is disallow
	     */
	    matches.user = (userinfo.caller.pw_uid == 0) ? 1 : 0;
	}
    } else {
	return Error(0, 0,
	"$$\n\tNo user patterns given on control line or in :global line.\n");
    }
    if (!matches.user)
	condition_match = 0;

    if (condition_match != 0) {
	if (globalinfo.timeafter.next ||
		localinfo.time.next ||
		globalinfo.timebefore.next) {
	    /* We have time fields to match */

	    /* Default depends on whether all time fields are inverted */
	    matches.time = -1;
	    matches.allinverted = 1;
	    matchtime(&userinfo.ourtime, &globalinfo.timeafter);
	    if (matches.time == -1) {
		matchtime(&userinfo.ourtime, &localinfo.time);
		if (matches.time == -1) {
		    matchtime(&userinfo.ourtime, &globalinfo.timebefore);
		}
	    }
	    if (matches.time == -1) {
		/* Default is permit execution if all patterns were inverting;
		 * deny execution if there were some non-inverting patterns.
		 */
		condition_match = matches.allinverted ? 1 : 0;
		if (debug || it_came_from_cmdline) {
		    if (matches.allinverted)
			fputs("\tPermission allowed (default for non-match \
when all times are !time~xxx)\n", stderr);
		    else
			fputs("\tPermission denied (default for non-match \
when >=1 time is non-negated)\n", stderr);
		}
	    } else {
		condition_match = matches.time;
	    }
	}
    }
    return condition_match;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Prints help information title */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
printhelp_hello(verbosity, usrcmd)
int verbosity;
char *usrcmd;
{
    static char *usage[] = {
"",
"  %s [-r path] command [args...]  executes command [args].",
"",
"  %s   ........................   basic listing of permitted cmds.",
"  %s [-H | -f] [-S] [MasqOpts]   list ok cmds: -H=verbose,-f=just-facts.",
"  %s [-H | -f] [-S] [MasqOpts] cmd  ...give help only for `cmd'.",
"  %s -h   .....................   print this usage info, then exits.",
"",
"  %s -b   .....................   lists builtin variables, then exits.",
"  %s -c [superfile]   .........   just check syntax of the superfile.",
"  %s -V   .....................   print version number, then exits.",
"  %s [-d | -D] [-S] [MasqOpts] cmd [args]   ...debug mode.",
"",
"Notes on options:",
"    -d   Basic debug: show what would be done, but don't execute anything.",
"    -D   Same as -d, plus tell more about variables, plus other details.",
"    -f   (just the facts, ma'm): lists your cmds as: Cmd FullPath [args]",
"	      Useful for scripts that want a list of what you may execute.",
"    -r path -- error if command doesn't expand to invoking <path>.",
"    -S -- force stdin to be used for reading passwords.",
"    -t -- test mode: do all checks excepting user input; generate normal",
"	      output (no special debug output); do not not execute anything.",
"	      Exit code=0 if ok to execute, else 1."
"",
"MasqueradeOptions:",
"    ** No command is ever executed when any MasqueradeOption is used.**",
"    Additionally, some debugging output is enabled when these are used:",
"	-F superfile -- names the super file to use.",
"	-G gid -- act as if the invoking user was group gid.",
"	-M mach -- act as if the command was executed from host mach.",
"	-T hh[:mm]/day -- act as if this is execution time.",
"	-U uid -- act as if the invoking user was user uid.",
};

    if (verbosity == HELP_FACTS)
	return;

    if (verbosity == HELP_USAGE) {
	int i;
	for (i = 0; i < NELEM(usage); i++) {
	    fprintf(stderr, usage[i], prog);
	    putc('\n', stderr);
	}
	printf("\nSuper.tab file: `%s'\n\n", superfile);
	return;
    }

    if (verbosity == HELP_FULL) {
	printf("(Use %s -h for general usage information.)\n", prog);
    }

    if (verbosity != HELP_BASIC) {
	printf("Super.tab file: `%s'\n\n", superfile);
    }

    if (usrcmd) {
	if (verbosity == HELP_BASIC)
	    (void) printf("Terse help on command `%s' for user %s:\n\n",
				usrcmd, userinfo.caller.pw_name);
	else if (verbosity == HELP_FULL)
	    (void) printf("Long help on command `%s' for user %s:\n\n",
				usrcmd, userinfo.caller.pw_name);
    } else {
	if (verbosity) {
	    (void) printf("===============================================\n");
	}
	(void) printf("Commands available to user %s%s:\n\n",
		userinfo.caller.pw_name,
		verbosity ? " (use option `-h' for a general usage listing)" :
		" (use option `-H' for a long-winded listing)");
	if (!verbosity) {
	    (void) printf("Command Name    Comments\n");
	    (void) printf("or Pattern              \n");
	    (void) printf("------------    --------\n");
	}
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Prints help information for a command. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
printhelp(verbosity)
int verbosity;
{
    char **p, *s;
    int i, j, n;
    ArgRangePat *arp;

    if (verbosity == HELP_BASIC) {
	if (localinfo.progs.n < 1) {
	    ;
	} else if (localinfo.progs.n == 1) {
	    /* The entry contains a single CmdPattern/FullPath pairs */

	    /* If the command contains a wildcard, print the full filename,
	     * otherwise just the command name.
	     */
	    if (strchr(localinfo.progs.cmd_file[0].Cmd, '*')) {
		printf("%s -> %s:\n",
			localinfo.progs.cmd_file[0].Cmd,
			localinfo.progs.cmd_file[0].File);
		if (localinfo.info)
		    printf("\t\t-- %s\n", localinfo.info);
	    } else {
		printf("%-15s %s\n", localinfo.progs.cmd_file[0].Cmd,
			localinfo.info ? localinfo.info : "");
	    }
	} else {
	    /* The entry contains multiple CmdPattern/FullPath pairs */

	    /* If any command contains a wildcard, print the
	     * full filenames, otherwise just the command name.
	     */
	    int havewildcard=0;
	    for (i=0; havewildcard==0 && i<localinfo.progs.n; i++) {
		havewildcard =
			(strchr(localinfo.progs.cmd_file[0].Cmd, '*') != NULL);
	    }
	    if (havewildcard) {
		for (i=0; i<localinfo.progs.n-1; i++) {
		    printf("%s: %s -> %s;\n", prog,
			localinfo.progs.cmd_file[i].Cmd,
			localinfo.progs.cmd_file[i].File);
		}
		i = localinfo.progs.n - 1;
		printf("%s: %s -> %s:\n", prog,
		    localinfo.progs.cmd_file[i].Cmd,
		    localinfo.progs.cmd_file[i].File);
		if (localinfo.info && *localinfo.info)
		    (void) printf("                -- %s\n", localinfo.info);

	    } else {
		/* Now wildcards in names */
		printf("%s", localinfo.progs.cmd_file[0].Cmd);
		for (i=1; i<localinfo.progs.n; i++)
		    printf(", %s", localinfo.progs.cmd_file[i].Cmd);
		if (localinfo.info && *localinfo.info)
		    (void) printf(":\n                -- %s\n",
			localinfo.info);
		else
		    (void) putchar('\n');
	    }
	}
	return;

    } else if (verbosity == HELP_FACTS) {
	for (i=0; i<localinfo.progs.n; i++)
	    (void) printf("%s %s\n", localinfo.progs.cmd_file[i].Cmd,
					localinfo.progs.cmd_file[i].File);
	return;

    } else if (verbosity == HELP_FULL) {
	for (i=0; i<localinfo.progs.n; i++)
	    (void) printf("%s %s -> %s\n", prog,
			localinfo.progs.cmd_file[i].Cmd,
			localinfo.progs.cmd_file[i].File);
    } else {
	(void) Error(0, 0, "$$printhelp(): Unknown verbosity level %d\n",
			verbosity);
	return;
    }

    /* Assert verbosity == HELP_FULL */
    if (localinfo.info)
	printf("\t## %s\n", localinfo.info);

    if (localinfo.die) {
	printf("\tCommand forces exit using 'die' option:\n\t\tdie=\"%s\"",
		localinfo.die);
	return;
    }

    if (localinfo.print)
	printf("\tCommand prints msg before execution:\n\t\t\"%s\"",
		localinfo.print);

    if (globalinfo.timebefore.next ||
	    globalinfo.timeafter.next ||
	    localinfo.time.next) {
	printf("\tPermitted times for execution (in reverse input order):\n");
	for (j = 0; j<3; j++) {
	    TimeList *tl;
	    switch (j) {
	    case 0: tl = globalinfo.timebefore.next; break;
	    case 1: tl = localinfo.time.next; break;
	    default: tl = globalinfo.timeafter.next; break;
	    }
	    for ( ; tl; tl=tl->next) {
		printf("\t\t%stime~%d:%02d-%d:%02d/%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));
	    }
	}
    }
    if ( *localinfo.user || *localinfo.euser ||
	    *localinfo.group || *localinfo.egroup || *localinfo.u_g ||
	    localinfo.fdlist || *localinfo.owner || localinfo.env) {
	(void) fputs("\tExecutes with:", stdout);
	if (*localinfo.euser) {
	    	(void) printf(" euid=%s", localinfo.euser);
	    if (*localinfo.user)
	    	(void) printf(" ruid=%s", localinfo.user);
	} else if (*localinfo.user) {
	    (void) printf("  uid=%s", localinfo.user);
	}
	if (*localinfo.egroup) {
	    	(void) printf(" egid=%s", localinfo.egroup);
	    if (*localinfo.group) {
	    	(void) printf("  gid=%s", localinfo.group);
	    }
	} else if (*localinfo.group) {
	    (void) printf(" gid=%s", localinfo.group);
	}
	if (*localinfo.u_g) (void) printf(" u+g=%s", localinfo.u_g);
	if (localinfo.env) {
	    for (p=localinfo.env; *p; p++)
		(void) printf("%s%s", p==localinfo.env ? " env=" : ",", *p);
	}
	if (localinfo.setenv) {
	    for (p=localinfo.setenv; *p; p++)
		(void) printf(" setenv=%s", *p);
	}
	if (localinfo.fdlist) (void) printf(" fdlist=%s", localinfo.fdlist);
	if (*localinfo.owner) (void) printf(" owner=%s", localinfo.owner);
	(void) fputs("\n", stdout);
    }

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

    if (localinfo.maxlen1arg < 0)
	(void) printf("\tNo per-arg length limit; ");
    else
	(void) printf("\tMax per-arg length: %ld chars; ",localinfo.maxlen1arg);

    if (localinfo.maxlenargs < 0)
	(void) printf("no limit on sum of all args.\n");
    else
	(void) printf("max over all args: %ld chars.\n", localinfo.maxlenargs);

    arp = (localinfo.argpats.next) ? localinfo.argpats.next :
						globalinfo.argpats.next;
    if (arp) {
	printf("\tUser-entered arguments must match following patterns:\n");
	for ( ; arp && arp->pat && arp->pat[0]; arp = arp->next) {
	    if (arp->arg1 == arp->arg2) {
		fprintf(stderr, "\t\tArg  %d must match pattern: %s\n",
			    arp->arg1, arp->pat); 
	    } else {
		fprintf(stderr, "\t\tArgs %d - %d must match pattern: %s\n",
			    arp->arg1, arp->arg2, arp->pat);
	    }
	}
    }

    if (rcl_nice_incr() != 0)
	printf("\tCommand executes with nice increment = %d.\n",
		    rcl_nice_incr());
    if (rcl_umask() != userinfo.orig_mask)
	printf("\tCommand executes with umask set to 0%o.\n", rcl_umask());


    if (localinfo.authinfo.required) {
	if (localinfo.authinfo.timeout == 0)
	    (void) printf("\t%s authentication is required on each use.\n",
		auth_name());
	else
	    (void) printf(
		"\t%s authentication is required; good for %d minutes%s.\n",
		auth_name(),
		localinfo.authinfo.timeout, localinfo.authinfo.renewtime ? 
		    ", extended with each use" : "");
    }
    if (localinfo.checkvar) {
	(void) printf("\tCaller must enter check-variables:");
	for (i=0; localinfo.checkvar[i]; i++) {
	    printf("  %s", localinfo.checkvar[i]);
	}
	(void) putchar('\n');
    }
    (void) putchar('\n');
}

