#!mkcmd
#* +--------------------------------------------------------------------+ */
#* | Copyright 1991, David Koblas.					| */
#* |   Permission to use, copy, modify, and distribute this software	| */
#* |   and its documentation for any purpose and without fee is hereby	| */
#* |   granted, provided that the above copyright notice appear in all	| */
#* |   copies and that both that copyright notice and this permission	| */
#* |   notice appear in supporting documentation.  This software is	| */
#* |   provided "as is" without express or implied warranty.		| */
#* +--------------------------------------------------------------------+ */
# This version has been modified substantially by KS Braunsdorf, for
# example it is coded in mkcmd rather than pure C, and it know about lots
# more mnemonic attributes, and -u, -g, -f options.
# $Info: sed -n -e "/UnIqueS[t]art/,/UNIqu[e]End/s!.*\tcase '\\(.*\\)': /\\* \\(.*\\)[\t ]\\*/!\\1 is \\2!p" %f
from '<stdio.h>'
from '<syslog.h>'
from '<pwd.h>'
from '<errno.h>'
from '<grp.h>'
from '<ctype.h>'
from '<sys/time.h>'
from '<sys/types.h>'
from '<sys/wait.h>'
from '<sys/socket.h>'
from '<sys/un.h>'
from '<sys/resource.h>'
from '<sys/param.h>'
from '<sys/stat.h>'
from '<sysexits.h>'
from '<unistd.h>'
from '<limits.h>'
from '<stdlib.h>'
from '<fcntl.h>'
from '<dirent.h>'
from '<string.h>'
from '<netdb.h>'
from '<regex.h>'

from '"machine.h"'
from '"main.h"'

require "util_getlogingr.m" "util_cache.m"
require "std_help.m" "std_version.m"

basename "op" ""
%i
static const char acDefPam[] =
	"op";

#if USE_PAM
#include <security/pam_appl.h>
#endif
static void CheckProg(const cmd_t *, const char *, int *, const struct stat *);
static char *FullPath(char *, const char *);
%%
named "Progname"

char* named 'FullProgname' {
	hidden help ""
}
init 1 'FullProgname = (char *)0 == argv[0] ? Progname : argv[0];'

init 3 'openlog(acDefPam, 0, LOG_AUTH);'
init 4 'CheckIndirect(%#, %@);'
after 'if (fReadExisting) ReadFile(acAccess); else pcAccess = acAccess;'

char* 'u' {
	named "pcUid" track "fUid"
	init "(char *)0"
	user "CheckColon(& %n, & %(respect)gn);"
	param "login"
	help "specify the login for the process, as allowed"
}
char* 'g' {
	named "pcGid" track "fGid"
	init "(char *)0"
	param "group"
	help "specify the group for the process, as allowed"
}
char* 'f' {
	named "pcSecFile" track "fSecFile"
	init "(char *)0"
	param "file"
	help "specify direct file for the $f macro, as allowed"
}

boolean 'S' {
	exclude "fguHl"
	named "fSanity"
	help "sanity check rule database"
	boolean 'n' {
		named "fReadExisting"
		init '1'
		help "do not read the existing rule-base"
	}
	list {
		named "SanityCheck"
		param "files"
		help "optional configuration files to check"
	}
}
integer named "fWhichList" {
	init "'_'"
	help ""
}
action 'lrw' {
	exclude "fguHS"
	help "list rules available to this session (list, run, why)"
	update "fWhichList = %w;"
	list {
		named "RulesAllowed"
		update "%n(fWhichList, %#, %@);"
		param "login"
		help "the superuser may request a list for any login"
	}
}

augment action 'V' {
	user "Version();"
}

action 'H' {
	update "ConfHelp(stdout);"
	aborts "exit(EX_OK);"
	help "show only help on the configuration of this program"
}

list {
	param "mnemonic"
	named "OldMain"
	update "%n(%#, (const char **)%@);"
	help "specify the mnemonic and any arguments"
}

zero {
	named ""
	update "/* none */"
	aborts 'fatal("Provide mnemonic, or use -h for help");'
}

%h
/* Each configuration stanza is represented by an `op definition' structure.
 */
typedef struct ODnode {
	const char	*pcname;	/* mnemonic name		*/
	int		nargs, nopts;	/* current counts		*/
	int		margs, mopts;	/* max allocated currently	*/
	int		iline;		/* defining line and file	*/
	const char	*pcfile;
	const char	**args, **opts;	/* vectors of args and attrs	*/
	struct ODnode	*pODnext;	/* next command in order	*/
	struct ODnode	*pODdefscope;	/* the last DEFAULT we saw	*/
	int		fcarp;		/* flag use in sanity checks	*/
	const char	*pcscript;	/* { in-line script } text	*/
} cmd_t;
%%

%i
static char rcsid[] =
	"$Id: op.m,v 2.155 2012/10/05 16:52:00 ksb Exp $";

#if !defined(HAVE_SETFIB)
#define HAVE_SETFIB	defined(SO_SETFIB)
#endif

#if !defined(OP_OPTIONS)		/* $^ support */
#define OP_OPTIONS	1
#endif

#if HAVE_SETFIB
#include <sys/sysctl.h>
#endif

#if VARARGS
#include <varargs.h>
#else /* VARARGS */
#include <stdarg.h>
#endif /* VARARGS */

#if !defined(ACCESS_FILE)
#define ACCESS_FILE	"/usr/local/lib/op/access.cf"
#endif

#if USE_GETSPNAM
#include <shadow.h>
#endif

#if !defined(OP_GIDLIST_MAX)
#if defined(NGROUPS)
#define OP_GIDLIST_MAX	NGROUPS
#else
#if defined(NGROUPS_MAX)
#define OP_GIDLIST_MAX	NGROUPS_MAX
#else
#define OP_GIDLIST_MAX	256
#endif
#endif
#endif

#if !defined(IOV_MAX)
#define IOV_MAX		16	/* min writev supports, that ksb has seen */
#endif

#if !defined(OP_MAX_LDWIDTH)
#define OP_MAX_LDWIDTH	48	/* length of a printf("__%ld", max_long) */
#endif				/* 128*l(2)/l(10) => 38.53, we round up */

#if !defined(PATH_SUDO)
#define PATH_SUDO	"sudo"
#endif

/* When an option looks like an environment definition return true
 */
#if !defined(ENV_OPEN)
#define ENV_OPEN(Mpc)	('$' == (Mpc)[0] && (isalpha((Mpc)[1]) || '_' == (Mpc)[1] || '$' == (Mpc)[1]))
#endif

static const char
	*pcPerp = "<notme>",
	*pcPerpHome = (const char *)0,
	*pcPerpShell = (const char *)0,
	*pcCmd = (const char *)0,
	*pcAccess = (const char *)0,
	acAccess[] = ACCESS_FILE,
	acDefault[] = "DEFAULT",
	acDefNotEnv[] = "[%/]",			/* from sudo's env_check */
	acDefShell[] = "/bin/sh",
#if USE_PAM || defined(TIOCNOTTY)
	acDevTty[] = "/dev/tty",
	acMyHost[] = "localhost",		/* pam session source */
#endif
	acDevNull[] = "/dev/null",		/* the null device */
	acShell[] = "$SHELL",			/* the name $S reads */
	acMagicShell[] = "MAGIC_SHELL",		/* emulate su, mostly */
	acEcho[] = "echo",			/* builtin echo command */
	acOptNetgroups[] = "netgroups",		/* allow netgroup members */
	acOptGroups[] = "groups",		/* allow by membership/gid */
	acOptUsers[] = "users",			/* allow by login name/uid */
	acOptPass[] = "password",		/* must type a password */
	acOptPam[] = "pam",			/* like password, with pam */
	acOptSession[] = "session",		/* let pam build a session */
	acOptCleanup[] = "cleanup",		/* close the session */
	acOptDaemon[] = "daemon",		/* run in background */
	acOptDir[] = "dir",			/* change pwd */
	acOptChroot[] = "chroot",		/* change root */
	acOptUid[] = "uid",			/* new real uid */
	acOptEuid[] = "euid",			/* different effective uid */
	acOptGid[] = "gid",			/* new real gid */
	acOptEgid[] = "egid",			/* different effective gid */
	acOptInitgrps[] = "initgroups",		/* set group list */
	acOptNice[] = "nice",			/* set priority */
	acOptJacket[] = "jacket",		/* manager process */
	acOptHelmet[] = "helmet",		/* authorization process */
	acOptNolog[] = "nolog",			/* e.g. run from cron */
	acOptMask[] = "umask",			/* set file creation mask */
	acOptStdin[] = "stdin",			/* change fd 0 */
	acOptStdout[] = "stdout",		/* change fd 1 */
	acOptStderr[] = "stderr",		/* change fd 2 */
	acOptMac[] = "mac",			/* set process label */
	acOptFib[] = "fib",			/* set network context */
	acOptEnv[] = "environment",		/* pass matching $ENVs */
	acOptArgv0[] = "basename",		/* set/mask argv[0] */
	acOptPATH[] = "$PATH",			/* set search path */
	acPatPerms[] = "fperms",		/* compat with Linux op */
	acPatFowners[] = "fowners",		/* compat with Linux op */
	acOptCount[] = "$#",			/* count params */
	acSpecF[] = "%f",			/* ask for file */
	acSpecD[] = "%d",			/* ask for dirname(file) */
	acSpecU[] = "%u",			/* ask for -u user */
	acSpecG[] = "%g",			/* ask for -g group */
	acSpecMe[] = "%l",			/* ask for Perp */
	acDupU[] = "initgroups,session,cleanup,gid,egid,uid,euid,chroot,dir",
	acKeyInitgroup[] = "%i",		/* copy initgroups */
	acOK[] = "Access granted",		/* little information */
	acSure[] = ", are you sure?",		/* grep -v this when you are */
	acBrokenCode[] = "Internal invariant failure",
#if OP_MAC_SUPPORT
	acMacDeny[] = "-m policy restricted",
#endif
	acBadAllow[] = "Permission denied",
	acBadConfig[] = "Configuration error",	/* help the Admin */
	acBadAlloc[] = "Out of memory",		/* out of resources */
	acBadList[] = "No matching parameter list", /* radiate some info */
	acBadParam[] = "Argument rejected",
	acBadNumber[] = "Wrong number of arguments",
	acBadFew[] = "Too few arguments",
	acBadMnemonic[] = "No such mnemonic";

static cmd_t *NewCmd(const char *pcName);

#if defined(DEBUG)
/* Dump out the given command about the way we read it.			(ksb)
 */
static const cmd_t *
CmdOutput(const cmd_t *cmd)
{
	register int i;

	fprintf(stderr, "%s\t", cmd->pcname);
	for (i = 0; i < cmd->nargs; ++i)
		fprintf(stderr, "%s ", cmd->args[i]);
	if ((cmd_t *)0 != cmd->pODdefscope) {
		fprintf(stderr, "[defaults from %s:%d] ", cmd->pODdefscope->pcfile, cmd->pODdefscope->iline);
	}
	fprintf(stderr, ";\n");
	for (i = 0; i < cmd->nopts; ++i)
		fprintf(stderr, "\t%s\n", cmd->opts[i]);
	fprintf(stderr, "\n");
	return cmd;
}
#else
#define CmdOutput(Mcmd)	(Mcmd)
#endif /* DEBUG */

/* Varargs function to output a message to stderr and exit failure.	(orig)
 * Also syslogs the access failure when pcCmd is set.
 */
#if VARARGS
static void
fatal(va_alist)
va_dcl
{
	auto va_list ap;
	register char *s;

	va_start(ap);
	if ((const char *)0 != pcCmd) {
		syslog(LOG_NOTICE, "user %s FAILED to execute %s", pcPerp, pcCmd);
	}
	s = va_arg(ap, char *);
	fprintf(stderr, "%s: ", Progname);
	vfprintf(stderr, s, ap);
	fputc('\n', stderr);
	va_end(ap);

	exit(EX_DATAERR);
	/*NOTREACHED*/
}
#else /* VARARGS */
static void
fatal(const char *format, ...)
{
	auto va_list ap;

	va_start(ap, format);
	if ((const char *)0 != pcCmd) {
		syslog(LOG_NOTICE, "user %s FAILED to execute %s", pcPerp, pcCmd);
	}
	fprintf(stderr, "%s: ", Progname);
	vfprintf(stderr, format, ap);
	fputc('\n', stderr);
	va_end(ap);

	exit(EX_DATAERR);
	/*NOTREACHED*/
}
#endif /* VARARGS */

#if !defined(RUN_AS)
#define RUN_AS	"/usr/local/bin/op"
#endif

/* If op is installed non-setuid, indirect through sudo to get root	(ksb)
 * access, that way we can run on a host that doesn't let us install setuid
 * but does allow a policy to delegate details to op.  How strange.
 * Naming a mnemonic "op" might confuse the loop check below.
 *
 * In sudoers allow "op" to run us with any args, euid as root, uid as
 * themselves, I'd leave the groups as-is.  I don't use this feature.
 */
static void
CheckIndirect(int argc, char **argv)
{
#if HAVE_issetugid
	/* We have a system call the check, use it
	 */
	if (issetugid())
		return;
#endif

#if RUN_AS_MORTAL
	/* we are setuid to a mortal login, and we can't see that
	 * because we don't have issetugid(2)
	 */
	return;
#else
	/* Try to indirect via sudo, this almost never works because the
	 * rule you need diables sudo for any other purpose. --ksb
	 */
	{
	register int i;
	register char **ppcNew;


	if (0 == getuid() || 0 == geteuid() || getegid() != getgid())
		return;

	if ((char *)0 != argv[1] && 0 == strcmp(Progname, argv[1]))
		fprintf(stderr, "%s: possible sudo/op loop\n", Progname);

	/* Building: "sudo" Progname argv[0] argv[1]... (char *)0
	 */
	if ((char **)0 == (ppcNew = calloc((argc|3)+5, sizeof(char *))))
		fatal(acBadAlloc);
	i = 0;
	ppcNew[i++] = PATH_SUDO;
	ppcNew[i++] = "-u";
	ppcNew[i++] = "root";
	for (ppcNew[i++] = RUN_AS; 0 != argc--; /* move along */)
		ppcNew[i++] = *argv++;
	ppcNew[i] = (char *)0;
	(void)execvp(*ppcNew, ppcNew);
	fatal("execvp: %s: %s", *ppcNew, strerror(errno));
	/*NOTREACHED*/
	}
#endif
}

/* Keep track of a gid list, only add the unique ones.			(ksb)
 */
static void
AddGid(const gid_t gNew, int *pnCount, gid_t *pwSet)
{
	register int i;

	for (i = 0; i < *pnCount; ++i) {
		if (*pwSet == gNew)
			return;
		++pwSet;
	}
	if (OP_GIDLIST_MAX == *pnCount) {
		fatal("%s: out of space in grouplist", acOptGid);
	}
	*pwSet = gNew;
	++*pnCount;
}

/* Search for keywords, like "uid" or "uid=pat"				(orig)
 */
static char *
FindOpt(const cmd_t *cmd, const char *pcAttr)
{
	register int i;
	register char *cp;
	register int iLook;
	static char acEmpty[2] = "";

	iLook = strlen(pcAttr);
	for (i = 0; i < cmd->nopts; ++i) {
		if ((char *)0 == (cp = strchr(cmd->opts[i], '='))) {
			/* uid w/o an = is the same as the empty string */
			if (0 == strcmp(cmd->opts[i], pcAttr))
				return acEmpty;
		} else if (iLook != (cp - cmd->opts[i])) {
			continue;
		} else if (0 == strncmp(cmd->opts[i], pcAttr, iLook)) {
			return cp+1;
		}
	}
	return (char *)0;
}

/* Copy out each of N comma separated values in order, a comma before	(ksb)
 * a comma removes the separator meaning -- better than a backslash.
 * Empty fields don't help op any.
 * Always EndField() to reset the sanity checks after you copy or don't
 * need the returned buffer.  N.B. you can't have more than 1 active loop.
 * Call with pcCur as NULL to free and reset the memory buffer.
 */
static const char *
GetField(const char *pcCur, char **ppcProcess)
{
	register char *pcOut;
	register size_t iLen;
	register int c;
	static char *pcKeep = (char *)0;
	static size_t iKeep = 0;
#if defined(DEBUG)
	static char **ppcLast = (char **)0;
#endif

	if ((char **)0 == ppcProcess) {
		if ((char *)0 != pcKeep) {
			free((void *)pcKeep);
			pcKeep = (char *)0;
			iKeep = 0;
		}
	}
	if ((const char *)0 == pcCur) {
#if defined(DEBUG)
		ppcLast = (char **)0;
#endif
		return (const char *)0;
	}
#if defined(DEBUG)
	if ((char **)0 == ppcLast)
		ppcLast = ppcProcess;
	else if (ppcProcess != ppcLast)
		fatal("GetField snark");
#endif
	if ('\000' == *pcCur) {
		*ppcProcess = (char *)0;
		return (char *)0;
	}
	if (((iLen = strlen(pcCur)) > iKeep) || (char *)0 == pcKeep) {
		/* Since we use the same buffer for all calls make
		 * it big enough we might not need to resize it.
		 */
		iKeep = (iLen|4095)+1;
		if ((char *)0 == pcKeep)
			pcKeep = malloc(iKeep);
		else
			pcKeep = realloc((void *)pcKeep, iKeep);
	}
	if ((char *)0 == pcKeep) {
		fatal(acBadAlloc);
	}
	if ((char **)0 != ppcProcess) {
		*ppcProcess = pcKeep;
	}
	for (pcOut = pcKeep; '\000' != (c = *pcCur); ++pcCur) {
		if (',' == c && ',' != pcCur[1])
			break;
		*pcOut++ = c;
		if (',' == c)
			++pcCur;
	}
	*pcOut = '\000';

	return ('\000' == *pcCur) ? pcCur : (pcCur+1);
}

#if !defined(EndField)
#if defined(DEBUG)
#define EndField()	GetField((char *)0, (char **)0)
#else
#define EndField()	/* nada, saving 544 bytes of text segment */
#endif
#endif /* already defined */

/* Since we only ever have two REs compiled at a time, and we want to	(ksb)
 * diddle the RE for some tags we focus all the RE compiles here.
 * When (char **)0 != ppcCarp, let the caller decide how to proceed,
 * in Check code we print errros, in run code we might syslog the issue
 * without telling the Customer.
 */
static regex_t *
ReComp(const char *pcRE, char **ppcCarp)
{
	static int fAlt = 0;
	static regex_t aRE[2];
	register int iErr;
	static char acMesg[1024];

	if ((const char *)0 == pcRE)
		return & aRE[fAlt];
	fAlt ^= 1;
	if (0 == (iErr = regcomp(& aRE[fAlt], pcRE, REG_EXTENDED|REG_NOSUB)))
		return & aRE[fAlt];
	(void)regerror(iErr, &aRE[fAlt], acMesg, sizeof(acMesg));
	if ((char **)0 == ppcCarp)
		fatal("regcomp: %s: %s", pcRE, acMesg);
	*ppcCarp = acMesg;
	return (regex_t *)0;
}

/* Match against the last compiled RE.					(ksb)
 */
static int
ReMatch(regex_t *pRE, const char *pcValue)
{
	register int iErr;
	auto regmatch_t aRM[1];
	auto char acMesg[1024];

	if (pRE != ReComp((const char *)0, (char **)0)) {
		syslog(LOG_ERR, "please submit a bug report, RE sync bug");
		fatal("Please report an internal RE sync bug!");
	}
	aRM[0].rm_so = 0;
	aRM[0].rm_eo = strlen(pcValue);
	switch (iErr = regexec(pRE, pcValue, 1, aRM, 0)) {
	case 0:
		return 1;
	case REG_NOMATCH:
		return 0;
	default:
		break;
	}
	(void)regerror(iErr, pRE, acMesg, sizeof(acMesg));
	fatal("regexec: %s", acMesg);
	/*NOTREACHED*/
	return 0;
}

/* Return 1 when any field from any listed attribute contains token.	(ksb)
 * List is a concatenation of strings terminated by a double-\000 (constr).
 */
static int
AnyField(const cmd_t *cmd, const char *pcToken, const char *psList)
{
	register const char *pcLook;
	register regex_t *reg;
	register int iRet;
	auto char *pcField, *pcCarp;

	if ((regex_t *)0 == (reg = ReComp(pcToken, &pcCarp))) {
		fatal("%s: %s:%d: regcomp: %s: %s", cmd->pcname, cmd->pcfile, cmd->iline, pcToken, pcCarp);
	}
	iRet = 0;
	for (/* param */; 0 == iRet && '\000' != *psList; psList += strlen(psList)+1) {
		if ((char *)0 == (pcLook = FindOpt(cmd, psList)))
			continue;
		while ((char *)0 != (pcLook = GetField(pcLook, &pcField)))
			if (0 != (iRet = ReMatch(reg, pcField)))
				break;
		EndField();
	}
	regfree(reg);
	return iRet;
}

/* Remember what a mnemonic `needs' on the command-line usage
 */
typedef struct NEnode {
	int	fUid,		/* saw %u, $u, $U			*/
		fGid,		/* saw %g, $g, $G			*/
		fSecFile,	/* saw %f, $f				*/
		fSecFd,		/* saw $F				*/
		fSecDir,	/* saw $d				*/
		fDirFd,		/* saw $D				*/
		fMacs,		/* saw $m				*/
		fDStar;		/* saw $*, $@: makes argc variable	*/
	long	lCmdParam,	/* highest $[0-9]+ in command template	*/
		lHelpParam,	/* highest $[0-9]+ in spec for -l	*/
		lTotalParam;	/* $# count or lCmdParam or -1		*/
} NEEDS;

/* Which indirect objects (-g, -u, -f, -m) we need from the command-line?
 * SecFd is only tripped by $F.  Look for the highest $n mentioned, and
 * limit argc to include that many words if no $* is mentioned.
 */
static int
Craving(const cmd_t *cmd, NEEDS *pNE)
{
	register int c, iArgx, iOptx, iRet;
	register const char *pcScan;
	auto char *pcEnd;
	auto long ul;

	pNE->fSecDir = AnyField(cmd, "^%d$", "uid\000euid\000gid\000egid\000initgroups\000session\000cleanup\000password\000dir\000chroot\000\000");
	pNE->fSecFile = pNE->fSecDir || AnyField(cmd, "^%f$", "uid\000euid\000gid\000egid\000initgroups\000session\000cleanup\000password\000dir\000chroot\000\000") || AnyField(cmd, "^[<>]*%f$", "stdin\000stdout\000stderr\000\000");
	pNE->fUid = AnyField(cmd, "^%u$", "dir\000chroot\000uid\000euid\000gid\000egid\000initgroups\000session\000cleanup\000password\000\000") || AnyField(cmd, "^$|^%u$", "!g@u\000%g@u\000\000");
	pNE->fGid = AnyField(cmd, "^%g$", "gid\000egid\000\000");
	pNE->fSecFd = pNE->fDirFd = 0;
	pNE->fMacs = 0;
	pNE->fDStar = 0;
	pNE->lCmdParam = 0;
	pNE->lTotalParam = pNE->lHelpParam = -1;
	iRet = 0;
	for (iOptx = iArgx = 0; (iOptx < cmd->nopts && (char *)0 != (pcScan = cmd->opts[iOptx++])) || (iArgx < cmd->nargs && (char *)0 != (pcScan = cmd->args[iArgx++])); /* double cond */) {
		/* if looking for an environment variable
		 */
		if (0 == iArgx) {
			register int fCheck = 0;

			if (0 == strncmp(pcScan, "mac=", 4)) {
				fCheck = 1;
			} else if (ENV_OPEN(pcScan)) {
				++pcScan;
				fCheck = 1;
			} else if ('!' == *pcScan || '$' == *pcScan) {
				++pcScan;
			} else {
				continue;
			}
			if ('@' == *pcScan || '*' == *pcScan)
				continue;
			if (isdigit(*pcScan)) {
				ul = strtol(pcScan, (char **)0, 10);
				/* we carp about junk on the end in Sanity */
				if (-1 == pNE->lHelpParam || ul > pNE->lHelpParam)
					pNE->lHelpParam = ul;
				continue;
			}
			if ('#' == *pcScan) {
				if ('=' != pcScan[1]) {
					if (fSanity)
						fprintf(stderr, "%s: %s: %s:%d: %c# has no default value\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcScan[-1]);
					iRet = 4;
					continue;
				}
				pNE->lTotalParam = strtol(pcScan+2, (char **)0, 10);
				if (0 <= pNE->lTotalParam)
					continue;
				pNE->lTotalParam = -1;
				iRet = 3;
				if (fSanity)
					fprintf(stderr, "%s: %s: %s:%d: %c# may not be negative\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcScan[-1]);
				else
					syslog(LOG_ERR, "%s: %s: %s:%d: %c# may not be negative (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcScan[-1], pcScan+2);
				continue;
			}
			if (!fCheck) {
				continue;
			}
			/* check rest for macro expansion */
		}
		while ('\000' != (c = *pcScan++)) {
			if ('$' != c) {
				continue;
			}
			if ('$' == (c = *pcScan++) || '_' == c || '.' == c || '|' == c || '@' == c || '+' == c || '-' == c || ',' == c || '*' == c || '#' == c || 'S' == c) {
				pNE->fDStar |= '*' == c || '@' == c;
				continue;
			}
			if ('s' == c) {
				if ((char *)0 == cmd->pcscript) {
					fprintf(stderr, "%s: %s: %s:%d: $s used without an in-line script\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					iRet = 5;
				}
				continue;
			}
			if ((char *)0 != strchr("cChHiIlLrRaAtToOnNeE~bBwWpPvVqQxkKyYzZ^", c)) {
				continue;
			}
			if ('{' == c) {
				register const char *pcKeep;
				pcKeep = pcScan;
				if ((const char *)0 == (pcEnd = strchr(pcScan, '}'))) {
					fprintf(stderr, "%s: %s: %s:%d: end of argument in ${env} expression\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					iRet = 6;
					break;
				}
				if (pcEnd == pcScan && fSanity) { /* ${} */
					fprintf(stderr, "%s: %s: %s:%d: ${} is meaningless\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					++pcScan;
					iRet = 7;
					break;
				}
				/* Tell sh programmers all digits ${...} is bad
				 */
				do {
					++pcScan;
				} while (isdigit(*pcScan));
				if (pcEnd == pcScan && fSanity) {
					fprintf(stderr, "%s: %s: %s:%d: ${%.*s} is all digits, did you mean $%.*s?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, (int)(pcScan-pcKeep), pcKeep, (int)(pcScan-pcKeep), pcKeep);
					/* we don't fail here, env/op allows */
				}
				pcScan = pcEnd+1;
				continue;
			}
			if ('\\' == c) {
				if ('\000' == *pcScan) {
					fprintf(stderr, "%s: %s: %s:%d: end of argument in backslash escape\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					iRet = 2;
					break;
				}
				++pcScan;
				continue;
			}
			if ('u' == c || 'U' == c) {
				pNE->fUid = 1;
				continue;
			}
			if ('g' == c || 'G' == c) {
				pNE->fGid = 1;
				continue;
			}
			if ('d' == c || 'D' == c) {
				pNE->fSecFile = 1;
				pNE->fSecDir = 1;
				pNE->fDirFd |= 'D' == c;
				continue;
			}
			if ('f' == c || 'F' == c) {
				pNE->fSecFile = 1;
				pNE->fSecFd |= 'F' == c;
				continue;
			}
			if ('m' == c || 'M' == c) {
#if !OP_MAC_SUPPORT
				static const char *pcVetchMac = (char *)0;

				if (pcVetchMac != cmd->pcfile  && fSanity) {
					fprintf(stderr, "%s: %s: %s:%d: unsupported MAC specifications in configuration file\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					pcVetchMac = cmd->pcfile;
				}
#endif
				pNE->fMacs = 'm' == c;
				continue;
			}
			if ('X' == c) {
				if (fSanity && (char *)0 == FindOpt(cmd, acOptChroot)) {
					fprintf(stderr, "%s: %s: %s:%d: uses $X without a chroot set\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
				}
				continue;
			}
			if (!isdigit(c)) {
				fatal("%s: unknown expansion $%c", cmd->pcname, c);
			}
			ul = strtoul(pcScan-1, & pcEnd, 10);
			pcScan = pcEnd;
			if (pNE->lCmdParam < ul) {
				pNE->lCmdParam = ul;
			}
		}
	}
	/* When no $* (or $@) is expanded set a fixed argument list.
	 */
	if (!pNE->fDStar && -1 == pNE->lTotalParam) {
		pNE->lTotalParam = pNE->lCmdParam;
	}
	if (-1 != pNE->lTotalParam && pNE->lHelpParam < pNE->lTotalParam) {
		pNE->lHelpParam = pNE->lTotalParam;
	}
	if (-1 != pNE->lTotalParam && pNE->lCmdParam > pNE->lTotalParam) {
		fprintf(stderr, "%s: %s: %s:%d: $%lu beyond given argument list\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pNE->lCmdParam);
		iRet = 1;
	}
	return iRet;
}

/* Generic attribute match, not a group or login, any(REs) ~= string.	(ksb)
 * Note that the OldSchool regexec we keep returns 1 for a match,
 * people used to the new interface be aware of that logic reversal.
 */
static int
GenMatch(const char *pcCause, const char *pcValue, const char *pcList)
{
	register const char *pcCur;
	register regex_t *reg;
	register int iRet;
	auto char *pcField, *pcCarp;

	iRet = 0;
	if ((char *)0 == pcList) {
		return iRet;
	}
	for (pcCur = GetField(pcList, &pcField); 0 == iRet && (char *)0 != pcCur; pcCur = GetField(pcCur, &pcField)) {
		if ((regex_t *)0 == (reg = ReComp(pcField, &pcCarp)))
			fatal("%s: regcomp: %s: %s", pcCause, pcField, pcCarp);
		iRet = ReMatch(reg, pcValue);
		regfree(reg);
	}
	EndField();
	return iRet;
}

/* Locate the command the Customer asked for from the list.		(ksb)
 * We match the $# first (if configured) then $1,$2,... here, but not $*
 *	rndc	/usr/sbin/rndc $1 ;
 *		$1=^start$,^stop$
 *	rndc	/etc/init.d/named $2 ;
 *		$1=^restart$
 */
static cmd_t *
Find(const char *pcMnemonic, const long iArgc, const char **ppcArgv, const char **ppcWrong)
{
	register int i, iCur;
	register cmd_t *cmd;
	register const char *pcCount, *pcAttr;
	auto char *pcEnd;
	auto char acCvt[OP_MAX_LDWIDTH];	/* $n from config	*/
	static char acDummy[] = "=.";		/* deault match for $n	*/

	for (cmd = NewCmd((const char *)0); (cmd_t *)0 != cmd; cmd = cmd->pODnext) {
		if (0 != strcmp(cmd->pcname, pcMnemonic)) {
			continue;
		}
		if ((char *)0 != (pcCount = FindOpt(cmd, acOptCount)) && iArgc != strtol(pcCount, &pcEnd, 0)+1) {
			if (acBadList != *ppcWrong)
				*ppcWrong = acBadNumber;
			continue;
		}
		if ((char *)0 != (pcCount = FindOpt(cmd, "!#")) && iArgc == strtol(pcCount, &pcEnd, 0)+1) {
			if (acBadList != *ppcWrong)
				*ppcWrong = acBadNumber;
			continue;
		}
		for (i = 0; i < cmd->nopts; ++i) {
			pcAttr = cmd->opts[i];
			if (!('!' == pcAttr[0] || '$' == pcAttr[0]) || !isdigit(pcAttr[1])) {
				continue;
			}
			iCur = strtol(pcAttr+1, &pcEnd, 0);

			/* Trap !3 means argc <= 3,  vs  !3=. means "" != $3
			 * otherwise we fail when argc <= n (no $n)
			 */
			if ('!' == *pcAttr && '\000' == *pcEnd) {
				if (iArgc > iCur)
					break;
				continue;
			}
			if (iArgc <= iCur) {
				break;
			}
			snprintf(acCvt, sizeof(acCvt), "%.*s", (int)(pcEnd-pcAttr), pcAttr);
			if ('\000' == *pcEnd) {
				pcEnd = acDummy;
			}
			if (GenMatch(acCvt, ppcArgv[iCur], pcEnd+1)) {
				if ('!' == pcAttr[0])
					break;
			} else if ('$' == pcAttr[0]) {
				break;
			}
		}
		if (i == cmd->nopts)
			break;
		*ppcWrong = acBadList;
	}
	return cmd;
}
%%

%c
static const char acCompOpts[] =
#if OP_OPTIONS
	"options,"
#if !SENTINEL
	"no"
#endif
	"sentinel,"
#if !OP_SHOW_RULES
	"no"
#endif
	"showrules,"
#if !RUN_AS_MORTAL
	"no"
#endif
	"mortal,"
#if !USE_PAM
	"no"
#endif
	"pam,"

#if !DEBUG
	"no"
#endif
	"debug,"
#if !defined(TEXT_HOSTTYPE)
	"nohosttype"
#else
	TEXT_HOSTTYPE
#endif
#if defined(HOSTOS)
#define op_sTrInG(Mx) #Mx /*space*/
#define op_InDirEct(My)	op_sTrInG(My)
	"(" op_InDirEct(HOSTOS) ")"
#undef op_sTrInG
#undef op_InDirEct
#endif
#else
	"nooptions"
#endif
	;
static char asFKnown[] =		/* see FileAttr below */
	"path\000dev\000ino\000nlink\000atime\000mtime\000ctime\000btime\000birthtime\000size\000blksize\000blocks\000uid\000login@g\000login\000owners\000gid\000group\000mode\000perms\000access\000type\000\000";
static const char acMyself[] =
	".";			/* token might change later */
static const char acCurDir[] =
	".";			/* UNIX name for current directory */

/* Everyplace we take "." for the Customer's login we should take %l	(ksb)
 */
static int /* inline */
IsMyself(const char *const pcCheck)
{
	return (char *)0 == pcCheck ? 0 :
		0 == strcmp(acMyself, pcCheck) ? 1 :
		0 == strcmp(acSpecMe, pcCheck) ? 1 :
		0;
}

static int ReadFile(const char *);
/* Info we need for version output.					(ksb)
 */
static void
Version()
{
	register int uReal, uPriv, gReal, gPriv;
	register struct passwd *pw;
	register struct group *grp;
	auto char acUser[96], acGroup[96];	/* login or group names */

	setpwent();
	pw = (struct passwd *)0;
	setgrent();
	grp = (struct group *)0;
	uPriv = geteuid();
	gPriv = getegid();
	uReal = getuid();
	gReal = getgid();

#if SENTINEL
#if !defined(SENTINEL_MODE_MASK)
#define SENTINEL_MODE_MASK	0022	/* don't allow group or world write */
#endif
	/* A mortal login in 5 groups may sentinel each group.  The super user
	 * may sentinel anything she likes.
	 */
	if (0 == uPriv || 0 == uReal || gPriv != gReal) {
		register char *pcTail;
		auto char acSentDir[MAXPATHLEN+4];
		auto struct stat stCfg;

		if (0 != strcmp(acAccess, strncpy(acSentDir, acAccess, sizeof(acSentDir))) || (char *)0 == (pcTail = strrchr(acSentDir, '/')) || 0 != strcmp(Progname, strncpy(pcTail+1, Progname, sizeof(acSentDir)-(pcTail-acSentDir)))) {
			printf("%s: sentinel computation overflows path length\n", Progname);
		} else if (-1 == stat(acSentDir, &stCfg)) {
			if (0 != strcmp(acDefPam, Progname))
				printf("%s: no sentinel configuration for group `%s\'\n", Progname, Progname);
		} else if ((struct group *)0 == (grp = getgrnam(Progname))) {
			printf("%s: sentinel: no group `%s\'\n", Progname, Progname);
		} else if (stCfg.st_gid != grp->gr_gid) {
			printf("%s: sentinel: group id (%ld) doesn't match directory gid (%ld)\n", Progname, (long)grp->gr_gid, (long)stCfg.st_gid);
		} else if (0 != (stCfg.st_mode&SENTINEL_MODE_MASK)) {
			printf("%s: sentinel: directory too permissive (%04o includes bits from %04o)\n", Progname, (stCfg.st_mode & 07777), SENTINEL_MODE_MASK);
		} else {
			uReal = -1;
			gReal = -1;
			uPriv = stCfg.st_uid;
			gPriv = stCfg.st_gid;
		}
	}
	/* move the the sentinel name if we have see one
	 */
	ReadFile(acAccess);
#endif
	printf("%s: access file `%s'\n", Progname, pcAccess);
	printf("%s: using regex\n", Progname);
	printf("%s: multiple configuration files accepted\n", Progname);
	printf("%s: in-line script and $s accepted\n", Progname);
#if OP_OPTIONS
	printf("%s: %s\n", Progname, acCompOpts);
#endif
#if USE_PAM
	printf("%s: with pam support, default application \"%s\"\n", Progname, acDefPam);
#endif
#if SENTINEL
	printf("%s: with sentinel mode mask %04o\n", Progname, SENTINEL_MODE_MASK);
#endif
	if (uReal == uPriv)
		uPriv = -1;
	else if ((struct passwd *)0 != (pw = getpwuid(uPriv)))
		snprintf(acUser, sizeof(acUser), "login %s", pw->pw_name);
	else
		snprintf(acUser, sizeof(acUser), "uid %ld", (long)uPriv);
	if (gReal == gPriv)
		gPriv = -1;
	else if ((struct group *)0 != (grp = getgrgid(gPriv)))
		snprintf(acGroup, sizeof(acGroup), "group %s", grp->gr_name);
	else
		snprintf(acGroup, sizeof(acGroup), "gid %ld", (long)gPriv);

	/* Sadly we don't have issetugid every place we need it.
	 */
#if HAVE_issetugid
	if (!issetugid() && 0 == getuid())
		printf("%s: run as root\n", Progname);
	else
#endif
	if (-1 != gPriv && -1 == uPriv && 0 == uReal)
		printf("%s: managing superuser, %s\n", Progname, acGroup);
	else if (-1 == gPriv && -1 == uPriv && 0 == uReal)
		printf("%s: managing superuser\n", Progname);
	else if (-1 != gPriv && -1 != uPriv)
		printf("%s: managing %s, %s\n", Progname, acUser, acGroup);
	else if (-1 != uPriv)
		printf("%s: managing %s\n", Progname, acUser);
	else if (-1 != gPriv)
		printf("%s: managing %s\n", Progname, acGroup);
	else /* RUN_AS_MORTAL is set */
		printf("%s: not setuid or setgid\n", Progname);
}

static const char *apcConfHelp[] = {
	"$1\000the specified positional parameter must match one of these REs",
	"$*\000every other positional parameter must match on of these REs",
	"$#\000count of the number of words allowed on the command line",
	"!1\000the specified parameter must not match any REs (others likewise)",
	"basename\000force a different argv[0] for the process",
	"chroot\000change root first, %f, %d, %u, %i, %l, or a path",
#if USE_PAM
	"cleanup\000close our PAM session, specification as session, %u, %f, %i, or . (to match session)",
#else
	"cleanup\000[unsupported] fork a process to close our PAM session, specification as session",
#endif
	"daemon\000double-fork the process into the background and redirect I/O to /dev/null, boolean",
	"dir\000change directory here first, %f, %d, %u, %i, %l, or a path",
	"environment\000allow all existing environment variables to pass, or limit to matching REs",
	"gid\000force the real uid to this group, gid, %f, %d, %u, %g, or %l (invoker's gid)",
	"egid\000make the effective gid different from the gid (same values)",
	"fowners\000an alias for %_.owners",
	"fperms\000an alias for %_.perms",
	"uid\000force the real uid to this login, uid, %f, %d, %u, or %l (invoker's uid)",
	"euid\000make the effective uid different from the uid (same values)",
	"users\000allow execution only for logins that match one of these REs",
	"groups\000allow execution only for logins that have a group matching one of these REs",
	"netgroups\000allow execution for logins included in any listed group",
	"helmet\000a program to run a final check before execution",
	"jacket\000a clean-up program which is the parent of the new program",
	"initgroups\000set the supplementary groups for login, %f, %d, or %u, or %l",
#if OP_MAC_SUPPORT
	"mac\000process MAC label, an expanded string include $m to force -m",
#endif
	"nice\000bias the new scheduling priority",
	"nolog\000skip logging for all successful executions",
#if HAVE_SETFIB
	"fib\000specify an alternate network view",
#else
	"fib\000[unsupported platform] specify an alternate network view",
#endif
	"password\000credential access via %f, %d, %u, %l, or login's password",
#if USE_PAM
	"pam\000pam application credentials required for execution, default as .",
	"session\000set pam session attributes for this login, %f, %d, %u, %l, or %i",
#else
	"pam\000[unsupported] pam application credentials required for execution, default as .",
	"session\000[unsupported] set pam session attributes for this login, %f, %d, %u, %l, or %i",
#endif
	"stdin\000set a forced standard input, %f or a path",
	"stdout\000set a forced standard output, %f or a path",
	"stderr\000set a forced standard error, %f or a path",
	"umask\000set the processes umask (default 022)",
	"$ENV\000pass environment $ENV as is, or set a specific value",
	"%{ENV}\000the new $ENV value must match one of these REs",
	"!{ENV}\000the new $ENV value must not match any of these REs",
	"%d.attr\000%d's stat(2) attr must match one of these REs",
	"!d.attr\000%d's stat(2) attr must not match any of these REs",
	"%u\000-u's login must match one of these REs",
	"!u\000when -u's login matches any of these REs, reject the attempt",
	"%u@g\000allow a -u login that is in at least one group that matches one of these REs",
	"!u@g\000reject any -u login that is in any group that matches one of these REs",
	"%g\000-g's group must match one of these REs",
	"!g\000-g's group must not match any of these REs",
	"%g@u\000allow a -g group that contains at least one login that matches one of these REs",
	"!g@u\000reject any -g group that contains any login that matches one of these REs",
	"%f.attr\000%f's stat(2) attr must match one of these REs",
	"!f.attr\000%f's stat(2) attr must not match any of these REs",
#if OP_MAC_SUPPORT
	"%m\000-m's spelling must match one of these REs",
	"!m\000-m's spelling must not match any of these REs",
#endif
	"%_.attr\000$_'s stat(2) attr must match one of these REs",
	"!_.attr\000$_'s stat(2) attr must not match any of these REs",
	"attr\000one of path,dev,ino,nlink,atime,mtime,ctime,btime,birthtime,size,blksize,blocks,uid,login,owners,gid,group,login@g,mode,perms,access or type",
	(const char *)0
};
static const char *apcExpHelp[] = {
	"$a  client groups (gid) list\000$b  privileged group (gid)",
	"$c  configuration file (directory)\000$d  -f specification's directory (fd)",
	"$e  privileged login (uid)\000$f  -f specification (fd)",
	"$g  -g specification (gid)\000$h  client login's home (targets)",
	"$i  initgroups login (uid)\000$j  unused macro (also unused)",
	"$k  client login's shell (targets)\000$l  client login (uid)",
#if OP_MAC_SUPPORT
	"$m  -m MAC (current) process label\000$n  new group (gid) list",
#else
	"$m  unsupported MAC label\000$n  new group (gid) list",
#endif
	"$o  target group (gid)\000$p  pam session login (uid)",
	"$q  basename of our name (as given)\000$r  client real group (gid)",
	"$s  in-line script (shell)\000$t  target login (uid)",
	"$u  -u login (uid)\000$v  version of op (numeric)",
	"$w  which configuration file (line)\000$x  target directory (root)",
	"$y  tty name (orignal umask)\000$z  escalated pid (original ppid)",
	"$~  privileged login's home\000${env} client's value of $env",
	"$_  target program or script\000$|  the empty string",
	"$1  positional parameter, $2...\000$#  count of parameters specified",
	"$@  parameters as words ($* one word)\000$.  break word here [internal]",
	"$-  request as words ($+ one word)\000$\\  tr escape [a,b,f,n,r,t,v,s,d,o,q]",
	"$$  a literal dollar\000$^  build options description",
	(const char *)0
};

/* Show the customer what op can take in a configuration file.		(ksb)
 */
static void
ConfHelp(FILE *fpOut)
{
	register int i, iWide, iLen;
	register const char *pc;

	fprintf(fpOut, "Configuration format:\n\tmnemonic\tcommand [args] ;\n\t\t\tkeyword[=value][,value...] ...\nmnemonic: a name the customer can remember, or the word \"%s\" (which picks\n\tup at keywords) to set default keywords until the next occurrence or EOF\ncommand: a macro expanded path, or the string %s, or an in-line script\n\tin curly braces.  The close curly must be the first nonwhite-space\n\tcharacter on a line.\nargs: expanded with the macros listed below to form the command's parameters\n\tReplacing the semicolon with an ampersand (&) sets the daemon option\nkeywords:\n", acDefault, acMagicShell);
	iWide = 3;
	for (i = 0; (const char *)0 != (pc = apcConfHelp[i++]); ) {
		iLen = strlen(pc);
		if (iLen > iWide)
			iWide = iLen;
	}
	for (i = 0; (const char *)0 != (pc = apcConfHelp[i++]); ) {
		fprintf(fpOut, "%*s %s\n", iWide, pc, pc+strlen(pc)+1);
	}
	fprintf(fpOut, "RE is an abbreviation for regular expression, see regex(3)\n");
	fprintf(fpOut, "\nTarget command parameters and environment macros (upper case):\n");
	iWide = 1;
	for (i = 0; (const char *)0 != (pc = apcExpHelp[i++]); ) {
		iLen = strlen(pc);
		if (iLen > iWide)
			iWide = iLen;
	}
	for (i = 0; (const char *)0 != (pc = apcExpHelp[i++]); ) {
		fprintf(fpOut, " %*s  %s\n", -iWide, pc, pc+strlen(pc)+1);
	}
	fflush(fpOut);
}

/* If -u ksb:sac then make it -u ksb and -g sac, like All Good Programs	(ksb)
 * _but_ should we set -g?  No.  That lets us suggest a group w/o forcing
 * the %g macro check.  Very subtle backward compatible, or a little broken.
 */
static void
CheckColon(char **ppcUser, char **ppcGroup)
{
	register char *pcColon;

	if ((char **)0 == ppcGroup || (char **)0 == ppcUser || (char *)0 == *ppcUser)
		return;
	if ((char *)0 == (pcColon = strchr(*ppcUser, ':')))
		return;
	*pcColon++ = '\000';
	*ppcGroup = pcColon;
}

/* Return a new command structure on the end of the list.		(ksb)
 * When given a name of (char *)0 return the head command structure.
 */
static cmd_t *
NewCmd(const char *pcName)
{
	static cmd_t *pCMHead = (cmd_t *)0;
	static cmd_t **ppCMEnd = & pCMHead;
	register cmd_t *cmd;

	if ((char *)0 == pcName) {
		return pCMHead;
	}
	if ((cmd_t *)0 == (cmd = malloc(sizeof(cmd_t)))) {
		fatal(acBadAlloc);
	}
	cmd->pcname = pcName;
	cmd->fcarp = 0;
	cmd->nargs = cmd->nopts = 0;
	cmd->margs = cmd->mopts = 16;
	cmd->pcfile = acDevNull;
	cmd->iline = -1;
	cmd->args = (const char **)malloc(sizeof(char *)*cmd->margs);
	cmd->opts = (const char **)malloc(sizeof(char *)*cmd->mopts);
	cmd->pcscript = (const char *)0;
	if ((const char **)0 == cmd->args || (const char **)0 == cmd->opts) {
		fatal(acBadAlloc);
	}
	cmd->pODnext = (cmd_t *)0;
	*ppCMEnd = cmd;
	ppCMEnd = & cmd->pODnext;

	return cmd;
}

/* Simple list accumulation, not the way I'd do it.			(ksb)
 * We save memory here because we don't run all but one of these.  Must rules
 * have < 15 options or aguments.
 */
static void
AddElem(int *piMax, int *piCur, const char ***pppcVec, const char *pcValue)
{
	if (*piMax == *piCur) {
		*piMax += 32;
		*pppcVec = (const char **)realloc((void *)(*pppcVec), sizeof(char *) * *piMax);
		if ((const char **)0 == (*pppcVec))
			fatal(acBadAlloc);
	}
	(*pppcVec)[(*piCur)++] = pcValue;
}

/* Parse a whole file into command structure.				(ksb)
 * (Replaces the huge lex spec with the mkcmd file-cache code.)
 */
static void
OneFile(int fdIn, char *pcFile)
{
	register int c, cSawSemi;
	register unsigned int uCur, fEat;
	register cmd_t *cmd, *cmdDef;
	register char *pcCur, *pcTok, *pcDef;
	auto unsigned int uLines;

	cmd = cmdDef = (cmd_t *)0;
	pcDef = pcTok = (char *)0;
	cSawSemi = '\000';
	pcCur = cache_file(fdIn, &uLines);
	for (uCur = 1; uCur <= uLines; ++uCur, ++pcCur) {
		if ('\000' == *pcCur || '#' == *pcCur) {
			pcCur += strlen(pcCur);
			continue;
		}
		if (';' == *pcCur || '&' == *pcCur) {
			fatal("%s:%d \"%c\" is not a possible mnemonic", pcFile, uCur, *pcCur);
		}
		if (!isspace(*pcCur)) {
			if ((cmd_t *)0 != cmd && '&' == cSawSemi) {
				AddElem(&cmd->mopts, &cmd->nopts, &cmd->opts, acOptDaemon);
			}
			for (pcDef = pcCur; '\000' != (c = *pcCur); ++pcCur) {
				if (isspace(c) || ';' == c || '&' == c)
					break;
			}
			while (isspace(c)) {
				*pcCur++ = '\000';
				c = *pcCur;
			}
			/* The DEFAULT pseudo mnemonic is auto terminated
			 */
			cSawSemi = (0 == strcmp(pcDef, acDefault)) ? ';' : '\000';
			cmd = NewCmd('\000' != cSawSemi ? acDefault : pcDef);
			cmd->pcfile = pcFile;
			cmd->iline = uCur;
			cmd->pODdefscope = cmdDef;
			if ('\000' != cSawSemi) {
				cmdDef = cmd;
			}
			if (';' == c || '&' == c) {
				cSawSemi = c;
				*pcCur = '\000';
				c = *++pcCur;
			}

			/* mnemonic { # long shell script that
			 *	# spans mulitple lines and ends with a
			 *	# close curly as the first non-white
			 *	} args ; ...
			 * run with $S -c $s $1 $2
			 * where $s is "the text w/o the outer curlies"
			 */
			switch (cSawSemi) {
				register char *pcEnd;
				static const char acDefCmd[] = "/bin/sync";
			case ';':
			case '&':
				if (cmd == cmdDef) {
					break;
				}
				/* Configured a null command, make it sync.
				 */
				AddElem(&cmd->margs, &cmd->nargs, &cmd->args, acDefCmd);
				if (fSanity) {
					fprintf(stderr, "%s: %s: %s:%d: empty argument list, assumed \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acDefCmd);
				}
				break;
			case '\000': /* look for in-line script */
				if ('{' != c || !('\000' == pcCur[1] || isspace(pcCur[1]))) /*}*/{
					break;
				}

				cmd->pcscript = pcCur+1;
				while (uCur < uLines) {
					++uCur;
					pcCur += strlen(pcCur);
					*pcCur++ = '\n';
					while (isspace(*pcCur))
						++pcCur;
					if (/*{*/'}' == *pcCur)
						break;
				} /*{*/
				if ('}' != *pcCur) { /*{*/
					fatal("%s:%d: Missing '}' for in-line script", pcFile, uCur);
				}
				pcEnd = pcCur++;
				do {
					*pcEnd-- = '\000';
				} while ('\n' != *pcEnd);
				AddElem(&cmd->margs, &cmd->nargs, &cmd->args, "$S");
				AddElem(&cmd->margs, &cmd->nargs, &cmd->args, "-c");
				AddElem(&cmd->margs, &cmd->nargs, &cmd->args, "$s");
				break;
			}
		}

		for (fEat = 0; '\000' != (c = *pcCur); /* nope */) {
			if (fEat || isspace(c)) {
				++pcCur;
				continue;
			}
			if (';' == c || '&' == c) {
				++pcCur;
				if ((cmd_t *)0 == cmd) {
					fatal("%s:%d %c doesn't terminate a parameter list", pcFile, uCur, c);
				}
				if ('\000' != cSawSemi) {
					fatal("%s:%d double argument separator", pcFile, uCur);
				}
				if (0 == cmd->nargs && acDefault != cmd->pcname) {
					fatal("%s:%d %s: has no command template", pcFile, uCur, cmd->pcname);
				}
				cSawSemi = c;
				continue;
			}
			if ('#' == c) {
				++pcCur;
				fEat = 1;
				continue;
			}
			for (pcTok = pcCur; '\000' != (c = *pcCur); ++pcCur) {
				if (isspace(c))
					break;
			}
			if ('\000' != *pcCur) {
				*pcCur++ = '\000';
			}
			if ('\000' == *pcTok) {
				break;
			}
			if ((cmd_t *)0 == cmd) {
				fatal("%s:%d %s definitions must start in column 1", pcFile, uCur, pcTok);
			}
			if (fSanity && ('&' == pcCur[-1] || ';' == pcCur[-1])) {
				fprintf(stderr, "%s: %s: %s:%d: \"%c\" on end of argument needs a space before it to begin attribute list\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcCur[-1]);
			}
			if ('\000' != cSawSemi) {
				AddElem(&cmd->mopts, &cmd->nopts, &cmd->opts, pcTok);
			} else {
				AddElem(&cmd->margs, &cmd->nargs, &cmd->args, pcTok);
			}
		}
	}
	if ((cmd_t *)0 != cmd && '\000' == cSawSemi) {
		fatal("%s:%d %s: missing definition", pcFile, uCur-1, cmd->pcname);
	}
	if ('&' == cSawSemi) {
		AddElem(&cmd->mopts, &cmd->nopts, &cmd->opts, acOptDaemon);
	}
}

/* Create the actual command spec from the default and the spec given.	(ksb)
 * If the first thing in access.cf is a DEFAULT rule then it applies
 * to all escalations w/o a DEAFAULT before their declatation.
 */
static cmd_t *
Build(cmd_t *cmdOrig)
{
	register cmd_t *cmdRet, *cmdDef;
	register int i, iDef;
	register size_t iLen;
	register char *pcEq;

	cmdDef = NewCmd((const char *)0);
	if ((cmd_t *)0 != cmdDef && acDefault != cmdDef->pcname) {
		cmdDef = (cmd_t *)0;
	}
	if ((cmd_t *)0 != cmdOrig->pODdefscope) {
		cmdDef = cmdOrig->pODdefscope;
	}
	if ((cmd_t *)0 == cmdDef) {
		return cmdOrig;
	}
	if ((cmd_t *)0 == (cmdRet = malloc(sizeof(cmd_t)))) {
		fatal(acBadAlloc);
	}

	cmdRet->pcname = cmdOrig->pcname;
	cmdRet->iline = cmdOrig->iline;
	cmdRet->pcfile = cmdOrig->pcfile;
	cmdRet->nargs = cmdOrig->nargs;
	cmdRet->margs = cmdOrig->margs;
	cmdRet->mopts = ((cmdOrig->nopts+cmdDef->nopts)|3)+1;
	cmdRet->args = cmdOrig->args;
	cmdRet->opts = (const char **)malloc(sizeof(char *)*cmdRet->mopts);
	cmdRet->pODnext = (cmd_t *)0;
	cmdRet->pODdefscope = (cmd_t *)0;
	cmdRet->fcarp = cmdOrig->fcarp;
	cmdRet->nopts = cmdOrig->nopts;
	cmdRet->pcscript = cmdOrig->pcscript;
	for (i = 0; i < cmdOrig->nopts; ++i) {
		cmdRet->opts[i] = cmdOrig->opts[i];
	}

	for (iDef = 0; iDef < cmdDef->nopts; ++iDef) {
		if ((char *)0 == (pcEq = strchr(cmdDef->opts[iDef], '='))) {
			iLen = strlen(cmdDef->opts[iDef]);
		} else {
			iLen = pcEq - cmdDef->opts[iDef];
		}
		for (i = 0; i < cmdRet->nopts; ++i) {
			if (0 != strncmp(cmdRet->opts[i], cmdDef->opts[iDef], iLen)) {
				continue;
			}
			switch (cmdRet->opts[i][iLen]) {
			case '\000':
			case '=':
				break;
			default:
				continue;
			}
			break;
		}
		if (i == cmdRet->nopts) {
			cmdRet->opts[cmdRet->nopts++] = cmdDef->opts[iDef];
		}
	}
	cmdRet->opts[cmdRet->nopts] = (char *)0;
	return (cmd_t *)CmdOutput(cmdRet);
}

/* Check out a match of a group or login against a list of REs.		(ksb/pv)
 * Set pcTag to (const char *)0 and we'll ignore RE comp errors.
 */
static int
AnyMatch(const char *pcRegList, const char *pcName, const int iId, char **ppcCarp)
{
	register const char *pcRE, *pcCheck;
	register int fMatch;
	register regex_t *reg;
	auto char acCvt[OP_MAX_LDWIDTH];
	auto char *pcField;

	snprintf(acCvt, sizeof(acCvt), "%d", iId);
	fMatch = 0;
	for (pcRE = GetField(pcRegList, &pcField); 0 == fMatch && (char *)0 != pcRE; pcRE = GetField(pcRE, &pcField)) {
		pcCheck = pcName;
		if ('#' == pcField[0]) {
			pcCheck = acCvt;
			if ((regex_t *)0 == (reg = ReComp(pcField+1, ppcCarp)))
				continue;
		} else if ((regex_t *)0 == (reg = ReComp(pcField, ppcCarp))) {
			continue;
		}
		if (ReMatch(reg, pcCheck))
			fMatch = 1;
		regfree(reg);
	}
	EndField();
	return fMatch;
}

/* Check the group file for membership of the login in any matching	(pv/ksb)
 * group.  Don't call getpw* here, the pcName is in the shared buf.
 */
static int
MemberMatch(const char *pcRegList, const char *pcName, const gid_t *pwGid, const char *pcTag)
{
	register struct group *grp;
	register int fMatch, i;
	register const char *pcRE;
	register regex_t *reg;
	auto char *pcField, *pcCarp;

	fMatch = 0;
	setgrent();
	pcCarp = (char *)0;
	while (0 == fMatch && (struct group *)0 != (grp = getgrent())) {
		for (pcRE = GetField(pcRegList, &pcField); 0 == fMatch && (char *)0 != pcRE; pcRE = GetField(pcRE, &pcField)) {
			if ((regex_t *)0 == (reg = ReComp(pcField, &pcCarp)))
				continue;
			if (0 == ReMatch(reg, grp->gr_name)) {
				regfree(reg);
				continue;
			}
			regfree(reg);
			if ((gid_t *)0 != pwGid && *pwGid == grp->gr_gid) {
				fMatch = 1;
				break;
			}
			for (i = 0; (char *)0 != grp->gr_mem[i]; ++i) {
				if (0 == strcmp(pcName, grp->gr_mem[i])) {
					fMatch = 1;
					break;
				}
			}
		}
		EndField();
	}
	if ((char *)0 != pcCarp) {
		syslog(LOG_ERR, "MemberMatch: %s: regcomp: %s", pcTag, pcCarp);
	}
	return fMatch;
}

/* Map a login name or #uid to a real uid, for %u.			(ksb)
 * Consult attributes %u, !u, and %u@g, !u@g
 *   %u list of REs, user must match at least one (^charon$,^entomb$)
 *   !u list of REs, user may never match any (^root$)
 *   %u@g list of REs, user must be a member of a matching group (^ops$)
 *   !u@g list of REs, user must not be a member of a matching group (^wheel$)
 */
static uid_t
MapUid(const char *pcThis, const cmd_t *cmd, gid_t *pwGroup)
{
	register struct passwd *pwd;
	register char *pcLimit;
	auto char *pcCarp;

	pcCarp = (char *)0;
	if ((char *)0 != pcThis && '#' == *pcThis) {
		++pcThis;
	}
	if ((char *)0 == pcThis || '\000' == *pcThis) {
		fatal("Mnemonic %s requires -u", cmd->pcname);
	}
	if (isdigit(*pcThis)) {
		if ((struct passwd *)0 == (pwd = getpwuid(atoi(pcThis)))) {
			fatal("%s: no such uid #%s", cmd->pcname, pcThis);
		}
	} else if ((struct passwd *)0 == (pwd = getpwnam(pcThis))) {
		fatal("%s: no such user %s", cmd->pcname, pcThis);
	}
	if ((gid_t *)0 != pwGroup) {
		*pwGroup = pwd->pw_gid;
	}
	/* apply any injunction against or a members-only list
	 */
	if ((char *)0 != (pcLimit = FindOpt(cmd, "!u"))) {
		if (AnyMatch(pcLimit, pwd->pw_name, pwd->pw_uid, &pcCarp))
			fatal("%s: login %s forbidden", cmd->pcname, pwd->pw_name);
	}
	if ((char *)0 != (pcLimit = FindOpt(cmd, "%u"))) {
		if (!AnyMatch(pcLimit, pwd->pw_name, pwd->pw_uid, &pcCarp))
			fatal("%s: login %s not in allowed list", cmd->pcname, pwd->pw_name);
	}
	if ((char *)0 != (pcLimit = FindOpt(cmd, "!u@g"))) {
		if (MemberMatch(pcLimit, pwd->pw_name, pwGroup, "!u@g"))
			fatal("%s: login %s black listed", cmd->pcname, pwd->pw_name);
	}
	if ((char *)0 != (pcLimit = FindOpt(cmd, "%u@g"))) {
		if (!MemberMatch(pcLimit, pwd->pw_name, pwGroup, "%u@g"))
			fatal("%s: login %s doesn't have a correct group", cmd->pcname, pwd->pw_name);
	}
	if ((char *)0 != pcCarp) {
		syslog(LOG_ERR, "MapUid: regcomp: %s", pcCarp);
	}
	return pwd->pw_uid;
}

/* Check if the login list specified contains a matching login		(ksb)
 * When any RE is "", "%u" or "^%u$" match pcUid as a whole word, if %u is
 * a number reverse it to a login name first, if not given (!fUid) then you
 * can't match it anyway (skip the check).  Members format "a\000b\000\000".
 * Bonus %e for exec login (geteuid) and %l for original login (getuid),
 * since none of those are specail in a regular expression we'll pass Sanity.
 */
static int /*ARGSUSED*/
MatchesMember(const char *pcRegList, const char *pcMembers, const char *pcTag)
{
	register int fMatch, cWhich;
	register const char *pcCursor, *pcRE, *pcCheck, *pcWhom;
	register regex_t *reg;
	auto char *pcField, *pcCarp;
	auto char acLoginRE[OP_MAX_LDWIDTH+4];	/* "^46600$" */

	fMatch = 0;
	pcCarp = (char *)0;
	for (pcRE = GetField(pcRegList, &pcField); 0 == fMatch && (char *)0 != pcRE; pcRE = GetField(pcRE, &pcField)) {
		register struct passwd *pwd;

		pcCheck = pcField;
		cWhich = 'u';
		if ('^' == *pcCheck) {
			++pcCheck;
		}
		if ('%' == *pcCheck && ('u' == pcCheck[1] || 'l' == pcCheck[1] || 'e' == pcCheck[1] || 'f' == pcCheck[1] || 'd' == pcCheck[1])) {
			cWhich = pcCheck[1];
			pcCheck += 2;
		}
		if ('$' == *pcCheck) {
			++pcCheck;
		}
		if ('\000' != *pcCheck) {
			if ((regex_t *)0 == (reg = ReComp(pcField, &pcCarp)))
				continue;
		} else {
			pcWhom = pcPerp;
			switch (cWhich) {
			/* To do %f and %d here we'd have to pass a lot more
			 * to MapUid. XXX
			 */
			case 'f':	/* -f's onwer */
			case 'd':	/* -f's dirname's owner */
				fatal("no support for checking %s against -f", pcTag);
				/*NOTREACHED*/
			case 'u':	/* -u target, only matches when given */
				if (!fUid)
					continue;
				pcWhom = pcUid;
				break;
			case 'l':	/* original login name */
				/* the default for -Wall flags */
				break;
			case 'e':	/* the setuid owner of op itself */
				snprintf(acLoginRE, sizeof(acLoginRE), "%ld", (long int)geteuid());
				pcWhom = acLoginRE;
				break;
			default:
				fatal("MapGid: %s: internal error", pcTag);
				/*NOTREACHED*/
			}
			if (isdigit(*pcWhom)) {
				if ((struct passwd *)0 == (pwd = getpwuid(atoi(pcWhom)))) {
					fatal("%s: no such uid #%s", pcTag, pcWhom);
				}
				snprintf(acLoginRE, sizeof(acLoginRE), "^%s$", pwd->pw_name);
				if ((regex_t *)0 == (reg = ReComp(acLoginRE, &pcCarp)))
					continue;
			} else {
				snprintf(acLoginRE, sizeof(acLoginRE), "^%s$", pcWhom);
				if ((regex_t *)0 == (reg = ReComp(acLoginRE, &pcCarp)))
					continue;
			}
		}

		for (pcCursor = pcMembers; '\000' != *pcCursor; pcCursor += strlen(pcCursor)+1) {
			if (ReMatch(reg, pcCursor)) {
				fMatch = 1;
				break;
			}
		}
		regfree(reg);
	}
	EndField();
	if ((char *)0 != pcCarp) {
		syslog(LOG_ERR, "MatchesMember: regcomp: %s", pcCarp);
	}
	return fMatch;
}

/* Same thing for the group, as %g.					(ksb)
 *   %g list of REs, group must match at least one (^operator$,^disk$,)
 *   !g list of REs, group may never match any (^wheel$)
 *   %g@u list of REs, group must include at least one user that matches (^ksb$)
 *   !g@u list of REs, groups may never contain a login that matches any RE (^root$)
 * N.B. For g@u the login must be listed in the group file, not a member by
 * primary login group (because we check from the perspective of the group).
 *	It would be easy to scan the password file looking for logins with
 * the group in question, that would also be wrong for our (local) model.
 */
static gid_t
MapGid(char *pcThis, const cmd_t *cmd)
{
	register struct group *grp;
	register char *pcLimit, *pcBlackList, *pcWhiteList;
	register gid_t gRet;
	auto char *pcCarp;

	pcCarp = (char *)0;
	if ((char *)0 != pcThis && '#' == *pcThis) {
		++pcThis;
	}
	if ((char *)0 == pcThis || '\000' == *pcThis) {
		fatal("Mnemonic %s requires -g", cmd->pcname);
	}
	if (isdigit(*pcThis)) {
		if ((struct group *)0 == (grp = getgrgid(atoi(pcThis))))
			fatal("%s: no such gid #%s", cmd->pcname, pcThis);
		if ((char *)0 == (pcThis = strdup(grp->gr_name)))
			fatal(acBadAlloc);
	} else if ((struct group *)0 == (grp = getgrnam(pcThis))) {
		fatal("%s: no such group %s", cmd->pcname, pcThis);
	}
	gRet = grp->gr_gid;
	if ((char *)0 != (pcLimit = FindOpt(cmd, "!g"))) {
		if (AnyMatch(pcLimit, pcThis, gRet, &pcCarp))
			fatal("%s: group %s black-listed", cmd->pcname, pcThis);
	}
	if ((char *)0 != (pcLimit = FindOpt(cmd, "%g"))) {
		if (!AnyMatch(pcLimit, pcThis, gRet, &pcCarp))
			fatal("%s: group %s not in allowed list", cmd->pcname, pcThis);
	}
	/* Chance here favors not reporting, since the failed RE may have
	 * forced a fatal error, but that case the Customer will complain.
	 */
	if ((char *)0 != pcCarp) {
		syslog(LOG_ERR, "MapGid: regcomp: %s", pcCarp);
	}

	/* Since MatchesMember might call getgrent(3) we can't hold the
	 * static buffer in libc when we call it, so save the member list.
	 * In the Scan loops below you'd add the setpwent loop if you
	 * wanted login group membership to count (which I think is wrong).
	 */
	pcBlackList = FindOpt(cmd, "!g@u");
	pcWhiteList = FindOpt(cmd, "%g@u");
	if ((char *)0 != pcWhiteList || (char *)0 != pcBlackList) {
		register const char *pcMembers;
		register char *pvDispose, *pcCursor, **ppcScan;
		register size_t wLen;

		wLen = 2;
		for (ppcScan = grp->gr_mem; (char *)0 != *ppcScan; ++ppcScan) {
			wLen += strlen(*ppcScan)+1;
		}
		pcMembers = pcCursor = pvDispose = malloc((wLen|15)+1);
		for (ppcScan = grp->gr_mem; (char *)0 != *ppcScan; ++ppcScan) {
			(void)strcpy(pcCursor, *ppcScan);
			pcCursor += strlen(pcCursor)+1;
		}
		*pcCursor = '\000';
		if ((char *)0 != pcWhiteList && !MatchesMember(pcWhiteList, pcMembers, "%g@u")) {
			fatal("%s: group %s doesn't have any matching login", cmd->pcname, pcThis);
		}
		if ((char *)0 != pcBlackList && MatchesMember(pcBlackList, pcMembers, "!g@u")) {
			fatal("%s: group %s is black-listed by membership", cmd->pcname, pcThis);
		}
		/* Done with pcMembers, dispose of it.
		 */
		free(pvDispose);
	}
	return gRet;
}

/* Return the highed explicit $n in the cmd's attributes list, for	(ksb)
 * counting how many command-line params participate in mnemonic matching.
 */
static long
MaxParams(const cmd_t *cmd)
{
	register int i;
	register const char *pcScan;
	register long lRet, lCur;
	auto char *pcRest;

	lRet = 0;
	for (i = 0; i < cmd->nopts; ++i) {
		pcScan = cmd->opts[i];
		if ('$' != pcScan[0] && '!' != *pcScan) {
			continue;
		}
		if (!isdigit(cmd->opts[i][1])) {
			continue;
		}
		if (0 == (lCur = strtoul(pcScan+1, & pcRest, 10))) {
			continue;
		}
		if (lCur > lRet)
			lRet = lCur;
	}
	return lRet;
}

/* Return 1 if every element in pcList is in pcListDup, given that	(ksb)
 * pcKeep is long enough to hold copy of any element from pcList.
 */
static int
HasEvery(const char *pcList, char *pcListDup, char *pcKeep)
{
	register int fSaw;
	register const char *pcScan;
	auto char *pcField, *pcFieldDup;

	while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
		(void)strcpy(pcKeep, pcField);
		EndField();
		fSaw = 0;
		for (pcScan = GetField(pcListDup, &pcFieldDup); (char *)0 != pcScan; pcScan = GetField(pcScan, &pcFieldDup)) {
			if (0 != strcmp(pcKeep, pcFieldDup))
				continue;
			fSaw = 1;
			break;
		}
		EndField();
		if (!fSaw)
			return 0;
	}
	EndField();
	return 1;
}

/* Do the command have exactly the same $1, $2, $3... $n RE lists?	(ksb)
 * We don't try to see if the REs match the same text, I'm not quite that
 * crazy.  But we do look for some other obvious cases, like a permuted list.
 * We check for a leading zero botch in the main sanity checker (e.g. $01).
 * We comment (but do not fail) if two mnemonics are spelled the same from
 * two different files.
 */
static int
SamePats(const cmd_t *cmd, const cmd_t *cmdDup)
{
	register long l, lMax, lMaxDup;
	register char *pcList, *pcListDup;
	register int fSaw;
	register char *pcMem;
	register size_t wLen;
	auto char acCvt[OP_MAX_LDWIDTH]; /* "$%ld" */

	lMax = MaxParams(cmd);
	lMaxDup = MaxParams(cmdDup);
	if (lMax < lMaxDup) {
		lMax = lMaxDup;
	}
	for (l = 1; l <= lMax; ++l) {
		if (sizeof(acCvt) <= snprintf(acCvt, sizeof(acCvt), "$%ld", l)) {
			fatal("%s: $%ld buffer overflow", cmd->pcname, l);
		}
		pcList = FindOpt(cmd, acCvt);
		pcListDup = FindOpt(cmdDup, acCvt);
		if ((char *)0 == pcList && (char *)0 == pcListDup)
			continue;
		if ((char *)0 == pcList || (char *)0 == pcListDup)
			return 0;

		/* The two lengths or'd must be at least as big as either
		 * or in 017 and add 1 for the \000 on the end
		  */
		wLen = (strlen(pcList)|strlen(pcListDup)|15)+1;
		if ((char *)0 == (pcMem = malloc(wLen))) {
			fatal(acBadAlloc);
		}
		fSaw = HasEvery(pcList, pcListDup, pcMem) && HasEvery(pcListDup, pcList, pcMem);
		free((void *)pcMem);
		if (!fSaw)
			return 0;
	}
	return 1;
}

/* Trusting the client user's environment might be a Bad Thing.		(ksb)
 * This checks trusting the value in the _old_ environment (1), or one
 * forced by an assignement (2), or not at all (0).
 */
static int
CheckTrust(const cmd_t *cmd, const char *pcCheck)
{
	register char *pcCur, *pcList;

	if ((char *)0 != (pcCur = FindOpt(cmd, pcCheck))) {
		if ('\000' != *pcCur)
			return 2; /* force to a know value, not `trusted' */
	} else if ((char *)0 == (pcList = FindOpt(cmd, acOptEnv))) {
		return 0;	/* no environment trusted */
	} else if ('\000' == *pcList) {
		/* allows all to pass */
	} else if (!GenMatch(acOptEnv, pcCheck+1, pcList)) {
		return 0; /* our target is not in the match list */
	}
	return 1;
}

/* Most of the next 940 lines (to CheckCompile) is meant to check the
 * combinations of chroot, dir, and command specification to be sure you are
 * running what you think you are.  We data-mine the REs matching variables,
 * paths, -f, -u, -g, and such to try to figure out if the configuration
 * provided can be tricked into running an unexpected binary.
 * This is not a cake walk. --ksb
 */
typedef struct FRnode {
	int afnonlit[2];		/* saw a non-literal		*/
	int frooted;			/* all non-lit REs at slash	*/
	int inegated;			/* number of matches !dropped	*/
	int iexcluded;			/* number of matches and'd out	*/
	int fswapbit;			/* passed in to ForceInit	*/
	size_t wmemmax;			/* max size of the list		*/
	size_t wmemcur;			/* the end of the list		*/
	char *pcmem;			/* free when done		*/
	const char *pslist;		/* all potential values found	*/
	const char *pcname;		/* error message name		*/
	const char *pcmatching;		/* positive match limit		*/
	const char *pcmatchname;	/* options the make the above	*/
	const char *pcforbid;		/* list neg REs e.g. from "!1"	*/
	const char *pcforbidname;	/* options that make the above	*/
	const char *pcand;		/* RE to and with matches	*/
	const char *pcandname;		/* options that make the above	*/
	const char *pcandnot;		/* RE to exclude matches	*/
	const char *pcandnotname;	/* options that make the above	*/
	const char *pckeep;		/* used when swapped for a hold	*/
					/* set by as needed: */
	int fswapped;			/*  we swapped and with primary	*/
	int ffallback;			/*  we are in fallback matches	*/
} FORCE_RE;
static const cmd_t
	*cmdVetchTrustEnv = (cmd_t *)0;


/* Adding a new match to the buffered disjunctions, which are packed	(ksb)
 * into a psList ("item\000next\000...last\000\000").  If the RE has
 * an unquoted '*' '+' or '.' in it we should skip it, or \{range\} notation.
 */
static int
AddRE(const char *pcRE, size_t wLen, FORCE_RE *pFR)
{
	register char *pcSnip;
	register int fExclude = 0;

	pcSnip = (char *)0;
	if (pFR->fswapped && (char *)0 != (pcSnip = strrchr(pcRE, '/'))) {
		if ('\000' == pcSnip[1]) {
			while (pcSnip > pcRE) {
				if ('/' != *--pcSnip)
					break;
			}
			while (pcSnip > pcRE+1) {
				if ('/' != *pcSnip)
					break;
				--pcSnip;
			}
		}
		*pcSnip = '\000';
	}
	if ((char *)0 != pFR->pcand && !GenMatch("and RE", pcRE, pFR->pcand))  {
		fExclude = 1;
	} else if ((char *)0 != pFR->pcandnot && GenMatch("and not RE", pcRE, pFR->pcandnot)) {
		fExclude = 1;
	}
	if ((char *)0 != pcSnip) {
		*pcSnip = '/';
	}

	if (fExclude) {
		pFR->iexcluded += 1;
		return 0;
	}
	if (pFR->wmemmax < (pFR->wmemcur+wLen+2)) {
		register size_t wResize;

		wResize = ((2+wLen)|16383)+1;
		while ((char *)0 == realloc((void *)pFR->pcmem, pFR->wmemmax+wResize)) {
			if ((wResize -= 1024) < wLen)
				fatal(acBadAlloc);
		}
		pFR->wmemmax += wResize;
	}
	(void)strncpy(pFR->pcmem+pFR->wmemcur, pcRE, wLen);
	pFR->wmemcur += wLen;
	/* This is where we might need the +2 in the realloc above
	 */
	if (pFR->fswapped) {
		if ('/' != pFR->pcmem[pFR->wmemcur-1])
			pFR->pcmem[pFR->wmemcur++] = '/';
		pFR->pcmem[pFR->wmemcur++] = '*';
	}
	pFR->pcmem[pFR->wmemcur++] = '\000';
	pFR->pcmem[pFR->wmemcur] = '\000';
	return 0;
}

/* When the RE has a range in it fill that in for us			(ksb)
 * We don't do [[:things:]], deal with it. --ksb
 * and [^13579] is a non-literal, we don't complemnt.
 */
static int
REBracket(const char *pcRE, size_t wLen, FORCE_RE *pFR)
{
	register size_t w, wOpen, wClose;
	register int c;
	register char *pcMem, *pcI;
	auto int iRet;

	/* find pcRE<0> ... <wOpen>[ range <wClose>]right <wLen
	 */
	wOpen = wClose = wLen;
	for (w = 0; w < wLen; ) {
		c = pcRE[w++];
		/* '}' drop {bounds} because we don't parse them yet */
		if ('{' == c && (isdigit(pcRE[w]) || ',' == pcRE[w])) {
			pFR->afnonlit[pFR->ffallback] = 1;
			return 0;
		}
		if ('\\' == c) {		/* \[ is not special */
			w += 1;
			continue;
		}
		if ('[' != c) {
			continue;
		}
		wOpen = w-1;
		wClose = w+1;
		while (wClose < wLen && ']' != pcRE[wClose])
			++wClose;
		if (wClose == wLen) {
			return 0;
		}
		break;
	}

	/* Negative ranges are non-literals, since we don't really
	 * want to match control characters and spaces.  Use of a
	 * negative match usually is something like "$1=^[^/]*$",
	 * which could just as well be exclude by "!1=/".  In either
	 * case we'll not find a literal match for $1.	--ksb
	 */
	if (wOpen+1 < wLen && '^' == pcRE[wOpen+1]) {
		pFR->afnonlit[pFR->ffallback] = 1;
		return 0;
	}

	/* No range operations, just add the RE to the list
	 */
	if (wClose == wLen) {
		return AddRE(pcRE, wLen, pFR);
	}

	/* Open a range, []0-7] matches 9 possible character.
	 */
	if ((char *)0 == (pcMem = malloc(((wOpen+wLen-wClose+1)|7)+1))) {
		fatal(acBadAlloc);
	}
	(void)memset(pcMem, '\000', wOpen+wLen-wClose+1);
	strncpy(pcMem, pcRE, wOpen);
	pcI = pcMem+wOpen;
	strncpy(pcMem+wOpen+1, pcRE+wClose+1, wLen-wClose-1);
	*pcI = '_';
	wLen = strlen(pcMem);
	iRet = 0;
	while (w < wClose) {
		c = pcRE[w++];
		if (w+1 < wClose && '-' == pcRE[w]) {
			++w;
			while (c <= pcRE[w]) {
				*pcI = c++;
				iRet |= REBracket(pcMem, wLen, pFR);
			}
			++w;
			continue;
		}
		*pcI = c;
		iRet |= REBracket(pcMem, wLen, pFR);
	}
	free((void *)pcMem);
	return iRet;
}

/* Assume nothing to produce a list of RE's w/o | in them		(ksb)
 * String with a | but no \(\) around it pass each down, w/o a | pass down.
 * String with ..A..\(B1 | B2 |...\)..Z.., pass A\(B1\)Z down
 *	to ourself, then A\(B2\)Z ... for each Bn
 * We pick the inner-most \(\) pair with an active | in it since that
 * reduces recursion and memory alloaction the most.
 */
static int
RESplit(const char *pcRE, size_t wLen, const char *pcPrefix, const char *pcSuffix, FORCE_RE *pFRCall)
{
	register int c;
	register const char *pcScan, *pcNewRE;
	register char *pcCallLeft, *pcCallRight;
	register size_t w;
	auto size_t wLeft, wCallLeft, wRight, wCallRight;
	auto int iRet, iDepth;
	typedef struct BPnode {
		int foccure, funoccure;
		int fdisjuncts;
		const char *pcleft, *pcright;
		const char *pcbleft, *pcbright;
	} BACK_PARENS;
	auto BACK_PARENS aBP[11];	/* \1...\9 + a spare */
	static const char acEmpty[] = "";

	for (iDepth = 0; iDepth < sizeof(aBP)/sizeof(aBP[0]); ++iDepth) {
		aBP[iDepth].fdisjuncts = 0;
		aBP[iDepth].foccure = aBP[iDepth].funoccure = 0;
		aBP[iDepth].pcleft = aBP[iDepth].pcright = (const char *)0;
		aBP[iDepth].pcbleft = aBP[iDepth].pcbright = (const char *)0;
	}
	iDepth = 0;
	aBP[0].pcleft = pcRE;
	for (w = 0; w < wLen; ) {
		if ('|' == (c = pcRE[w++]))
			aBP[iDepth].fdisjuncts = 1;
		if ('\\' == c) {
			++w;
			continue;
		}
		if ('(' == c) {
			if (++iDepth > 9) {
				fatal(acBrokenCode);
			}
			if (0 == aBP[iDepth].foccure++) {
				aBP[iDepth].pcleft = pcRE+w;
			}
		}
		if (')' == c) {
			if (iDepth < 1) {
				/* When we found an unclosed we have an
				 * unquoted comma in the RE, so quit.
				 */
				return -1;
			}
			/* \(a\)other\(x|y|z\)...
			 * we need to ignore \(a\)
			 */
			if (!aBP[iDepth].fdisjuncts) {
				--aBP[iDepth].foccure;
			} else if (0 == aBP[iDepth].funoccure++) {
				aBP[iDepth].pcright = pcRE+w-1;
			}
			--iDepth;
		}
	}
	aBP[0].pcright = pcRE+w;
	if (0 != iDepth) {
		return -1;
	}
	for (iDepth = 10; --iDepth > 0; ) {
		if (aBP[iDepth].fdisjuncts)
			break;
	}
	if (0 != iDepth) {
		/* fall out */
	} else if ((char *)0 == pcPrefix && (char *)0 == pcSuffix) {
		register size_t wCursor;
		for (wCursor = w = 0; w < wLen; ++w) {
			if ('\\' == pcRE[w] && '|' == pcRE[w+1]) {
				++w;
				continue;
			}
			if ('|' != pcRE[w])
				continue;
			REBracket(pcRE+wCursor, (w-wCursor), pFRCall);
			wCursor = w+1;
		}
		return REBracket(pcRE+wCursor, w-wCursor, pFRCall);
	} else {
		/* The left or right side may have a \(re\) in it, or
		 * even make one, so put them back together and try again.
		 * nested inside a \(re\).
		 */
		register size_t wMem;
		register char *pcMem;

		if ((char *)0 == pcPrefix)
			pcPrefix = acEmpty;
		if ((char *)0 == pcSuffix)
			pcSuffix = acEmpty;

		wMem = ((strlen(pcPrefix)+wLen+strlen(pcSuffix))|7)+1;
		if ((char *)0 == (pcMem = malloc(wMem))) {
			fatal(acBadAlloc);
		}
		snprintf(pcMem, wMem, "%s%.*s%s", pcPrefix, (int)wLen, pcRE, pcSuffix);
		iRet = RESplit(pcMem, strlen(pcMem), (char *)0, (char *)0, pFRCall);
		free((void *)pcMem);
		return iRet;
	}

	wLeft = wCallLeft = aBP[iDepth].pcleft-pcRE;
	if ((char *)0 != pcPrefix)
		wCallLeft += strlen(pcPrefix);
	wRight = wCallRight = wLen - (aBP[iDepth].pcright-pcRE);
	if ((char *)0 != pcSuffix)
		wCallRight += strlen(pcSuffix);
	wCallLeft |= 7;
	wCallRight |= 7;

	pcCallLeft = (char *)0;
	if ((char *)0 == (pcCallRight = malloc(wCallRight+1)) || (char *)0 == (pcCallLeft = malloc(wCallLeft+1))) {
		fatal(acBadAllow);
	}
	snprintf(pcCallLeft, wCallLeft+1, "%s%.*s", (char *)0 == pcPrefix ? acEmpty : pcPrefix, (int)wLeft, pcRE);
	snprintf(pcCallRight, wCallRight+1, "%.*s%s", (int)wRight, aBP[iDepth].pcright, (char *)0 == pcSuffix ? acEmpty : pcSuffix);

	iRet = 0;
	for (pcNewRE = pcScan = aBP[iDepth].pcleft; pcScan < aBP[iDepth].pcright; ++pcScan) {
		register int iFudge;
		if ('\\' == (c = *pcScan)) {
			++pcScan;
			continue;
		}
		if ('|' == c) {
			iFudge = 1; /* rm it in length */
		} else if (pcScan == aBP[iDepth].pcright-1) {
			iFudge = 0;
		} else {
			continue;
		}
		++pcScan;
		iRet = RESplit(pcNewRE, (pcScan-pcNewRE)-iFudge, pcCallLeft, pcCallRight, pFRCall);
		if (pcScan == aBP[iDepth].pcright || 0 != iRet)
			break;
		pcNewRE = pcScan;
	}

	if ((char *)0 != pcCallLeft && pcCallLeft != pcPrefix) {
		free(pcCallLeft);
	}
	if ((char *)0 != pcCallRight && pcCallRight != pcSuffix) {
		free(pcCallRight);
	}
	return iRet;
}

/* Carp (once) if the command has an unquoted comma in an RE		(ksb)
 */
static int
CheckREFormat(const cmd_t *cmd, const char *pcName, const char *pcValue)
{
	register const char *pcScan;
	register int c;
	static int fSupport = 1;
	auto int iRGComma, iUQComma, iParens, iBracket;
	auto int iBackComma, iRange, iNonDigit, fRange;

	iRGComma = iUQComma = iParens = iBracket = 0;
	iBackComma = iRange = iNonDigit = fRange = 0;
	for (pcScan = pcValue; '\000' != (c = *pcScan++); /* nada */) {
		switch (c) {
		case ',':
			if (',' == *pcScan) {
				++pcScan;
				continue;
			}
			if (0 != iParens || 0 != iBracket)
				++iUQComma;
			continue;
		case '(':
			++iParens;
			continue;
		case ')':
			--iParens;
			continue;
		case '[':
			++iBracket;
			if (']' == *pcScan)
				++pcScan;
			continue;
		case ']':
			--iBracket;
			continue;
		case '\\':
			if ('\000' == (c = *pcScan))
				break;
			if (',' == c)
				++iBackComma;
			++pcScan;
			continue;
		case '{':
			if ('\000' == (c = *pcScan))
				break;
			if (',' != c && !isdigit(c))
				continue;	/* re_format says not a range */
			fRange = 1;
			++iRange;
			do {
				if (',' == c) {
					if (',' == *pcScan)
						++pcScan;
					else
						++iRGComma;
					continue;
				} else if (/*{*/ '}' == c) {
					++pcScan;
					--iRange;
					break;
				}
				if (!isdigit(c))
					++iNonDigit;
			} while ('\000' != (c = *pcScan++));
			continue;
		}
	}
	if (iUQComma)
		fprintf(stderr, "%s: %s: %s:%d: %s: commas inside parenthesis must be spelled \",,\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (0 != iRGComma)
		fprintf(stderr, "%s: %s: %s:%d: %s: commas inside '{...}' must be spelled \",,\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (iBackComma)
		fprintf(stderr, "%s: %s: %s:%d: %s: quote commas with \",,\", not \"\\,\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (iParens > 0)
		fprintf(stderr, "%s: %s: %s:%d: %s: missing ')' in RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (iParens < 0)
		fprintf(stderr, "%s: %s: %s:%d: %s: missing '(' in RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName); /*)*/
	else if (0 != iRange)
		fprintf(stderr, "%s: %s: %s:%d: %s: unbalanced '{...}' in RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (0 != iNonDigit)
		fprintf(stderr, "%s: %s: %s:%d: %s: range '{...}' contains non-digit limits\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else if (0 != iBracket)
		fprintf(stderr, "%s: %s: %s:%d: %s: unbalanced '[...]' in RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
	else {
		if (fRange && fSupport) {
			fprintf(stderr, "%s: %s: %s:%d: %s: no support to check REs with bounds expressions ({n,m})\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcName);
			fSupport = 0;
		}
		return 0;
	}
	return 1;
}

/* Given a list of posible options create a list of the ones that	(ksb)
 * exist, return a cat of RE values (sep with commas for GetField).
 * If none in the list exist, return pcDefRE, set *ppcLabel to "none";
 */
static char *
Gather(const cmd_t *cmd, char const **ppcScan, char const **ppcLabel, char const **ppcList, char *pcDefRE)
{
	register int iLabLen, iListLen, i, iReal;
	register const char *pcOpt, *pcValue;
	register char *pcLabel, *pcRet;

	*ppcList = (const char *)0;
	*ppcLabel = (const char *)0;
	if ((const char **)0 == ppcScan)	/* no output buffer */
		return (char *)0;
	iReal = iLabLen = iListLen = 0;
	for (i = 0; (char *)0 != (pcOpt = ppcScan[i++]); /* nothing */) {
		if ((char *)0 == (pcValue = FindOpt(cmd, pcOpt)) || '\000' == *pcValue)
			continue;
		iListLen += strlen(pcValue)+1;	/* "," */
		iLabLen += strlen(pcOpt)+2;	/* ", " */
		++iReal;			/* for adding "and" */
	}
	if (0 == iReal) {
		if ((char *)0 != pcDefRE)
			*ppcLabel = strdup("none");
		return pcDefRE;
	}
	if (iReal > 1) {
		iLabLen += 3;			/* actually size for "and" */
	}
	*ppcList = pcRet = calloc((iListLen|7)+1, sizeof(char));
	*ppcLabel = pcLabel = calloc((iLabLen|7)+1, sizeof(char));
	if ((char *)0 == pcRet || (char *)0 == pcLabel) {
		fatal(acBadAlloc);
	}
	*pcRet = '\000';
	for (i = 0; (char *)0 != (pcOpt = ppcScan[i++]); /* nothing */) {
		if ((char *)0 == (pcValue = FindOpt(cmd, pcOpt)) || '\000' == *pcValue)
			continue;
		if ('\000' == *pcValue || CheckREFormat(cmd, pcOpt, pcValue))
			continue;
		if ('\000' != pcRet[0])
			strncat(pcRet, ",", iListLen+1);
		strncat(pcRet, pcValue, iListLen+1);
		--iReal;
		if ('\000' != pcLabel[0])
			strncat(pcLabel, 0 == iReal ? " and " : ", ", iListLen+1);
		strncat(pcLabel, pcOpt, iLabLen+1);
	}
	return pcRet;
}

/* Setup for the ForceRE loop						(ksb)
 * Now handles disjunctions like $2=^bin|sbin$,^/bin|/sbin$ by mapping
 * them to lists "$2=^bin$,^sbin$,^/bin$,^/sbin$".
 */
static int
ForceInit(const cmd_t *cmd, FORCE_RE *pFR, const char *pcName, const char **ppcAllow, const char **ppcRENot, const char **ppcREAnd, const char **ppcREAndNot, const int fSwapBit)
{
	auto char *pcField, *pcCopy;
	register const char *pcList;

	pFR->afnonlit[0] = pFR->afnonlit[1] = 0;
	pFR->frooted = pFR->fswapped = 0;
	pFR->iexcluded = pFR->inegated = pFR->ffallback = 0;
	pFR->wmemcur = 0;
	pFR->wmemmax = 0;
	pFR->pslist = (char *)0;
	pFR->pcmatching = pFR->pcforbid = (char *)0;
	pFR->fswapbit = fSwapBit;
	pFR->pckeep = pFR->pcand = pFR->pcandnot = (const char *)0;
	pFR->pcname = pcName;
	Gather(cmd, ppcAllow, &pFR->pcmatchname, &pFR->pcmatching, (char *)0);
	Gather(cmd, ppcRENot, &pFR->pcforbidname, &pFR->pcforbid, (char *)0);
	Gather(cmd, ppcREAnd, &pFR->pcandname, &pFR->pcand, (char *)0);
	Gather(cmd, ppcREAndNot, &pFR->pcandnotname, &pFR->pcandnot, (char *)0);
	/* When we return 0 there is a small memory leak from Gathers above
	 */

	if ((char *)0 != (pcList = pFR->pcmatching)) {
		/* OK */
	} else if ((char *)0 != (pcList = pFR->pcand)) {
		pFR->ffallback = 1;
		pFR->fswapped = pFR->fswapbit;
		pFR->pcand = (char *)0;
	} else if ((char *)0 != pFR->pcforbid || (char *)0 != pFR->pcandnot) {
		fprintf(stderr, "%s: %s: %s:%d: %s is only negatively limited (by %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pFR->pcname, (char *)0 != pFR->pcforbid ?  pFR->pcforbidname : pFR->pcandnotname);
		return 0;
	} else if ('$' == pFR->pcname[0]) {	 /* $var only in cmd checks */
		if (cmd != cmdVetchTrustEnv)
			fprintf(stderr, "%s: %s: %s:%d: completely trusts the client's value of %s as part of the command path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pFR->pcname);
		cmdVetchTrustEnv = cmd;
		return 0;
	} else {
		fprintf(stderr, "%s: %s: %s:%d: %s is not limited at all\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pFR->pcname);
		return 0;
	}
	pFR->wmemmax = (strlen(pcList)|4095)+1;
	if ((char *)0 == (pFR->pcmem = malloc(pFR->wmemmax))) {
		fatal(acBadAlloc);
	}

	/* Remeber if all the discarded alternations were rooted at slash.
	 */
	pFR->frooted = 1;
	while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
		pcCopy = strdup(pcField);
		EndField();
		RESplit(pcCopy, strlen(pcCopy), (char *)0, (char *)0, pFR);
		free((void *)pcCopy);
	}
	EndField();
	/* Try the limiting positive for matches if no positives, use the
	 * original positive match as the and-limit.
	 */
	if (0 == pFR->wmemcur) {
		if ((char *)0 == (pcList = pFR->pcand)) {
			free((void *)pFR->pcmem);
			pFR->pcmem = (char *)0;
			return pFR->inegated || pFR->iexcluded || pFR->afnonlit[0];
		}
		pFR->ffallback = 1;
		pFR->fswapped = pFR->fswapbit;
		pFR->pckeep = pFR->pcand;
		pFR->pcand = pFR->pcmatching;
		while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
			pcCopy = strdup(pcField);
			EndField();
			RESplit(pcCopy, strlen(pcCopy), (char *)0, (char *)0, pFR);
			free((void *)pcCopy);
		}
		EndField();
		pFR->pcand = pFR->pckeep;
		if (0 == pFR->wmemcur) {
			free((void *)pFR->pcmem);
			pFR->pslist = pFR->pcmem = (char *)0;
			return pFR->inegated || pFR->iexcluded || pFR->afnonlit[0];
		}
	}
	pFR->pcmem[pFR->wmemcur] = '\000';
	pFR->pslist = pFR->pcmem;
	return 1;
}

/* Free any memory and return true if we saw any non-literal elems.	(ksb)
 * We pass the cmd here for debugging, handy in gdb, it does produce
 * a carp from gcc -Wextra, but I can live with that.
 */
static int	/*ARGSUSED*/
ForceEnd(const cmd_t *cmd, FORCE_RE *pFR)
{
	if ((char *)0 != pFR->pcmatchname) free((void *)pFR->pcmatchname);
	if ((char *)0 != pFR->pcmatching) free((void *)pFR->pcmatching);
	if ((char *)0 != pFR->pcforbidname) free((void *)pFR->pcforbidname);
	if ((char *)0 != pFR->pcforbid) free((void *)pFR->pcforbid);
	if ((char *)0 != pFR->pcandname) free((void *)pFR->pcandname);
	if ((char *)0 != pFR->pcand) free((void *)pFR->pcand);
	if ((char *)0 != pFR->pcandnotname) free((void *)pFR->pcandnotname);
	if ((char *)0 != pFR->pcandnot) free((void *)pFR->pcandnot);

	if ((char *)0 != pFR->pcmem) {
		free((void *)pFR->pcmem);
		pFR->pcmem = (char *)0;
	}
	return pFR->afnonlit[0];
}

/* If the RE forces a literal string return that string.		(ksb)
 * For example this matches 3 fixed strings:
 *	$1=^/bin/\(ls|pwd\)$,^/usr/bin/sort$
 * We are called with just spec from the list at a time.
 */
static char *
ForceRE(const cmd_t *cmd, FORCE_RE *pFR)
{
	register int i, iBack, iSearch, fAbort;
	register const char *pcScan;
	auto const char *pcOrig;
	auto char *pcCarp, *pcCopy;
	auto char acPath[MAXPATHLEN+4];
	auto const char *aapcBakRef[10][2];
	auto regex_t *reg;
	static const cmd_t *cmdNegative = (cmd_t *)0;

	/* If we are at the end of the list try the pcand list
	 * to see if we can get some more matches.  We'll and with
	 * the old list, in case it was all *'s and ?'s and +'s.
	 * When we finish that list we end up here to put the FRnode
	 * back in order and return control with a NULL.
	 */
	if ((char *)0 == (pcScan = pFR->pslist)) {
		register const char *pcList;
		auto char *pcField;

		if ((char *)0 == pFR->pcmem) {	/* all excluded case */
			return (char *)0;
		}
		pcList = pFR->pcand;
		if (pFR->ffallback) {	/* done with reverse case */
			pFR->pcand = pFR->pckeep;
			pFR->ffallback = 0;
			pFR->fswapped = 0;
			return (char *)0;
		}
		if ((char *)0 == pcList) {
			return (char *)0;
		}

		/* Reverse the positive matches, reset the buffer and
		 * load with potentials for the new positive RE list.
		 * Then recurse for that list.
		 */
		pFR->ffallback = 1;
		pFR->fswapped = pFR->fswapbit;
		pFR->wmemcur = 0;
		pFR->pcmem[0] = '\000';
		while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
			pcCopy = strdup(pcField);
			EndField();
			RESplit(pcCopy, strlen(pcCopy), (char *)0, (char *)0, pFR);
			free((void *)pcCopy);
		}
		EndField();
		pFR->pslist = pFR->pcmem;
		return ForceRE(cmd, pFR);
	}
	i = strlen(pcScan)+1;
	pFR->pslist = ('\000' == pcScan[i]) ? (char *)0 : pcScan+i;

	for (i = 0; i < 10; ++i) {
		aapcBakRef[i][1] = aapcBakRef[i][0] = (char *)0;
	}

	pcOrig = pcScan;
	if ('^' != *pcScan++) {
		pFR->afnonlit[pFR->ffallback] = 1;
		pFR->frooted = 0;
		return ForceRE(cmd, pFR);
	}
	if (0 == pFR->frooted) {
		/* nada */;
	} else if ('/' == pcScan[0] && !('*' == pcScan[1] || '?' == pcScan[1])) {
		/* ok */;
	} else if ('[' == pcScan[0] && '/' == pcScan[1] && ']' == pcScan[2] && !('*' == pcScan[3] || '?' == pcScan[3])) {
		/* ok */;
	} else {
		pFR->frooted = 0;
	}

	fAbort = iBack = 0;
	acPath[0] = '\000';
	aapcBakRef[0][0] = acPath;
	for (i = 0; '\000' != *pcScan; ++i) {
		if (i > MAXPATHLEN) {
			/* too large */
			fAbort = 1;
			break;
		}

		/* In a$b$d the dollars are not special to the RE engine
		 * in the first position *, +, and ? are not special.
		 */
		if ('$' == pcScan[0] && '\000' == pcScan[1])
			break;
		if ('*' == pcScan[0] && i > 0)
			break;
		if ('+' == pcScan[0] && i > 0)
			break;
		if ('?' == pcScan[0] && i > 0)
			break;
		if ('|' == pcScan[0])
			break;
		if ('.' == pcScan[0])
			break;

		acPath[i] = *pcScan;
		if ('(' == *pcScan) {
			acPath[i] = *++pcScan;
			aapcBakRef[++iBack][0] = &acPath[i];
		} else if (')' == *pcScan) {
			iSearch = iBack;
			while (iSearch > 0 && (const char *)0 != aapcBakRef[iSearch][1])
				--iSearch;
			if (0 == iSearch) {
				fprintf(stderr, "%s: RE: %s: unbalanced close of a back-reference\n", Progname, pcOrig);
				fAbort = 1;
				break;
			}
			--i;
			aapcBakRef[iSearch][1] = &acPath[i];
		} else if ('\\' != *pcScan) {
			/* nada */;
		} else if ('\000' == *++pcScan) {
			/* regcomp complains about trailing backslash for us */
			fAbort = 1;
			break;
		} else if (isdigit(*pcScan)) {
			register int iT, iLen;

			iT = *pcScan-'0';
			if ((const char *)0 == aapcBakRef[iT][1]) {
				fprintf(stderr, "%s: RE: %s: unbalanced open of a back-reference\n", Progname, pcOrig);
				fAbort = 1;
				break;
			}
			iLen = aapcBakRef[iT][1]-aapcBakRef[iT][0];
			if (MAXPATHLEN < i + iLen) {
				/* too large? */
				fAbort = 1;
				break;
			}
			strncpy(&acPath[i], aapcBakRef[iT][0], iLen);
			i += iLen-1;
		}
		++pcScan;
	}
	acPath[i] = '\000';
	/* Non-constant RE, is it no anchored?
	 */
	if (fAbort) {
		if ('/' != acPath[0]) {
			pFR->frooted = 0;
		}
		return ForceRE(cmd, pFR);
	}

	/* We found a string, now look for unclosed backrefs if we made it
	 * to the end of the string.
	 */
	iSearch = iBack;
	while (iSearch > 0 && (const char *)0 != aapcBakRef[iSearch][1])
		--iSearch;
	if ((('$' == pcScan[0] && '\000' == pcScan[1]) || '\000' == *pcScan) && 0 != iSearch) {
		fprintf(stderr, "%s: RE: %s: unclosed back-reference\n", Progname, pcOrig);
		return ForceRE(cmd, pFR);
	}
	if ('$' != pcScan[0] || '\000' != pcScan[1]) {
		pFR->afnonlit[pFR->ffallback] = 1;
		return ForceRE(cmd, pFR);
	}

	/* Are we foiled by a configuration error!  The positive RE forces a
	 * literal string which the negative RE also discards.
	 */
	pcCarp = (char *)0;
	if ((char *)0 != pFR->pcforbid && (regex_t *)0 != (reg = ReComp(pFR->pcforbid, &pcCarp)) && ReMatch(reg, acPath)) {
		pFR->inegated += 1;
		if (cmd != cmdNegative)
			fprintf(stderr, "%s: %s: %s:%d: %s forces a fixed match (%s), but %s (%s) forbids that string\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pFR->pcmatchname, acPath, pFR->pcforbidname, pFR->pcforbid);
		cmdNegative = cmd;
		return ForceRE(cmd, pFR);
	}
	pcCarp = (char *)0;
	if ((char *)0 != pFR->pcandnot && (regex_t *)0 != (reg = ReComp(pFR->pcandnot, &pcCarp)) && ReMatch(reg, acPath)) {
		pFR->inegated += 1;
		if (cmd != cmdNegative)
			fprintf(stderr, "%s: %s: %s:%d: %s forces a fixed match (%s), but %s (%s) forbids that string\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pFR->pcmatchname, acPath, pFR->pcandnotname, pFR->pcandnot);
		cmdNegative = cmd;
		return ForceRE(cmd, pFR);
	}

	return strdup(acPath);
}

/* See if $PATH searches "." or ".."					(ksb)
 * N.B.  PATH=bin:... PATH=:/usr/local/bin... and PATH=/something::/other
 * all do.
 */
static int
CheckPATHTrust(const char *pcPath)
{
	if ((char *)0 == pcPath) {
		return 1;
	}
	while ('/' == *pcPath) {
		while ('\000' != *pcPath && ':' != *pcPath)
			++pcPath;
		if ('\000' == *pcPath)
			return 1;
		if (':' == pcPath[0] && ':' == pcPath[1])
			return 0;
		++pcPath;	/* skik the colon sep */
	}
	return '\000' == *pcPath;
}

/* When the RE matches any string return true. N.B. ^$ is special.	(ksb)
 * We don't handle parentheses, I'm not that crazy about the message.
 */
static int
IsDotStar(const char *pcRE)
{
	if ('^' == *pcRE) {
		++pcRE;
		if ('$' == *pcRE)
			return 0;
	}
	while ('.' == pcRE[0] && ('*' == pcRE[1] || '?' == pcRE[1]))
		pcRE += 2;
	if ('$' == *pcRE)
		++pcRE;
	return '\000' == *pcRE;
}

/* Reduce the number of arguments passed through the functions below	(ksb)
 */
typedef struct SPgrp {
	int iexit;
	const char *pcspecF;		/* who used -f			*/
	const char *pcspecG;
	const char *pcspecU;
	const char *pcspecM;
	int freal;			/* $1 built still might exist	*/
	const char *pcroot;		/* any know, active chroot	*/
	const char *pcdir;		/* the computed dir		*/
	char *pcexecok;			/* we did stat(2) a cmd here	*/
	char *pcexecmiss;		/* we failed stat(2) a cmd here	*/
	int fwabsroot;			/* warned const nonasb root	*/
	int fwabsdir;			/* warned const nonasb chdir	*/
	int ierrno;			/* ... the errno from that stat	*/
	NEEDS *pNE;			/* for $* in FinishExec		*/
	char acpwd[MAXPATHLEN*2+256];
	char accmd[MAXPATHLEN*2+256];
} SPEC;

static const char
	acPos_Cmd[] = "%_",
	acPos_CmdP[] = "%_.path",
	acNeg_Cmd[] = "!_",
	acNeg_CmdP[] = "!_.path",
	acSlash[] = "/";
static const char
	*apcPosF[] = { acPos_CmdP, acPos_Cmd, "%f.path", "%f", (char *)0 },
	*apcNegF[] = { acNeg_CmdP, acNeg_Cmd, "!f.path", "!%f", (char *)0 },
	*apcPosG[] = { acPos_CmdP, acPos_Cmd, "%g", (char *)0},
	*apcNegG[] = { acNeg_CmdP, acNeg_Cmd, "!g", (char *)0},
	*apcPosP[] = { acPos_CmdP, acPos_Cmd, acDevNull, "$*", (char *)0},
	*apcNegP[] = { acNeg_CmdP, acNeg_Cmd, acDevNull, "!*", (char *)0},
	*apcPosU[] = { acPos_CmdP, acPos_Cmd, "%u", (char *)0},
	*apcNegU[] = { acNeg_CmdP, acNeg_Cmd, "!u", (char *)0},
	*apcPosE[] = { acPos_CmdP, acPos_Cmd, (char *)0, (char *)0},
	*apcNegE[] = { acNeg_CmdP, acNeg_Cmd, (char *)0, (char *)0},
#define SKIP_WHOLE_PATS	2	/* skip the %_ and !_ limiters above */
	*apcPosD[] = { "%d.path", "%d", (char *)0},
	*apcNegD[] = { "!d.path", "!d", (char *)0};
static char
	acFakeDir[16],			/* sential for run-time value	*/
	acFakeRoot[16];

/* We passed the chroot and dir checks, now look at the program path	(ksb)
 * Pathed can become absolute or relative, but the others lock us in.
 * We go to a lot of trouble to parse the whole strings (maybe a few
 * times) to check for unsafe combinations.
 */
static void
FinishExec(const cmd_t *cmd, SPEC *pSP, const char *pcScan, char *pcBuild)
{
	register char *pcEnd;
	register int c;
	auto int iDShorts, iSkip;
	auto char *pcFreeNeg, *pcFreePos;
	auto const char *pcExcludes = (char *)0;
	auto FORCE_RE FR;
	auto struct stat stExec;
	auto char acToExec[MAXPATHLEN*4+512];
	static const char acMe[] = "command";
	/* Each cmd complains only once for each case
	 */
	static const cmd_t
		*cmdUsesF = (cmd_t *)0,
		*cmdUsesG = (cmd_t *)0,
		*cmdUsesU = (cmd_t *)0,
		*cmdUsesQ = (cmd_t *)0,
		*cmdUses_ = (cmd_t *)0,
		*cmdVetchLitWhite = (cmd_t *)0,
		*cmdVetchDollarStar = (cmd_t *)0,
		*cmdVetchSingle = (cmd_t *)0,
		*cmdVetchFake = (cmd_t *)0,
		*cmdVetchPath = (cmd_t *)0,
		*cmdVetchRel = (cmd_t *)0,
		*cmdVetchAbs = (cmd_t *)0,
		*cmdVetchRej = (cmd_t *)0,
		*cmdMissedDir = (cmd_t *)0;

	pcFreeNeg = pcFreePos = (char *)0;	/* fix a small memory leak */
	if (0 == strcmp(acMagicShell, pcScan))
		pcScan = "$S";
	while ('\000' != (c = (*pcBuild = *pcScan++))) {
		if ('/' == c) {	/* but, for looks, do not double them */
			if (pcBuild == pSP->accmd || '/' != pcBuild[-1])
				++pcBuild;
			continue;
		}
		if ('$' != c) {
			++pcBuild;
			continue;
		}
		switch (*pcScan) {
		case '\000':	/* "...$" */
			fprintf(stderr, "%s: %s: %s:%d: trailing dollar sign\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			continue;
		case '$': /* $$ -> $ */
			++pcBuild, ++pcScan;
			continue;
		case '|': /* $| -> empty */
			++pcScan;
			continue;
		case '.': /* split word */
			*pcBuild = '\000';
			pcScan += strlen(pcScan);
			continue;
		case '\\':
			switch (c = *++pcScan) {
			case 'a':
				*pcBuild++ = '\007'; break;
			case 'b':
				*pcBuild++ = '\b'; break;
			case 'd':
				*pcBuild++ = '"'; break;
			case 'f':
				*pcBuild++ = '\f'; break;
			case 'n':
				*pcBuild++ = '\n'; break;
			case 'o':
				*pcBuild++ = '`'; break;
			case 'q':
				*pcBuild++ = '\''; break;
			case 'r':
				*pcBuild++ = '\r'; break;
			case 's':
				*pcBuild++ = ' '; break;
			case 't':
				*pcBuild++ = '\t'; break;
			case 'v':
				*pcBuild++ = '\v'; break;
			case '\000':	/* trailing backslash */
				--pcScan;
				*pcBuild = '\\'; break;
			default:
				*pcBuild++ = c; break;
			}
			if (isspace(pcBuild[-1]) && cmd != cmdVetchLitWhite) {
				fprintf(stderr, "%s: %s: %s:%d: forced white-space in the command path?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
				cmdVetchLitWhite = cmd;
			}
			++pcScan;
			continue;
		case 'x':
			strcpy(pcBuild, ((char *)0 == pSP->pcdir) ? acCurDir : pSP->pcdir);
			pcBuild += strlen(pcBuild);
			continue;
		case 'X':
			strcpy(pcBuild, ((char *)0 == pSP->pcroot) ? acSlash : pSP->pcroot);
			pcBuild += strlen(pcBuild);
			continue;
		case 's': /* { script } is trusting acShell, basically */
		case 'S': /* this is even more explicit */
			{
			auto char *pcRecur, *pcOpt;
			auto int wLen;

			++pcScan;
			switch (CheckTrust(cmd, acShell)) {
			case 0: /* not allowed so we get our default */
				strcpy(pcBuild, acDefShell);
				break;
			case 1:
				fprintf(stderr, "%s: %s: %s:%d: trusting %s from the user's environment\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acShell);
				pSP->iexit = EX_DATAERR;
				strcpy(pcBuild, acShell);
				pSP->freal = 0;
				break;
			case 2:	/* the value is in our config, check it */
				if ((char *)0 == (pcOpt = FindOpt(cmd, acShell)))
					fatal("Internal error: check trust failure");
				wLen = (strlen(pcScan)+strlen(pcOpt))|3;
				if ((char *)0 == (pcRecur = malloc(++wLen)))
					fatal(acBadAlloc);
				snprintf(pcRecur, wLen, "%s%s", pcOpt, pcScan);
				FinishExec(cmd, pSP, pcRecur, pcBuild);
				free((void *)pcRecur);
				return;
			}
			pcBuild += strlen(pcBuild);
			}
			continue;
		case 'f':
		case 'd':
			pcBuild[0] = '$';
			pcBuild[1] = *pcScan;
			pcBuild[2] = '\000';
			++pcScan;
			if ((char *)0 != pSP->pcspecF) {
				if (cmd != cmdUsesF)
					fprintf(stderr, "%s: %s: %s:%d: -f used for both command and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->pcspecF);
				pSP->iexit = EX_DATAERR;
				cmdUsesF = cmd;
			}
			/* $f/$d may occure twice in a command, we don't bump it
			 */
			pSP->pcspecF = acMe;

			iDShorts = 'd' == pcScan[-1];
			iSkip = (pSP->accmd == pcBuild) ? 0 : SKIP_WHOLE_PATS;
			if (ForceInit(cmd, &FR, "file as a command", apcPosF+iSkip, apcNegF+iSkip, apcPosD, apcNegD, 1)) {
				pcExcludes = "%d or !d path limits";
				break;
			}
			*pcBuild++ = '$';
			*pcBuild++ = pcScan[-1];
			pSP->freal = 0;
			continue;
		case 'g': /* you are kidding, right? */
			++pcScan;
			if ((char *)0 == pSP->pcspecG) {
				pSP->pcspecG = acMe;
			} else if (acMe != pSP->pcspecG) {
				if (cmd != cmdUsesG)
					fprintf(stderr, "%s: %s: %s:%d: -g used for both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acMe, pSP->pcspecG);
				pSP->iexit = EX_DATAERR;
				cmdUsesG = cmd;
			}
			iSkip = (pSP->accmd == pcBuild) ? 0 : SKIP_WHOLE_PATS;
			if (ForceInit(cmd, &FR, "group as a command", apcPosG+iSkip, apcNegG+iSkip, (const char **)0, (const char **)0, 0)) {
				pcExcludes = (char *)0;
				iDShorts = 0;
				break;
			}
			strcpy(pcBuild, "$g");
			pcBuild += strlen(pcBuild);
			pSP->freal = 0;
			continue;
		case 'u':
			++pcScan;
			if ((char *)0 == pSP->pcspecU) {
				pSP->pcspecU = acMe;
			} else if (acMe != pSP->pcspecU) {
				if (cmd != cmdUsesU)
					fprintf(stderr, "%s: %s: %s:%d: -u used by both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acMe, pSP->pcspecU);
				pSP->iexit = EX_DATAERR;
				cmdUsesU = cmd;
			}
			iSkip = (pSP->accmd == pcBuild) ? 0 : SKIP_WHOLE_PATS;
			if (ForceInit(cmd, &FR, "login as a command", apcPosU+iSkip, apcNegU+iSkip, (const char **)0, (const char **)0, 0)) {
				pcExcludes = (char *)0;
				iDShorts = 0;
				break;
			}
			strcpy(pcBuild, "$u");
			pcBuild += strlen(pcBuild);
			pSP->freal = 0;
			continue;
		case '{': /*} a variable from the client environment */
			{
			static char *pcDup = (char *)0;
			auto const char *pcMark;
			auto size_t wLen;

			if ((char *)0 == (pcEnd = strchr(pcScan, /*{*/'}'))) {
				fprintf(stderr, "%s: %s: %s:%d: unbalanced curly reference\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
				pSP->iexit = EX_CONFIG;
				return;
			}
			if ((char *)0 != pcDup)		/* free last version */
				free((void *)pcDup);

			/* When we can't find a value use ${NAME} below
			 */
			pcMark = ++pcScan;
			pcScan = pcEnd+1;
			wLen = ((pcEnd-pcMark)|7)+9;
			snprintf(pcBuild, wLen, "$%.*s", (int)(pcEnd-pcMark), pcMark);

			/* Let Dup be !{NAME}, then %{NAME} to fetch RE
			 * limits, then $NAME for environment set.
			 */
			if ((char *)0 == (pcDup = malloc(wLen)))
				fatal(acBadAlloc);
			snprintf(pcDup, wLen, "!{%.*s}", (int)(pcEnd-pcMark), pcMark);
			apcNegE[SKIP_WHOLE_PATS] = pcFreeNeg = strdup(pcDup);
			snprintf(pcDup, wLen, "%%{%.*s}", (int)(pcEnd-pcMark), pcMark);
			apcPosE[SKIP_WHOLE_PATS] = pcFreePos = strdup(pcDup);
			snprintf(pcDup, wLen, "$%.*s", (int)(pcEnd-pcMark), pcMark);
			iSkip = (pSP->accmd == pcBuild) ? 0 : SKIP_WHOLE_PATS;
			if (ForceInit(cmd, &FR, pcDup, apcPosE+iSkip, apcNegE+iSkip, (const char **)0, (const char **)0, 0)) {
				pcExcludes = (char *)0;
				iDShorts = 0;
				break;
			}
			/* Didn't used them, so dispose of them
			 */
			free((void *)pcFreeNeg);
			free((void *)pcFreePos);
			apcNegE[SKIP_WHOLE_PATS] = pcFreeNeg = (char *)0;
			apcPosE[SKIP_WHOLE_PATS] = pcFreePos = (char *)0;

			pSP->freal = 0;
			if (cmd != cmdVetchTrustEnv)
				fprintf(stderr, "%s: %s: %s:%d: completely trusts the client's value of %s as part of the command path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcBuild);
			cmdVetchTrustEnv = cmd;
			pcBuild += strlen(pcBuild);
			continue;
			}

			{
			register long lCheck;
			auto int iFound;
			auto char *pcEol, *pcCount;
			auto char acMust[OP_MAX_LDWIDTH];	/* $%ld */
			auto char acCannot[OP_MAX_LDWIDTH];	/* !%ld */

		case '0': /* nmemonic name, or misleading `octal' */
		case '1': case '2': case '3':
		case '4': case '5': case '6':
		case '7': case '8': case '9':
			lCheck = strtol(pcScan, &pcEol, 0);
			pcScan = pcEol;
			goto param;
		case ',': /* makes the command "$2 $3 $4..."	*/
		case '+': /* makes the command "argv[1] argv[2]..." */
		case '*': /* makes the command "$1 $2 $3..."	*/
			lCheck = ',' == *pcScan ? 1 : '+' == *pcScan ? 0 : ((NEEDS *)0 == pSP->pNE ? 0 : pSP->pNE->lCmdParam)+('*' == *pcScan ? 1 : 2 /*&*/);
			if ((char *)0 == (pcCount = FindOpt(cmd, acOptCount)) || (lCheck == (iFound = atoi(pcCount))) || lCheck == iFound+1) {
				/* $* could be the empty string */
				if ((char *)0 != pcCount && lCheck == iFound+1) {
					++pcScan;
					continue;
				}
				if (cmd == cmdVetchDollarStar)
					/* nada */;
				else if ((char *)0 == pcCount)
					fprintf(stderr, "%s: %s: %s:%d: $%c may have embedded spaces, since an unknown number of parameters are allowed, replace with $%c\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, *pcScan, '*' == *pcScan ? '@' : '-');
				else
					fprintf(stderr, "%s: %s: %s:%d: $%c may have embedded spaces, since more than %ld %s allowed, replace with $%c\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, *pcScan, lCheck, 1 == lCheck ? "parameter is" : "parameters are", '*' == *pcScan ? '@' : '-');
				cmdVetchDollarStar = cmd;
				return;
			}
			++pcScan;
			goto param;
		case '@': /* splits words so treat as $1$. */
		case '-': /* makes the command argv[0] */
			lCheck = '-' == *pcScan ? 0 : ((NEEDS *)0 == pSP->pNE ? 0 : pSP->pNE->lCmdParam)+('@' == *pcScan ? 1 : 2/*!*/);
			/* We can't always we correct here, 2 params or 1?
			 * And I'm not going to try both (even though I could).
			 */
			if ((char *)0 == (pcCount = FindOpt(cmd, acOptCount)) || (lCheck == (iFound = atoi(pcCount))) || lCheck == iFound+1)
				++pcScan;	/* assume abutted */
			else
				pcScan += strlen(pcScan);
			/*FALLINTO*/
 param:
			/* We know $0, is the name of the command */
			if (0 == lCheck) {
				strcpy(pcBuild, cmd->pcname);
				pcBuild += strlen(pcBuild);
				continue;
			}
			snprintf(acMust, sizeof(acMust), "$%ld", lCheck);
			snprintf(acCannot, sizeof(acCannot), "!%ld", lCheck);
			strcpy(pcBuild, acMust);
			apcPosP[SKIP_WHOLE_PATS] = acMust;
			apcNegP[SKIP_WHOLE_PATS] = acCannot;
			iSkip = (pSP->accmd == pcBuild) ? 0 : SKIP_WHOLE_PATS;
			if (ForceInit(cmd, &FR, acMust, apcPosP+iSkip, apcNegP+iSkip, (const char **)0, (const char **)0, 0)) {
				pcExcludes = "$* or !* value limits";
				iDShorts = 0;
				break;
			}
			pcBuild += strlen(pcBuild);
			pSP->freal = 0;
			}
			continue;
		case '#':	/* $# known count, viz. $f.$# */
			{
			register long lCheck;
			auto char *pcEol, *pcCount;

			if ((char *)0 != (pcCount = FindOpt(cmd, acOptCount))) {
				lCheck = strtol(pcCount, &pcEol, 0);
				snprintf(pcBuild, 32, "%ld", lCheck);
			} else {
				strcpy(pcBuild, acOptCount);
				pSP->freal = 0;
			}
			pcBuild += strlen(pcBuild);
			++pcScan;
			}
			continue;
		case '_': /* you are not funny */
			if (cmdUses_ != cmd)
				fprintf(stderr, "%s: %s: %s:%d: $%c may not be used as part of a command path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, *pcScan);
			cmdUses_ = cmd;
			return;
		case 'q': /* these could allow symlink races */
		case 'Q':
			*pcBuild++ = '$';
			*pcBuild++ = *pcScan;
			*pcBuild = '\000';
			if (cmdUsesQ != cmd)
				fprintf(stderr, "%s: %s: %s:%d: $%c should not be trusted as part of a command path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, *pcScan);
			cmdUsesQ = cmd;
			/*FALLTHROUGH*/
		default:
			/* We could do these, not worth it:
			 *	$b/$B/$c/C/$e/$E/$h/$H/$i/$I/$o/$O/
			 *	$p/$P/$t/$T/$v/$v/$w/$W/$~
			 * Sometimes these (not always, and lots of code):
			 *	$K - code to find uid (match %u?), get shell
			 *	$k - match users= and get shell(s)
			 * We can't ever do:
			 *	$a/$A/$h/$H/$m/$M/$l/$L/$r/$R
			 */
			++pcBuild;	/* accept the $ */
			*pcBuild++ = *pcScan++;
			pSP->freal = 0;
			continue;
		}

		/* We just inited an RE loop, make it go.
		 * When this RE is the first RE and it is always
		 * anchored at slash we have a special case below.
		 * N.B. c == 'd' when we are looking at %d rather than %f
		 * iDShorts is non-zero when we are looking at %f
		 * and really want %d.
		 */
		{
		auto int iCnt, fKeep;
		auto const char *pcTemp, *pcForced;

		fKeep = pSP->freal;
		iCnt = 0;
		pcForced = acDevNull;	/* hush lint */
		while ((char *)0 != (pcTemp = ForceRE(cmd, &FR))) {
			register char *pcSnip;

			/* Remove spliced double-slash so we look nice,
			 * if looking at %d trim last path.
			 */
			if (0 == iDShorts)
				/* nothing */;
			else if ((char *)0 != (pcSnip = strrchr(pcTemp, '/')))
				*pcSnip = '\000';
			else
				pcTemp = "";	/* use "./" for DEBUG */

			if (pcBuild > pSP->accmd && '/' == pcBuild[-1] && '/' == pcTemp[0])
				++pcTemp;
			(void)strcpy(pcBuild, pcTemp);
			FinishExec(cmd, pSP, pcScan, pcBuild+strlen(pcBuild));
			++iCnt;
			pcForced = pcTemp;
		}

		if (ForceEnd(cmd, &FR)) {
			register char *pcSwap;
			if (pcBuild == pSP->accmd && FR.frooted) {
				if ((char *)0 == (pcSwap = strdup(pcBuild)))
				fatal(acBadAlloc);
			*pcBuild = '/';
			strcpy(pcBuild+1, (char *)0 == pcSwap ? "$_" : pcSwap);
			if ((char *)0 != pcSwap)
				free((void *)pcSwap);
			}
			pSP->freal = 0;
			FinishExec(cmd, pSP, pcScan, pcBuild+strlen(pcBuild));
		} else if (1 == iCnt && cmd != cmdVetchSingle && !iDShorts) {
			register int iHack;

			/* We don't get here for ${FIRST} in the case where
			 * ${FIRST}/${SECOND} is presented and ${SECOND} is
			 * also 1-limited because of the cmd trap.  Check.
			 */
			for (iHack = 1; '$' != pcScan[-iHack]; ++iHack)
				;
			fprintf(stderr, "%s: %s: %s:%d: %.*s is limited to single value (%s); why make the Customer specify it?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iHack, pcScan-iHack, pcForced);
			cmdVetchSingle = cmd;
		} else if (0 != FR.iexcluded && (char *)0 != pcExcludes) {
			fprintf(stderr, "%s: %s: %s:%d: all matches excluded by %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcExcludes);
		} else if (0 != FR.inegated) {
			fprintf(stderr, "%s: %s: %s:%d: all matches excluded by negative matches\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
		}
		/* Reset $d flag and free memory we may have lost the
		 * pointer to in a recursive call, like ${TOP}/${CMD},
		 */
		iDShorts = 0;
		if ((char *)0 != pcFreeNeg)
			free((void *)pcFreeNeg);
		if ((char *)0 != pcFreePos)
			free((void *)pcFreePos);
		apcNegE[SKIP_WHOLE_PATS] = (char *)0;
		apcPosE[SKIP_WHOLE_PATS] = (char *)0;
		apcNegP[SKIP_WHOLE_PATS] = (char *)0;
		apcPosP[SKIP_WHOLE_PATS] = (char *)0;
		/* We (or recursion) may have reset this, put it back
		 */
		pSP->freal = fKeep;
		return;
		}
	}
	if (!pSP->freal && '$' == pSP->accmd[0]) {
		if (cmd != cmdVetchFake)
			fprintf(stderr, "%s: %s: %s:%d: cannot verify program path (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd);
		cmdVetchFake = cmd;
		return;
	}

	if ('\000' == pSP->accmd[0]) {
		fprintf(stderr, "%s: %s: %s:%d: null command?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
		pSP->iexit = EX_CONFIG;
		return;
	}

	/* dir and chroot may be either: set, runtime (RT), not set (NULL),
	 * or any (*).  The command may be relative to . (.), absolute (/)
	 * or path-searched (p).  That gives us 27 the combinations:
	 * {set, RT, NULL} * {set, RT, NULL} * {p, /, .}:
	 * pthd chroot	 dir	 disposition
	 * p	*	 *	 carp when any $PATH elements are relative to .
	 * /	RT	 *	 carp that chroot is controlled by client
	 * /	*	 *	 check file
	 * .	set|NULL set	 check file
	 * .	set|NULL RT|NULL carp
	 * .	RT	 *	 carp
	 */
	{
	register char *pcPath, *pcOpt;
	register int cPathed;

	/* Strange shell rule, if it doesn't start with a slash, but
	 * there is a slash in it, then it must be relative.
	 */
	pcPath = pSP->accmd;
	cPathed = pcPath[0];
	if ('/' != cPathed && (char *)0 != strchr(pcPath, '/')) {
		cPathed = '.';
	}
	switch (cPathed) {
	default:	/* case pathed (p) */
		/* Why would you escalate an echo?  Well, a forced
		 * stdout might make that worth the effort.	(ksb)
		 * No shell I know path-searches for "echo", so.
		 */
		if (0 == strcmp(acEcho, pSP->accmd)) {
			/* not really searching $PATH */
		} else if (cmd == cmdVetchPath) {
			/* don't carp again about it */
		} else if ((char *)0 == (pcOpt = FindOpt(cmd, acOptPATH))) {
			fprintf(stderr, "%s: %s: %s:%d: trusts the client's %s (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, pSP->accmd);
			cmdVetchPath = cmd;
		} else if (!CheckPATHTrust(pcOpt)) {
			fprintf(stderr, "%s: %s: %s:%d: trusts a relative %s (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, pSP->accmd);
			cmdVetchPath = cmd;
		}
		return;
	case '.':	/* relative */
		/* We already carped about being relative to an unspec'd
		 * dir, so just return.  If it is shell built-in we were
		 * wrong to vetch, I think.
		 */
		if (acFakeRoot == pSP->pcroot || acFakeDir == pSP->pcdir) {
			if (cmd == cmdVetchRel)
				/* nada */;
			else if (acFakeDir == pSP->pcdir && '~' == acFakeDir[0])
				fprintf(stderr, "%s: %s: %s:%d: trusts an unknown logins home directory (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd);
			else
				fprintf(stderr, "%s: %s: %s:%d: trusts a relative file in an unknown directory (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd);
			cmdVetchRel = cmd;
			return;
		}
		strcpy(acToExec, pSP->acpwd);
		if ((char *)0 == FullPath(acToExec, pSP->accmd)) {
			return;
		}
		return;
	case '/':
		if (acFakeRoot == pSP->pcroot) {
			if (cmd != cmdVetchAbs)
				fprintf(stderr, "%s: %s: %s:%d: trusts an absolute path against a run-time chroot (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcPath);
			cmdVetchAbs = cmd;
			return;
		}
		if (!pSP->freal)
			break;

		if ((char *)0 != pSP->pcroot) {
			strcpy(acToExec, pSP->pcroot);
			pcPath = FullPath(acToExec, pSP->accmd);
		} else {
			pcPath = strcpy(acToExec, pcPath);
		}
		if ((char *)0 == pcPath) {
			fatal("Internal error: full path failed");
		}
		break;
	}
	}

	/* We may not be real, be we can be sure any directory prefix
	 * any exists (e.g. /usr/local/sbin/foo.$1), even if $1 has a
	 * slash in it, the base still should still exist.  We think it is
	 * Poor Form to use "/opt/local/$u/bin/$n", but we'll check for
	 * "/opt/local" none-the-less.
	 */
	{
	register char *pcCut = (char *)0;
	register const char *pcRE;

	if (!pSP->freal) {
		if ((char *)0 == (pcCut = strchr(acToExec, '$')))
			return;
		do {
			*pcCut-- = '\000';
		} while (pcCut > acToExec && '/' != *pcCut);
		if (pcCut == acToExec)
			return;
	}

	if (cmd != cmdVetchRej && (const char *)0 != (pcRE = FindOpt(cmd, acPos_CmdP)) && !GenMatch(acPos_CmdP, pSP->accmd, pcRE)) {
		fprintf(stderr, "%s: %s: %s:%d: command %s rejected by %s non-match\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd, acPos_CmdP);
		cmdVetchRej = cmd;
	}
	if (cmd != cmdVetchRej && (const char *)0 != (pcRE = FindOpt(cmd, acPos_Cmd)) && !GenMatch(acPos_Cmd, pSP->accmd, pcRE)) {
		fprintf(stderr, "%s: %s: %s:%d: command %s rejected by %s non-match\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd, acPos_Cmd);
		cmdVetchRej = cmd;
	}
	if (cmd != cmdVetchRej && (const char *)0 != (pcRE = FindOpt(cmd, acNeg_CmdP)) && GenMatch(acNeg_CmdP, pSP->accmd, pcRE)) {
		fprintf(stderr, "%s: %s: %s:%d: command %s rejected by %s match\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd, acNeg_CmdP);
		cmdVetchRej = cmd;
	}
	if (cmd != cmdVetchRej && (const char *)0 != (pcRE = FindOpt(cmd, acNeg_Cmd)) && GenMatch(acNeg_Cmd, pSP->accmd, pcRE)) {
		fprintf(stderr, "%s: %s: %s:%d: command %s rejected by %s match\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd, acNeg_Cmd);
		cmdVetchRej = cmd;
	}
	if (cmd == cmdVetchRej) {
		return;
	}

	/* Make sure we can stat the command (or directory) we're
	 * going to run (consult)
	 * XXX checking all the %_. and !_. attrs is diminishing returns
	 */
	if (0 != stat(pSP->accmd, & stExec)) {
		if ((char *)0 == pSP->pcexecmiss) {
			pSP->pcexecmiss = strdup(pSP->accmd);
			pSP->ierrno = errno;
		}
		return;
	}
	if ((char *)0 == pSP->pcexecok) {
		pSP->pcexecok = strdup(pSP->accmd);
	}
	if ((char *)0 != pcCut) {
		if (S_ISDIR(stExec.st_mode))
			return;
		if (cmd != cmdMissedDir)
			fprintf(stderr, "%s: %s: %s:%d: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acToExec, strerror(ENOTDIR));
		cmdMissedDir = cmd;
		return;
	}
	}

	if (pSP->freal) {
		CheckProg(cmd, acToExec, &pSP->iexit, &stExec);
	}
}

/* Find the program(s) we are going to run, if possible from chroot and	(ksb)
 * the dir spec.  Either root or dir can be acFake.... for an unknown path
 * specified at runtime, NULL for none set.
 */
static void
CheckPossible(const cmd_t *cmd, SPEC *pSP)
{
	register int iLen;
	register const char *pcDir;
	auto const char *pcScan;

	pcDir = pSP->pcdir;
#if defined(OP_EXTRA_VERBOSE)
	/* Documented in the man page, don't read it to me.
	 */
	if ((char *)0 != pSP->pcroot && (char *)0 == pcDir) {
		static int fOnce = 0;
		if (!fOnce)
			fprintf(stderr, "%s: %s: %s:%d: %s: without a dir specification implies dir=/\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot);
		fOnce = 1;
	}
#endif
	/* Look for the directory we'll be in for the command.
	 */
	pSP->acpwd[0] = '\000';
	if (acFakeRoot == pSP->pcroot && (char *)0 != pcDir && '/' == pcDir[0]) {
		iLen = strlen(acFakeRoot);
		while (iLen > 0 && '/' == acFakeRoot[iLen-1])
			--iLen;
		snprintf(pSP->acpwd, sizeof(pSP->acpwd), "%.*s%s", iLen, acFakeRoot, pcDir);
	} else if ((char *)0 == pSP->pcroot && (char *)0 == pcDir) {
		/* we don't know anything, we don't cd or chroot */
	} else if (((char *)0 == pSP->pcroot || '/' == *pSP->pcroot) && ((char *)0 == pcDir || '/' == *pcDir)) {
		strncpy(pSP->acpwd, (char *)0 == pSP->pcroot ? acSlash : pSP->pcroot, sizeof(pSP->acpwd));
		iLen = strlen(pSP->acpwd);
		while (iLen > 0 && '/' == pSP->acpwd[iLen-1])
			--iLen;
		if ((char *)0 != pcDir)
			strncpy(pSP->acpwd+iLen, pcDir, sizeof(pSP->acpwd)-iLen);
	}

	pcScan = cmd->args[0];
	if ('.' == pcScan[0] && ('/' == pcScan[1] || ('.' == pcScan[1] && '/' == pcScan[2]))) {
		if (acFakeDir == pSP->pcdir && '~' == acFakeDir[0])
			fprintf(stderr, "%s: %s: %s:%d: trusts an unknown logins home directory (at least for %s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->accmd);
		else if ('\000' == pSP->acpwd[0])
			fprintf(stderr, "%s: %s: %s:%d: %s: explicitly forces a relative path to an unspecified directory\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcScan);
	}

	/* For the next steps we might have to recurses -- get it started.
	 */
	pSP->accmd[0] = '\000';
	FinishExec(cmd, pSP, pcScan, pSP->accmd);
}

/* Looks like the dir is a constant string				(ksb)
 */
static void
ConstDir(const cmd_t *cmd, SPEC *pSP)
{
	register const char *pcDir;
	auto struct stat stDir;

	pcDir = pSP->pcdir;
	if ('/' != *pcDir) {
		if (!pSP->fwabsdir) {
			fprintf(stderr, "%s: %s: %s:%d: %s: should be an absolute path (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcDir);
			pSP->fwabsdir = 1;
		}
	} else if (-1 == stat(pcDir, &stDir)) {
		/* -1 handles root longer than MAXPATHLEN too */
		fprintf(stderr, "%s: %s: %s:%d: %s: stat: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcDir, strerror(errno));
		pSP->iexit = EX_NOINPUT;
	} else if (!S_ISDIR(stDir.st_mode)) {
		fprintf(stderr, "%s: %s: %s:%d: %s: not a directory %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcDir);
		pSP->iexit = EX_NOINPUT;
	}
	CheckPossible(cmd, pSP);
}

/* If %f.mode or %f.perms forces the -f specification to be a		(ksb)
 * directory ('d') or nonexistant file ('n')  return true.  The logic here
 * is too naive because we can provide a list and/or disjunction REs to
 * match.  For example %f of "^[df]" and !f of "^[f]" would actually force
 * the right mode.  Don't config that. --ksb
 */
static int
Forced_F(const cmd_t *cmd, const int cType)
{
	register const char *pc;

	if (((char *)0 != (pc = FindOpt(cmd, "%f.mode")) && '^' == pc[0] && cType == pc[1]) ||
		((char *)0 != (pc = FindOpt(cmd, "!f.mode")) && '^' == pc[0] && '[' == pc[1] && '^' == pc[2] && cType == pc[3] && ']' == pc[4]))
		return 1;
	if (((char *)0 != (pc = FindOpt(cmd, "%f.perms")) && '^' == pc[0] && cType == pc[1]) ||
		((char *)0 != (pc = FindOpt(cmd, "!f.perms")) && '^' == pc[0] && '[' == pc[1] && '^' == pc[2] && cType == pc[3] && ']' == pc[4]))
		return 1;
	return 0;
}
/* Same checks as above for %d/-d					(ksb)
 */
static int
Forced_D(const cmd_t *cmd, const int cType)
{
	register const char *pc;

	if (((char *)0 != (pc = FindOpt(cmd, "%d.mode")) && '^' == pc[0] && cType == pc[1]) ||
		((char *)0 != (pc = FindOpt(cmd, "!d.mode")) && '^' == pc[0] && '[' == pc[1] && '^' == pc[2] && cType == pc[3] && ']' == pc[4]))
		return 1;
	if (((char *)0 != (pc = FindOpt(cmd, "%d.perms")) && '^' == pc[0] && cType == pc[1]) ||
		((char *)0 != (pc = FindOpt(cmd, "!d.perms")) && '^' == pc[0] && '[' == pc[1] && '^' == pc[2] && cType == pc[3] && ']' == pc[4]))
		return 1;
	return 0;
}

/* We allow dir=%u starting in 2.104, because the home directory of	(ksb)
 * the target login is very useful.
 */
static void
CheckUserDir(const cmd_t *cmd, SPEC *pSP, FORCE_RE *pFR)
{
	register int iCnt, iBad;
	register struct passwd *pw;
	register const char *pcNewLogin, *pcLastForced;
	auto int fKeep;
	auto char acHome[MAXPATHLEN+4];

	fKeep = pSP->freal;
	iBad = iCnt = 0;
	pcLastForced = acDevNull;	/* hush lint */
	while ((char *)0 != (pcNewLogin = ForceRE(cmd, pFR))) {
		pcLastForced = pcNewLogin;
		++iCnt;
		if ((struct passwd *)0 == (pw = getpwnam(pcNewLogin))) {
			++iBad;
			continue;
		}
		snprintf(acHome, sizeof(acHome), "%s", pw->pw_dir);
		pSP->freal = fKeep;
		pSP->pcdir = acHome;
		ConstDir(cmd, pSP);
	}
	if (ForceEnd(cmd, pFR)) {
		pSP->freal = 0;
		pSP->pcdir = acFakeDir;
		CheckPossible(cmd, pSP);
	} else if (1 == iCnt) {
		fprintf(stderr, "%s: %s: %s:%d: %s: forces an explicit -u option? (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcLastForced);
	} else if (0 != pFR->inegated) {
		fprintf(stderr, "%s: %s: %s:%d: %s: patterns suggest an explicit -u option, but that login is excluded\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir);
	} else if (iBad == iCnt && 0 != iCnt) {
		fprintf(stderr, "%s: %s: %s:%d: %s: none of the %d possible logins presently exist\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, iBad);
	}
	pSP->freal = fKeep;
}

/* We passed the chroot checks, now look at the directory spec		(ksb)
 * look for fixed paths too.  We can be called multiple times (for
 * alternate %f/%d expansion).
 */
static void
CheckDir(const cmd_t *cmd, SPEC *pSP)
{
	auto const char *pcChDir;
	auto FORCE_RE FR;
	auto int fKeep;
	static const cmd_t
		*cmdUsesF = (cmd_t *)0,
		*cmdHome = (cmd_t *)0;

	/* The code for chroot forces a cd to / even if chroot is empty
	 */
	fKeep = pSP->freal;
	if ((char *)0 == (pcChDir = FindOpt(cmd, acOptDir))) {
		pSP->pcdir = ((char *)0 != FindOpt(cmd, acOptChroot)) ? acSlash : (char *)0;
		CheckPossible(cmd, pSP);
		return;
	}
	if ('\000' == *pcChDir) {
		pcChDir = acSlash;
	}
	/* %u -> ~$u */
	snprintf(acFakeDir, sizeof(acFakeDir), "$%c", pcChDir[1]);
	if (0 == strcmp(acSpecU, pcChDir)) {
		/* A pseudo-user grouping a password and dir is OK,
		 * and doesn't conflict with anything but chroot.
		 */
		if (acOptChroot == pSP->pcspecU) {
			fprintf(stderr, "%s: %s: %s:%d: -u used for both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pSP->pcspecU);
		} else if ((char *)0 == pSP->pcspecU) {
			pSP->pcspecU = acOptDir;
		}
		snprintf(acFakeDir, sizeof(acFakeDir), "~$%c", pcChDir[1]);
		if (ForceInit(cmd, &FR, "login as a directory", apcPosU+SKIP_WHOLE_PATS, apcNegU+SKIP_WHOLE_PATS, (const char **)0, (const char **)0, 0)) {
			CheckUserDir(cmd, pSP, &FR);
		} else if (cmdHome != cmd) {
			cmdHome = cmd;
			fprintf(stderr, "%s: %s: %s:%d: %s: %s may not match any logins at runtime\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, acFakeDir);
			pSP->freal = 0;
			pSP->pcdir = acFakeDir;
			CheckPossible(cmd, pSP);
		}
		pSP->freal = fKeep;
		return;
	}
	if (0 == strcmp(acSpecMe, pcChDir)) {
		snprintf(acFakeDir, sizeof(acFakeDir), "~$%c", pcChDir[1]);
		pSP->freal = 0;
		pSP->pcdir = acFakeDir;
		CheckPossible(cmd, pSP);
		pSP->freal = fKeep;
		return;
	}
	if (0 != strcmp(acSpecF, pcChDir) && 0 != strcmp(acSpecD, pcChDir)) {
		pSP->pcdir = pcChDir;
		ConstDir(cmd, pSP);
		return;
	}

	if ((char *)0 != pSP->pcspecF) {
		if (cmd != cmdUsesF)
			fprintf(stderr, "%s: %s: %s:%d: -f used by both dir and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->pcspecF);
		cmdUsesF = cmd;
		pSP->iexit = EX_SOFTWARE;
		/* we could pile-on here: set pcspecF = "%s and dir" */
	} else {
		pSP->pcspecF = acOptDir;
	}

	if ('d' == pcChDir[1]) {
		/* Else %d, which is always a directory, unless %f is forced
		 * to be a nonexistent file.  So see if %d is forced to exist.
		 */
		if (Forced_D(cmd, 'd')) {
			/* OK */
		} else if (Forced_D(cmd, 'n')) {
			fprintf(stderr, "%s: %s: %s:%d: %s: forced to be a nonexistent directory (expanded from dir=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcChDir);
		} else if (Forced_F(cmd, 'n')) {
			fprintf(stderr, "%s: %s: %s:%d: %s: nonexistent files might be in a nonexistent directory (expanded from dir=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcChDir);
		}
	} else {
		/* LLL could be forced to be a link to a directory */
		if (!Forced_F(cmd, 'd')) {
			fprintf(stderr, "%s: %s: %s:%d: %s: is not forced to be a directory (expanded from dir=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcChDir);
		}
	}

	/* If %f.path doesn't forces a litteral directory clain runtime.
	 * When %f.path forces a single literal dir why make them
	 * type it?
	 */
	if (ForceInit(cmd, &FR, "file as a directory", apcPosF+SKIP_WHOLE_PATS, apcNegF+SKIP_WHOLE_PATS, apcPosD, apcNegD, 1)) {
		register int iCnt;
		register char *pcChop;
		register const char *pcNewPwd, *pcLastForced;

		iCnt = 0;
		while ((char *)0 != (pcNewPwd = ForceRE(cmd, &FR))) {
			pcLastForced = pcNewPwd;
			if (0 != strcmp(acSpecD, pcChDir)) {
				/* OK take the whole */
			} else if ((char *)0 == (pcChop = strrchr(pcNewPwd, '/'))) {
				fprintf(stderr, "%s: %s: %s:%d: %s: explicit value forces dir=. (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcNewPwd);
				pcNewPwd = acCurDir;
			} else {
				while (pcChop > pcNewPwd && '/' == *pcChop)
					*pcChop-- = '\000';
				if (pcChop == pcNewPwd)
					pcChop[1] = '\000';
			}
			pSP->freal = fKeep;
			pSP->pcdir = pcNewPwd;
			ConstDir(cmd, pSP);
			++iCnt;
		}
		if (ForceEnd(cmd, &FR)) {
			/* We don't know the value, but it must start from root
			 */
			if (FR.frooted) {
				snprintf(acFakeDir, sizeof(acFakeDir), "/$%c", pcChDir[1]);
			}
			pSP->freal = 0;
			pSP->pcdir = acFakeDir;
			CheckPossible(cmd, pSP);
		} else if (1 == iCnt) {
			fprintf(stderr, "%s: %s: %s:%d: %s: forces an explicit -f option? (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir, pcLastForced);
		} else if (0 != FR.iexcluded || 0 != FR.inegated) {
			fprintf(stderr, "%s: %s: %s:%d: %s: patterns suggest an explicit -f option, but that path is excluded\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptDir);
		}
		pSP->freal = fKeep;
		return;
	}
	pSP->freal = 0;
	pSP->pcdir = acFakeDir;
	CheckPossible(cmd, pSP);
	pSP->freal = fKeep;
}

/* Looks like the chroot is a constant string				(ksb)
 * We could go crazy here looking to see if the new slash looked like
 * a valid root directory.  I think I'll leave that for someone else. :-]
 */
static void
ConstRoot(const cmd_t *cmd, SPEC *pSP)
{
	auto struct stat stDir;
	register const char *pcChRoot;

	pcChRoot = pSP->pcroot;
	if ('/' != *pcChRoot) {
		if (!pSP->fwabsroot) {
			fprintf(stderr, "%s: %s: %s:%d: %s: should be an absolute path (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot);
			pSP->fwabsroot = 1;
		}
	} else if (-1 == stat(pcChRoot, &stDir)) {
		/* -1 handles root longer than MAXPATHLEN too */
		fprintf(stderr, "%s: %s: %s:%d: %s: stat: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot, strerror(errno));
		pSP->iexit = EX_NOINPUT;
	} else if (!S_ISDIR(stDir.st_mode)) {
		fprintf(stderr, "%s: %s: %s:%d: %s: not a directory %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot);
		pSP->iexit = EX_NOINPUT;
	}
	/* Yes, slash should have a /etc, and /bin, and /libexec on most
	 * modern systems.  If a jacket is going to fix it we can't tell
	 * here, so I guess we won't complain.  The jacket could mount
	 * a new filesystem under us before we start the process.
	 */
	CheckDir(cmd, pSP);
}

/* We allow chroot=%u starting in 2.107, because the home directory of	(ksb)
 * the target login is very useful as a variable root.
 */
static void
CheckUserRoot(const cmd_t *cmd, SPEC *pSP, FORCE_RE *pFR)
{
	register int iCnt, iBad;
	register struct passwd *pw;
	register const char *pcNewLogin, *pcLastForced;
	auto int fKeep;
	auto char acHome[MAXPATHLEN+4];

	fKeep = pSP->freal;
	iBad = iCnt = 0;
	pcLastForced = acDevNull;	/* hush lint */
	while ((char *)0 != (pcNewLogin = ForceRE(cmd, pFR))) {
		pcLastForced = pcNewLogin;
		++iCnt;
		if ((struct passwd *)0 == (pw = getpwnam(pcNewLogin))) {
			++iBad;
			continue;
		}
		snprintf(acHome, sizeof(acHome), "%s", pw->pw_dir);
		pSP->freal = fKeep;
		pSP->pcroot = acHome;
		ConstRoot(cmd, pSP);
	}
	if (ForceEnd(cmd, pFR)) {
		pSP->freal = 0;
		pSP->pcroot = acFakeRoot;
		ConstRoot(cmd, pSP);
	} else if (1 == iCnt) {
		fprintf(stderr, "%s: %s: %s:%d: %s: forces an explicit -u option? (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcLastForced);
	} else if (0 != pFR->inegated) {
		fprintf(stderr, "%s: %s: %s:%d: %s: patterns suggest an explicit -u option, but that login is excluded\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot);
	} else if (iBad == iCnt && 0 != iCnt) {
		fprintf(stderr, "%s: %s: %s:%d: %s: none of the %d possible logins presently exist\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, iBad);
	}
	pSP->freal = fKeep;
}


/* Pass in the cmd (viz. root, dir, exec, and search path) we check	(ksb)
 * possible the target chroot directories.  Note (char *)0 is none
 * N.B. chroot and dir can be %f/%d or literal text only.
 * If 2 specifications use %f/%d we might vetch about that.
 *  we really don't want a relative path (we can't check any below)
 *  world +w, or in a world +w dir (group +w we don't check)
 *  not available to the user running the request
 *  exist and not be +x to anyone
 * We set the proposed exit code, but never "fail" (the program might
 * legitimately not be installed yet, for example).
 */
static void
CheckRoot(const cmd_t *cmd, SPEC *pSP)
{
	auto const char *pcChRoot;
	auto FORCE_RE FR;
	auto int fKeep;
	static const cmd_t
		*cmdHome = (cmd_t *)0;

	fKeep = pSP->freal;
	if ((char *)0 == (pcChRoot = FindOpt(cmd, acOptChroot)) || '\000' == *pcChRoot) {
		pSP->pcroot = (char *)0;
		CheckDir(cmd, pSP);
		return;
	}
	pSP->pcroot = pcChRoot;
	if (0 == strcmp(pcChRoot, acSlash)) {
		if ((char *)0 == FindOpt(cmd, acOptDir))
			fprintf(stderr, "%s: %s: %s:%d: using chroot=/ to force a dir=/ is poor form\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
		else
			fprintf(stderr, "%s: %s: %s:%d: chroot=/ is redundant\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
		/* When we want to turn off a DEFAULT chroot use "chroot=",
		 * which is trapped above.
		 */
		CheckDir(cmd, pSP);
	}
	if (0 == strcmp(acSpecU, pcChRoot)) {
		/* A pseudo-user home specification for a chroot is clever.
		 * and doesn't conflict with any pSP->pcspecU.
		 */
		if ((char *)0 == pSP->pcspecU) {
			pSP->pcspecU = acOptChroot;
		}
		snprintf(acFakeRoot, sizeof(acFakeRoot), "~$%c", pcChRoot[1]);
		if (ForceInit(cmd, &FR, "login for chroot", apcPosU+SKIP_WHOLE_PATS, apcNegU+SKIP_WHOLE_PATS, (const char **)0, (const char **)0, 0)) {
			CheckUserRoot(cmd, pSP, &FR);
		} else if (cmdHome != cmd) {
			cmdHome = cmd;
			fprintf(stderr, "%s: %s: %s:%d: %s: %s may not match any logins at runtime\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, acFakeDir);
			pSP->freal = 0;
			pSP->pcroot = acFakeDir;
			CheckDir(cmd, pSP);
		}
		pSP->freal = fKeep;
		return;
	}
	if (0 == strcmp(acSpecMe, pcChRoot)) {
		snprintf(acFakeDir, sizeof(acFakeRoot), "~$%c", pcChRoot[1]);
		pSP->freal = 0;
		pSP->pcroot = acFakeRoot;
		CheckDir(cmd, pSP);
		pSP->freal = fKeep;
		return;
	}
	if (0 != strcmp(acSpecF, pcChRoot) && 0 != strcmp(acSpecD, pcChRoot)) {
		ConstRoot(cmd, pSP);
		return;
	}
	snprintf(acFakeRoot, sizeof(acFakeRoot), "$%c", pcChRoot[1]);

	/* This can't happen here in the current flow, defensive coding
	 */
	if ((char *)0 != pSP->pcspecF) {
		fprintf(stderr, "%s: %s: %s:%d: -f used by both chroot and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pSP->pcspecF);
		pSP->iexit = EX_SOFTWARE;
	} else {
		pSP->pcspecF = acOptChroot;
	}

	/* If we are looking for the dirname check some perms forced
	 */
	if ('d' == pcChRoot[1]) {
		if (Forced_D(cmd, 'd')) {
			/* OK */
		} else if (Forced_D(cmd, 'n')) {
			fprintf(stderr, "%s: %s: %s:%d: %s: forced to be a nonexistent directory (expanded from chroot=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot);
		} else if (Forced_F(cmd, 'n')) {
			fprintf(stderr, "%s: %s: %s:%d: %s: nonexistent files might be in a nonexistent directory (from chroot=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot);
		}
	} else if (!Forced_F(cmd, 'd')) {
		fprintf(stderr, "%s: %s: %s:%d: %s: is not forced to be a directory (from chroot=%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcChRoot);
	}

	/* If %f.path forces a litteral directory we shall check it.
	 * But if that's the case why make them type the -f?
	 * For %d this is 1 componentn too long, we'll chomp it later
	 */
	if (ForceInit(cmd, &FR, "file as a chroot", apcPosF+SKIP_WHOLE_PATS, apcNegF+SKIP_WHOLE_PATS, apcPosD, apcNegD, 1)) {
		register int iCnt;
		register char *pcChop;
		register const char *pcNewSlash, *pcLastForced;

		iCnt = 0;
		while ((char *)0 != (pcNewSlash = ForceRE(cmd, &FR))) {
			pcLastForced = pcNewSlash;
			if (0 != strcmp(acSpecD, pcChRoot)) {
				/* OK take the whole */
			} else if ((char *)0 == (pcChop = strrchr(pcNewSlash, '/'))) {
				fprintf(stderr, "%s: %s: %s:%d: %s: explicit value forces chroot=. (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcNewSlash);
				pcNewSlash = acCurDir;
			} else {
				while (pcChop > pcNewSlash && '/' == *pcChop)
					*pcChop-- = '\000';
				if (pcChop == pcNewSlash)
					pcChop[1] = '\000';
			}
			pSP->freal = fKeep;
			pSP->pcroot = pcNewSlash;
			ConstRoot(cmd, pSP);
			++iCnt;
		}
		/* Also matches a run-time string
		 */
		if (ForceEnd(cmd, &FR)) {
			/* We don't know the value, but it must hang from slash
			 */
			pSP->freal = 0;
			if (FR.frooted) {
				snprintf(acFakeRoot, sizeof(acFakeRoot), "/$%c", pcChRoot[1]);
			}
			pSP->pcroot = acFakeRoot;
			CheckDir(cmd, pSP);
		} else if (1 == iCnt) {
			fprintf(stderr, "%s: %s: %s:%d: %s: forces an explicit -f option? (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot, pcLastForced);
		} else if (0 != FR.iexcluded || 0 != FR.inegated) {
			fprintf(stderr, "%s: %s: %s:%d: %s: patterns suggest an explicit -f option, but that path is excluded\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptChroot);
		}
		pSP->freal = fKeep;
		return;
	}

	pSP->pcroot = acFakeRoot;
	CheckDir(cmd, pSP);
}

/* Find any RE in the list that doesn't compile.			(ksb)
 */
static char *
CheckCompile(const char *pcList, char **ppcCarp)
{
	register regex_t *reg;
	register char *pcRet;
	auto char *pcField;

	if ((char *)0 == pcList || '\000' == *pcList) {
		return (char *)0;
	}
	pcRet = (char *)0;
	while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
		if ((regex_t *)0 != (reg = ReComp(pcField, ppcCarp))) {
			regfree(reg);
			continue;
		}
		pcRet = strdup(pcField);
		break;
	}
	EndField();
	return pcRet;
}

/* Convert the RE into a usage strings (which must be shorter than the	(ksb)
 * original RE or the word "number"). The expression must be anchored.
 * An expression is easy to convert to a usage:
 *	"^start$" => "start",
 *	"^(start|stop|end)$" or
 *	"^start$|^stop$|^end$" => "start|stop|end"
 *	"(^start$)|(^stop)$" => start|stop
 * nested parens stop us because we don't expand them:
 *	"^bl(y|ue|t|x)$" => default since "bly|blue|blt|blx" is longer
 * For more complex things return the default.  We scribble over the RE
 * to prodce a suggested usage message.
 *
 * ForceRE is the wrong tool to use here, it is too strict.
 */
static const char *
IsAnchored(char *pcRE, const char *pcDef)
{
	register size_t i, iOut, iLen;
	register int fParens, iDots, iSlash;
	register const char *pcCheck;

	fParens = 0;
	if ('(' == pcRE[0] && '^' == pcRE[1]) /*)*/ {
		fParens = 1;
		pcRE += 2;
	} else if ('^' != pcRE[0]) {
		return pcDef;
	} else if ('(' == pcRE[1]) /*)*/ {
		fParens = 2;
		pcRE += 2;
	} else {
		++pcRE;
	}
	if (0 == (iLen = strlen(pcRE))) {
		return pcDef;
	}
	switch (fParens) {
	case 1:	/* "(^ .... $)" */
		if (/*(*/ ')' != pcRE[--iLen])
			return pcDef;
		/*FALLTHROUGH*/
	case 0:	/* ^foo$ */
		if ('$' !=  pcRE[--iLen])
			return pcDef;
		break;
	case 2:	/* "^( ... )$ */
		if ('$' != pcRE[--iLen])
			return pcDef;
		if (/*(*/ ')' != pcRE[--iLen])
			return pcDef;
		break;
	}

	/* A pattern that matches all digits is a "number"
	 *	digit | '[' digit ']' | '[' digit '-' digit ']' | '*' | '+'
	 * so we accept '**1' as a number: don't give us a bad RE, we also
	 * can match an IP address (a number with 3 dots in it) or a CIDR.
	 */
	iOut = 1;
	iSlash = iDots = 0;
	for (pcCheck = pcRE, i = 0; i < iLen; ) {
		pcCheck = pcRE+i;
		if (isdigit(*pcCheck))
			i += 1;
		else if ('/' == *pcCheck)
			iOut = 0, i += 1, iSlash += 1;
		else if ('\\' == pcCheck[0] && '/' == pcCheck[1])
			iOut = 0, i += 2, iSlash += 1;
		else if ('+' == *pcCheck || '*' == *pcCheck)
			iOut = 0, i += 1;
		else if ('\\' == pcCheck[0] && '.' == pcCheck[1])
			iOut = 0, iDots += 1, i += 2;
		else if ('[' != pcCheck[0] || ']' == pcCheck[1])
			break;
		else {	/* [0-9] | [.0-9] | [0-9.] | [234] */
			register int iSawDot = 0;

			do {
				++i;
				if ('-' == pcRE[i] || isdigit(pcRE[i]))
					/* nada */;
				else if ('/' == pcRE[i])
					iSlash += 1;
				else if ('.' == pcRE[i])
					iSawDot = 1;
				else
					break;
			} while (']' != pcRE[i]);
			if (']' != pcRE[i])
				break;
			if (iSawDot) {
				iDots = '*' == pcRE[i+1] || '+' == pcRE[i+1] ? 3 : iDots+1;
			}
			++i, iOut = 0;
		}
	}
	if (i == iLen) {
		pcRE[iLen] = '\000';
		return iOut ? pcRE : strcpy(pcRE, 3 == iDots ? 1 == iSlash ? "CIDR" : "IP" : "number");
	}
	iOut = 0;
	for (i = 0; i < iLen; /* below */) {
		switch ((pcRE[iOut++] = pcRE[i++])) {
		case '(':
		case ')': /* assume balanced if -S ran, allow it */
			continue;
		case '$':
			switch (fParens) {
			case 0:
			case 1:
				if ('|' != pcRE[i] || '^' != pcRE[i+1])
					return pcDef;
				i += 2;
				pcRE[iOut-1] = '|';
				break;
			case 2:	/* a literal dollar */
			default:
				break;
			}
			continue;
		case '|':	/* is ok if in parens, type 2 */
			/* ^foo|bar$ -S will pitch about missing anchors
			 * should be ^foo$|^bar$, but allow it: carping at
			 * the Customer won't help anyone.
			 */
			continue;
		case '*': case '+': case '?':
		case '.': case '[': case ']':
		case '{': case '}':
			return pcDef;
		case '\\':
			if (i == iLen)	/* missparse the $, its a \$ */
				return pcDef;
			if (isdigit(pcRE[i]))
				return pcDef;
			pcRE[iOut-1] = pcRE[i];
			continue;
		case ',':	/* outside of {a,b} is ok */
		default:
			continue;
		}
	}
	pcRE[iOut] = '\000';
	return pcRE;
}

/* Compile the REs in the list return 1 if any match a string in data.	(ksb)
 * Vetch about things we don't like, and for non-zero exits on errors.
 */
static int
CheckMatch(const cmd_t *cmd, const char *pcWhich, char **ppcData, const char *pcList, int *piRet)
{
	register int i, iMatch;
	register int fSawAnchor, fAllAnchors;
	register regex_t *reg;
	register const char *pcCheck;
	auto char *pcField, *pcCarp;
	static const cmd_t *cmdCarp = (cmd_t *)0;

	iMatch = 0;
	fSawAnchor = 0;
	fAllAnchors = 1;
	pcCarp = (char *)0;
	while ((char *)0 != (pcList = GetField(pcList, &pcField))) {
		if ('\000' == *pcField) {
			if (cmd != cmdCarp)
				fprintf(stderr, "%s: %s: %s:%d: %s: empty RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcWhich);
			*piRet = EX_DATAERR;
			cmdCarp = cmd;
			continue;
		}
		/* A # pattern matches a numeric form of a login/group, for
		 * a netgroup it doesn't, but we're not perfect and a hash
		 * can't really ever be in a netgroup name as it is a comment.
		 */
		if ('#' == *pcField) {
			++pcField;
		}
		if ((regex_t *)0 == (reg = ReComp(pcField, &pcCarp))) {
			if (cmd != cmdCarp)
				fprintf(stderr, "%s: %s: %s:%d: %s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcWhich, pcField, pcCarp);
			*piRet = EX_DATAERR;
			cmdCarp = cmd;
			continue;
		}
		/* If the patern is a .* we do match the whole string,
		 * which is what we mean by "anchored" here --ksb
		 */
		pcCheck = IsDotStar(pcField) ? acDevNull : IsAnchored(pcField, (char *)0);
		fAllAnchors &= (const char *)0 != pcCheck;
		fSawAnchor |= (const char *)0 != pcCheck;
		for (i = 0; (char *)0 != ppcData[i]; ++i) {
			if (ReMatch(reg, ppcData[i])) {
				iMatch = 1;
				break;
			}
		}
		regfree(reg);
	}
	EndField();
	if (cmd == cmdCarp) {
		/* do not clutter the output */
	} else if (!fSawAnchor) {
		fprintf(stderr, "%s: %s: %s:%d: %s: no anchors in any REs%s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcWhich, acSure);
		/* might be worth a non-zero exit */
	} else if (!fAllAnchors) {
		fprintf(stderr, "%s: %s: %s:%d: %s: some REs unanchored%s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcWhich, acSure);
		/* not be worth a non-zero exit */
	}

	return iMatch;
}

/* See if this program measures up to our standards.			(ksb)
 */
static void
CheckProg(const cmd_t *cmd, const char *pcProg, int *piRet, const struct stat *pstProg)
{
	auto struct stat stProg;

	if (acDefault == cmd->pcname) {
		return;
	}
	if ('/' != pcProg[0]) {
		fprintf(stderr, "%s: %s: %s:%d: %s: should be an absolute path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcProg);
		*piRet = EX_PROTOCOL;
	}
	if ((struct stat *)0 == pstProg) {
		if (-1 == stat(pcProg, &stProg)) {
			fprintf(stderr, "%s: %s: %s:%d: stat: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcProg, strerror(errno));
			*piRet = EX_NOINPUT;
			return;
		}
		pstProg = &stProg;
	}
	/* LLL When the binary is setuid (0 != (pstProg->st_mode&S_ISUID))
	 * (or setgid with S_ISGID) we might complain if we are setting a
	 * uid/gid too, unless it is /bin/su or /bin/login (to defeat the
	 * PAM password checks); we should suggest MAGIC_SHELL in that case.
	 * I don't want to recode access(2) here to check to see if the
	 * proposed euid/egid can run the binary, but if access(2) took the
	 * uid and gid as parameters I would use it.
	 */
	if (0 == (pstProg->st_mode&0111)) {
		fprintf(stderr, "%s: %s: %s:%d: %s: not executable\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcProg);
		*piRet = EX_DATAERR;
	} else if (0 != (pstProg->st_mode&0002)) {
		fprintf(stderr, "%s: %s: %s:%d: %s: world writable!\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcProg);
		*piRet = EX_NOPERM;
	}
}

/* Check a stdin, stdout, stderr redir for sanity.			(ksb)
 * See PrivOpen for specs (below).
 */
static int
CheckRedir(const cmd_t *cmd, const char *pcRedirExpected, const char *pcAttr)
{
	register char *pcSpec;
	auto struct stat stCheck;

	if ((char *)0 == (pcSpec = FindOpt(cmd, pcAttr))) {
		return 1;
	}
	if ('<' == *pcSpec || '>' == *pcSpec) {
		if (*pcSpec != *pcRedirExpected) {
			fprintf(stderr, "%s: %s: %s:%d: %s: I/O direction is not standard (%c presented, %c expected)%s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcAttr, *pcSpec, *pcRedirExpected, acSure);
			/* not worth a non-zero exit code */
		}
		if ('>' == *++pcSpec)
			++pcSpec;
	}
	/* We fail in %d as well as %f, we'll not actually process %d (yet)
	 */
	if (0 == strcmp(acSpecF, pcSpec) || 0 == strcmp(acSpecD, pcSpec)) {
		return 1;
	}
	if ('/' == *pcSpec) {
		if (-1 == stat(pcSpec, &stCheck)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: stat: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcAttr, pcSpec, strerror(errno));
			return 0;
		}
		/* don't check for type, etc */
		return 1;
	}
	if ((char *)0 == FindOpt(cmd, acOptDir) && (char *)0 == FindOpt(cmd, acOptChroot)) {
		fprintf(stderr, "%s: %s: %s:%d: %s is relative to $PWD\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcAttr);
		/* not worth a non-zero exit code */
	}
	return 0;
}

/* Verify that each configuration option in this cmd is unique		(ksb)
 */
static void
CheckUniq(const cmd_t *cmd, int *piRet)
{
	register int i, j;
	register size_t wLen;
	register const char *pcEq, *pcI, *pcJ;

	for (i = 0; i < cmd->nopts; ++i) {
		pcI = cmd->opts[i];
		if ((const char *)0 == (pcEq = strchr(pcI, '=')))
			wLen = strlen(pcI);
		else
			wLen = pcEq - pcI;
		for (j = i+1; j < cmd->nopts; ++j) {
			pcJ = cmd->opts[j];
			if (0 != strncmp(pcI, pcJ, wLen))
				continue;
			if ('\000' != pcJ[wLen] && '=' != pcJ[wLen])
				continue;
			fprintf(stderr, "%s: %s: %s:%d: %.*s given more than once\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, (int)wLen, pcI);
			*piRet = EX_CONFIG;
			return;
		}
	}
}

/* Netgroups, pam application names and such are not specified as	(ksb)
 * regular expressions, but some people try that.  Check them at the door.
 * N.B. A single dot (acMyself) doesn't look like an RE to this function.
 */
static int
LooksLikeRE(const char *pcThis)
{
	return (char *)0 != strchr(pcThis, '^') ||
		(char *)0 != strchr(pcThis, '$') ||
		(char *)0 != strchr(pcThis, '|') ||
		(char *)0 != strchr(pcThis, '['/*]*/) ||
		(char *)0 != strchr(pcThis, '('/*)*/);
}

/* $PATH is set as a option, check the entries				(ksb)
 * We could do more here if we kept track of a bit for "is escalated" in
 * the caller (uid, euid, gid, egid, initgroups or the like is set to
 * someone other than %l or .), and chroot status.  Actually that same
 * set could be used in FinishExec, in both cases to check for a value that
 * might not work in the chroot, or is allowed to be relative because there
 * is no escalattion of identity (maybe just "nice" value or "stdout").
 * When we only run built-in command (e.g. echo) we shouldn't vetch at all.
 */
static void
CheckPath(const cmd_t *cmd, const char *pcExp)
{
	register int c;
	register const char *pcScan, *pcQ;
	auto const char *pcRoot;
	auto struct stat stComp;
	auto char acComp[MAXPATHLEN+4];
	static const cmd_t *cmdVetchQuote = (cmd_t *)0;

	if ('\000' == *pcExp) {
		fprintf(stderr, "%s: %s: %s:%d: %s= sets the path to the empty string: remove the \"=\" markup to copy the client value, or make it \"=$|\" to force an explicitly empty value\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH);
		return;
	}
	pcRoot = FindOpt(cmd, acOptChroot);
	pcScan = pcExp;
	do {
		c = *pcScan;
		if (':' != c && '\000' != c) {
			++pcScan;
			continue;
		}
		snprintf(acComp, sizeof(acComp), "%.*s", (int)(pcScan-pcExp), pcExp);
		if ('\000' == acComp[0])
			strncpy(acComp, acCurDir, sizeof(acComp));
		pcExp = pcScan;
		if ('\000' != c)
			++pcScan, ++pcExp;

		if ((char *)0 == (pcQ = strchr(acComp, '"')) && (char *)0 == (pcQ = strchr(pcExp, '\''))) {
			/* OK no quotes */
		} else if (cmd == cmdVetchQuote) {
			/* already complained */
			continue;
		} else {
			fprintf(stderr, "%s: %s: %s:%d: %s literal quote (%c) in a path entry?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, *pcQ);
			cmdVetchQuote = cmd;
			continue;
		}
		/* Don't check inside chroot, or expanded paths
		 */
		if ((char *)0 != strchr(acComp, '$') || (char *)0 != pcRoot) {
			continue;
		}
		if ('/' != acComp[0]) {
			fprintf(stderr, "%s: %s: %s:%d: %s explicitly set a relative entry (%s)?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, acComp);
			continue;
		}
		if (-1 == stat(acComp, &stComp)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: stat: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, acComp, strerror(errno));
			continue;
		}
		if (0 != (stComp.st_mode & 0002)) {
			fprintf(stderr, "%s: %s: %s:%d: %s entry %s is world writable\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPATH, acComp);
		}
	} while ('\000' != c);
}

/* Given an RE that is supposed to match a perms string, see if it may	(ksb)
 * This does loop for 45,035 itterations, but that takes almost no time.
 * If the RE is a list and any single 1 doesn't match anything we ignore it.
 */
static int
CheckPermRE(const char *pcList, char **ppcCarp)
{
	static const char acFirst[]= "-dlbcnpsDPw";
	register int i;
	register const char *pcFirst;
	register regex_t *pRE;
	auto char *pcRE;
	auto char acCheck[16];	/* "drwxrwxrwx" */

	*ppcCarp = (char *)0;
	while ((char *)0 != (pcList = GetField(pcList, &pcRE))) {
		if ((regex_t *)0 == (pRE = ReComp(pcRE, ppcCarp))) {
			EndField();
			return -1; /* should never happen, caller checked */
		}

		acCheck[10] = '\000';
		for (pcFirst = acFirst; '\000' != (acCheck[0] = *pcFirst++); ) {
			for (i = 0; i < 010000; ++i) {
				acCheck[1] = i & 0400 ? 'r' : '-';
				acCheck[2] = i & 0200 ? 'w' : '-';
				acCheck[3] = i & 0100 ? (i & S_ISUID) ? 's' : 'x' : (i & S_ISUID) ? 'S' : '-';
				acCheck[4] = i & 0040 ? 'r' : '-';
				acCheck[5] = i & 0020 ? 'w' : '-';
				acCheck[6] = i & 0010 ? (i & S_ISGID) ? 's' : 'x' : (i & S_ISGID) ? 'S' : '-';
				acCheck[7] = i & 0004 ? 'r' : '-';
				acCheck[8] = i & 0002 ? 'w' : '-';
				acCheck[9] = i & 0001 ? (i & S_ISVTX) ? 't' : 'x' : (i & S_ISVTX) ? 'T' : '-';
				if (ReMatch(pRE, acCheck)) {
					EndField();
					return 1;
				}
			}
		}
	}
	EndField();
	return 0;
}

/* same thing for mode RE, does it match and octal 0 to 7777		(ksb)
 */
static int
CheckModeRE(const char *pcList, char **ppcCarp)
{
	register int i;
	register regex_t *pRE;
	auto char *pcRE;
	auto char acCheck[16];	/* "0000" to "7777" */

	*ppcCarp = (char *)0;
	while ((char *)0 != (pcList = GetField(pcList, &pcRE))) {
		if ((regex_t *)0 == (pRE = ReComp(pcRE, ppcCarp))) {
			EndField();
			return -1; /* should never happen, caller checked */
		}

		for (i = 0; i < 010000; ++i) {
			snprintf(acCheck, sizeof(acCheck), "%04lo", (long)i);
			if (ReMatch(pRE, acCheck)) {
				EndField();
				return 1;
			}
		}
	}
	EndField();
	return 0;
}

/* See if -f use abused and overloaded, this is not always right	(ksb)
 * but it does find some bugs
 */
static void
DupCheck(const cmd_t *cmd, const char *pcOpt, const char **ppcCur, const char *pcUse, const char *pcOK)
{
	auto char *pcField;

	if ((const char *)0 == *ppcCur) {
		*ppcCur = pcUse;
		return;
	}
	while ((const char *)0 != (pcOK = GetField(pcOK, &pcField))) {
		if (0 == strcmp(pcField, *ppcCur))
			return;
	}
	EndField();
	fprintf(stderr, "%s: %s: %s:%d: %s used for both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcOpt, pcUse, *ppcCur);
}

/* Sanity check our rule database.				(ksb/petef)
 * We've read the default configuration files, what would break
 * if we added the others?
 */
static void
SanityCheck(int argc, char **argv)
{
	register int iIndex;
	register char *pcUser, *pcGroup, **ppcGroups, **ppcUsers;
	auto int iRet;
	auto NEEDS Need;

	iRet = EX_OK;
	if (0 != argc) {
		register int uOrig, uPriv, fdRead;
		uOrig = getuid();
		uPriv = geteuid();

		if (setreuid(uPriv, uOrig) < 0) {
			fatal("setreuid: %ld,%ld: %s", (long)uPriv, (long)uOrig, strerror(errno));
		}
		for (iIndex = 0; iIndex < argc; ++iIndex) {
			if (-1 == (fdRead = open(argv[iIndex], O_RDONLY, 0400))) {
				fatal("open: %s: %s", argv[iIndex], strerror(errno));
			}
			OneFile(fdRead, argv[iIndex]);
		}
		/* If we put the euid back then anyone can stat(2) any file on
		 * our filesystem for existance.  Radiates too much info. --ksb
		 */
	}

	/* yuck: we have to get a cache of all possible users + groups to
	 * make sure that users= and groups= matches something
	 */
	{
	register struct passwd *ppassThis;
	register struct group *pgroupThis;
	register int iMax;

	setgrent();
	iMax = 512;
	iIndex = 0;
	if ((char **)0 == (ppcGroups = (char **)calloc(iMax, sizeof(char *))))
		fatal(acBadAlloc);
	while ((struct group *)0 != (pgroupThis = getgrent())) {
		if ((char *)0 == (ppcGroups[iIndex++] = strdup(pgroupThis->gr_name)))
			fatal(acBadAlloc);
		if (iMax == iIndex) {
			iMax *= 2;
			ppcGroups = (char **)realloc(ppcGroups, iMax*sizeof(char *));
			if ((char **)0 == ppcGroups)
				fatal(acBadAlloc);
		}
	}
	ppcGroups[iIndex] = (char *)0;
	endgrent();

	setpwent();
	iMax = 512;
	iIndex = 0;
	if ((char **)0 == (ppcUsers = (char **)calloc(iMax, sizeof(char *))))
		fatal(acBadAlloc);
	while ((struct passwd *)0 != (ppassThis = getpwent())) {
		if ((char *)0 == (ppcUsers[iIndex++] = strdup(ppassThis->pw_name)))
			fatal(acBadAlloc);
		if (iMax == iIndex) {
			iMax *= 2;
			ppcUsers = (char **)realloc(ppcUsers, iMax*sizeof(char *));
			if ((char **)0 == ppcUsers)
				fatal(acBadAlloc);
		}
	}
	endpwent();
	}

	/* Sanity check complied options
	 */
	if ('/' != acAccess[0]) {
		fprintf(stderr, "%s: configuration path is not absolute (%s)\n", Progname, acAccess);
		iRet = EX_CONFIG;
	}
#if USE_PAM
	{
	static const char acPam[] = OP_PAM_IN;
	static const char acPamConf[] = OP_PAM_FILE;
	register FILE *fpPam;
	auto struct stat stCheck;
	auto char acPamPolicy[MAXPATHLEN+4];

	snprintf(acPamPolicy, sizeof(acPamPolicy), "%s/%s", acPam, acDefPam);
	if ((FILE *)0 != (fpPam = fopen(acPamConf, "r"))) {
		register char *pcFetch;
		auto size_t wLen;

		while ((char *)0 != (pcFetch = fgetln(fpPam, & wLen))) {
			if ('#' == *pcFetch || isspace(*pcFetch))
				continue;
			if (0 == strncmp(acDefPam, pcFetch, sizeof(acDefPam)-2) && isspace(pcFetch[sizeof(acDefPam)-1]))
				break;
		}
		fclose(fpPam);
		if ((char *)0 == pcFetch) {
			fprintf(stderr, "%s: %s: doesn't list %s\n", Progname, acPamConf, acDefPam);
		}
	} else if (-1 == stat(acPam, &stCheck) || S_IFDIR != (stCheck.st_mode & S_IFMT)) {
		fprintf(stderr, "%s: PAM configuration directory: stat: %s: %s\n", Progname, acPam, strerror(errno));
		iRet = EX_CONFIG;
	} else if (-1 == stat(acPamPolicy, &stCheck)) {
		fprintf(stderr, "%s: PAM policy: stat: %s: %s \n", Progname, acPamPolicy, strerror(errno));
		iRet = EX_CONFIG;
	}
	}
#endif

	/* Sanity check each command
	 */
	{
	register cmd_t *cmd, *cmdScan;	/* only so we can set the carp flag */
	register const cmd_t *cmdDup;
	register char *pcCur;
	register const char *pcRead, *pcDup;
	register long lCur, lNot, lDup;
	auto char *pcEnd, *pcField, *pcCarp;
	auto const char *pcExplInitGrp;
	auto SPEC SP;
	auto int iMatch;

	cmd = (cmd_t *)0;
	for (cmdScan = NewCmd((const char *)0); (cmd_t *)0 != cmdScan; cmdScan = cmdScan->pODnext) {
		/* a DEFAULT cannot have $#, $1, $2... because we check
		 * them before we Build the command
		 */
		if (acDefault == cmdScan->pcname) {
			if (0 == iIndex && 0 == strcmp(cmdScan->pcfile, pcAccess)) {
				/* always OK */
			} else if ((cmd_t *)0 == cmdScan->pODnext || acDefault == cmdScan->pODnext->pcname) {
				fprintf(stderr, "%s: %s: %s:%d non-empty, but applies to no rules\n", Progname, acDefault, cmdScan->pcfile, cmdScan->iline);
				/* not worth a non zero exit code */
			}
			for (iIndex = 0; iIndex < cmdScan->nopts; ++iIndex) {
				pcRead = cmdScan->opts[iIndex];
				if ('$' != pcRead[0] && '!' != pcRead[0]) {
					continue;
				}
				if ('#' == pcRead[1]) {
					fprintf(stderr, "%s: %s: %s:%d: mnemonic matching option %c# misplaced?\n", Progname, acDefault, cmdScan->pcfile, cmdScan->iline, pcRead[0]);
					iRet = EX_CONFIG;
					continue;
				}
				if (!isdigit(pcRead[1])) {
					continue;
				}
				fprintf(stderr, "%s: %s: %s:%d: mnemonic matching option %c", Progname, cmdScan->pcname, cmdScan->pcfile, cmdScan->iline, pcRead[0]);
				while (isdigit(*++pcRead)) {
					fputc(*pcRead, stderr);
				}
				fprintf(stderr, " misplaced?\n");
				iRet = EX_CONFIG;
			}
		}

		/* Make sure all options are unique, we don't process
		 * duplicate options well.
		 */
		CheckUniq(cmdScan, & iRet);

		/* We have to check all merged DEFAULT options
		 */
		cmd = Build(cmdScan);

		/* See if the -f, -g, -u options are asked for and checked.
		 * Also look for the program we provision, if possible.
		 */
		SP.pcspecF = SP.pcspecG = SP.pcspecU = SP.pcspecM = (char *)0;
		SP.freal = 1;
		SP.iexit = EX_OK;
		SP.ierrno = 0;
		SP.fwabsroot = SP.fwabsdir = 0;
		SP.pcroot = SP.pcexecok = SP.pcexecmiss = (char *)0;
		SP.pNE = (NEEDS *)0;
		(void)memset(SP.acpwd, '\000', sizeof(SP.acpwd));
		(void)memset(SP.accmd, '\000', sizeof(SP.accmd));

		/* If there is no args[0] then it's a syntax error, only
		 * happens at the end-of-file when missing all args
		 */
		if (0 == cmd->nargs && acDefault != cmd->pcname) {
			static int fTold = 0;

			if (! fTold) {
				fprintf(stderr, "%s: %s: %s:%d: configuration file syntax error (%s)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acDefault == cmd->pcname ? "the default option must be first" : "file truncated?");
				fTold = 1;
			}
			iRet = EX_CONFIG;
			continue;
		}
		if (0 != cmdScan->nopts) {
			/* ok */
		} else if (acDefault == cmdScan->pcname) {
			/* This is the documented way to remove the global
			 * DEFAULT in access.cf, so not an error. --ksb
			 */
		} else {
			register int iLook;

			for (iLook = 0; iLook < cmdScan->nargs; ++iLook) {
				if ((char *)0 != strchr(cmdScan->args[iLook], '='))
					break;
			}
			fprintf(stderr, "%s: %s: %s:%d: a missing semicolon may have consumed all options", Progname, cmdScan->pcname, cmdScan->pcfile, cmdScan->iline);
			if (0 != iLook && iLook < cmdScan->nargs) {
				fprintf(stderr, " (maybe after \"%s\")", cmdScan->args[iLook-1]);
			}
			fprintf(stderr, "\n");
		}
		lNot = lCur = -2;
		if ((char *)0 != (pcCur = FindOpt(cmd, "!#"))) {
			lNot = strtol(pcCur, &pcEnd, 0);
			if ((char *)0 != pcEnd && '\000' != *pcEnd) {
				fprintf(stderr, "%s: %s: %s:%d: cannot parse count in \"!#=%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcCur);
				lNot = -1;
			} else if (0 >= lNot) {
				fprintf(stderr, "%s: %s: %s:%d: !# value %ld won't reject any mnemonic rules\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lNot);
			}
		}
		if ((char *)0 != (pcCur = FindOpt(cmd, acOptCount))) {
			lCur = strtol(pcCur, &pcEnd, 0);
			if ((char *)0 != pcEnd && '\000' != *pcEnd) {
				fprintf(stderr, "%s: %s: %s:%d: cannot parse count in \"$#=%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcCur);
				lCur = -1;
			} else if (0 > lCur) {
				fprintf(stderr, "%s: %s: %s:%d: $# value %ld won't match any mnemonic rules\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lCur);
			}
		}
		if (lNot == lCur && -2 != lCur) {
			fprintf(stderr, "%s: %s: %s:%d: $# and !# have the same value (%ld) which won't match any mnemonic rules\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lCur);
			iRet = EX_DATAERR;
		}
		/* We don't look at lNot here and we could, but the logic
		 * is really tricky (maybe later LLL) --ksb
		 * Start loop with the original's next pointer, not the copy's.
		 */
		for (cmdDup = cmdScan->pODnext; (cmd_t *)0 != cmdDup && acDefault != cmd->pcname; cmdDup = cmdDup->pODnext) {
			if (0 != strcmp(cmd->pcname, cmdDup->pcname))
				continue;

			if (!cmd->fcarp && 0 != strcmp(cmd->pcfile, cmdDup->pcfile)) {
				fprintf(stderr, "%s: %s should only be defined in a single configuration file (not both %s and %s)\n", Progname, cmd->pcname, cmd->pcfile, cmdDup->pcfile);
				cmd->fcarp = 1;
			}
			pcDup = FindOpt(cmdDup, acOptCount);
			if ((char *)0 == pcCur && (char *)0 == pcDup) {
				if (SamePats(cmd, cmdDup))
					fprintf(stderr, "%s: %s: duplicated in %s:%d and %s:%d\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, cmdDup->pcfile, cmdDup->iline);
				continue;
			}
			if ((char *)0 == pcCur || (char *)0 == pcDup) {
				continue;
			}
			if (lCur != (lDup = strtol(pcDup, &pcEnd, 0))) {
				continue;
			}
			if (SamePats(cmd, cmdDup)) {
				fprintf(stderr, "%s: %s: duplicated in %s:%d and %s:%d, argument count %ld\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, cmdDup->pcfile, cmdDup->iline, lDup);
			}
		}

		if (acDefault == cmd->pcname) {
			if ((char *)0 != (pcDup = FindOpt(cmd, acOptNolog))) {
				fprintf(stderr, "%s: %s: %s:%d: including %s here is presumptuous, since logging cannot enabled per command\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptNolog);
				/* no exit code for this */
			}
			continue;
		}
		if (0 != Craving(cmd, & Need)) {
			iRet = EX_DATAERR;
		}
		SP.pNE = & Need;

		CheckRoot(cmd, &SP);

		/* When we failed to stat(2) a command and we never saw
		 * an alternate that we could stat, then we complain.
		 * Don't complain for every failed stat call: you may have
		 * a Linux path and an Solaris path as alternatives, viz.:
		 *	$1=^(/sbin|/usr/sbin)/foo$
		 */
		if ((char *)0 != SP.pcexecmiss && (char *)0 == SP.pcexecok) {
			fprintf(stderr, "%s: %s: %s:%d: stat: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, SP.pcexecmiss, strerror(SP.ierrno));
			iRet = EX_UNAVAILABLE;
		}
		if ((char *)0 != SP.pcexecok) {
			free((void *) SP.pcexecok);
		}
		if ((char *)0 != SP.pcexecmiss) {
			free((void *)SP.pcexecmiss);
		}
		if (EX_OK != SP.iexit) {
			iRet = SP.iexit;
		}

		/* Give up because we'll not match any FindOpts below, and
		 * we don't need any options to work
		 */
		if (0 == cmd->nopts && !Need.fUid && !Need.fGid && !Need.fSecFile) {
			continue;
		}

		/* check to see we have a valid uid= and gid= */
		if ((char *)0 == (pcDup = pcUser = FindOpt(cmd, acOptUid))) {
			if (acDefault != cmd->pcname)
				fprintf(stderr, "%s: %s: %s:%d: uid defaults to the superuser, make that explicit\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
		} else if (IsMyself(pcUser) || '\000' == *pcUser) {
			/* OK */
		} else if (0 == strcmp(pcUser, acSpecF) || 0 == strcmp(pcUser, acSpecD)) {
			DupCheck(cmd, "-f", &SP.pcspecF, acOptUid, acDupU);
		} else if (0 == strcmp(pcUser, acSpecU)) {
			DupCheck(cmd, "-u", &SP.pcspecU, acOptUid, acDupU);
		} else if (0 == strcmp(pcUser, acSpecG)) {
			fprintf(stderr, "%s: %s: %s:%d: -g cannot use specification as a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOUSER;
		} else if ((char *)0 != strchr(pcUser, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: %s=%s: should not be a list -- just a single login specification\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptUid, pcUser);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcUser)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: looks like a regular expression, should be a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptUid, pcUser);
			iRet = EX_CONFIG;
		} else if (isdigit(*pcUser) && (struct passwd *)0 == getpwuid(atoi(pcUser))) {
			fprintf(stderr, "%s: %s: %s:%d: no such uid \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcUser);
			iRet = EX_NOUSER;
		} else if (!isdigit(*pcUser) && (struct passwd *)0 == getpwnam(pcUser)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: no such user \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptUid, pcUser);
			iRet = EX_NOUSER;
		}
		if ((char *)0 == (pcUser = FindOpt(cmd, acOptEuid))) {
			/* We already carped about default to the superuser
			 */
		} else if (IsMyself(pcUser)) {
			/* nothing going on here */
		} else if (0 == strcmp(pcUser, acSpecF) || 0 == strcmp(pcUser, acSpecD)) {
			DupCheck(cmd, "-f", &SP.pcspecF, acOptEuid, acDupU);
		} else if (0 == strcmp(acSpecU, pcUser)) {
			DupCheck(cmd, "-u", &SP.pcspecU, acOptEuid, acDupU);
		} else if (0 == strcmp(acSpecG, pcUser)) {
			fprintf(stderr, "%s: %s: %s:%d: cannot use -g's specification as a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOUSER;
		} else if ((char *)0 != strchr(pcUser, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: %s=%s should not be a list -- just a single login specification\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptEuid, pcUser);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcUser)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: %s: looks like a regular expression, should be a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptEuid, pcUser);
			iRet = EX_CONFIG;
		} else if (isdigit(*pcUser) && (struct passwd *)0 == getpwuid(atoi(pcUser))) {
			fprintf(stderr, "%s: %s: %s:%d: %s: no such euid \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptEuid, pcUser);
			iRet = EX_NOUSER;
		} else if (!isdigit(*pcUser) && (struct passwd *)0 == getpwnam(pcUser)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: no such user \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptEuid, pcUser);
			iRet = EX_NOUSER;
		}
		if ((char *)0 == (pcExplInitGrp = FindOpt(cmd, acOptInitgrps))) {
			if ((char *)0 == FindOpt(cmd, acOptUid) && (char *)0 == FindOpt(cmd, acOptEuid) && (char *)0 == FindOpt(cmd, acOptGid) && (char *)0 == FindOpt(cmd, acOptEgid))
				fprintf(stderr, "%s: %s: %s:%d: %s: groups defaults to the original real gid, use \"gid=.\" to make that explicit\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps);
		} else if ('\000' == *(pcRead = pcExplInitGrp)) {
			if ((char *)0 == pcDup && (char *)0 == pcUser) {
				fprintf(stderr, "%s: %s: %s:%d: %s: no uid or euid set to copy\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps);
				iRet = EX_NOUSER;
			}
		} else if (IsMyself(pcRead)) {
			/* nothing special */
		} else if (0 == strcmp(acSpecF, pcRead) || 0 == strcmp(acSpecD, pcRead)) {
			DupCheck(cmd, "-f", &SP.pcspecF, acOptInitgrps, acDupU);
		} else if (0 == strcmp(acSpecU, pcRead)) {
			DupCheck(cmd, "-u", &SP.pcspecU, acOptInitgrps, acDupU);
		} else if (0 == strcmp(acSpecG, pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: %%g cannot be used as a login name\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps);
			iRet = EX_NOUSER;
		} else if ((char *)0 != strchr(pcRead, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: %s=%s: should not be a list -- just a single login name\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps, pcRead);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: looks like a regular expression, should be a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps, pcRead);
		} else if (isdigit(*pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: must be a login name, %%f, %%u, %%l, or .\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps, pcRead);
			iRet = EX_NOUSER;
		} else if ((struct passwd *)0 == getpwnam(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: no such %s login \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptInitgrps, pcRead);
			iRet = EX_NOUSER;
		}

		/* N.B. we use pcDup to avoid fetching the session again below
		 */
		if ((char *)0 == (pcRead = pcDup = FindOpt(cmd, acOptSession)) || '\000' == *pcRead) {
			/* no session set, which is usually OK */
			pcDup = (char *)0;
		} else if (IsMyself(pcRead)) {
			/* noting special */
		} else if (0 == strcmp(pcRead, acKeyInitgroup)) {
			if ((const char *)0 == pcExplInitGrp)
				fprintf(stderr, "%s: %s: %s:%d: session %s: no explicit initgroups to copy\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acKeyInitgroup);
		} else if (0 == strcmp(pcRead, acSpecF) || 0 == strcmp(pcRead, acSpecD)) {
			DupCheck(cmd, "-f", &SP.pcspecF, acOptSession, acDupU);
		} else if (0 == strcmp(acSpecU, pcRead)) {
			DupCheck(cmd, "-u", &SP.pcspecU, acOptSession, acDupU);
		} else if (0 == strcmp(pcRead, acSpecG)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: cannot use -g as a login name\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptSession);
			iRet = EX_NOUSER;
		} else if ((char *)0 != strchr(pcRead, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: session %s: is not a list -- just a single login specification\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: looks like a regular expression, should be a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptSession, pcRead);
			iRet = EX_CONFIG;
		} else if (isdigit(*pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: session %s: must be a login name, %%f, %%i, %%u, %%l, or %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead, acSpecMe);
			iRet = EX_NOUSER;
		} else if ((struct passwd *)0 == getpwnam(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: no such session login \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead);
			iRet = EX_NOUSER;
		}
		if ((char *)0 == (pcRead = FindOpt(cmd, acOptCleanup)) || '\000' == *pcRead) {
			/* no cleanup, which is ok, even if we session'd */
		} else if (IsMyself(pcRead)) {
			if ((char *)0 == pcDup) {
				fprintf(stderr, "%s: %s: %s:%d: %s %s: no matching %s specified\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptCleanup, pcRead, acOptSession);
				/* not worth the non-zero exit code */
			}
		} else if (0 == strcmp(pcRead, acKeyInitgroup)) {
			if ((const char *)0 == pcExplInitGrp)
				fprintf(stderr, "%s: %s: %s:%d: %s %s: no explicit initgroups to copy\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptCleanup, acKeyInitgroup);
		} else if (0 == strcmp(acSpecF, pcRead) || 0 == strcmp(acSpecD, pcRead)) {
			DupCheck(cmd, "-f", &SP.pcspecF, acOptCleanup, acDupU);
		} else if (0 == strcmp(acSpecU, pcRead)) {
			DupCheck(cmd, "-u", &SP.pcspecU, acOptCleanup, acDupU);
		} else if (0 == strcmp(acSpecG, pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s: cannot use -g as a login name\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptSession);
			iRet = EX_NOUSER;
		} else if ((char *)0 != strchr(pcRead, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: is not a list -- just a single application specification or %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptCleanup, pcRead, acMyself);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: %s %s: looks like a regular expression, should be a login\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptCleanup, pcRead);
			iRet = EX_CONFIG;
		} else if ((struct passwd *)0 == getpwnam(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: no such cleanup login \"%s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead);
			iRet = EX_NOUSER;
		}
		if ((char *)0 != (pcRead = FindOpt(cmd, acOptGid))) {
			while ((char *)0 != (pcRead = GetField(pcRead, &pcField))) {
				if (IsMyself(pcField)) {
					/* nada */
				} else if (0 == strcmp(acSpecF, pcField) || 0 == strcmp(acSpecD, pcField)) {
					DupCheck(cmd, "-f", &SP.pcspecF, acOptGid, acDupU);
				} else if (0 == strcmp(acSpecU, pcField)) {
					DupCheck(cmd, "-u", &SP.pcspecU, acOptGid, acDupU);
				} else if (0 == strcmp(acSpecG, pcField)) {
					DupCheck(cmd, "-g", &SP.pcspecG, acOptGid, acDupU);
				} else if (isdigit(*pcField)) {
					if ((struct group *)0 == getgrgid(atoi(pcField))) {
						fprintf(stderr, "%s: %s: %s:%d: no such gid %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcField);
						iRet = EX_NOUSER;
					}
				} else if ((struct group *)0 == getgrnam(pcField)) {
					fprintf(stderr, "%s: %s: %s:%d: no such group %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcField);
					iRet = EX_NOUSER;
				}
			}
			EndField();
		}
		if ((char *)0 != (pcGroup = FindOpt(cmd, acOptEgid))) {
			if (IsMyself(pcGroup)) {
				/* nada */
			} else if (0 == strcmp(acSpecF, pcGroup) || 0 == strcmp(acSpecD, pcGroup)) {
				DupCheck(cmd, "-f", &SP.pcspecF, acOptEgid, acDupU);
			} else if (0 == strcmp(acSpecU, pcGroup)) {
				DupCheck(cmd, "-u", &SP.pcspecU, acOptEgid, acDupU);
			} else if (0 == strcmp(acSpecG, pcGroup)) {
				DupCheck(cmd, "-g", &SP.pcspecG, acOptEgid, acDupU);
			} else if (isdigit(*pcGroup)) {
				if ((struct group *)0 == getgrgid(atoi(pcGroup))) {
					fprintf(stderr, "%s: %s: %s:%d: no such egid %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcGroup);
					iRet = EX_NOUSER;
				}
			} else if ((struct group *)0 == getgrnam(pcGroup)) {
				fprintf(stderr, "%s: %s: %s:%d: no such effective group %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcGroup);
				iRet = EX_NOUSER;
			}
		}

		/* Look for any posible groups, users, that might match this
		 * rule and carp when there is not even a possible match, also
		 * look through each specified netgroup, try to guess if this
		 * group might match someone.  If we match the host and the
		 * host allows anyone in the current login list, for example.
		 * We don't look at domain.  We do debug your /etc/netgroup.
		 */
		iMatch = 0;
		if ((char *)0 != (pcGroup = FindOpt(cmd, acOptGroups))) {
			iMatch |= CheckMatch(cmd, acOptGroups, ppcGroups, pcGroup, &iRet);
		}
		if ((char *)0 != (pcRead = FindOpt(cmd, acOptUsers))) {
			iMatch |= CheckMatch(cmd, acOptUsers, ppcUsers, pcRead, &iRet);
		}
		if (((char *)0 != (pcGroup = FindOpt(cmd, acOptNetgroups)))) {
			auto char *pcHost, *pcLogin, *pcDomain;
			auto char acMyName[512]; /* traditionally 256	*/
			auto char acPat[128]; /* temp RE: m/^$login$/	*/

			if (-1 == gethostname(acMyName, sizeof(acMyName))) {
				(void)strncpy(acMyName, ":", sizeof(acMyName));
			}
			pcRead = pcGroup;
			while ((char *)0 != (pcRead = GetField(pcRead, &pcField))) {
				if (LooksLikeRE(pcField)) {
					fprintf(stderr, "%s: %s: %s:%d: netgroup %s: looks like a regular expression, should be a name\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcField);
					/* is not 100% sure, don't fail */
				}
				if ('-' == pcField[0] && '\000' == pcField[1]) {
					continue;
				}
				setnetgrent(pcField);
				while (getnetgrent(&pcHost, &pcLogin, &pcDomain)) {
					pcGroup = (char *)0;
					if ((char *)0 != pcHost && 0 != strcmp(acMyName, pcHost)) {
						if ('*' == pcHost[0]) {
							fprintf(stderr, "%s: netgroup %s: use the empty field for any host (not an explicit '*')\n", Progname, pcField);
							continue;
						}
						continue;
					}
					if ((char *)0 == pcLogin) {
						iMatch |= 1;
						continue;
					}
					if ('*' == pcLogin[0]) {
						fprintf(stderr, "%s: netgroup %s: use the empty field for anyone (not an explicit '*')\n", Progname, pcField);
						continue;
					}
					snprintf(acPat, sizeof(acPat), "^%s$", pcLogin);
					iMatch |= CheckMatch(cmd, acOptNetgroups, ppcUsers, acPat, &iRet);
				}
				endnetgrent();
			}
			EndField();
			if ((char *)0 != pcGroup) {
				fprintf(stderr, "%s: %s: %s:%d none of these netgroups exist: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcGroup);
				/* not worth a nonzero exit code */
			}
		}
		if (((char *)0 != (pcRead = FindOpt(cmd, acOptHelmet)))) {
			CheckProg(cmd, pcRead, &iRet, (struct stat *)0);
		}
		if (((char *)0 != (pcRead = FindOpt(cmd, acOptJacket)))) {
			CheckProg(cmd, pcRead, &iRet, (struct stat *)0);
		}
		if (acDefault != cmd->pcname && 0 == iMatch) {
			fprintf(stderr, "%s: %s: %s:%d: no users, groups, or netgroups match\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOUSER;
		}
		if ((char *)0 != (pcDup = FindOpt(cmd, acOptFib))) {
			register long lFib;
			auto int iMaxFib = 1;
#if HAVE_SETFIB
			auto int aiMib[16];
			auto size_t iMibLen, iDataLen;
			static const char acNetFibs[] = "net.fibs";
#endif

			lFib = strtol(pcDup, &pcEnd, 0);
			if ((char *)0 != pcEnd && '\000' != *pcEnd) {
				fprintf(stderr, "%s: %s: %s:%d: %s: \"%s\" badly formatted counting number\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptFib, pcDup);
				iRet = EX_DATAERR;
			}

#if HAVE_SETFIB
			iMibLen = sizeof(aiMib)/sizeof(aiMib[0]);
			iDataLen = sizeof(iMaxFib);
			if (-1 == sysctlnametomib(acNetFibs, aiMib, &iMibLen)) {
				fprintf(stderr, "%s: %s: %s:%d: %s: %s: no such mib?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptFib, acNetFibs);
			} else if (-1 == sysctl(aiMib, iMibLen, &iMaxFib, &iDataLen, NULL, 0)) {
				fprintf(stderr, "%s: %s: %s:%d: %s: sysctl: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptFib, strerror(errno));
			}
#endif

			if (0 > lFib) {
				fprintf(stderr, "%s: %s: %s:%d: %ld: fib values are counting numbers\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lFib);
				iRet = EX_PROTOCOL;
			} else if (iMaxFib <= lFib) {
				fprintf(stderr, "%s: %s: %s:%d: %ld: fib value out of range (limit %ld)\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lFib, (long)iMaxFib);
				iRet = EX_PROTOCOL;
			}
		}
		if ((char *)0 != (pcDup = FindOpt(cmd, acOptNice))) {
			register long lNice;

			lNice = strtol(pcDup, &pcEnd, 0);
			if ((char *)0 != pcEnd && '\000' != *pcEnd) {
				fprintf(stderr, "%s: %s: %s:%d: %s: \"%s\" should be an integer from -20..20\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptNice, pcDup);
				iRet = EX_DATAERR;
			}
			if (-20 > lNice || 20 < lNice) {
				fprintf(stderr, "%s: %s: %s:%d: %ld: nice value out of range\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, lNice);
				iRet = EX_PROTOCOL;
			}
		}
		if (!CheckRedir(cmd, "<", acOptStdin)) {
			iRet = EX_PROTOCOL;
		}
		if (!CheckRedir(cmd, ">", acOptStdout)) {
			iRet = EX_PROTOCOL;
		}
		if (!CheckRedir(cmd, ">", acOptStderr)) {
			iRet = EX_PROTOCOL;
		}
		if ((char *)0 == (pcDup = FindOpt(cmd, acPatFowners)) || '\000' == pcDup[0]) {
			/* OK */
		} else if ((char *)0 != (pcDup = CheckCompile(pcDup, &pcCarp))) {
			fprintf(stderr, "%s: %s: %s:%d: %s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acPatFowners, pcDup, pcCarp);
		}
		if ((char *)0 == (pcDup = FindOpt(cmd, acPatPerms)) || '\000' == pcDup[0]) {
			/* OK */
		} else if ((char *)0 != (pcDup = CheckCompile(pcDup, &pcCarp))) {
			fprintf(stderr, "%s: %s: %s:%d: %s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acPatPerms, pcDup, pcCarp);
		}

		if ((char *)0 != (pcDup = FindOpt(cmd, acOptMask))) {
			register long lMask;

			lMask = strtol(pcDup, &pcEnd, 8);
			if ((char *)0 != pcEnd && '\000' != *pcEnd) {
				fprintf(stderr, "%s: %s: %s:%d: %s: %s: should be an octal number\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptMask, pcDup);
				iRet = EX_DATAERR;
			}
			if (0 > lMask || 0777 < lMask) {
				fprintf(stderr, "%s: %s: %s:%d: %s: %04lo: value out of range\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptMask, lMask);
				iRet = EX_PROTOCOL;
			}
		}
		if ((char *)0 != (pcDup = FindOpt(cmd, acOptEnv)) && '\000' != *pcDup && (char *)0 != (pcField = CheckCompile(pcDup, &pcCarp))) {
			fprintf(stderr, "%s: %s: %s:%d: %s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptEnv, pcField, pcCarp);
			iRet = EX_DATAERR;
		}
		if ((char *)0 == (pcRead = FindOpt(cmd, acOptPam)) || '\000' == *pcRead) {
			/* no pam requested */
		} else if (IsMyself(pcRead)) {
			/* default pam requested */
		} else if ((char *)0 != strchr(pcRead, ',')) {
			fprintf(stderr, "%s: %s: %s:%d: pam %s: specify a single application name or %s, not a list\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead, acMyself);
			iRet = EX_CONFIG;
		} else if (LooksLikeRE(pcRead)) {
			fprintf(stderr, "%s: %s: %s:%d: pam %s: specify a single application name or %s, not a regular expression\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead, acMyself);
			iRet = EX_CONFIG;
#if !USE_PAM
		} else {
			fprintf(stderr, "%s: %s: %s:%d: pam unsupported\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_UNAVAILABLE;
#endif
		} /* else we could try to open a PAM context with it */
		if ((char *)0 != (pcRead = FindOpt(cmd, acOptPass))) {
			iMatch = 0;
			if ('\000' == *pcRead) {
				pcRead = acMyself;
			}
			while ((char *)0 != (pcRead = GetField(pcRead, &pcField))) {
				if (0 == strcmp(acSpecU, pcField)) {
					if ((char *)0 != SP.pcspecU) {
						fprintf(stderr, "%s: %s: %s:%d: -u used for both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPass, SP.pcspecU);
					}
					SP.pcspecU = acOptPass;
					iMatch = 1;
				} else if (0 == strcmp(pcField, acSpecF) || 0 == strcmp(pcField, acSpecD)) {
					if ((char *)0 != SP.pcspecF) {
						fprintf(stderr, "%s: %s: %s:%d: -f used for both %s and %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acOptPass, SP.pcspecF);
					}
					SP.pcspecF = acOptPass;
					iMatch = 1;
				} else if (IsMyself(pcField) || (struct passwd *)0 != getpwnam(pcField)) {
					iMatch = 1;
				}
			}
			EndField();
			if (0 == iMatch) {
				fprintf(stderr, "%s: %s: %s:%d: no passwords available from logins in list\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
				iRet = EX_NOUSER;
			}
		}
		for (iIndex = 0; iIndex < cmd->nopts; ++iIndex) {
			register const char *pcScan, *pcAttr;
			register int iLen, i;

			pcRead = cmd->opts[iIndex];
			if ((char *)0 == (pcScan = strchr(pcRead, '='))) {
				iLen = strlen(pcRead);
			} else {
				iLen = pcScan-pcRead;
			}

			/* Just ignore $ENVs, $# should be a number (above),
			 * compile $1,$2, etc and $*, all $N <= lHelpParam,
			 * fall into !g, !u, !f checks (they match isalpha).
			 */
			if (ENV_OPEN(pcRead)) {
				/* N.B.  "$$l=$f" is an env */
				if (0 == strncmp(pcRead, acOptPATH, sizeof(acOptPATH)-1) && pcScan+1 == pcRead+sizeof(acOptPATH)) {
					CheckPath(cmd, pcScan+1);
				}
				continue;
			} else if ('%' == pcRead[0]) {
				/* see next */
			} else if ('!' == pcRead[0] && isalpha(pcRead[1])) {
				/* see the next stanza */
			} else if ('#' == pcRead[1]) {
				/* did above */
				continue;
			} else if (isdigit(pcRead[1])) {
				lDup = strtol(pcRead+1, &pcEnd, 0);
				/* lTotalParam or lHelpParam XXX? */
				if (-1 != Need.lHelpParam && Need.lHelpParam < lDup) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: parameter too large to match forced limit of %ld\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, Need.lHelpParam);
					iRet = EX_UNAVAILABLE;
				}
				if (pcScan != pcEnd && '\000' != *pcEnd) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: junk on the end of parameter number\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					iRet = EX_CONFIG;
				}
				if (0 == lDup) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: misleading '0': positional parameters are not numbered in octal\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
				} else if ('0' == pcRead[1]) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: misleading '0': positional parameters are not numbered in octal\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					iRet = EX_CANTCREAT;
				}
				if ((const char *)0 == pcScan) {
					continue;
				}
				pcCarp = (char *)0;
				if ((char *)0 != (pcField = CheckCompile(pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				}
				continue;
			} else if ('*' == pcRead[1] && 2 == iLen) {
				if ((const char *)0 == pcScan) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: is meaningless without an RE\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					continue;
				}
				++pcScan;
				if ((char *)0 != (pcField = CheckCompile(pcScan, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				}
				if (IsDotStar(pcScan)) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: rejects all parameters\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					iRet = EX_DATAERR;
				}
				continue;
			} /* $<trash> in the environment is ok by me */

			if (!('!' == pcRead[0] || '%' == pcRead[0])) {
				/* nada */
			} else if ('{' == pcRead[1]) {	/*} %{ENV} !{ENV} */
				register char *pcMem;

				if ((char *)0 == pcScan && '%' == pcRead[0]) {
					fprintf(stderr, "%s: %s: %s:%d: %s: has no default value\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead);
					iRet = EX_DATAERR;
				} else if (/*{*/'}' != pcRead[iLen-1]) {/*{*/
					fprintf(stderr, "%s: %s: %s:%d: %.*s: missing close curly (`}')\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen-2, pcRead+2);
					iRet = EX_DATAERR;
				} else if ((char *)0 != (pcField = CheckCompile((char *)0 == pcScan ? acDefNotEnv : pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				} else if ((char *)0 != (pcMem = calloc((iLen|7)+1, sizeof(char)))) {
					snprintf(pcMem, iLen+1, "$%.*s", iLen-3, pcRead+2);
					if (0 != CheckTrust(cmd, pcMem)) {
						free((void *)pcMem);
						continue;
					}
					snprintf(pcMem, iLen+1, "{%.*s}", iLen-3, pcRead+2);
					fprintf(stderr, "%s: %s: %s:%d: $%s: is limited but not allowed in the escalated environment\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcMem);
					free((void *)pcMem);
				}
				continue;
			} else if ('m' == pcRead[1]) {
				SP.pcspecM = "read";
				if ((char *)0 != (pcField = CheckCompile(pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.2s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				}
				if (! Need.fMacs) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: specification never used\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					/* not worth a non-zero exit code */
				}
				continue;
			} else if ('g' == pcRead[1]) {
				SP.pcspecG = "read";
				switch (iLen) {
				case 2: /* %g, !g */
					break;
				case 4: /* %g@u, !g@u */
					if ('@' != pcRead[2] || 'u' != pcRead[3]) {
						/*FALLTHROUGH*/
				default:
						fprintf(stderr, "%s: %s: %s:%d: %%u unknown qualifier \"%.*s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen-2, pcRead+2);
						iRet = EX_USAGE;
						break;
					}
					break;
				}
				if ((char *)0 != (pcField = CheckCompile(pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.2s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				}
				if (! Need.fGid) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: specification never used\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					/* not worth a non-zero exit code */
				}
				continue;
			} else if ('u' == pcRead[1]) {
				SP.pcspecU = "read";
				switch (iLen) {
				case 2: /* %u, !u */
					break;
				case 4: /* %u@g, !u@g */
					if ('@' != pcRead[2] || 'g' != pcRead[3]) {
						/*FALLTHROUGH*/
				default:
						fprintf(stderr, "%s: %s: %s:%d: %%u unknown qualifier \"%.*s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen-2, pcRead+2);
						iRet = EX_USAGE;
						break;
					}
					break;
				}
				if ((char *)0 != (pcField = CheckCompile(pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				}
				if (! Need.fUid) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: specification never used\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					/* not worth a non-zero exit code */
				}
				continue;
			} else if ('f' == pcRead[1] || 'd' == pcRead[1] || '_' == pcRead[1]) {
				register int fPos, fForD;
				auto char acCheckF[64]; /* %c%c.attr */
				static cmd_t *aacmdDupLimit[2][3] = {
					{(cmd_t *)0, (cmd_t *)0, (cmd_t *)0},
					{(cmd_t *)0, (cmd_t *)0, (cmd_t *)0}
				};
				if ('_' != pcRead[1]) {
					SP.pcspecF = "limited";
				}
				if (!('.' == pcRead[2] || '=' == pcRead[2])) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: unknown specification\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					continue;
				}
				if ('_' != pcRead[1] && ! Need.fSecFile) {
					fprintf(stderr, "%s: %s: %s:%d: %%f: specification never used\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
					/* not worth a non-zero exit code */
					Need.fSecFile = 1;
				}
				fPos = '%' == pcRead[0];
				fForD = 'f' == pcRead[1] ? 1 : '_' == pcRead[1] ? 2 : 0;
				if ('=' == pcRead[2]) {
					snprintf(acCheckF, sizeof(acCheckF), "%c%c.%s", pcRead[0], pcRead[1], asFKnown);
					if ((char *)0 != FindOpt(cmd, acCheckF)) {
						if (cmd != aacmdDupLimit[fPos][fForD])
							fprintf(stderr, "%s: %s: %s:%d: both %.2s and %s limit the same path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acCheckF, acCheckF);
						aacmdDupLimit[fPos][fForD] = cmd;
					}
					continue;
				}
				if (3 == iLen) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: dot is missing an attr, or remove the dot?\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
					iRet = EX_USAGE;
					continue;
				}
				snprintf(acCheckF, sizeof(acCheckF), "%c%c", pcRead[0], pcRead[1]);
				for (pcAttr = asFKnown; '\000' != *pcAttr; pcAttr += strlen(pcAttr)+1) {
					if (0 == strncmp(pcAttr, pcRead+3, iLen-3))
						break;
				}
				if ('\000' == *pcAttr) {
					fprintf(stderr, "%s: %s: %s:%d: %%f: unknown attribute \"%.*s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen-3, pcRead+3);
					iRet = EX_USAGE;
				} else if ((char *)0 != (pcField = CheckCompile(pcScan+1, &pcCarp))) {
					fprintf(stderr, "%s: %s: %s:%d: %.*s: regcomp: %s: %s\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead, pcField, pcCarp);
					iRet = EX_DATAERR;
				} else if (0 == strcmp(asFKnown, pcAttr) && (char *)0 != FindOpt(cmd, acCheckF)) {
					if (cmd !=  aacmdDupLimit[fPos][fForD])
						fprintf(stderr, "%s: %s: %s:%d: both %.2s and %.3s%s limit the same path\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acCheckF, pcRead, pcAttr);
					aacmdDupLimit[fPos][fForD] = cmd;
				} else if (0 == strcmp("perms", pcAttr) && 0 == CheckPermRE(pcScan+1, &pcCarp)) {
					fprintf(stderr, "%s: %s: %s:%d: %s.%s limit RE \"%s\" never matches\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acCheckF, pcAttr, pcScan+1);
					iRet = EX_DATAERR;
				} else if (0 == strcmp("mode", pcAttr) && 0 == CheckModeRE(pcScan+1, &pcCarp)) {
					fprintf(stderr, "%s: %s: %s:%d: %s.%s limit RE \"%s\" never matches\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, acCheckF, pcAttr, pcScan+1);
					iRet = EX_DATAERR;
				}
				continue;
			} else {
				fprintf(stderr, "%s: %s: %s:%d: %.2s: unknown parameter limiter\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, pcRead);
				continue;
			}
			for (i = 0; (char *)0 != (pcScan = apcConfHelp[i]); ++i) {
				if (0 == strncmp(pcRead, pcScan, iLen))
					break;
			}
			if ((char *)0 == pcScan) {
				fprintf(stderr, "%s: %s: %s:%d: unknown keyword \"%.*s\"\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline, iLen, pcRead);
				iRet = EX_USAGE;
			}
		}
		if (acDefault == cmd->pcname) {
			continue;
		}
		if ((char *)0 == SP.pcspecU && Need.fUid) {
			fprintf(stderr, "%s: %s: %s:%d: %%u: is unlimited\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOPERM;
		}
		if ((char *)0 == SP.pcspecG && Need.fGid) {
			fprintf(stderr, "%s: %s: %s:%d: %%g: is unlimited\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOPERM;
		}
		if ((char *)0 == SP.pcspecF && Need.fSecFile) {
			fprintf(stderr, "%s: %s: %s:%d: %%f: is unlimited\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_NOPERM;
		}
		/* When we didn't see %m or !m and you used the raw -m as
		 * a the whole process mac label we carp about it.
		 */
		if ((char *)0 == SP.pcspecM && Need.fMacs) {
			fprintf(stderr, "%s: %s: %s:%d: %%m: is unlimited\n", Progname, cmd->pcname, cmd->pcfile, cmd->iline);
			iRet = EX_TEMPFAIL;
		}
	}

	if ((cmd_t *)0 == cmd) {
		fprintf(stderr, "%s: no rules configured%s\n", Progname, acSure);
		iRet = EX_TEMPFAIL;
	}
	}

	exit(iRet);
}

/* If a NEEDs structure wants more than 1 argument return "s"		(ksb)
 * else the empty string.
 */
static const char *
NeedsPlural(const NEEDS *pNE)
{
	static const char acS[] = "s";
	register const char *pcPlural = acS+1;

	if (-1 == pNE->lTotalParam) {
		pcPlural = acS;
	} else if (0 == pNE->lCmdParam) {
		if (1 != pNE->lTotalParam)
			pcPlural = acS;
	} else if (pNE->lCmdParam < pNE->lTotalParam) {
		pcPlural = acS;
	}
	return pcPlural;
}

/* Given the complete RE-list for a parameter make a usage string.	(ksb)
 * If we build the string you can free it, if we return pcDef you must
 * know what to do with it.  Since the return from all the fields must
 * be at most the size of all the fields minus the anchors we know it must
 * fit in a buffer as long as the input.  No other overrun checks needed.
 */
static const char *
FieldToUsage(const char *pcREList, const char *pcDef, void **ppvAlloc )
{
	register char *pcRet;
	register const char *pcCheck;
	register size_t wMin;
	auto char *pcField;
	auto void *pvLocal;

	if ((void **)0 != ppvAlloc) {
		*ppvAlloc = (void *)0;
	}
	/* We know that the longest fixed string IsAnchored returns is number:
	 * we should never overflow be number of commas times 8 + length(RE).
	 */
	wMin = 1;
	if ((char *)0 != (pcCheck = pcREList)) while ((char *)0 != (pcCheck = strchr(pcCheck, ','))) {
		++pcCheck, ++wMin;
	}
	if ((const char *)0 == pcREList || (void *)0 == (pvLocal = malloc((strlen(pcREList)|15)+wMin*8+1))) {
		return pcDef;
	}
	pcRet = pvLocal;
	*pcRet = '\000';
	while ((char *)0 != (pcREList = GetField(pcREList, &pcField))) {
		pcCheck = IsAnchored(pcField, (const char *)0);
		if ((char *)0 == pcCheck) {
			free(pvLocal);
			return pcDef;
		}
		if ('\000' != *pcRet) {
			(void)strcat(pcRet, "|");
		}
		(void)strcat(pcRet, pcCheck);
	}
	EndField();
	if ((void **)0 != ppvAlloc) {
		*ppvAlloc = pvLocal;
	}
	return pcRet;
}

/* We split the difference by default we'll show you the rules, but you
 * can remove it with a -DOP_SHOW_RULES=0
 */
#if !defined(OP_SHOW_RULES)
#define OP_SHOW_RULES	1
#endif

/* Output $0 [-g group] [-u user] [-f file] mnemonic...			(ksb)
 * We drop for this, as we shouldn't have to do anything super-ish.
 * We can view the used (-l), or the run-time (-r) or that plus why (-w).
 */
static void
RulesAllowed(int fWhichView, int argc, char **argv)
{
#if OP_SHOW_RULES
	register cmd_t *cmd, *cmdScan;
	register long lScan, lEnd;
	register const char *pcRead, *pcCodeCheck, *pcLook, *pcDef;
	register size_t i;
	auto const char *pcUserMay;
	auto int uOrig, fTold, iExit;
	auto struct passwd *pw;
	auto struct group *gr;
	auto void *pvDispose;
	auto char *pcField, *pcCarp;
	auto char acCvt[OP_MAX_LDWIDTH];	/* "$%ld" or "#%ld" */
	auto char acMyName[512];
	auto NEEDS Need;
	static const char acArg[] = "arg", acScript[] = "script",
		acCommand[] = "command", acArgs[] = "argument";

	pcCarp = (char *)0;
	iExit = EX_OK;
	uOrig = getuid();
	switch (argc) {
	case 0:
		if ((struct passwd *)0 == (pw = getpwuid(uOrig)))
			fatal("getpwuid: %ld: %s", (long)uOrig, strerror(errno));
		break;
	case 1:
		if (0 != uOrig)
			fatal(acBadAllow);
		if ((struct passwd *)0 == (pw = getpwnam(argv[0])))
			fatal("getpwnam: %s: %s", argv[0], strerror(errno));
		if (0 != initgroups(argv[0], pw->pw_gid))
			fatal("%s: %s", acOptInitgrps, strerror(errno));
#if HAVE_setresgid
		if (0 != setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid))
			fatal("setresgid: %ld,%ld,%ld: %s", (long)pw->pw_gid, (long)pw->pw_gid, (long)pw->pw_gid, strerror(errno));
#else
		if (0 != setgid(pw->pw_gid))
			fatal("setgid: %ld: %s", (long)pw->pw_gid, strerror(errno));
#endif
		uOrig = pw->pw_uid;
		break;
	default:
		fatal("usage: -%c login", fWhichView);
	}
#if HAVE_setresuid
	if (setresuid(uOrig, uOrig, uOrig) < 0) {
		fatal("setreuid: %ld,%ld: %s", (long)uOrig, (long)uOrig, strerror(errno));
	}
#else
	if (setreuid(uOrig, uOrig) < 0) {
		fatal("setreuid: %ld,%ld: %s", (long)uOrig, (long)uOrig, strerror(errno));
	}
#endif
	if (-1 == gethostname(acMyName, sizeof(acMyName))) {
		fatal("gethostname: %s", strerror(errno));
	}

	fTold = 0;
	for (cmdScan = NewCmd((const char *)0); (cmd_t *)0 != cmdScan; cmdScan = cmdScan->pODnext) {
		if (acDefault == cmdScan->pcname)
			continue;
		if ((cmd_t *)0 == (cmd = Build(cmdScan)))
			continue;

		/* Can the Customer run it?  Allow root to see them all,
		 * but flag ones she can't run (with a hash comment).
		 */
		pcUserMay = (const char *)0;
		if ((const char *)0 == pcUserMay && (char *)0 != (pcRead = FindOpt(cmd, acOptGroups))) {
			if (IsDotStar(pcRead+('#' == pcRead[0]))) {
				pcUserMay = '#' == pcRead[0] ? "any gid" : "any group";
			} else if (AnyMatch(pcRead, ":", pw->pw_gid, &pcCarp)) {
				pcUserMay = "login group by gid";
			} else if ((struct group *)0 == (gr = getgrgid(pw->pw_gid))) {
				/* Can't reverse login group, tell Admin
				 */
				syslog(LOG_ERR, "login %s gid #%ld: doesn't reverse", pw->pw_name, (long)pw->pw_gid);
			} else if (AnyMatch(pcRead, gr->gr_name, gr->gr_gid, &pcCarp)) {
				pcUserMay = "login group name";
			}
			setgrent();
			while ((const char *)0 == pcUserMay && (struct group *)0 != (gr = getgrent())) {
				for (i = 0; (char *)0 != gr->gr_mem[i]; ++i) {
					if (0 == strcmp(gr->gr_mem[i], pw->pw_name))
						break;
				}
				if (((char *)0 != gr->gr_mem[i]) && AnyMatch(pcRead, gr->gr_name, gr->gr_gid, &pcCarp)) {
					pcUserMay = "group membership";
					break;
				}
			}
			/* We ignore pcCarp becasue we don't want to tell the
			 * Customer that the config has a bad RE. (ksb)
			 */
		}
		if ((const char *)0 == pcUserMay && ((char *)0 != (pcRead = FindOpt(cmd, acOptUsers)))) {
			if (IsDotStar(pcRead+('#' == pcRead[0]))) {
				pcUserMay = '#' == pcRead[0] ? "any uid" : "any login";
			} else if (AnyMatch(pcRead, ":", pw->pw_uid, &pcCarp)) {
				pcUserMay = acOptUid;
			} else if (AnyMatch(pcRead, pw->pw_name, pw->pw_uid, &pcCarp)) {
				pcUserMay = "login name";
			}
		}
		if ((const char *)0 == pcUserMay && ((char *)0 != (pcRead = FindOpt(cmd, acOptNetgroups)))) {
			while ((char *)0 != (pcRead = GetField(pcRead, &pcField))) {
				if (innetgr(pcField, acMyName, pw->pw_name, (char *)0)) {
					pcUserMay = "netgroup membership";
					break;
				}
			}
			EndField();
		}

		if ((const char *)0 == pcUserMay && 0 != uOrig) {
			continue;
		}

		/* if craving fails it is a format error in the params
		 */
		if (0 != Craving(cmd, & Need)) {
			if (!fTold) {
				fTold = 1;
				syslog(LOG_ERR, "%s: %s:%d: user notified that you need to run -S more often", cmd->pcname, cmd->pcfile, cmd->iline);
				fprintf(stderr, "%s: Please ask the administrator to run -S and fix our configuration.\n", Progname);
			}
			iExit = EX_CONFIG;
			printf("#");
		} else if (0 == uOrig && (const char *)0 == pcUserMay) {
			printf("#");
		}

		/* under -r or -w list the command we are talking about
		 */
		if ('r' == fWhichView || 'w' == fWhichView) {
			auto const char *pcLazen;

			if ((const char *)0 == (pcCodeCheck = FindOpt(cmd, acOptJacket))) {
				pcCodeCheck = FindOpt(cmd, acOptHelmet);
			}
			printf("%s", cmd->pcname);
			pvDispose = (void *)0;
			for (lScan = 0; lScan < cmd->nargs; ++lScan) {
				pcRead = pcLook = cmd->args[lScan];
				if ('$' != pcLook[0]) {
					pcRead = pcLook;
				} else if (isdigit(pcLook[1]) && (strtol(pcLook+1, & pcField, 0), '\000' == *pcField) && (char *)0 != (pcRead = FindOpt(cmd, pcLook))) {
					pcRead = FieldToUsage(pcRead, pcLook, &pvDispose);
				} else if ('\000' == pcLook[1] || '\000' != pcLook[2]) {
					/* as is */
				} else switch (pcLook[1]) {
				case '0':
					printf(" %s", cmd->pcname);
					continue;
				case '@':
				case '*':
					pcRead = FieldToUsage(FindOpt(cmd, "$*"), acArg, &pvDispose);
					printf(" %s%s", pcRead, NeedsPlural(&Need));
					goto release;
				case 's':
					printf(" {script}");
					continue;
				case 'S':
					printf(" %s", acShell);
					continue;
				case '$':
					printf(" $");
					continue;
				case '|':	/* a forced empty word */
					printf(" ''");
					break;
				default:
					break;
				}
				if ((char *)0 == pcRead) {
					printf(" %s", pcLook);
					continue;
				}
				printf(" %s", pcRead);
 release:
				if ((void *)0 != pvDispose) {
					free(pvDispose);
					pvDispose = (void *)0;
				}
			}
			if (1 == cmd->nargs && 0 == strcmp(acMagicShell, cmd->args[0])) {
				printf(" [-c command]");
			}
			if ((char *)0 != (pcLazen = FindOpt(cmd, acOptDaemon))) {
				pcLazen = acDevNull;
			}
			if ((char *)0 != (pcRead = FindOpt(cmd, acOptStdin)) || (char *)0 != (pcRead = pcLazen)) {
				printf(" ");
				if ('<' != *pcRead && '>' != *pcRead)
					printf("<");
				printf("%s", pcRead);
			}
			if ((char *)0 != (pcRead = FindOpt(cmd, acOptStdout)) || (char *)0 != (pcRead = pcLazen)) {
				printf(" ");
				if ('<' != *pcRead && '>' != *pcRead)
					printf(">");
				printf("%s", pcRead);
			}
			if ((char *)0 != (pcRead = FindOpt(cmd, acOptStderr)) || (char *)0 != (pcRead = pcLazen)) {
				printf(" 2");
				if ('<' != *pcRead && '>' != *pcRead)
					printf(">");
				printf("%s", pcRead);
			}
			if ((char *)0 != pcLazen) {
				printf(" &");
			}
			if ('w' == fWhichView) {
				register const char *pcUserAuth;

				if ((const char *)0 == pcUserMay) {
					printf(" [may not");
				} else if ((const char *)0 != pcCodeCheck) {
					printf(" [%s plus %s", pcUserMay, pcCodeCheck);
				} else {
					printf(" [by %s", pcUserMay);
				}
				pcRead = FindOpt(cmd, acOptPam);
				if ((char *)0 != pcRead && '\000' == *pcRead) {
					pcRead = (const char *)0;
				}
				if ((char *)0 == FindOpt(cmd, acOptPass)) {
					pcUserAuth = ((char *)0 == pcRead) ? (const char *)0 : acOptPam;
				} else {
					pcUserAuth = ((char *)0 == pcRead) ? "password" : "either pam or password";
				}
				if ((const char *)0 != pcUserAuth && (const char *)0 != pcUserMay) {
					printf(" with %s authentication", pcUserAuth);
				}
				printf("]");
			}
			printf("\n");
			continue;
		}

		/* under -l list the command-line form
		 */
		printf("%s", Progname);
		if (Need.fUid)
			printf(" -u uid");
		if (Need.fGid)
			printf(" -g gid");
		if (Need.fSecFile)	/* doing more here might radiate */
			printf(" -f %s", Forced_F(cmd, 'd') ? "dir" : "file");
		if (Need.fMacs)
			printf(" -m mac");
		printf(" %s", cmd->pcname);
		if (1 == cmd->nargs && 0 == strcmp(acMagicShell, cmd->args[0]))
			printf(" [command]");
		pcDef = FindOpt(cmd, "$*");
		if (-1 == (lEnd = Need.lHelpParam))
			lEnd = Need.lCmdParam;
		for (lScan = 1; lScan <= lEnd; ++lScan) {
			register const char *pcBetter;
			static const char acFiction[] = "argv[1]";

			snprintf(acCvt, sizeof(acCvt), "$%ld", lScan);
			/* Look for a better tag based on using a shell as
			 * the command, which is really common in practice.
			 * You can fool us, e.g. /etc/nologin as a shell.
			 */
			pcBetter = acCvt;
			if (2 > cmd->nargs || !GenMatch(acCvt, cmd->args[0], "^[$](S|[{]SHELL[}]|k|K)$,^/bin/.*sh$"))
				/* not a shell -c or shell -x */;
			else if (0 == strcmp(cmd->args[1], acCvt))
				pcBetter = acScript;
			else if (0 != strcmp(cmd->args[2], acCvt))
				/* not too clever here */;
			else if (GenMatch(acFiction, cmd->args[1], "^-[^cs]+$"))
				pcBetter = acScript;
			else if (GenMatch(acFiction, cmd->args[1], "^-.*s"))
				pcBetter = acArgs; /* sh says string, bah */
			else if (GenMatch(acFiction, cmd->args[1], "^-.*c"))
				pcBetter = acCommand; /* sh says string, bah */
			else if ('$' != cmd->args[1][0] || !isdigit(cmd->args[1][1]) || (char *)0 == (pcRead = FindOpt(cmd, cmd->args[1])) || (char *)0 == (pcRead = FieldToUsage(pcRead, (char *)0, &pvDispose)))
				/* out of luck on RE match too */;
			else {
				if (GenMatch(acFiction, pcRead, "^-[^cs]+$"))
					pcBetter = acScript;
				else if (GenMatch(acFiction, pcRead, "^-.*s"))
					pcBetter = acArgs;
				else if (GenMatch(acFiction, pcRead, "^-.*c"))
					pcBetter = acCommand;
				free((void *)pvDispose);
				pvDispose = (void *)0;
			}

			if ((char *)0 == (pcRead = FindOpt(cmd, acCvt)))
				pcRead = pcDef;
			if ((char *)0 == pcRead) {
				printf(" %s", pcBetter);
				continue;
			}

			/* If the pattern looks like a usage "^(a|b|c)$"
			 * or "^word$" then we'll output it filtered, else
			 * just output $n (or somethig better).
			 */
			pcRead = FieldToUsage(pcRead, pcBetter, &pvDispose);
			printf(" %s", pcRead);
			if ((void *)0 != pvDispose) {
				free(pvDispose);
				pvDispose = (void *)0;
			}
		}

		/* If we have a $* figure out if it is plural:
		 * viz. when no total param set, also when no spec. max and
		 * total param > 1, else when max is less than the forced total
		 */
		if (Need.fDStar && (-1 == Need.lTotalParam || Need.lHelpParam < Need.lTotalParam)) {
			pcRead = FieldToUsage(pcDef, acArg, &pvDispose);
			printf(" [%s%s]", pcRead, NeedsPlural(&Need));
			if ((void *)0 != pvDispose) {
				free(pvDispose);
				pvDispose = (void *)0;
			}
		}
		printf("\n");
	}
	exit(iExit);
#else
	fatal("Sorry, that option is forbidden by site policy");
	exit(EX_UNAVAILABLE);
#endif
}

#if USE_PAM
/* call-back to us from pam_* functions for pam option			(ksb)
 * Another Sun compiler botch: because I mark this static to limit the
 * scope the compiler tells me the type is different in the assignment to
 * any (struct pam_conv).conv, storage class is not a type, bozos.
 */
static int
OpConv(int iMsg, const struct pam_message **ppmsg, struct pam_response **ppresp, void *pvAppData)
{
	register int i;
	register char *pcFetch;
	auto size_t wLen;

	if ((struct pam_response *)0 == (*ppresp = calloc(iMsg, sizeof(struct pam_response)))) {
		fatal(acBadAlloc);
	}
	(void)pvAppData;	/* the cmd structure we don't need (yet) */
	for (i = 0; i < iMsg; ++i) {
		ppresp[i]->resp = (char *)0;
		ppresp[i]->resp_retcode = 0;
		switch (ppmsg[i]->msg_style) {
		case PAM_PROMPT_ECHO_ON:
			if ((char *)0 == (pcFetch = fgetln(stdin, & wLen)))
				fatal("fgetln: %s", strerror(errno));
			if (wLen > 0 && '\n' == pcFetch[wLen-1])
				--wLen, pcFetch[wLen] = '\000';
			break;
		case PAM_PROMPT_ECHO_OFF:
			if ((char *)0 == (pcFetch = getpass("Password:")))
				fatal("getpass: %s", strerror(errno));
			wLen = strlen(pcFetch);
			break;
		case PAM_TEXT_INFO:
			if ((char *)0 != ppmsg[i]->msg)
				puts(ppmsg[i]->msg);
			continue;
		case PAM_ERROR_MSG:
			if ((char *)0 != ppmsg[i]->msg)
				fprintf(stderr, "%s\n", ppmsg[i]->msg);
			continue;
		default:
			fatal("pam sent a message type (%d)", ppmsg[i]->msg_style);
			return PAM_CONV_ERR;
		}
		if ((char *)0 == (ppresp[i]->resp = calloc((wLen|15)+1, sizeof(char))))
			fatal(acBadAlloc);
		(void)strncpy(ppresp[i]->resp, pcFetch, wLen+1);
	}
	return PAM_SUCCESS;
}
#endif

/* Provide a dynamic copy of the reason this access is being granted,	(ksb)
 * viz.	cred:value\000
 */
static char *
KeepMay(const char *pcCred, const char *pcValue)
{
	register size_t iLen;
	register char *pcRet;

	iLen = ((strlen(pcCred)+1+strlen(pcValue))|15)+1;
	if ((char *)0 == (pcRet = malloc(iLen)))
		fatal(acBadAlloc);
	snprintf(pcRet, iLen, "%s:%s", pcCred, pcValue);
	return pcRet;
}

/* Find the mnemonic based on the command-line arguments given.		(ksb)
 * When both pam and password are set, must pass one of them (not both).
 */
static const char *
Verify(const cmd_t *cmd, const int argc, const char **argv, const char **ppcFail)
{
	register int iParam, i;
	register const char *pcPass, *pcScan, *pcAskPass, *pcPam;
	register struct passwd *pw;
	register struct group *gr;
#if USE_GETSPNAM
	register struct spwd *spw;
#endif
	auto char *pcField, *pcRest, *pcCarp;
	auto const char *pcUserMay;
	auto char acCvt[OP_MAX_LDWIDTH];	/* "#%ld" */
	static char acDummy[] = "=.";

	pcUserMay = (const char *)0;
	*ppcFail = acBadAllow;
#if OP_MAC_SUPPORT
	if ((char *)0 != pcMac && (char *)0 != (pcScan = FindOpt(cmd, "%m"))) {
		if (!GenMatch("-m", pcMac, pcScan))
			return acMacDeny;
	}
	if ((char *)0 != pcMac && (char *)0 != (pcScan = FindOpt(cmd, "!m"))) {
		if (GenMatch("-m", pcMac, pcScan))
			return acMacDeny;
	}
#endif
	pcAskPass = FindOpt(cmd, acOptPass);
	if ((const char *)0 == (pcPam = FindOpt(cmd, acOptPam))) {
		/* nada */
	} else if ('\000' == *pcPam) {
		/* allow a reset to no pam, when DEFAULT is pam auth */
		pcPam = (const char *)0;
	} else {
#if USE_PAM
		static char acPromptTempl[] = "%s credentials:";
		auto struct pam_conv local_conv;
		auto int iStatus, iMem;
		auto pam_handle_t *pPCLocal;
		register char *pcPrompt;

		local_conv.appdata_ptr = (void *)cmd;
		local_conv.conv = OpConv;
		if ('.' == pcPam[0] && '\000' == pcPam[1]) {
			pcPam = acDefPam;
		}
		if (PAM_SUCCESS != (iStatus = pam_start(pcPam, pcPerp, &local_conv, &pPCLocal))) {
			fatal("pam_start: %s: %s", pcPam, pam_strerror(pPCLocal, errno));
		}
		(void)pam_set_item(pPCLocal, PAM_TTY, acDevTty);
		(void)pam_set_item(pPCLocal, PAM_RHOST, acMyHost);
		(void)pam_set_item(pPCLocal, PAM_RUSER, pcPerp);
		iMem = (sizeof(acPromptTempl)+strlen(pcPam))|15;
		if ((char *)0 == (pcPrompt = calloc(iMem+1, sizeof(char))))
			fatal(acBadAlloc);
		snprintf(pcPrompt, iMem, acPromptTempl, pcPam);
		(void)pam_set_item(pPCLocal, PAM_USER_PROMPT, pcPrompt);

		switch (iStatus = pam_authenticate(pPCLocal, 0)) {
		case PAM_SUCCESS:
			/* pam likes us, don't ask for a password either
			 */
			pcPam = pcAskPass = (const char *)0;
			break;
		case PAM_AUTH_ERR:
		case PAM_CONV_ERR:
		case PAM_CRED_INSUFFICIENT:
		case PAM_MAXTRIES:
		case PAM_PERM_DENIED:
			/* check password below */
			break;
		default:
		/* PAM_{AUTHINFO_UNAVAIL,ABORT,BUF_ERR,SERVICE_ERR}
		 * PAM_{SYMBOL_ERR,SYSTEM_ERR,USER_UNKNOWN}:
		 */
			fatal("%s: %s: %s", acOptPam, pcPam, pam_strerror(pPCLocal, iStatus));
			/*NOTREACHED*/
		}
		if (PAM_SUCCESS != (iStatus = pam_end(pPCLocal, iStatus))) {
			fatal("pam_end: %s: %s", pcPam, pam_strerror(pPCLocal, iStatus));
		}
#else
		fprintf(stderr, "%s: %s: %s: no support on this host\n", Progname, acOptPam, pcPam);
#endif
	}

	/* Must know a password on one of these logins, default to ours
	 */
	if ((const char *)0 != pcAskPass) {
		register const char *pcIttr;
		auto struct stat stPass;
		extern char *crypt();

		if ((char *)0 == (pcPass = getpass("Password:"))) {
			return "No password available";
		}
		if ('\000' == *pcAskPass) {
			pcAskPass = acMyself;
		}
		pcScan = pcAskPass;
		while ((char *)0 != (pcScan = GetField(pcScan, &pcField))) {
			static const char acNoDashF[] = "No -f specified";
			register char *pcSlash;
			register const char *pcStatMe;

			if (IsMyself(pcField) || '\000' == *pcField) {
				pcIttr = pcPerp;
			} else if (0 == strcmp(pcField, acSpecD)) {
				if ((char *)0 == pcSecFile) {
					*ppcFail = acNoDashF;
					continue;
				}
				/* -f /dev/ -> strip off dev/ for %d
				 */
				if ((char *)0 != (pcSlash = strrchr(pcSecFile, '/')) && '\000' == pcSlash[1]) {
					while (pcSlash > pcSecFile) {
						if ('/' != pcSlash[-1])
							break;
						--pcSlash;
					}
					while (pcSlash > pcSecFile) {
						if ('/' == pcSlash[-1])
							break;
						--pcSlash;
					}
				}
				if ((char *)0 == pcSlash) {
					pcStatMe = acCurDir;
				} else if (pcSecFile == pcSlash) {
					pcStatMe = acSlash;
					pcSlash = (char *)0;
				} else {
					*pcSlash = '\000';
					pcStatMe = pcSecFile;
				}
				i = stat(pcStatMe, &stPass);
				if ((char *)0 != pcSlash) {
					*pcSlash = '/';
				}
				if (-1 == i) {
					*ppcFail = "Cannot stat -f's directory for password lookup";
					continue;
				}
				if ((struct passwd *)0 == (pw = getpwuid(stPass.st_uid))) {
					*ppcFail = "-f owner unmapped for password check";
					continue;
				}
				/* small memory leak here, we don't care */
				if ((char *)0 == (pcIttr = strdup(pw->pw_name))) {
					pcIttr = pw->pw_name;
				}
			} else if (0 != strcmp(pcField, acSpecF)) {
				if ((char *)0 == pcSecFile) {
					*ppcFail = acNoDashF;
					continue;
				}
				if (-1 == stat(pcSecFile, &stPass)) {
					*ppcFail = "Cannot stat -f target for password lookup";
					continue;
				}
				if ((struct passwd *)0 == (pw = getpwuid(stPass.st_uid))) {
					*ppcFail = "-f owner unmapped for password check";
					continue;
				}
				/* small memory leak here */
				if ((char *)0 == (pcIttr = strdup(pw->pw_name)))
					pcIttr = pw->pw_name;
			} else if (0 != strcmp(pcField, acSpecU)) {
				pcIttr = pcScan;
			} else if ((char *)0 == (pcIttr = pcUid)) {
				*ppcFail = "No -u specified";
				continue;
			}
#if USE_GETSPNAM
			if ((struct spwd *)0 == (spw = getspnam(pcIttr)))
				continue;
#else
			if ((struct passwd *)0 == (pw = getpwnam(pcIttr)))
				continue;
#endif
#if USE_GETSPNAM
			if (0 != strcmp(crypt(pcPass, spw->sp_pwdp), spw->sp_pwdp))
				continue;
#else
			if (0 != strcmp(crypt(pcPass, pw->pw_passwd), pw->pw_passwd))
				continue;
#endif
			break;
		}
		EndField();
		if ((char *)0 == pcScan) {
			return (const char *)0;
		}
		pcPam = (const char *)0;
	}
	if ((char *)0 != pcPam) {
		return (const char *)0;
	}

	/* I don't think we ever should allow running as an unmapped uid.
	 * But if your primary login group is unmapped we'll call it ":",
	 * which can't really be a group, but you could match in a rule.
	 * Why you'd want a rule to allow an unmapped primary group, I don't
	 * really know.  Be liberal with what we allow in the configuration.
	 */
	if ((struct passwd *)0 == (pw = getpwuid(getuid()))) {
		return (const char *)0;
	}

	pcCarp = (char *)0;
	if ((const char *)0 == pcUserMay && (char *)0 != (pcScan = FindOpt(cmd, acOptGroups))) {
		if ((struct group *)0 == (gr = getgrgid(pw->pw_gid))) {
			if (AnyMatch(pcScan, ":", pw->pw_gid, &pcCarp)) {

				snprintf(acCvt, sizeof(acCvt), "#%ld", (long)pw->pw_gid);
				pcUserMay = KeepMay(acOptGroups, acCvt);
			}
		} else if (AnyMatch(pcScan, gr->gr_name, gr->gr_gid, &pcCarp)) {
			pcUserMay = KeepMay(acOptGroups, gr->gr_name);
		}
		setgrent();
		while ((const char *)0 == pcUserMay && (struct group *)0 != (gr = getgrent())) {
			for (i = 0; (char *)0 != gr->gr_mem[i]; ++i) {
				if (0 == strcmp(gr->gr_mem[i], pw->pw_name))
					break;
			}
			if (((char *)0 != gr->gr_mem[i]) && AnyMatch(pcScan, gr->gr_name, gr->gr_gid, &pcCarp)) {
				pcUserMay = KeepMay(acOptGroups, gr->gr_name);
				break;
			}
		}
	}
	if ((const char *)0 == pcUserMay && ((char *)0 != (pcScan = FindOpt(cmd, acOptUsers)))) {
		if (AnyMatch(pcScan, pw->pw_name, pw->pw_uid, &pcCarp))
			pcUserMay = KeepMay(acOptUsers, pw->pw_name);
	}
	if ((char *)0 != pcCarp) {
		syslog(LOG_ERR, "%s: %s:%d: Verify: regcomp: %s (run -S)", cmd->pcname, cmd->pcfile, cmd->iline, pcCarp);
	}
	if ((const char *)0 == pcUserMay && ((char *)0 != (pcScan = FindOpt(cmd, acOptNetgroups)))) {
		auto char acMyName[512];

		if (-1 == gethostname(acMyName, sizeof(acMyName))) {
			fatal("gethostname: %s", strerror(errno));
		}
		while ((char *)0 != (pcScan = GetField(pcScan, &pcField))) {
			if (innetgr(pcField, acMyName, pw->pw_name, (char *)0)) {
				pcUserMay = KeepMay(acOptNetgroups, pcField);
				break;
			}
		}
		EndField();
	}

	/* One might think that the superuser (real uid) might be able
	 * to do anything -- they don't need op to do that!
	 */
	if ((const char *)0 == pcUserMay) {
		return (const char *)0;
	}
	*ppcFail = acOK;

	/* Look for $*, $[0-9]*, and repeat the check from Find.
	 * $5 w/o an R.E. means must be non-empty (default RE of /./)
	 */
	for (i = 0; i < cmd->nopts; ++i) {
		pcScan = cmd->opts[i];
		if ('$' != pcScan[0] && '!' != pcScan[0]) {
			continue;
		}
		/* look for $*=REs
		 */
		if ('*' == pcScan[1]) {
			/* must check in Go */
			continue;
		}
		if (!isdigit(cmd->opts[i][1])) {
			continue;
		}
		/* Found $[0-9]+=REs, $0 is configuration error, dude.
		 * I thought about using it... too unclear (match our name)
		 * "$5" is the same as "$5=." (has at least 1 character)
		 * "!5" is the same as $# < 5
		 */
		if (0 == (iParam = strtoul(pcScan+1, & pcRest, 10))) {
			return acBadConfig;
		}
		if ('\000' == *pcRest) {
			/* set the deault and allow the termination code
			 * to write over the '=' above, as if to end the
			 * number in "$7=." for the tag
			 */
			pcRest = acDummy+1;
		} else if ('=' != *pcRest++) {
			return acBadConfig;
		}
		if (iParam >= argc) {
			if ('!' == pcScan[0] && acDummy+1 == pcRest)
				continue;
			return acBadFew;
		}
		pcRest[-1] = '\000';
		if (! GenMatch(pcScan, argv[iParam], pcRest)) {
			pcRest[-1] = '=';
			if ('!' == pcScan[0])
				continue;
			return acBadParam;
		}
		pcRest[-1] = '=';
		if ('!' == pcScan[0]) {
			return acBadParam;
		}
	}
	return pcUserMay;
}

/* Append the tail onto the dir to make an absolute path.		(ksb)
 * I wish every OS had realpath.  Sigh.
 * Inv.: pcDir points to at least MAXPATHLEN characters.
 */
static char *
FullPath(char *pcDir, const char *pcTail)
{
	register int iStrip, iLen, c;
	register char *pcSnip, *pcLast;
	auto char acLink[MAXPATHLEN+4];

	if ('\000' == *pcTail) {
		return pcDir;
	}
	for (iStrip = 0; '.' == pcTail[0]; /* nada */) {
		if ('.' == pcTail[1] && ('\000' == pcTail[2] || '/' == pcTail[2]))
			++iStrip, pcTail += 2;
		else if ('/' == pcTail[1] || '\000' == pcTail[1])
			pcTail += 1;
		else
			break;
		while ('/' == *pcTail)
			++pcTail;
	}
	while (iStrip-- > 0) {
		if ('\000' == pcDir[1])
			break;
		if ((char *)0 == (pcSnip = strrchr(pcDir, '/')))
			break;
		while ('/' == *pcSnip && pcDir != pcSnip)
			*pcSnip-- = '\000';
		if (pcDir == pcSnip)
			pcDir[1] = '\000';
	}
	iLen = strlen(pcDir);
	if (iLen+2 >= MAXPATHLEN) {
		/* out of space */
		return (char *)0;
	}
	pcLast = pcDir+iLen;
	if ('/' != pcDir[iLen-1] && '\000' != *pcTail) {
		pcDir[iLen++] = '/';
	}
	while ('\000' != (c = *pcTail++)) {
		if ('/' != c) {
			pcDir[iLen++] = c;
			if (iLen >= MAXPATHLEN) {
				/* out of space */
				return (char *)0;
			}
			continue;
		}
		pcDir[iLen] = '\000';
		while ('/' == *pcTail)
			++pcTail;
		if (-1 != (iLen = readlink(pcDir, acLink, sizeof(acLink)-1))) {
			*pcLast = '\000';
			acLink[iLen] = '\000';
			if ('/' == acLink[0]) {
				pcDir[0] = '/';
				pcDir[1] = '\000';
				for (pcSnip = acLink; '/' == *pcSnip; ++pcSnip)
					/* nada */;
				pcDir = FullPath(pcDir, pcSnip);
			} else {
				pcDir = FullPath(pcDir, acLink);
			}
			if ((char *)0 == pcDir) {
				return (char *)0;
			}
		}
		return FullPath(pcDir, pcTail);
	}
	pcDir[iLen] = '\000';
	return pcDir;
}

/* Convert a file mode into a type character.				(ksb)
 *	[-cbdpsl] like common ls,
 *	n for a non-existant file,
 *	D doors from Solaris,
 *	P ports from Solaris/Xenix,
 *	w white-outs,
 * I removed contiguous file support as I've not seen it in decades
 */
static char
FileType(const struct stat *pstThis)
{
	if ((struct stat *)0 == pstThis)
		return 'n';
#if defined(S_IFIFO)
	if (S_IFIFO == (pstThis->st_mode&S_IFMT))
		return 'p';
#endif
#if defined(S_IFDOOR)
	if (S_IFDOOR == (pstThis->st_mode&S_IFMT))
		return 'D';
#endif
#if defined(S_IFSOCK)
	if (S_IFSOCK == (pstThis->st_mode&S_IFMT))
		return 's';
#endif
#if defined(S_IFBLK)
	if (S_IFBLK == (pstThis->st_mode&S_IFMT))
		return 'b';
#endif
#if defined(S_IFCHR)
	if (S_IFCHR == (pstThis->st_mode&S_IFMT))
		return 'c';
#endif
#if defined(S_IFREG)
	if (S_IFREG == (pstThis->st_mode&S_IFMT))
		return '-';
#endif
#if defined(S_IFDIR)
	if (S_IFDIR == (pstThis->st_mode&S_IFMT))
		return 'd';
#endif
#if defined(S_IFLNK)
	if (S_IFLNK == (pstThis->st_mode&S_IFMT))
		return 'l';
#endif
#if defined(S_IFWHT)
	if (S_IFWHT == (pstThis->st_mode&S_IFMT))
		return 'w';
#endif
#if defined(S_IFPORT)
	if (S_IFPORT == (pstThis->st_mode&S_IFMT))
		return 'P';
#endif
	return '-';
}

/* Is the given directory a mount-point?				(ksb)
 * In this case we know that the path is an absolute path to a dir, but we
 * are pretty general.  On some OS's st_ino == 2 is a tell, on others
 * it is not :-(.  We check ../ being a different st_dev, or being "/"
 * (even in a chroot op should think of slash as being a mount-point).
 * N.B.: We can write on pcPath, but we always put it back.
 * INV: strlen(pcPath) <= MAXPATHLEN
 */
static int
IsMountPt(const char *pcPath, const struct stat *pstPath)
{
	register char *pcTail;
	register int iKeep;
	auto char acAbove[MAXPATHLEN+8];	/* +"/..\000" */
	auto struct stat stAbove;

	if ((struct stat *)0 == pstPath) {
		return 0;
	}
	while ('/' == pcPath[0] && '/' == pcPath[1]) {
		++pcPath;
	}
	if ('/' == pcPath[0] && '\000' == pcPath[1]) {
		return 1;
	}

	if ((char *)0 == (pcTail = strrchr(pcPath, '/'))) {
		snprintf(acAbove, sizeof(acAbove), "%s/..", pcPath);
		if (-1 == stat(acAbove, &stAbove))
			return 0;
	} else if (pcTail == pcPath) {
		if (-1 == stat(acSlash, &stAbove))
			return 0;
	} else {
		while (pcTail > pcPath && '/' == pcTail[-1])
			--pcTail;
		*pcTail = '\000';
		iKeep = stat(pcPath, &stAbove);
		*pcTail = '/';
		if (-1 == iKeep)
			return 0;
	}
	return stAbove.st_dev != pstPath->st_dev;
}

/* Return 1 if the directory is empty.					(ksb)
 */
static int
IsEmpty(const char *pcDir)
{
	register struct dirent *pDE;
	register DIR *pDR;
	register int iRet;

	if ((DIR *)0 == (pDR = opendir(pcDir))) {
		return 0;
	}
	iRet = 1;
	while ((struct dirent *)0 != (pDE = readdir(pDR))) {
		if ('.' != pDE->d_name[0])
			/* found one */;
		else if ('\000' == pDE->d_name[1])
			continue;
		else if ('.' != pDE->d_name[1])
			/* found a hidden file */;
		else if ('\000' == pDE->d_name[2])
			continue;
		iRet = 0;
		break;
	}
	closedir(pDR);
	return iRet;
}

/* Make the "type" string for This stat pointer				(ksb)
 * pcHere must have at least 5 char's avaliable worst-case ("ldme\000").
 */
char *
MakeFileType(const char *pcFull, char *pcHere, size_t wLen, const struct stat *pstThis)
{
	auto struct stat stIndir;

	snprintf(pcHere, wLen, "%c", FileType(pstThis));
	/* add the type of the other end on a 'l' (symbolic link)
	 */
	if ('l' == pcHere[0]) {
		if (-1 == stat(pcFull, &stIndir)) {
			pcHere[1] = FileType((struct stat *)0);
		} else {
			pcHere[1] = FileType(& stIndir);
		}
		pcHere[2] = '\000';
	}
	if ('d' == pcHere[0] && IsMountPt(pcFull, pstThis)) {
		strncat(pcHere, "m", wLen);
	} else if ('d' == pcHere[1] && IsMountPt(pcFull, &stIndir)) {
		strncat(pcHere, "m", wLen);
	}
	if (('d' == pcHere[0] || 'd' == pcHere[1]) && IsEmpty(pcFull)) {
		strncat(pcHere, "e", wLen);
	}
	return pcHere;
}

/* Copied from install's special.c, which I wrote.			(ksb)
 */
static void
EBit(char *pcBit, const int bSet, const int cSet, const int cNotSet)
/*    pcBit pointer the the alpha bit			*/
/*     bSet should it set over struck			*/
/*     cSet overstrike with this			*/
/*  cNotSet it should have been this, else upper case	*/
{
	if (0 != bSet) {
		*pcBit = (cNotSet == *pcBit) ? cSet : toupper(cSet);
	}
}

/* Process the R.E. magic for a %f.$attr expression (or !f.$attr).	(ksb)
 * The largest text attribute a file can have is the path (MAXPATHLEN).
 * When changing asFKnown add code here to match the new member.
 */
static int
FileAttr(const char *pcFull, const struct stat *pstThis, const char *pcRE, const char *pcAttr)
{
	register struct passwd *pw;
	register struct group *gr;
	register int i, iBit;
	auto char acTarget[MAXPATHLEN+256];
	static const char acOwners[] = "owners";

	if (0 == strcmp(pcAttr, "path")) {
		snprintf(acTarget, sizeof(acTarget), "%s", pcFull);
	} else if (0 == strcmp(pcAttr, "perms")) {
		/* copied from my install code -- the ls(1) symbolic mode
		 */
		(void)strcpy(acTarget, "-rwxrwxrwx");
		acTarget[0] = FileType(pstThis);
		for (i = 0, iBit = 0400; i < 9; ++i, iBit >>= 1) {
			if ((struct stat *)0 == pstThis || 0 == (pstThis->st_mode & iBit))
				acTarget[i+1] = '-';
		}
		/* Sun changes plain files group 'x' to 'l' -- but we don't
		 * it would just make it even harder to RE match -- ksb
		 */
		if ((struct stat *)0 != pstThis) {
			EBit(acTarget+3, S_ISUID & pstThis->st_mode, 's', 'x');
			EBit(acTarget+6, S_ISGID & pstThis->st_mode, 's', 'x');
			EBit(acTarget+9, S_ISVTX & pstThis->st_mode, 't', 'x');
		}
	} else if (0 == strcmp(pcAttr, "type")) {
		MakeFileType(pcFull, acTarget, sizeof(acTarget), pstThis);
	} else if (0 == strcmp(pcAttr, "access")) {
		(void)strcpy(acTarget, "rwxf");
		if (0 != access(pcFull, R_OK))
			acTarget[0] = '-';
		if (0 != access(pcFull, W_OK))
			acTarget[1] = '-';
		if ((struct stat *)0 == pstThis || 0 == (0111 & pstThis->st_mode) || 0 != access(pcFull, X_OK))
			acTarget[2] = '-';
		if (0 != access(pcFull, F_OK))
			acTarget[2] = '-';
	} else if ((struct stat *)0 == pstThis) {
		fatal("%s: %s", pcFull, strerror(ENOENT));
	} else if (0 == strcmp(pcAttr, "dev")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_dev);
	} else if (0 == strcmp(pcAttr, "ino")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_ino);
	} else if (0 == strcmp(pcAttr, "nlink")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_nlink);
	} else if (0 == strcmp(pcAttr, "atime")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_atime);
	} else if (0 == strcmp(pcAttr, "mtime")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_mtime);
	} else if (0 == strcmp(pcAttr, "ctime")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_ctime);
	} else if (0 == strcmp(pcAttr, "birthtime") || 0 == strcmp(pcAttr, "btime")) {
#if HAVE_BIRTHTIME
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_birthtime);
#else
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_mtime);
#endif
	} else if (0 == strcmp(pcAttr, "size")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_size);
	} else if (0 == strcmp(pcAttr, "blksize")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_blksize);
	} else if (0 == strcmp(pcAttr, "blocks")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_blocks);
	} else if (0 == strcmp(pcAttr, "uid")) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_uid);
	} else if (0 == strcmp(pcAttr, "login@g")) {
		if ((struct passwd *)0 == (pw = getpwuid(pstThis->st_uid)))
			fatal("getpwuid: %ld: no such login", (long)pstThis->st_uid);
		return MemberMatch(pcRE, pw->pw_name, & pw->pw_gid, "login@g");
	} else if (0 == strcmp(pcAttr, "login") || 0 == strcmp(pcAttr, acOwners)) {
		if ((struct passwd *)0 == (pw = getpwuid(pstThis->st_uid)))
			fatal("getpwuid: %ld: no such login", (long)pstThis->st_uid);
		snprintf(acTarget, sizeof(acTarget), "%s", pw->pw_name);
		if (0 == strcmp(pcAttr, acOwners)) {
			strncat(acTarget, ":", sizeof(acTarget));
			if ((struct group *)0 == (gr = getgrgid(pstThis->st_gid)))
				fatal("getgrgid: %ld: no such group", (long)pstThis->st_gid);
			strncat(acTarget, gr->gr_name, sizeof(acTarget));
		}
	} else if (0 == strcmp(pcAttr, acOptGid)) {
		snprintf(acTarget, sizeof(acTarget), "%ld", (long)pstThis->st_gid);
	} else if (0 == strcmp(pcAttr, "group")) {
		if ((struct group *)0 == (gr = getgrgid(pstThis->st_gid)))
			fatal("getgrgid: %ld: no such group", (long)pstThis->st_gid);
		snprintf(acTarget, sizeof(acTarget), "%s", gr->gr_name);
	} else if (0 == strcmp(pcAttr, "mode")) {
		snprintf(acTarget, sizeof(acTarget), "%04lo", (long)(07777&pstThis->st_mode));
	} else {
		fatal("%%f.%s: unknown attribute", pcAttr);
	}
	return GenMatch(pcAttr, acTarget, pcRE);
}

/* Check %{ENV} and !{ENV} patterns against the proposed environ	(ksb)
 * Find any options that look like envronment limits and check them.
 */
static int
CheckEnv(const cmd_t *cmd, const char **ppcNewEnv)
{
	register int i, iLen, fMatch;
	register const char *pc, *pcRead, **ppcScan, *pcCCurly, *pcEnv;

	for (i= 0; i< cmd->nopts; ++i) {
		pcRead = cmd->opts[i];
		if (('%' != pcRead[0] && '!' != pcRead[0]) || '{' != pcRead[1])
			continue;
		if ((char *)0 == (pcCCurly = strchr(pcRead, '}')))
			fatal("configuration error in environment limit");
		pcEnv = pcRead+2;
		iLen = pcCCurly - pcEnv;
		if ('\000' != *++pcCCurly)
			;
		else if ('!' == pcRead[0])
			pcCCurly = acDefNotEnv;
		else
			continue; /* no default positive env RE match */

		ppcScan = ppcNewEnv;
		while ((char *)0 != (pc = *ppcScan++)) {
			if (0 != strncmp(pcEnv, pc, iLen) || '=' != pc[iLen])
				continue;
			fMatch = GenMatch("environment limit", pc+iLen+1, pcCCurly);
			if (('%' == *pcRead) == (0 != fMatch))
				continue;
			return 1;
		}
	}
	return 0;
}

/* Approve the direct file and return the full-path to it.		(ksb)
 * By the way: -f "" == -f `pwd`.
 */
static char *
DirectFile(const cmd_t *cmd, const char *pcOrig, struct stat *pstThis)
{
	register char *pcLim, *pcAttr, *pcFull, *pcThis;
	register int fRemove, iLen, iCheck;
	register const char *pcRead;
	auto struct stat *pstD, stFull;
	auto char acLook[MAXPATHLEN+256+4];
	auto char acLead[4];
	auto char acTail[256+4];
	static char acFPerms[] = "-f: Permission denied";

	/* Remove trailing slashes from the input filename, which is not NULL
	 * also remove multiple leading slashes for remove checks below.
	 */
	if ((const char *)0 == pcOrig) {
		/* premission denied error in caller */
		return (char *)0;
	}
	while ('/' == pcOrig[0]) {
		if ('.' == pcOrig[1] && '.' == pcOrig[2] && '/' == pcOrig[3])
			pcOrig += 3;	/* "/../" -> "/"	*/
		else if ('.' == pcOrig[1] && '/' == pcOrig[2])
			pcOrig += 2;	/* "/./" -> "/"		*/
		else if ('/' == pcOrig[1])
			pcOrig += 1;	/* "//" -> "/"		*/
		else
			break;
	}
	if ((char *)0 == (pcThis = strdup(pcOrig)))
		fatal("-f %s", acBadAlloc);
	while ((char *)0 != (pcLim = strrchr(pcThis, '/')) && pcLim > pcThis && '\000' == pcLim[1]) {
		*pcLim = '\000';
	}

	/* When the file doesn't exist, or is an allowed symbolic link
	 * compress the path w/o the last component.
	 * When they OK a symbolic link (or mandaite it) then we allow it,
	 * otherwise map it to the contents of the link
	 */
	acTail[0] = '\000';
	fRemove = 0;	/* 0 = no, 1 = was_tail_link, 2 = no_such_file */
	if (-1 == lstat(pcThis, pstThis)) {
		fRemove = 2;
	} else if (S_ISLNK(pstThis->st_mode)) {
		auto char acTempType[8];

		MakeFileType(pcOrig, acTempType, sizeof(acTempType), pstThis);
		fRemove = 1;
		if ((char *)0 != (pcRead = FindOpt(cmd, "%f.type"))) {
			fRemove = !GenMatch("type", acTempType, pcRead);
		}
		if ((char *)0 != (pcRead = FindOpt(cmd, "!f.type")) && GenMatch("type", acTempType, pcRead)) {
			fRemove = 1;
		}
	}
	if (0 != fRemove) {
		if ((char *)0 == pcLim) {
			(void)strncpy(acTail, pcThis, sizeof(acTail));
			strcpy(acLead, acCurDir);
			pcThis = acLead;
		} else if (pcLim == pcThis) {
			(void)strncpy(acTail, pcThis+1, sizeof(acTail));
			acLead[0] = pcThis[0];
			acLead[1] = '\000';
			pcThis = acLead;
		} else {
			(void)strncpy(acTail, pcLim+1, sizeof(acTail));
			do {
				*pcLim-- = '\000';
			} while (pcThis < pcLim && '/' == *pcLim);
		}
	}
	if ('/' == pcThis[0]) {
		acLook[0] = '/';
		acLook[1] = '\000';
		while ('/' == *pcThis)
			++pcThis;
	} else if ((char *)0 == getcwd(acLook, sizeof(acLook))) {
		fatal("getcwd: %s", strerror(errno));
	}
	if ((char *)0 == (pcLim = FullPath(acLook, pcThis))) {
		fatal("$f: no such path");
	}
	/* Put the part we took off back on then end, if we did that
	 */
	if (0 != fRemove) {
		iLen = strlen(pcLim);
		if (strlen(acTail)+1+iLen > sizeof(acLook))
			fatal("-f resolved path too long");
		/* checked above, don't need any strncat check below */
		if ('/' != pcLim[iLen-1]) {
			(void)strcat(pcLim, acSlash);
		}
		(void)strcat(pcLim, acTail);
	}
	iCheck = lstat(pcLim, & stFull);
	switch (fRemove) {
	case 0:	/* must be the same node */
		if (-1 == iCheck) {
			fatal("-f: fullpath doesn't find any file at %s", pcLim);
		}
		if (stFull.st_dev != pstThis->st_dev || stFull.st_ino != pstThis->st_ino) {
			fatal("-f: %s is not the same file %s", pcThis, pcLim);
		}
		break;
	case 1: /* must be the same symbilic link */
		if (!S_ISLNK(stFull.st_mode)) {
			fatal("-f: %s: is not a symbolic link", pcLim);
		}
		if (stFull.st_dev != pstThis->st_dev || stFull.st_ino != pstThis->st_ino) {
			fatal("-f: %s is not the same symbolic link %s", pcThis, pcLim);
		}
		break;
	case 2: /* should still not exist */
		if (-1 != iCheck) {
			fatal("-f: missing file race for %s", pcLim);
		}
		pstThis = (struct stat *)0;
		break;
	}
	if ((char *)0 == (pcFull = strdup(pcLim)))
		fatal("-f %s", acBadAlloc);

	/* When the config provides %f=RE we assume it means %f.path, which
	 * is the first one in the asFKnown list.
	 */
	if ((char *)0 != (pcLim = FindOpt(cmd, acSpecF))) {
		if (!FileAttr(pcFull, pstThis, pcLim, asFKnown))
			fatal(acFPerms);
	}
	if ((char *)0 != (pcLim = FindOpt(cmd, "!f"))) {
		if (FileAttr(pcFull, pstThis, pcLim, asFKnown))
			fatal(acFPerms);
	}
	for (pcAttr = asFKnown; '\000' != *pcAttr; pcAttr += strlen(pcAttr)+1) {
		snprintf(acLook, sizeof(acLook), "%%f.%s", pcAttr);
		if ((char *)0 != (pcLim = FindOpt(cmd, acLook))) {
			if (!FileAttr(pcFull, pstThis, pcLim, pcAttr))
				fatal(acFPerms);
		}
		snprintf(acLook, sizeof(acLook), "!f.%s", pcAttr);
		if ((char *)0 != (pcLim = FindOpt(cmd, acLook))) {
			if (FileAttr(pcFull, pstThis, pcLim, pcAttr))
				fatal(acFPerms);
		}
	}

	/* OK, check %d as well.  The allows a check of dir perms, which
	 * is the same pattern as the checks above.
	 */
	pcThis = strrchr(pcFull, '/');
	if (pcFull == pcThis) {
		pcRead = acSlash;
		pcThis = (char *)0;
	} else {
		*pcThis = '\000';
	}
	pstD = &stFull;
	if (-1 == lstat(pcFull, & stFull)) {
		pstD = (struct stat *)0;
	}
	if ((char *)0 != (pcLim = FindOpt(cmd, acSpecD))) {
		if (!FileAttr(pcFull, pstD, pcLim, asFKnown))
			fatal(acFPerms);
	}
	if ((char *)0 != (pcLim = FindOpt(cmd, "!d"))) {
		if (FileAttr(pcFull, pstD, pcLim, asFKnown))
			fatal(acFPerms);
	}
	for (pcAttr = asFKnown; '\000' != *pcAttr; pcAttr += strlen(pcAttr)+1) {
		snprintf(acLook, sizeof(acLook), "%%d.%s", pcAttr);
		if ((char *)0 != (pcLim = FindOpt(cmd, acLook))) {
			if (!FileAttr(pcFull, pstD, pcLim, pcAttr))
				fatal(acFPerms);
		}
		snprintf(acLook, sizeof(acLook), "!d.%s", pcAttr);
		if ((char *)0 != (pcLim = FindOpt(cmd, acLook))) {
			if (FileAttr(pcFull, pstD, pcLim, pcAttr))
				fatal(acFPerms);
		}
	}
	if ((char *)0 != pcThis) {
		*pcThis = '/';
	}
	return pcFull;
}

/* Allow the configuration to specfiy a forced input/output fd.		(ksb)
 * Accpet: <file  >file  <>file >>file
 * as:	RDONLY, WRONLY, RDWR, RW|O_APPEND (like the shell)
 *
 * We do _NOT_ create files.  That's the point.  The empty file is /dev/null.
 * We will try to connect to a socket, for wrapper logic.  How nice.
 * If you want to create the file with the correct modes, etc, use
 * a separate op rule with install(1) -c /dev/null, or installus.  You
 * could even put mk(1) markup in the config file to install the file
 * when the configuration file is installed.  How cool is that?
 */
static int
PrivOpen(const cmd_t *cmd, const char *pcRedirExpected, const char *pcSpec, const char *pcSecPath)
{
	register const char *pcMode;
	register int iOpts, iRet, iLen;
	auto struct sockaddr_un run;

	if ('<' == *pcSpec || '>' == *pcSpec) {
		pcMode = pcSpec;
		if ('>' == *++pcSpec)
			++pcSpec;
	} else {
		pcMode = pcRedirExpected;
	}
	iOpts = '<' == pcMode[0] ? ('>' == pcMode[1] ? O_RDWR : O_RDONLY) : ('>' == pcMode[0] ? ('>' == pcMode[1] ? (O_APPEND|O_WRONLY) : O_WRONLY|O_TRUNC) : O_RDONLY);
	if (0 == strcmp(acSpecD, pcSpec)) {
		syslog(LOG_ERR, "%s: %s:%d: redirection to/from a directory (via %s)", cmd->pcname, cmd->pcfile, cmd->iline, acSpecD);
		fatal("%s: redirection to a directory forbidden (use $D for a readonly descriptor", cmd->pcname);
	} else if (0 != strcmp(acSpecF, pcSpec))
		/* fine, we have it */;
	else if ((char *)0 == (pcSpec = pcSecPath))
		fatal("%s: redirection requires -f", cmd->pcname);
	if ('\000' == *pcSpec) {
		pcSpec = acDevNull;
		if (O_RDONLY == iOpts)
			iOpts = O_RDWR;
	}
	if (-1 != (iRet = open(pcSpec, iOpts, 0600))) {
		return iRet;
	}
	if (EOPNOTSUPP != errno) {
		fatal("%s: open: %s: %s", cmd->pcname, pcSpec, strerror(errno));
	}
	if ((iLen = strlen(pcSpec)) >= sizeof(run.sun_path)) {
		fatal("%s: connect: %s", cmd->pcname, strerror(ENAMETOOLONG));
	}
	if (-1 == (iRet = socket(AF_UNIX, SOCK_STREAM, 0))) {
		fatal("%s: socket: %s", cmd->pcname, strerror(errno));
	}
#if HAVE_SUN_LEN
	run.sun_len = iLen;
	++iLen;
#endif
	run.sun_family = AF_UNIX;
	(void)strncpy(run.sun_path, pcSpec, sizeof(run.sun_path));
	if (-1 == connect(iRet, (struct sockaddr *)&run, iLen+sizeof(run.sun_family))) {
		fatal("%s: connect: %s: %s", cmd->pcname, pcSpec, strerror(errno));
	}
	/* We could shutdown the socket to honor the redir, nah.
	 */
	return iRet;
}

/* Find the right slot the current value, and the current list.		(ksb)
 * The list holds the max count hidden in ppcList[-1], since the environment
 * before the first slot is never used by anyone else :-).  We find a proposed
 * value from the OldEnv (if we have one) then see is we can overwrite an
 * old value in the list, else we add it on the end.
 *
 * Init the list to a (const char **)0, then call me to add elements.
 */
static void
AddVec(const char ***pppcList, const char *pcEnv, char **ppcOldEnv, int fUniq)
{
#define DEF_ENV_SLOT	768	/* cannot be 0				*/
#define ADD_ENV_SLOTS	256	/* how many to add as we run out	*/
	register int j, iMax, iWas, iFound;
	register size_t iLen;
	register char const **ppcSetEnv, *pcUserEnv, *pcEq;
	register char **ppcMem;

	if ((const char **)0 == (ppcSetEnv = *pppcList)) {
		iWas = 0;
		iMax = DEF_ENV_SLOT;
		ppcMem = (char **)calloc(iMax+1, sizeof(char *));
		if ((char **)0 == ppcMem)
			fatal(acBadAlloc);
		if ((char *)0 == (*ppcMem = (char *)malloc(OP_MAX_LDWIDTH)))
			fatal(acBadAlloc);
		snprintf(ppcMem[0], OP_MAX_LDWIDTH, "%d", iMax);
		ppcSetEnv = (const char **)(ppcMem+1);
	} else {
		iWas = iMax = strtol(ppcSetEnv[-1], (char **)0, 0);
	}

	if ((const char *)0 == pcEnv) {
		iLen = 0;
	} else if ((const char *)0 != (pcEq = strchr(pcEnv, '='))) {
		iLen = pcEq-pcEnv;
	} else {
		iLen = strlen(pcEnv);
	}
	for (iFound = 0; (const char *)0 != pcEnv && (const char *)0 != (pcEq = ppcSetEnv[iFound]); ++iFound) {
		if (fUniq && 0 == strncmp(pcEq, pcEnv, iLen) && ('\000' == pcEq[iLen] || '=' == pcEq[iLen]))
			break;
	}

	/* If we didn't have a value given for the variable, look for
	 * one from the OldEnv, if there is not one clear the request.
	 */
	if ((const char *)0 == pcEnv) {
		/* trying to put the (char *)0 on the end, I guess */
	} else if (fUniq && '\000' == pcEnv[iLen]) {
		pcUserEnv = (const char *)0;
		for (j = 0; (char **)0 != ppcOldEnv && (const char *)0 != (pcUserEnv = ppcOldEnv[j]); ++j) {
			if (0 == strncmp(pcUserEnv, pcEnv, iLen) && '=' == pcUserEnv[iLen])
				break;
		}
		pcEnv = pcUserEnv;
	}
	if ((const char *)0 == pcEnv) {
		/* nothing */
	} else if ((const char *)0 != ppcSetEnv[iFound]) {
		ppcSetEnv[iFound] = pcEnv;
	} else {
		if (iFound == iMax-1) {
			iMax += ADD_ENV_SLOTS;
			ppcMem = (char **)realloc((void *)(ppcSetEnv-1), sizeof(char *)*iMax);
			if ((char **)0 == ppcMem)
				fatal(acBadAlloc);
			snprintf(ppcMem[0], OP_MAX_LDWIDTH, "%d", iMax);
			ppcSetEnv = (const char **)(ppcMem+1);
			for (j = iWas; j < iMax; ++j) {
				ppcSetEnv[j] = (const char *)0;
			}
		}
		ppcSetEnv[iFound] = pcEnv;
	}
	*pppcList = ppcSetEnv;
#undef DEF_ENV_SLOT
#undef ADD_ENV_SLOTS
}

/* Use the same code to build argv and environ
 */
#define AddEnv(Mpppc, MpcEnv, MppcOldEnv) AddVec((Mpppc), (MpcEnv), (MppcOldEnv), 1)
#define AddArg(Mpppc, MpcArg)	AddVec((Mpppc), (MpcArg), (char **)0, 0)

/* Delete instances of an environment varible.				(ksb)
 * Works on the same structure as the AddEnv above.
 */
static void
RmEnv(const char ***pppcList, char *pcEnv)
{
	register const char **ppcRmEnv, *pcCur, *pcEq;
	register size_t iLen;
	register int j, iFold;

	if ((const char **)0 == (ppcRmEnv = *pppcList) || (char *)0 == pcEnv) {
		return;
	}
	if ((char *)0 != (pcEq = strchr(pcEnv, '='))) {
		iLen = pcEq-pcEnv;
	} else {
		iLen = strlen(pcEnv);
	}
	for (iFold = j = 0; (char *)0 != (pcCur = ppcRmEnv[j]); ++j) {
		if (0 == strncmp(pcCur, pcEnv, iLen) && '=' == pcCur[iLen])
			continue;
		ppcRmEnv[iFold++] = pcCur;
	}
	ppcRmEnv[iFold] = (char *)0;
}

/* Reveal environment variables hidden by a prefix so "foo_NAME=agt"	(ksb)
 * becomes "NAME=agt" with a token of "foo_".  The name "foo_" goes
 * away, of course.  Works on the same structure as above.
 */
static void
RevealEnv(const char ***pppcList, const char *pcToken)
{
	register const char **ppcFixEnv, *pcCur;
	register size_t iLen;
	register int j, iKeep;

	if ((const char **)0 == (ppcFixEnv = *pppcList) || (char *)0 == pcToken) {
		return;
	}
	iLen = strlen(pcToken);
	for (iKeep = j = 0; (char *)0 != (pcCur = ppcFixEnv[j]); ++j) {
		if (0 != strncmp(pcCur, pcToken, iLen)) {
			ppcFixEnv[iKeep++] = pcCur;
			continue;
		}
		/* delete the whole prefix name
		 */
		if ('\000' == pcCur[iLen] || '=' == pcCur[iLen]) {
			continue;
		}
		ppcFixEnv[iKeep++] = pcCur+iLen;
	}
	ppcFixEnv[iKeep] = (char *)0;
}

/* Add or remove an environment variable based on the the output from	(ksb)
 * a helmet/jacket process.  Commands are whole lines:
 *	#comment	a comment
 *	"cmd"		C quote command with embedded newlines (see below)
 *	-VAR		remove this from the environment
 *	$VAR=value	set this in the environment
 *	$VAR		copy this from the old environment
 *	~prefix		remove prefix from any matching environment vars
 *	[0-9]+		exit code for failure
 *	.*		why we don't love you, fail for me
 *	&0</dev/null	force stdin from /dev/null
 * XXX	|0domain/key	open socket, send wrapw request to get rights (0,1,2)
 *
 * To convert a multi-line command into text we can read:
 *	sed -e 's/"/\\d/' -e '$s/$/"/' -e '1s/^/"/' <command
 * If you are using the shell to echo it add -e 's/\\/\\\\/g'
 * This is how "stamp" can reset $TERMCAP.
 *
 * To be sure to over-write, you should remove before adding: we
 * won't document what happens if you try to put in duplicates.
 */
static int
ExtInput(const cmd_t *cmd, int fdIn, const char *pcFrom, const char ***pppcEnviron, int *piFds, const char *pcSecPath)
{
	extern char **environ;
	register char *pcLine, *pcQuote, *pcScan;
	register char *pcSrc, *pcDst;
	register unsigned int u;
	register int iRet, iFd;
	auto unsigned int uLines;
	auto const char *pcEol;

	iRet = 0;
	pcDst = pcSrc = pcQuote = (char *)0;
	pcLine = cache_file(fdIn, &uLines);
	for (u = 1; u <= uLines; pcLine += strlen(pcLine)+1, ++u) {
		pcScan = pcLine;
 retry:
		while ((char *)0 != pcQuote) {
			/* Look for clean end "...stuff"\000, we do not do
			 * \ooo expansion, not needed in op at all.
			 */
			if ('"' == pcSrc[0] && '\000' == pcSrc[1]) {
				*pcDst = '\000';
				pcLine = pcSrc+1;
				pcScan = pcQuote+1;
				pcQuote = (char *)0;
				goto skip;
			}
			/* not finished, more lines?
			 */
			if ('\000' == pcSrc[0] || ('\\' == pcSrc[0] && '\000' == pcSrc[1])) {
				if (u++ > uLines) {
					fprintf(stderr, "%s: %s: incomplete quoted external command\n", Progname, cmd->pcname);
					return EX_IOERR;
				}
				if ('\000' == *pcSrc)
					pcSrc[0] = '\n';
				else
					pcSrc[1] = '\n';
			}
			if ('\\' != pcSrc[0]) {
				*pcDst++ = *pcSrc++;
				continue;
			}
			switch (*++pcSrc) {	/* \n, \", \^M \o \\ */
			case '\n': /* \^M is the empty string in sh and C */
				break;
			case 'a':
				*pcDst++ = '\007'; break;
			case 'b':
				*pcDst++ = '\b'; break;
			case 'd':
				*pcDst++ = '"'; break;	/* op special */
			case 'f':
				*pcDst++ = '\f'; break;
			case 'n':
				*pcDst++ = '\n'; break;
			case 'o':
				*pcDst++ = '`'; break;	/* op special */
			case 'q':
				*pcDst++ = '\''; break;	/* op special */
			case 'r':
				*pcDst++ = '\r'; break;
			case 's':
				*pcDst++ = ' '; break;
			case 't':
				*pcDst++ = '\t'; break;
			case 'v':
				*pcDst++ = '\v'; break;
			default: /* includes \\ */
				*pcDst++ = *pcSrc; break;
			}
			++pcSrc;
		}
		while (isspace(*pcLine))
			++pcLine;
		if ('"' == *pcLine) {	/* we found a C string, processed  */
			pcQuote = pcLine--;
			pcDst = pcSrc = pcQuote+1;
			goto retry;
		}
 skip:
#if DEBUG > 1
		fprintf(stderr, "external input: `%s'\n", pcScan);
#endif
		switch (*pcScan) {
		case '\000':
			/* take a blank line, why not? */
			break;
		case '#':
#if DEBUG
			fprintf(stderr, "%s: %s\n", pcFrom, pcScan);
#endif
			break;
		case '&':	/* jacket fd manipulation */
			/* '&' (0|1|2) [privopen-args]
			 * '&' 3|4|5... close all above this fd
			 */
			iFd = strtol(pcScan+1, (char **)&pcEol, 0);
			if ('\000' == *pcEol)
				pcEol = acDevNull;
			if (iFd < 3)
				piFds[iFd] = PrivOpen(cmd, 0 == iFd ? "<" : ">", pcEol, pcSecPath);
			else if (-1 == piFds[3] || iFd < piFds[3])
				piFds[3] = iFd;
			break;
		case '$':
			AddEnv(pppcEnviron, pcScan+1, environ);
			break;
		case '-':
			RmEnv(pppcEnviron, pcScan+1);
			break;
		case '~':
			RevealEnv(pppcEnviron, pcScan+1);
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			iRet = strtol(pcScan, (char **)&pcEol, 0);
			if ('\000' == *pcEol)
				break;
			/*FALLTHROUGH*/
		default:	/* I guess it is misdirected errors? */
			fprintf(stderr, "%s: %s: %s\n", Progname, pcFrom, pcScan);
			iRet = EX_PROTOCOL;
			break;
		}
	}
	return iRet;
}

/* The string in *ppcMem is split into a prefix and a suffix.  Between	(ksb)
 * them we need to build "$(prefix)$1$.$2$.$3...$(iArgc-1).
 */
static char *
Replace(char **ppcMem, char *pcScan, int iStart, int iArgc)
{
	register char *pcHad, *pcRet, *pcCursor;
	register const char *pcSep;
	register size_t wLen;
	static const char acDollarDot[] = "$.";
	auto char acCvt[OP_MAX_LDWIDTH];	/* "$%ld" */

	/* Find the max field width for any positional parameter
	 * That makes the space required about:
	 * (pcScan-pcHad)-1 + (iArgc-iStart)*(strlen(acCvt)+2) + strlen(pcScan)
	 * the +2 for each "$." separator.  The total is + 4 chars too big,
	 * worst case.
	 */
	pcHad = *ppcMem;
	snprintf(acCvt, sizeof(acCvt), "$%ld", (long)iArgc);
	wLen = (pcScan-pcHad)+(iArgc-iStart)*(strlen(acCvt)+2)+strlen(pcScan);
	wLen = (wLen|15)+(OP_MAX_LDWIDTH+1);
	if ((char *)0 == (pcRet = malloc(wLen))) {
		fatal(acBadAlloc);
		/*NOTREACHED*/
	}
	pcScan[-1] = '\000';
	++pcScan;
	pcCursor = pcRet;
	pcSep = acDollarDot+(sizeof(acDollarDot)-1);
	for (strcpy(pcCursor, pcHad); iStart < iArgc; ++iStart) {
		strcat(pcRet, pcSep);
		snprintf(acCvt, sizeof(acCvt), "$%ld", (long)iStart);
		strcat(pcRet, acCvt);
		pcSep = acDollarDot;
	}
	/* stash the new dynamic buffer start, pick up at the suffix, and
	 * free the old (shorter) buffer.
	 */
	*ppcMem = pcRet;
	pcRet += strlen(pcRet);
	strcpy(pcRet, pcScan);
	free(pcHad);
	return *ppcMem;
}

/* Find any word breaking expander that would make multiple words,	(ksb)
 * return the new argv[0] expander template and update the template
 * for $_ in *ppcUnder (which is a mutable copy of the original argv[0]).
 * N.B. *ppcUnder must be malloc'd space, because we may realloc or free it.
 */
static char *
FindUnder(const cmd_t *cmd, const NEEDS *pNE, const int iArgc, char **ppcUnder)
{
	register int c;
	register char *pcScan, *pcD_;

	/* Replace all the expanders that make lists from argv: $@, $-, $+
	 */
	pcScan = *ppcUnder;
	while ('\000' != (c = *pcScan++)) {
		if ('$' != c)
			continue;
		switch (c = *pcScan) {
		case '@': /* $n$.$n+1$.$n+2... used for filter lists*/
			pcScan = Replace(ppcUnder, pcScan, pNE->lCmdParam+1, iArgc);
			continue;
		case '-': /* $0$.$1$.$2$.$3... used largely for {script} */
			pcScan = Replace(ppcUnder, pcScan, 0, iArgc);
			continue;
		case ',': /* $1$.$2$.$3... used largely for {script} */
			pcScan = Replace(ppcUnder, pcScan, 1, iArgc);
			continue;
		case '\\':
			if ('\000' != pcScan[1])
				++pcScan; /* $\$ mayhap */
			/*FALLTHROUGH*/
		case '_': /* do not have to replace, possible error below */
		default:
			++pcScan;
			continue;
		case '.': /* word break here, see loop below */
			break;
		}
		break;
	}
	pcScan = *ppcUnder;
	while ('\000' != (c = *pcScan++)) {
		if ('$' != c)
			continue;
		switch (c = *pcScan) {
		case '_': /* that's Bad $_ inside itself */
			syslog(LOG_ERR, "%s: %s:%d uses $_ as part of itself", cmd->pcname, cmd->pcfile, cmd->iline);
			fatal("$_ may not be used as part of itself");
			/*NOTREACHED*/
		case '.': /* a cut point! Yay! */
			/* "bar$l$." => $_ = foo$l , argv[0] = $_, if you
			 * Force an empty string as s/[$][.]$/$.$|/1, friend.
			 */
			if ('\000' == pcScan[1]) {
				pcScan[-1] = '\000';
				return "$_";
			}
			/* "foo$1$.$2" => $_ = foo$1 , argv[0] = $_$.$2
			 * If (strlen(foo) < 1) we'd make buffer bigger,
			 * but the called added OP_MAX_LDWIDTH for us.
			 */
			pcScan[-1] = '\000';
			if ((char *)0 == (pcD_ = strdup(*ppcUnder)))
				fatal(acBadAlloc);
			*--pcScan = '$';
			memmove((*ppcUnder)+2, pcScan, strlen(pcScan)+1);
			pcScan = *ppcUnder;
			pcScan[0] = '$';
			pcScan[1] = '_';
			*ppcUnder = pcD_;
			return pcScan;
		case '\\':
			if ('\000' != pcScan[1])
				++pcScan; /* $\$ mayhap */
			break;
		default:
			break;
		}
		++pcScan;
	}
	/* no breaks, this is all 1 word, return it as-is */
	return "$_";
}

/* return the strdup of the login's homedir path			(ksb)
 */
static const char *
HomeDir(const char *pcLogin)
{
	register struct passwd *pw;
	register const char *pcRet;

	if ((struct passwd *)0 == (pw = getpwnam(pcLogin)))
		fatal("%s: no such login", pcLogin);
	if ((const char *)0 == (pcRet = strdup(pw->pw_dir)))
		fatal(acBadAlloc);
	return pcRet;
}

/* Expand command $_, $1, $2.., $*, $i <initgroups login>, $u -u username,
 * $U (-u uid), $g (-g groupname), $G (-g uid), $$, $0, $f/$F, $| (empty string)
 * then execute the command (overlay this program with it).  This function
 * is broken up into blocks that share a common set of local variables.
 */
static int
Go(cmd_t *cmd, int argc, const char **argv, const char *pcMay)
{
	extern char **environ;
	register char *pcLook;
	register const char *pcRead;
	register int i, j, iHeTail;
	auto const char *pcSetGroups;
	auto int iSecFd, iDirFd, iNewGroups, iOrigAccess, aiNewIO[4];
	auto int iOrigPpid, iOrigMask;
	auto const char *pcExecPath, *pcSecPath, *pcSecDir;
	auto const char *pcSetSession, *pcEndSession, *pcNewMac;
	auto const char **ppcNewArgs, **ppcNewEnv, **ppcHelmet;
	auto const char *pcNewDir, *pcNewRoot;
	auto uid_t uReal, uEffective, uOrig, uPriv;
	auto gid_t gReal, gEffective, gOrig, gPriv;
	auto const char *pcNewHome;
	auto gid_t agNewList[OP_GIDLIST_MAX], agOrig[OP_GIDLIST_MAX];
	auto struct stat stSecStat, stSecDirStat;
	auto NEEDS Need;
	static char *apcMagic[4] = {
		"$S", "-c", "$*", (char *)0
	};

	if (0 != Craving(cmd, & Need)) {
		exit(EX_DATAERR);
	}
	if (argc < Need.lCmdParam+1) {
		fatal(acBadNumber);
	}

	/* The max helmet argv is:
	 *	jacket-path -P					# 2
	 *	pid|helmet-path					# + 1 = 3
	 *	-u user -g group -f file			# + 6 = 9
	 *	-R root -C $cf					# + 4 = 13
	 *	--						# + 1 = 14
	 *	$mnemonic $program $euid:$egid $cred_type:$cred	# + 4 = 19
	 *	(char *)0					# + 1 = 20
	 * so I allocated 48.
	 */
	iOrigPpid = getppid();
	pcNewMac = (char *)0;
	ppcNewEnv = ppcNewArgs = (const char **)0;
	ppcHelmet = (const char **)0;
	iHeTail = 0;
	pcLook = (char *)0;
	if ((char *)0 != (pcRead = FindOpt(cmd, acOptHelmet)) || ((char *)0 != (pcLook = FindOpt(cmd, acOptJacket)))) {
		if ((const char **)0 == (ppcHelmet = (const char **)calloc(48, sizeof(const char *))))
			fatal(acBadAlloc);
		ppcHelmet[iHeTail++] = pcLook;
		ppcHelmet[iHeTail++] = "-P";
		ppcHelmet[iHeTail++] = pcRead;
	}

	/* Check the parameter count and $g/$u actions before we commit.
	 * N.B. $* is funny in the code, we have to find the highest explicit
	 * number, $* starts after that (this replaces CountArgs from lex.l).
	 * So $2.$1 $* means replace the $* with "$3" "$4" "$5"... strange
	 * DWIM compression.  More odd $g/$G trip %g on, also $u/$U, $f/$F.
	 */
	{
	if (!fUid && Need.fUid) {
		fatal("%s: command requires -u", cmd->pcname);
	}
	if (!fGid && Need.fGid) {
		fatal("%s: command requires -g", cmd->pcname);
	}
#if OP_MAC_SUPPORT
	if ((char *)0 == pcMac && Need.fMacs) {
		fatal("%s: command requires -m", cmd->pcname);
	}
#endif
	if (!fSecFile && (Need.fSecFile || Need.fSecDir)) {
		fatal("%s: command requires -f", cmd->pcname);
	}
	if (fUid && !Need.fUid) {
		fatal("Command line -u %s not allowed", pcUid);
	}
#if OP_MAC_SUPPORT
	if ((char *)0 != pcMac && !Need.fMacs) {
		fatal("Command line -m %s not allowed", pcMac);
	}
#endif
	if (fGid && !Need.fGid) {
		fatal("Command line -g %s not allowed", pcGid);
	}
	pcSecPath = pcSecDir = (const char *)0;
	if (fSecFile) {
		register char *pcMem, *pcCursor;

		if (!(Need.fSecFile || Need.fSecDir))
			fatal("Command line -f %s not allowed", pcSecFile);
		if ((char *)0 == (pcSecPath = DirectFile(cmd, pcSecFile, &stSecStat)))
			fatal("%s: %s: permission denied", cmd->pcname, pcSecPath);
		if (Need.fSecDir) {
			if ((char *)0 == (pcLook = strrchr(pcSecPath, '/'))) {
				pcSecDir = acCurDir;
			} else if (pcSecPath == pcLook) {
				pcSecDir = acSlash;
			} else { /* dup and remove last component */
				if ((char *)0 == (pcMem = strdup(pcSecPath)))
					fatal("$d: %s", acBadAlloc);
				pcCursor = pcMem + strlen(pcMem);
				while (pcMem < pcCursor && '/' != *pcCursor)
					*pcCursor-- = '\000';
				while (pcMem < pcCursor && '/' == *pcCursor)
					*pcCursor-- = '\000';
				pcSecDir = pcMem;
			}
			if (-1 == stat(pcSecDir, & stSecDirStat)) {
				fatal("stat: %s: %s", pcSecDir, strerror(errno));
			}
		}
	}
	if ((const char **)0 != ppcHelmet) {
		if (fSecFile) {
			ppcHelmet[iHeTail++] = "-f";
			ppcHelmet[iHeTail++] = pcSecPath;
		}
		if (fUid) {
			ppcHelmet[iHeTail++] = "-u";
			ppcHelmet[iHeTail++] = pcUid;
		}
		if (fGid) {
			ppcHelmet[iHeTail++] = "-g";
			ppcHelmet[iHeTail++] = pcGid;
		}
	}

	if (0 == Need.fSecFd) {
		iSecFd = -1;
	} else if (-1 != (iSecFd = open(pcSecPath, O_RDWR|O_NOCTTY, 0600))) {
		/* OK */;
	} else if (!(EPERM == errno || EROFS == errno || EISDIR == errno) || -1 == (iSecFd = open(pcSecPath, O_RDONLY, 0600))) {
		fatal("open: %s: %s", pcSecPath, strerror(errno));
	}
	if (0 == Need.fDirFd) {
		iDirFd = -1;
	} else if (-1 == (iDirFd = open(pcSecDir, O_RDONLY, 0700))) {
		fatal("open: %s: %s", pcSecDir, strerror(errno));
	}
	}

	if ((char *)0 != (pcLook = FindOpt(cmd, acOptFib))) {
		register int iFib;
#if HAVE_SETFIB
		extern int setfib(int);	/* FreeBSD lacks extern in 7.2 */
#endif

		iFib = atoi(pcLook);
		if (iFib < 0)
			iFib = 0;
#if HAVE_SETFIB
		if (-1 == setfib(iFib)) {
			fatal("setfib: %d: %s", iFib, strerror(errno));
		}
#else
		if (0 != iFib) {
			fatal("No support for alternate routing tables (FIBs)");
		}
#endif
	}
	if ((char *)0 != (pcLook = FindOpt(cmd, acOptNice))) {
		register int iPrio;

		iPrio = atoi(pcLook);
		if (iPrio < -20)
			iPrio = -20;
		if (iPrio > 20)
			iPrio = 20;
		(void)setpriority(PRIO_PROCESS, 0, iPrio);
	}

	/* Setup our uid and gid creds, but don't change them yet
	 */
	{
	register struct passwd *pw;
	register struct group *gr;
	auto char *pcField = (char *)0, *pcMem = (char *)0;
	auto gid_t gMap, *pwAdd;
	auto const char *pcEGid, *pcProposed;
	auto char *pcEmptySetGroups;
	register int iLen;

	uEffective = uPriv = geteuid();
	uOrig = getuid();
	if (uOrig == uPriv && 0 != uPriv) {
		uPriv = -1;
	}
	uReal = -1;

	gEffective = gPriv = getegid();
	gOrig = getgid();
	if (gOrig == gPriv) {
		gPriv = -1;
	}
	gReal = -1;

	if (-1 == (iOrigAccess = getgroups(OP_GIDLIST_MAX, agOrig))) {
		fatal("getgroups: %s", strerror(errno));
	}
	pcSetGroups = pcEmptySetGroups = (char *)0;
	iNewGroups = 0;
	if ((char *)0 != (pcEGid = FindOpt(cmd, acOptEgid))) {
		if ('\000' == pcEGid[0] || IsMyself(pcEGid)) {
			gEffective = gOrig;
		} else if (0 == strcmp(acSpecF, pcEGid)) {
			gEffective = stSecStat.st_gid;
		} else if (0 == strcmp(acSpecD, pcEGid)) {
			gEffective = stSecDirStat.st_gid;
		} else if (0 == strcmp(acSpecU, pcEGid)) {
			(void) MapUid(pcUid, cmd, &gEffective);
		} else if (0 == strcmp(acSpecG, pcEGid)) {
			gEffective = MapGid(pcGid, cmd);
		} else if ((struct group *)0 != (gr = getgrnam(pcEGid))) {
			gEffective = gr->gr_gid;
		} else if (isdigit(*pcEGid)) {
			gEffective = atoi(pcEGid);
		} else {
			fatal("Invalid effective group: %s", pcEGid);
		}
	}
	pcNewHome = (const char *)0;
	if ((char *)0 == (pcEmptySetGroups = pcLook = FindOpt(cmd, acOptUid))) {
		uReal = uEffective;
	} else if (IsMyself(pcLook) || '\000' == *pcLook) {
		uReal = uOrig;
		if ((char *)0 == pcEGid) {
			gEffective = gOrig;
			pcEGid = "from real gid";
		}
	} else if (0 == strcmp(acSpecF, pcLook)) {
		uReal = stSecStat.st_uid;
		if ((char *)0 == pcEGid) {
			gEffective = stSecStat.st_gid;
			pcEGid = "from gid of %f";
		}
	} else if (0 == strcmp(acSpecD, pcLook)) {
		uReal = stSecDirStat.st_uid;
		if ((char *)0 == pcEGid) {
			gEffective = stSecDirStat.st_gid;
			pcEGid = "from gid of %d";
		}
	} else if (0 == strcmp(acSpecU, pcLook)) {
		uReal = MapUid(pcUid, cmd, &gMap);
		if ((char *)0 == pcSetGroups)
			pcSetGroups = pcUid;
		if ((char *)0 == pcEGid) {
			gEffective = gMap;
			pcEGid = "from login group of %u";
		}
	} else if ((struct passwd *)0 != (pw = getpwnam(pcLook))) {
		if ((char *)0 == pcSetGroups) {
			pcSetGroups = pcLook;
		}
		if ((const char *)0 == pcNewHome && (char *)0 == (pcNewHome = strdup(pw->pw_dir))) {
			fatal("$h: %s", acBadAlloc);
		}
		if ((char *)0 == pcEGid) {
			gEffective = pw->pw_gid;
			pcEGid = "from login group of uid";
		}
		uReal = pw->pw_uid;
	} else if (!isdigit(*pcLook)) {
		/* we don't allow -1 as a uid, sorry */
		fatal("Invalid real uid: %s", pcLook);
	} else {
		uReal = atoi(pcLook);
	}
	if ((char *)0 == (pcLook = FindOpt(cmd, acOptEuid))) {
		uEffective = uReal;
	} else if (IsMyself(pcLook)) {
		uEffective = uOrig;
		if ((char *)0 == pcEGid) {
			gEffective = gOrig;
			pcEGid = "from real gid";
		}
	} else if (0 == strcmp(acSpecF, pcLook)) {
		uEffective = stSecStat.st_uid;
		if ((char *)0 == pcEGid) {
			gEffective = stSecStat.st_gid;
			pcEGid = "from gid of %f";
		}
	} else if (0 == strcmp(acSpecD, pcLook)) {
		uEffective = stSecDirStat.st_uid;
		if ((char *)0 == pcEGid) {
			gEffective = stSecDirStat.st_gid;
			pcEGid = "from gid of %d";
		}
	} else if (0 == strcmp(acSpecU, pcLook)) {
		uEffective = MapUid(pcUid, cmd, &gMap);
		if ((char *)0 == pcSetGroups)
			pcSetGroups = pcUid;
		if ((char *)0 == pcEGid) {
			gEffective = gMap;
			pcEGid = "from login group of %u";
		}
	} else if ((struct passwd *)0 != (pw = getpwnam(pcLook))) {
		if ((char *)0 == pcSetGroups)
			pcSetGroups = pcLook;
		if ((char *)0 == pcEGid) {
			gEffective = gMap;
			pcEGid = "from login group of euid";
		}
		uEffective = pw->pw_uid;
	} else if (!isdigit(*pcLook)) {
		fatal("Invalid effective uid: %s", pcLook);
	} else {
		uEffective = atoi(pcLook);
	}
	if ((char *)0 == pcEmptySetGroups) {
		pcEmptySetGroups = pcLook;
	}

	/* An initgroups spec of %u or %f sets by the command-line, and a
	 * spec of "." or %l leaves them, a name does the obvious, else delete
	 * the groups we had.  Let gid= set more as listed.  This is not
	 * what the original code did, but it is more sane.
	 */
	if ((char *)0 == (pcLook = FindOpt(cmd, acOptInitgrps))) {
		pcSetGroups = (char *)0;
	} else if ('\000' == pcLook[0]) {
		if ((char *)0 == (pcLook = pcEmptySetGroups))
			fatal("%s: no uid or euid set", acOptInitgrps);
		pcSetGroups = pcLook;
	} else if (IsMyself(pcLook)) {
		pcSetGroups = acMyself;
	} else if (0 == strcmp(acSpecF, pcLook) || 0 == strcmp(acSpecD, pcLook)) {
		register const struct stat *pst;
		pst = 0 == strcmp(acSpecF, pcLook) ? &stSecStat : &stSecDirStat;
		if ((struct passwd *)0 == (pw = getpwuid(pst->st_uid))) {
			fatal("getpwuid: -f owner #%ld unmapped for %s", (long)pst->st_uid, acOptInitgrps);
		}
		if ((char *)0 == (pcSetGroups = strdup(pw->pw_name)))
			fatal("%s: %s", acOptInitgrps, acBadAlloc);
		if ((char *)0 == pcEGid) {
			gEffective = pst->st_gid;
			pcEGid = 0 == strcmp(acSpecF, pcLook) ? "from %f's gid" : "from %d's gid";
		}
	} else if (0 == strcmp(acSpecU, pcLook)) {
		pcSetGroups = pcUid;
		(void)MapUid(pcSetGroups, cmd, &gMap);
		if ((char *)0 == pcEGid) {
			gEffective = gMap;
			pcEGid = "from login group of %u";
		}
	} else {
		pcSetGroups = pcLook;
		(void)MapUid(pcSetGroups, cmd, &gMap);
		if ((char *)0 == pcEGid) {
			gEffective = gMap;
			iLen = ((24+strlen(pcSetGroups))|15)+1; /* fmt below */
			if ((char *)0 == (pcMem = malloc(iLen)))
				fatal(acBadAlloc);
			snprintf(pcMem, iLen, "from login group of %s", pcSetGroups);
			pcEGid = pcMem;
		}
	}

	/* A session is a pam version of initgroups, setting other resource
	 * limits and details that we otherwise won't set.  We don't set
	 * all the setrlimit(2) values [or even ulimit(3)], let pam do it.
	 * A session of "" turns this off, like pam="" turns off pam auth.
	 * We end the session under "cleanup=.", at the cost of a fork(2).
	 */
	if ((char *)0 == (pcLook = FindOpt(cmd, acOptSession)) || '\000' == *pcLook) {
		pcSetSession = (const char *)0;
	} else if (IsMyself(pcLook)) {
		pcSetSession = pcPerp;
	} else if (0 == strcmp(acSpecF, pcLook) || 0 == strcmp(acSpecD, pcLook)) {
		register const struct stat *pst;
		pst = 0 == strcmp(acSpecF, pcLook) ? &stSecStat : &stSecDirStat;
		if ((struct passwd *)0 == (pw = getpwuid(pst->st_uid))) {
			fatal("getpwuid: -f owner #%ld unmapped for session", (long)pst->st_uid);
		}
		if ((char *)0 == (pcSetSession = strdup(pw->pw_name)))
			fatal("%s: %s", acOptSession, acBadAlloc);
	} else if (0 == strcmp(acSpecU, pcLook)) {
		pcSetSession = pcUid;
	} else if (0 == strcmp(acKeyInitgroup, pcLook)) {
		pcSetSession = pcSetGroups;
		if ((char *)0 == pcSetSession && (char *)0 == (pcSetSession = pcEmptySetGroups))
			fatal("%s: no %s, %s or %s to copy", acOptSession, acOptUid, acOptEuid, acOptInitgrps);
	} else {
		pcSetSession = pcLook;
	}
	if ((char *)0 == (pcLook = FindOpt(cmd, acOptCleanup)) || '\000' == *pcLook) {
		pcEndSession = (const char *)0;
	} else if (IsMyself(pcLook)) {
		pcEndSession = pcPerp;
	} else if (0 == strcmp(acSpecF, pcLook) || 0 == strcmp(acSpecD, pcLook)) {
		register const struct stat *pst;
		pst = 0 == strcmp(acSpecF, pcLook) ? &stSecStat : &stSecDirStat;
		if ((struct passwd *)0 == (pw = getpwuid(pst->st_uid))) {
			fatal("getpwuid: -f owner #%ld unmapped for cleanup", (long)pst->st_uid);
		}
		if ((char *)0 == (pcEndSession = strdup(pw->pw_name)))
			fatal("%s: %s", acOptCleanup, acBadAlloc);
	} else if (0 == strcmp(acSpecU, pcLook)) {
		pcEndSession = pcUid;
	} else if (0 == strcmp(acKeyInitgroup, pcLook)) {
		pcEndSession = pcSetGroups;
		if ((char *)0 == pcEndSession && (char *)0 == (pcEndSession = pcEmptySetGroups))
			fatal("%s: no %s, %s or %s to copy", acOptCleanup, acOptUid, acOptEuid, acOptInitgrps);
	} else {
		pcEndSession = pcLook;
	}

	/* When we asked for a gid=list we have to build it, else use initgroups
	 */
	if ((char *)0 == (pcProposed = FindOpt(cmd, acOptGid))) {
		static const char acOrig[] = "from client's real gid";

		if ((const char *)0 == pcEGid) {
			gEffective = gOrig;
			pcEGid = acOrig;
		}
		if ((char *)0 == pcSetGroups) {
			setgroups(1, & gEffective);
			gReal = gEffective;
		} else if (IsMyself(pcSetGroups)) {
			/* leave them be */
		} else if (0 != initgroups(pcSetGroups, gEffective)) {
			fatal("%s: %s,%ld: %s", acOptInitgrps, pcSetGroups, (long)gEffective, strerror(errno));
		}
		if (-1 == gReal) {
			gReal = gEffective;
		}
	} else {
		/* Start with our effective, if presented, then add any
		 * groups from initgroups list, only if asked, then from
		 * the gid=list.  gid= means gid=.
		 */
		if ('\000' == *pcProposed) {
			pcProposed = acMyself;
		}
		if ((char *)0 != pcEGid) {
			AddGid(gEffective, & iNewGroups, agNewList);
		}
		if ((char *)0 == pcSetGroups) {
			/* nada we have the whole list */
		} else if (IsMyself(pcSetGroups)) {
			for (i = 0; i < iOrigAccess; ++i) {
				AddGid(agOrig[i], & iNewGroups, agNewList);
			}
		} else {
			j = util_getlogingr(pcSetGroups, &pwAdd);
			for (i = 0; i < j; ++i, ++pwAdd) {
				AddGid(*pwAdd, & iNewGroups, agNewList);
			}
		}
		for (pcRead = GetField(pcProposed, &pcField); OP_GIDLIST_MAX > iNewGroups && (char *)0 != pcRead; pcRead = GetField(pcRead, &pcField)) {
			if (IsMyself(pcField)) {
				AddGid(gOrig, & iNewGroups, agNewList);
			} else if (0 == strcmp(acSpecF, pcField)) {
				AddGid(stSecStat.st_gid, & iNewGroups, agNewList);
			} else if (0 == strcmp(acSpecD, pcField)) {
				AddGid(stSecDirStat.st_gid, & iNewGroups, agNewList);
			} else if (0 == strcmp(acSpecU, pcField)) {
				(void) MapUid(pcUid, cmd, &gMap);
				AddGid(gMap, & iNewGroups, agNewList);
			} else if (0 == strcmp(acSpecG, pcField)) {
				AddGid(MapGid(pcGid, cmd), & iNewGroups, agNewList);
			} else if ((struct group *)0 != (gr = getgrnam(pcField))) {
				AddGid(gr->gr_gid, & iNewGroups, agNewList);
			} else if (isdigit(*pcField)) {
				AddGid(atoi(pcField), & iNewGroups, agNewList);
			} else {
				fatal("%s: invalid real group: %s", acOptGid, pcField);
			}
			/* The first group in the gid list is the real
			 */
			if (-1 == gReal) {
				gReal = agNewList[iNewGroups-1];
			}
		}
		EndField();
		if (-1 == gReal) {
			fatal("Invalid empty group list");
		}
		/* When not setuid we cannot setgroups. So look for the
		 * first group we don't have for effective.
		 */
		if (0 != uPriv) {
			for (i = 0; gEffective == gReal && i < iNewGroups; ++i)
				gReal = agNewList[i];
		} else if (setgroups(iNewGroups, agNewList) < 0) {
			fatal("setgroups: %s", strerror(errno));
		}
		if ((char *)0 == pcEGid) {
			gEffective = gReal;
			pcEGid = "from first in gid list";
		}
	}
	/* There is no way to tell is setfsuid(2) worked.  So we just
	 * hope for the best.  If we have the same fsuid we might get
	 * the same value back, which is also the error we get if it
	 * failed.  Who knows which it was?  They don't set errno.
	 */
#if HAVE_SETFSUID
	(void)setfsuid(uEffective);
#endif
#if HAVE_SETFSGID
	(void)setfsgid(gEffective);
#endif
	/* If op is setgid and we have a saved gid drop it
	 */
#if HAVE_setresgid
	if (setresgid(gReal, gReal, gReal) < 0) {
		fatal("setresgid: %ld,%ld,%ld: %s", (long)gReal, (long)gReal, (long)gReal, strerror(errno));
	}
#else
	if (gReal == gEffective) {
		if (setgid(gReal) < 0) {
			fatal("setgid: %ld: %s", (long)gReal, strerror(errno));
		}
	} else if (setregid(gReal, gEffective) < 0) {
		fatal("setregid: to %ld,%ld (from gid %s %ld,%ld): %s", (long)gReal, (long)gEffective, pcEGid, (long)getgid(), (long)getegid(), strerror(errno));
	}
#endif
	} /* end uid gid creds */

	/* Setup any forced file descriptios for std{in,out,err}, which
	 * depends on the code that assures that 0,1,2 are open already.
	 * We open them before we chroot, of course.  Which can make some
	 * chroot's fail (respecting kern.chroot_allow_open_directories).
	 */
	{
	register const char *pcDaemon;

	aiNewIO[0] = aiNewIO[1] = aiNewIO[2] = aiNewIO[3] = -1;
	pcDaemon = ((char *)0 != FindOpt(cmd, acOptDaemon)) ? acDevNull : (const char *)0;
	/* We should be able to read from $n, maybe write too, but not
	 * connect to the socket?
	 */
	if ((const char *)0 != (pcRead = FindOpt(cmd, acOptStdin)) || (const char *)0 != (pcRead = pcDaemon)) {
		aiNewIO[0] = PrivOpen(cmd, "<", pcRead, pcSecPath);
	}
	if ((const char *)0 != (pcRead = FindOpt(cmd, acOptStdout)) || (const char *)0 != (pcRead = pcDaemon)) {
		aiNewIO[1] = PrivOpen(cmd, ">", pcRead, pcSecPath);
	}
	if ((const char *)0 != (pcRead = FindOpt(cmd, acOptStderr)) || (const char *)0 != (pcRead = pcDaemon)) {
		aiNewIO[2] = PrivOpen(cmd, ">", pcRead, pcSecPath);
	}
	/* aiNewIO[3] is only set by ExtInput's & spec */

	/* Don't chroot until we are done with the password/group files,
	 * then chdir (an empty spec is slash).  Under any chroot move to
	 * the new root directory by default.  To avoid this use "chdir=.".
	 * Don't actually do it until after any jacket lets us go.
	 */
	if ((const char *)0 != (pcNewRoot = FindOpt(cmd, acOptChroot))) {
		if ('\000' == pcNewRoot[0])
			pcNewRoot = (char *)0;
		else if (0 == strcmp(acSpecF, pcNewRoot))
			pcNewRoot = pcSecPath;
		else if (0 == strcmp(acSpecD, pcNewRoot))
			pcNewRoot = pcSecDir;
		else if (0 == strcmp(acSpecU, pcNewRoot))
			pcNewDir = HomeDir(pcUid);
		else if (0 == strcmp(acSpecMe, pcNewRoot))
			pcNewRoot = pcPerpHome;
	}
	if ((char *)0 != (pcNewDir = FindOpt(cmd, acOptDir))) {
		if (0 == strcmp(acSpecF, pcNewDir))
			pcNewDir = pcSecPath;
		else if (0 == strcmp(acSpecD, pcNewDir))
			pcNewDir = pcSecDir;
		else if (0 == strcmp(acSpecU, pcNewDir))
			pcNewDir = HomeDir(pcUid);
		else if (0 == strcmp(acSpecMe, pcNewDir))
			pcNewDir = pcPerpHome;
		else if ('\000' == *pcNewDir)
			pcNewDir = acSlash;
	} else if ((char *)0 != pcNewRoot) {
		pcNewDir = acSlash;
	}

	if ((const char **)0 != ppcHelmet) {
		if ((char *)0 != pcNewRoot) {
			ppcHelmet[iHeTail++] = "-R";
			ppcHelmet[iHeTail++] = pcNewRoot;
		}
		ppcHelmet[iHeTail++] = "-C";
		ppcHelmet[iHeTail++] = cmd->pcfile;
	}
	}

	/* The manual says umask never fails.  I suppose that's true.
	 */
	if ((char *)0 == (pcLook = FindOpt(cmd, acOptMask))) {
		iOrigMask = umask(0022);	/* as per original spec */
	} else {
		register long lMask;
		auto char *pcEnd;

		lMask = strtoul(pcLook, &pcEnd, 8);
		iOrigMask = umask((int)lMask);
	}

	{
	register const char *pcEq;
	register char *pcEnv;
	register int iKeep;
	auto char *pcField;

	/* Copy in the user environment, then overlay ours, if any.
	 * environment		=> environment=.
	 * environment=RES	=> match each RE as:
	 *	RE contains an '=' => match against whole VAR=value
	 *	RE w/o any '=' => match against name only.
	 *	match just values use ^[^=]*=RE
	 */
	if ((char *)0 != (pcRead = FindOpt(cmd, acOptEnv))) {
		register regex_t *reg;
		auto char *pcCarp;

		if ('\000' == *pcRead) {
			pcRead = ".";	/* RE that matchs a non-empty string */
		}
		while ((char *)0 != (pcRead = GetField(pcRead, &pcField))) {
			pcEq = strchr(pcField, '=');
			for (i = 0; (char *)0 != (pcEnv = environ[i]); ++i) {
				if ((char *)0 == (pcEnv = strchr(pcEnv, '=')))
					continue;
				if ((char *)0 == pcEq)
					*pcEnv = '\000';
				if ((regex_t *)0 == (reg = ReComp(pcField, &pcCarp)))
					fatal("%s: regcomp: %s: %s", acOptEnv, pcField, pcCarp);
				iKeep = ReMatch(reg, environ[i]);
				regfree(reg);
				*pcEnv = '=';
				if (!iKeep) {
					continue;
				}
				AddEnv(&ppcNewEnv, environ[i], (char **)0);
			}
		}
		EndField();
	}
	}

	/* Produce the command template if it was "MAGIC_SHELL"
	 * Magic shell maps $* -> ${max}, defaults to $S -c $*
	 * also the cmd record may be fixed a little, it is magic.
	 * then check $* (now that we have the final argv list).
	 */
	if (argc < Need.lCmdParam+3) {
		register const char **ppcClone;

		if ((const char **)0 == (ppcClone = calloc(((Need.lCmdParam+3)|3)+1, sizeof(const char *)))) {
			fatal(acBadAlloc);
		}
		for (i = 0; i < argc; ++i) {
			ppcClone[i] = argv[i];
		}
		for (/* above */; i < Need.lCmdParam+3; ++i)
			ppcClone[i] = (const char *)0;
		argv = ppcClone;
	}
	/* When you config just MAGIC_SHELL and there are extra parameters we
	 * make the command template "$S -c $*" , else "$S", as documented.
	 */
	if (0 == strcmp(acMagicShell, cmd->args[0])) {
		if (1 == argc && 1 == cmd->nargs) {
			cmd->args[0] = apcMagic[0];
		} else {
			cmd->args = (const char **)apcMagic;
			cmd->nargs = 3;
		}
	}
	if ((char *)0 != (pcLook = FindOpt(cmd, "$*"))) {
		for (j = Need.lCmdParam+1; j < argc; ++j) {
			if (! GenMatch("!*", argv[j], pcLook))
				fatal(acBadParam);
		}
	}
	if ((char *)0 != (pcLook = FindOpt(cmd, "!*"))) {
		for (j = Need.lCmdParam+1; j < argc; ++j) {
			if (GenMatch("$*", argv[j], pcLook))
				fatal(acBadParam);
		}
	}

	/* Build the actual command argument to execute
	 */
	{
	register int c, iCurArg;
	register char *pcMem;
	register int iMax, iLen, fAllowEmpty;
	register const char *pcSplice, *pcScan;
	register int iArgx, iOptx;
	auto char acCvt[OP_MAX_LDWIDTH];	/* "xx%ld" */
	auto char *pcEnd;
	auto unsigned long ul;
	auto int iRep, fEoa;
	auto char *pcNew;
	auto gid_t gIgnore, gPrint;
	auto uid_t uPrint;
	auto struct group *gr;
	auto struct passwd *pw;
	auto struct stat *pstFull;
#define MAX_BEFORE	4
	auto char *pcD_, *pcShell;
	auto char acBack[4], **appcBefore[MAX_BEFORE];
	auto struct stat stFull;
#if OP_MAC_SUPPORT
	auto mac_t macCvt;
#endif
	auto char acStatPath[MAXPATHLEN+4];
	static const char acCmdPerms[] = "$_: %s: Permission denied";

	iMax = 10240;
	if ((char *)0 == (pcMem = (char *)malloc(iMax))) {
		fatal(acBadAlloc);
	}
	fAllowEmpty = iCurArg = 0;

	/* Double conditional loop, for options and env variables, expand
	 * skipping $*, $1, $2 and the like set all the forced $env=values
	 * to their expansion.  Then expand the parameters.  We must expand
	 * the envronment first for $S in the args list.  But first we must
	 * exapand $_.  Which is a pain to setup.
	 */
	iLen = ((strlen(cmd->args[0])+3)|15)+(OP_MAX_LDWIDTH+1);
	if ((char *)0 == (pcD_ = malloc(iLen))) {
		fatal(acBadAlloc);
	}
	snprintf(pcD_, iLen, "%s", cmd->args[0]);
	cmd->args[0] = FindUnder(cmd, &Need, argc, &pcD_);

	/* The negative Optx indexes the Before list, which all need to
	 * expanded and replaced with the expansion before the env or
	 * args can be expanded.  See /0 > iOptx)/ below for the updates.
	 * Presently we need first $S then $_ before we can build the rest.
	 */
	iOptx = 0;
	appcBefore[iOptx++] = &pcD_;
	switch (CheckTrust(cmd, acShell)) {
	case 1: /* they set, or default value */
		if ((char *)0 == (pcShell = getenv(acShell+1)))
	case 0: /* not allowed so we get our default */
			pcShell = (char *)acDefShell;
		break;
	case 2:	/* the value is in our config, expand it first */
		pcShell = FindOpt(cmd, acShell);
		appcBefore[iOptx++] = &pcShell;
		break;
	default:
		fatal("CheckTrust: internal error");
		/*NOTREACHED*/
	}
	appcBefore[iOptx] = (char **)0;
	iOptx = -iOptx;
	for (iArgx = 0; (iOptx < 0 ? (pcScan = *appcBefore[-(iOptx+1)], 1) : (iOptx < cmd->nopts && (char *)0 != (pcScan = cmd->opts[iOptx++]))) || (iArgx < cmd->nargs && (char *)0 != (pcScan = cmd->args[iArgx++])); /* double cond */) {
		auto int fMac = 0;
		/* when looking for an environment variable, or mac spec
		 */
		if (0 == iArgx && iOptx > 0) {
			/* We expand the mac value, like an environment var
			 */
			if (0 == strncmp("mac=", pcScan, 4)) {
				fMac = 1;
			} else if (*pcScan++ != '$') {
				continue;
			}
			if ('#' == *pcScan || '@' == *pcScan || '*' == *pcScan || isdigit(*pcScan)) {
				continue;
			}
		}
		iLen = 0, fEoa = 0;
		iRep = -1;
		while ('\000' != (c = (pcMem[iLen++] = *pcScan++))) {
			if (iLen+2 >= iMax) {
				iMax += 4096;
				if ((char *)0 == (pcNew = realloc((void *)pcMem, iMax))) {
					fatal(acBadAlloc);
				}
				pcMem = pcNew;
			}
			if ('$' != c) {
				continue;
			}
			if ('$' == (c = *pcScan++)) {	/* $$ -> $ */
				continue;
			}
			--iLen;
			pcSplice = (const char *)0;
			switch (c) {/*UnIqueStart*/
			case '.': /* internal only expander -- break word */
				pcSplice = "";
				if (0 != iArgx)
					fEoa = 1;
				else if (iLen > 0 && !isspace(pcMem[iLen-1]))
					pcSplice = " ";
				break;
			case '@': /* the parameters a words */
			case '*': /* the parameters as a single word */
			case '-': /* whole command-line as words */
			case '+': /* whole command-line as a single word */
			/* there is no *++1 */
			case ',': /* internal only expander -- $-+1 */
				{
				auto int fFirst;

				/* When in environment don't break words.
				 */
				if (0 == iArgx) {
					c = '@' == c ? '*' : '-' == c ? '+' : c;
				}
				fFirst = 0;
				if (-1 == iRep) {
					fAllowEmpty = '@' == c || '-' == c || ',' == c;
					iRep = Need.lCmdParam;
					if ('-' == c || '+' == c || ',' == c)
						iRep = -1;
					if (',' == c)
						++iRep;
					fFirst = 1;
				}
				if (++iRep >= argc) {
					iRep = -1;
					pcSplice = "";
					break;
				}
				pcSplice = argv[iRep];

				/* We need a space when we are in $* or $&
				 * and we are not the first element
				 */
				if (!fFirst && ('+' == c || '*' == c)) {
					pcMem[iLen++] = ' ';
				}
				/* We need an end-of-arg when in $@, $-, $,
				 */
				fEoa = iRep < argc-1 && ('-' == c || '@' == c || ',' == c);
				pcMem[iLen] = '\000';
				}
				pcScan -= 2;
				break;
			case '#': /* the number of command line params */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)argc-1);
				pcSplice = acCvt;
				break;
			case '0': /* Easter Egg $0 is the mnemonic name */
			case '1': /* $1, $2... from the command line */
			case '2': case '3':
			case '4': case '5': case '6':
			case '7': case '8': case '9':
				ul = strtoul(pcScan-1, &pcEnd, 10);
				if (argc < ul) {
					fatal("$%lu beyond given argument list", ul);
				}
				pcScan = pcEnd;
				pcSplice = argv[ul];
				fAllowEmpty = 1;
				break;
			case '{': /* allow interpolation of OLD environment */
				if ((char *)0 == (pcEnd = strchr(pcScan, '}')))
					fatal("Missing close curly in environment variable substitution");
				*pcEnd = '\000';
				if ((char *)0 == (pcSplice = getenv(pcScan)))
					pcSplice = ""; /*{*/
				*pcEnd++ = '}';
				pcScan = pcEnd;
				break;
			case '\\': /* allow tr list */
				acBack[1] = '\000';
				switch (c = *pcScan++) {
				case '\000':
					syslog(LOG_ERR, "%s: %s:%d: backslash at end of %s, user notified", cmd->pcname, cmd->pcfile, cmd->iline, 0 == iArgx ? "parameter" : "argument");
					fatal("%s: end of string in backslash escape, please ask the administrator to run \"%s -S\"", cmd->pcname, Progname);
					/*NOTREACHED*/
					exit(EX_SOFTWARE);
				case 'a':
					acBack[0] = '\007'; break;
				case 'b':
					acBack[0] = '\b'; break;
				case 'd':
					acBack[0] = '"'; break;
				case 'f':
					acBack[0] = '\f'; break;
				case 'n':
					acBack[0] = '\n'; break;
				case 'o':
					acBack[0] = '`'; break;
				case 'q':
					acBack[0] = '\''; break;
				case 'r':
					acBack[0] = '\r'; break;
				case 's':
					acBack[0] = ' '; break;
				case 't':
					acBack[0] = '\t'; break;
				case 'v':
					acBack[0] = '\v'; break;
				default:
					acBack[0] = c; break;
				}
				pcSplice = acBack;
				break;
			case '|': /* empty string */
				pcSplice = "";
				break;
			case 'C': /* the configuration directory */
				{
				register char *pcTail;

				if ((char *)0 == (pcSplice = strdup(pcAccess))) {
					fatal("$%c: %s", c, acBadAlloc);
				}
				if ((char *)0 != (pcTail = strrchr(pcSplice, '/'))) {
					pcTail[1] = '\000';
					while ('/' == *pcTail && pcTail > pcSplice)
						*pcTail-- = '\000';
				} else {
					pcSplice = acCurDir;
				}
				}
				break;
			case 'c': /* the base configuration file */
				pcSplice = pcAccess;
				break;
			case 's': /* { script } payload */
				pcSplice = cmd->pcscript;
				break;
			case 'S': /* the shell specified */
				pcSplice = pcShell;
				if ((char *)0 == pcSplice) {
					pcSplice = acDefShell;
				}
				break;
			case '_': /* target script, or shell */
				if (-1 > iOptx) {
					syslog(LOG_ERR, "%s: %s:%d uses $_ as part of itself", cmd->pcname, cmd->pcfile, cmd->iline);
					fatal("$_ may not be used as part of itself");
				}
				pcSplice = pcD_;	/* <path> */
				break;
			case 'u': /* -u login */
				/* check passes %u attributes */
				uPrint = MapUid(pcUid, cmd, &gIgnore);
				pcSplice = pcUid;
				break;
			case 'U': /* -u by uid */
				uPrint = MapUid(pcUid, cmd, &gIgnore);
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)uPrint);
				pcSplice = acCvt;
				break;
			case 'g': /* -g group */
				/* check passes %g attributes */
				gPrint = MapGid(pcGid, cmd);
				pcSplice = pcGid;
				break;
			case 'G': /* -g by gid */
				gPrint = MapGid(pcGid, cmd);
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)gPrint);
				pcSplice = acCvt;
				break;
			case 'd': /* -f's directory */
				if ((const char *)0 == pcSecDir) {
					fatal("No -f to match $d");
				}
				pcSplice = pcSecDir;
				break;
			case 'D': /* -f's directory, by open fd */
				if (-1 == iDirFd) {
					fatal("No -f to match $D");
				}
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)iDirFd);
				pcSplice = acCvt;
				break;
			case 'f': /* -f file */
				pcSplice = pcSecPath;
				break;
			case 'F': /* -f file by fd */
				if (-1 == iSecFd) {
					fatal("No -f to match $F");
				}
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)iSecFd);
				pcSplice = acCvt;
				break;
#if OP_MAC_SUPPORT
			case 'm': /* -m label specified */
				if ((char *)0 == pcMac) {
					fatal("No -m to match $m");
				}
				pcSplice = pcMac;
				break;
			case 'M': /* our current process MAC label */
				{
				auto char *pcParam;

				if (-1 == mac_prepare_process_label(& macCvt)) {
					fatal("$M: mac_prepare: %s", strerror(errno));
				}
				if (-1 == mac_get_proc(macCvt)) {
					fatal("$M: mac_get_proc: %s", strerror(errno));
				}
				if (-1 == mac_to_text(macCvt, &pcParam)) {
					fatal("$M: mac_to_text: %s", strerror(errno));
				}
				/* N.B. we never free the MAC label copy
				 */
				pcSplice = pcParam;
				}
				break;
#else
			case 'm':
			case 'M':
				fatal("No Mandatory Access Control available");
#endif
			case 't': /* target login */
				if ((struct passwd *)0 != (pw = getpwuid(uReal))) {
					pcSplice = pw->pw_name;
				} else {
					snprintf(acCvt, sizeof(acCvt), "#%ld", (long)uReal);
					pcSplice = acCvt;
				}
				break;
			case 'T': /* target login by uid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)uReal);
				pcSplice = acCvt;
				break;
			case 'i': /* initgroups target */
			case 'I': /* initgroups target but uid */
				if (IsMyself(pcSetGroups) || (char *)0 == (pcSplice = pcSetGroups)) {
					pcSplice = pcPerp;
				}
				if ('I' == c && (struct passwd *)0 != (pw = getpwnam(pcSplice))) {
					snprintf(acCvt, sizeof(acCvt), "%ld", (long)pw->pw_uid);
					pcSplice = acCvt;
				}
				break;
			case 'a': /* original group access list */
			case 'A': /* by gid */
				if (++iRep == iOrigAccess) {
					iRep = -1;
					pcSplice = "";
					break;
				}
				if ('a' == c && (struct group *)0 != (gr = getgrgid(agOrig[iRep]))) {
					snprintf(acCvt, sizeof(acCvt), ",%s", gr->gr_name);
				} else {
					snprintf(acCvt, sizeof(acCvt), ",%s%ld", 'a' == c ? "#" : "", (long)agOrig[iRep]);
				}
				pcSplice = acCvt;
				if (0 == iRep) {
					++pcSplice;
				}
				pcScan -= 2;
				break;
			case 'n': /* new group list */
			case 'N': /* group list by gid */
				if (++iRep == iNewGroups) {
					iRep = -1;
					pcSplice = "";
					break;
				}
				if ('n' == c && (struct group *)0 != (gr = getgrgid(agNewList[iRep]))) {
					snprintf(acCvt, sizeof(acCvt), ",%s", gr->gr_name);
				} else {
					snprintf(acCvt, sizeof(acCvt), ",%s%ld", 'n' == c ? "#" : "", (long)agNewList[iRep]);
				}
				pcSplice = acCvt;
				if (0 == iRep) {
					++pcSplice;
				}
				pcScan -= 2;
				break;
			case 'e': /* exec suid name ("root" usually) */
			case '~': /* exec suid home directory ("/") */
				if (-1 == uPriv || (struct passwd *)0 == (pw = getpwuid(uPriv))) {
					break;
				}
				if ('~' == c) {
					pcSplice = pw->pw_dir;
				} else {
					pcSplice = pw->pw_name;
				}
				break;
			case 'E': /* our original setuid id */
				if (-1 == uPriv)
					break;
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)geteuid());
				pcSplice = acCvt;
				break;
			case 'b': /* effective group, not usually used */
				if (-1 == gPriv || (struct group *)0 == (gr = getgrgid(gPriv))) {
					break;
				}
				pcSplice = gr->gr_name;
				break;
			case 'B': /* effective group by gid, symmetry */
				if (-1 == gPriv)
					break;
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)gPriv);
				pcSplice = acCvt;
				break;
			case 'h': /* ~perp */
				pcSplice = pcPerpHome;
				break;
			case 'H': /* ~new-real-uid */
				if ((const char *)0 != pcNewHome) {
					/* nada */
				} else if ((struct passwd *)0 != (pw = getpwuid(uReal))) {
					pcNewHome = strdup(pw->pw_dir);
				}
				pcSplice = pcNewHome;
				break;
			case 'K': /* new-real-uid's shell */
				pcSplice = (char *)0;
				if ((struct passwd *)0 != (pw = getpwuid(uReal))) {
					pcSplice = pw->pw_shell;
				}
				/*FALLTHROUGH*//*CONSTANTCOND*/
				if (0) {
			case 'k': /* perp's shell */
					pcSplice = pcPerpShell;
				}
				if ((char *)0 == pcSplice || '\000' == *pcSplice) {
					pcSplice = acDefShell;
				}
				break;
			case 'l': /* original login */
				pcSplice = pcPerp;
				break;
			case 'L': /* original uid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)uOrig);
				pcSplice = acCvt;
				break;
			case 'p': /* pam session login */
				pcSplice = pcSetSession;
				break;
			case 'P': /* pam session uid */
				if ((char *)0 == pcSetSession) {
					fprintf(stderr, "%s: $P: no session set\n", Progname);
					exit(EX_SOFTWARE);
				}
				if ((struct passwd *)0 != (pw = getpwnam(pcSetSession))) {
					snprintf(acCvt, sizeof(acCvt), "%ld", (long)pw->pw_uid);
					pcSplice = acCvt;
				}
				break;
			case 'r': /* original real group */
				if ((struct group *)0 != (gr = getgrgid(gOrig))) {
					pcSplice = gr->gr_name;
				} else {
					snprintf(acCvt, sizeof(acCvt), "#%ld", (long)gOrig);
					pcSplice = acCvt;
				}
				break;
			case 'R': /* original read gid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)gOrig);
				pcSplice = acCvt;
				break;
			case 'o': /* target group */
				if ((struct group *)0 != (gr = getgrgid(gReal))) {
					pcSplice = gr->gr_name;
				} else {
					snprintf(acCvt, sizeof(acCvt), "#%ld", (long)gReal);
					pcSplice = acCvt;
				}
				break;
			case 'O': /* target gid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)gReal);
				pcSplice = acCvt;
				break;
			case 'q': /* the basename of this program */
				pcSplice = Progname;
				break;
			case 'Q': /* the full path to this program */
				pcSplice = FullProgname;
				break;
			case '^': /* version 3 compile options, back-ported */
				pcSplice = acCompOpts;
				break;
			case 'v': /* version of op */
				pcSplice = rcsid;
				break;
			case 'V': /* version as a number */
				pcSplice = rcsid; /* "tok: name number ..." */
				i = j = 0;
				while ('\000' != (c = *pcSplice++)) {
					if (isspace(c)) {
						++j;
						continue;
					}
					if (2 == j) {
						acCvt[i++] = c;
					}
				}
				acCvt[i] = '\000';
				pcSplice = acCvt;
				break;
			case 'w': /* which configuration file */
				pcSplice = cmd->pcfile;
				break;
			case 'W': /* where in the configuration file */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)cmd->iline);
				pcSplice = acCvt;
				break;
			case 'x': /* target directory */
				if ((char *)0 == (pcSplice = pcNewDir))
					pcSplice = acCurDir;
				break;
			case 'X': /* target root, notify if not chroot'd */
				if ((char *)0 == (pcSplice = pcNewRoot)) {
					syslog(LOG_ERR, "%s: %s:%d: $X without chroot set", cmd->pcname, cmd->pcfile, cmd->iline);
					pcSplice = acSlash;
				}
				break;
			case 'y': /* our tty name from 2, 0, 1 */
				if ((char *)0 != (pcSplice = ttyname(2)))
					/* got it from stderr */;
				else if ((char *)0 != (pcSplice = ttyname(0)))
					/* got it from stdin */;
				else /* try stdout, or fail */
					pcSplice = ttyname(1);
				break;
			case 'Y': /* our original umask */
				snprintf(acCvt, sizeof(acCvt), "%04lo", (long)iOrigMask);
				pcSplice = acCvt;
				break;
			case 'z': /* our pid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)getpid());
				pcSplice = acCvt;
				break;
			case 'Z': /* our ppid */
				snprintf(acCvt, sizeof(acCvt), "%ld", (long)iOrigPpid);
				pcSplice = acCvt;
				break;
			default:
				break;
			}/*UNIqueEnd*/
			if ((const char *)0 == pcSplice) {
				fatal("%s: $%c: no data found", cmd->pcname, c);
			}
			if (iLen+strlen(pcSplice) >= iMax) {
				iMax += (4095|strlen(pcSplice))+1;
				if ((char *)0 == (pcNew = realloc((void *)pcMem, iMax))) {
					fatal("$%c: %s", c, acBadAlloc);
				}
				pcMem = pcNew;
			}
			(void)strcpy(pcMem+iLen, pcSplice);
			iLen += strlen(pcSplice);
			if (fEoa && (fAllowEmpty || '\000' != pcMem[0])) {
				AddArg(&ppcNewArgs, strdup(pcMem));
				++iCurArg;
				iLen = 0, fEoa = 0;
			}
		}
		/* If we just finished a before expansion, resolve it.
		 */
		if (0 > iOptx) {
			++iOptx;
			if ((char *)0 == (*appcBefore[-iOptx] = strdup(pcMem)))
				fatal("before: %s", acBadAlloc);
			if (appcBefore[-iOptx] == &pcShell && (char *)0 != strstr(pcShell, "perl"))
				apcMagic[1] = "-e";
			continue;
		}
		if (0 == iArgx) {
			if (!fMac)
				AddEnv(&ppcNewEnv, strdup(pcMem), environ);
			else if ((char *)0 == (pcNewMac = strdup(pcMem)))
				fatal("%s: %s", acOptMac, acBadAlloc);
			continue;
		}
		/* N.B iLen is 1 too big since we counted the \000 */
		if (1 == iLen && !fAllowEmpty) {
			continue;
		}
		fAllowEmpty = 0;
		AddArg(&ppcNewArgs, strdup(pcMem));
		++iCurArg;
	}

	/* When basename is set we lie about argv[0]
	 */
	if ((const char **)0 == ppcNewArgs || (char *)0 == ppcNewArgs[0] || '\000' == ppcNewArgs[0][0]) {
		fatal("%s: no command generated from given specification", cmd->pcname);
	}
	pcExecPath = ppcNewArgs[0];
	if ((char *)0 != (pcLook = FindOpt(cmd, acOptArgv0))) {
		ppcNewArgs[0] = pcLook;
	}

	if ((const char **)0 != ppcNewEnv && CheckEnv(cmd, ppcNewEnv)) {
		fatal("environment limits forbid this access");
	}

	pcSplice = pcExecPath;	/* real path under chroot */
	pstFull = &stFull;
	if (0 == strcmp(pcExecPath, acEcho)) {
		pstFull = (struct stat *)0;
	} else {
		if ((char *)0 != pcNewRoot && 0 != strcmp("/", pcNewRoot)) {
			snprintf(acStatPath, sizeof(acStatPath), "%s%s%s", pcNewRoot, '/' != *pcExecPath ? "/" : "", pcExecPath);
			pcSplice = acStatPath;
		}
		if (-1 == lstat(pcSplice, & stFull)) {
			pstFull = (struct stat *)0;
		}
	}
	if ((char *)0 != (pcScan = FindOpt(cmd, "%_"))) {
		if (!FileAttr(pcSplice, pstFull, pcScan, asFKnown))
			fatal(acCmdPerms, pcSplice);
	}
	if ((char *)0 != (pcScan = FindOpt(cmd, "!_"))) {
		if (FileAttr(pcSplice, pstFull, pcScan, asFKnown))
			fatal(acCmdPerms, pcSplice);
	}
	for (pcRead = asFKnown; '\000' != *pcRead; pcRead += strlen(pcRead)+1) {
		auto char acLook[((4+sizeof(asFKnown))|7)+1];

		snprintf(acLook, sizeof(acLook), "%%_.%s", pcRead);
		if ((char *)0 != (pcScan = FindOpt(cmd, acLook))) {
			if (!FileAttr(pcSplice, pstFull, pcScan, pcRead))
				fatal(acCmdPerms, pcSplice);
		}
		snprintf(acLook, sizeof(acLook), "!_.%s", pcRead);
		if ((char *)0 != (pcScan = FindOpt(cmd, acLook))) {
			if (FileAttr(pcSplice, pstFull, pcScan, pcRead))
				fatal(acCmdPerms, pcSplice);
		}
	}
	if ((char *)0 != (pcRead = FindOpt(cmd, acPatFowners)) && '\000' != pcRead && !FileAttr(pcSplice, pstFull, pcRead, acPatFowners)) {
		fatal(acCmdPerms, pcSplice);
	}
	if ((char *)0 != (pcRead = FindOpt(cmd, acPatPerms)) && '\000' != pcRead && !FileAttr(pcSplice, pstFull, pcRead, acPatPerms)) {
		fatal(acCmdPerms, pcSplice);
	}
#undef MAX_BEFORE
	}

	/* Make all the processes we were asked for
	 */
	{
	register char **ppcHold;
	auto char acCvt[OP_MAX_LDWIDTH];	/* "%ld" */
	auto pid_t wPid, wReap;
	auto int iStatus, iFdOldErr, aiPipe[2];

	iFdOldErr = 2;
#define RESTORE_STDERR()	do { register int iE = errno; if (2 != iFdOldErr) { dup2(iFdOldErr, 2); } errno = iE; } /*CONSTANTCOND*/ while (0)
	/* Finish helmet/jacket args with
	 *	[-m mac] -- $mnemonic $path $euid:$egid $cred_type:$cred NULL
	 * then maybe run helmet to check any other stuff the config wants.
	 */
	if ((const char **)0 != ppcHelmet) {
#if OP_MAC_SUPPORT
		if ((char *)0 != pcNewMac) {
			ppcHelmet[iHeTail++] = "-m";
			ppcHelmet[iHeTail++] = pcNewMac;
		}
#endif
		ppcHelmet[iHeTail++] = "--";
		ppcHelmet[iHeTail++] = cmd->pcname;
		ppcHelmet[iHeTail++] = pcExecPath;
		snprintf(acCvt, sizeof(acCvt), "%ld:%ld", (long)uEffective, (long)gEffective);
		if ((char *)0 == (ppcHelmet[iHeTail++] = strdup(acCvt)))
			fatal("%s: %s", acOptHelmet, acBadAlloc);
		ppcHelmet[iHeTail++] = pcMay;
		ppcHelmet[iHeTail++] = (char *)0;
	}
	if ((const char **)0 != ppcHelmet && (const char *)0 != ppcHelmet[2]) {
		if (-1 == pipe(aiPipe)) {
			fatal("pipe: %s", strerror(errno));
		}
		switch (wPid = fork()) {
		case -1:
			fatal("fork: %s", strerror(errno));
			/*NOTREACHED*/
		case 0:
			/* Check the helmet to make sure we're safe to ride.
			 * Let's not give the checker our special redirections.
			 */
			(void)close(0);
			(void)open(acDevNull, O_RDONLY, 0666);
			(void)dup2(aiPipe[1], 1);
			(void)close(aiPipe[0]);
			if (-1 != iSecFd) {
				close(iSecFd);
			}
			if (-1 != iDirFd) {
				close(iDirFd);
			}
			for (i = 0; i < 3; ++i) {
				if (-1 != aiNewIO[i])
					close(aiNewIO[i]);
			}
			/* When we drop uid the client can ptrace us to mess
			 * with the return code: so we can't do that.  Bummer.
			 * Never drop uid to the original mortal in a helmet.
			 */
			execve(ppcHelmet[2], (char **)(ppcHelmet+2), (char **)ppcNewEnv);
			fprintf(stderr, "%s: execve: %s: %s\n", Progname, ppcHelmet[2], strerror(errno));
			exit(EX_UNAVAILABLE);
			/*NOTREACHED*/
		default:
			(void)close(aiPipe[1]);
			break;
		}
		if (0 != (iStatus = ExtInput(cmd, aiPipe[0], acOptHelmet, &ppcNewEnv, aiNewIO, pcSecPath))) {
			exit(iStatus);
		}
		close(aiPipe[0]);
		while (wPid != (wReap = wait(& iStatus))) {
			if (-1 == wReap) {
				fatal("wait: %s", strerror(errno));
			}
		}
		if (0 == iStatus) {
			/* access allowed */
		} else if (WIFEXITED(iStatus)) {
			exit(WEXITSTATUS(iStatus));
		} else {
			exit(iStatus);
		}
	}

	/* Let's start a detached process from op, under daemon.
	 */
	for (i = 0; i < 3; ++i) {
		if (-1 == aiNewIO[i] || i == aiNewIO[i])
			continue;
		if (iFdOldErr == i) {
			iFdOldErr = dup(i);
			(void)fcntl(iFdOldErr, F_SETFD, 1);
		}
		if (-1 == dup2(aiNewIO[i], i)) {
			RESTORE_STDERR();
			fatal("dup2: %d: %s", aiNewIO[i], strerror(errno));
		}
		close(aiNewIO[i]);
	}

	if ((char *)0 != FindOpt(cmd, acOptDaemon)) {
		switch ((i = fork())) {
		case -1:
			/* We really need the semantics of the double fork
			 */
			RESTORE_STDERR();
			fprintf(stderr, "%s: fork: %s\n", Progname, strerror(errno));
			exit(EX_UNAVAILABLE);
			/*NOTREACHED*/
		case 0:
			break;
		default:
			exit(EX_OK);
		}
#if defined(TIOCNOTTY)
		if (-1 != (i = open(acDevTty, 2))) {
			ioctl(i, TIOCNOTTY, 0);
			close(i);
		}
#else
		setsid();
#endif
	}

	/* Wrap ourself $jacket -P $pid helmet-args, the args slot for the
	 * pid is the helmet program path (or a NULL pointer), overwrite it.
	 */
	if ((const char **)0 != ppcHelmet && (const char *)0 != *ppcHelmet) {
		if (-1 == pipe(aiPipe)) {
			fatal("pipe: %s", strerror(errno));
		}
		switch ((wPid = fork())) {
		case -1:
			RESTORE_STDERR();
			fatal("fork: %s", strerror(errno));
			/*NOTREACHED*/
		case 0:
			(void)close(aiPipe[1]);
			if (0 != (iStatus = ExtInput(cmd, aiPipe[0], acOptJacket, &ppcNewEnv, aiNewIO, pcSecPath))) {
				exit(iStatus);
			}
			close(aiPipe[0]);
			break;
		default:
			if (-1 != iSecFd) {
				close(iSecFd);
			}
			if (-1 != iDirFd) {
				close(iDirFd);
			}
			dup2(aiPipe[1], 1);
			(void)close(aiPipe[1]);
			(void)close(aiPipe[0]);
			snprintf(acCvt, sizeof(acCvt), "%ld", (long)wPid);
			ppcHelmet[2] = acCvt;
			execve(*ppcHelmet, (char **)ppcHelmet, (char **)ppcNewEnv);
			RESTORE_STDERR();
			fprintf(stderr, "%s: execve: %s: %s\n", Progname, ppcHelmet[0], strerror(errno));
			exit(EX_UNAVAILABLE);
		}
	}
	if ((char *)0 != pcNewRoot && '\000' != *pcNewRoot && 0 > chroot(pcNewRoot)) {
		fatal("%s: %s: %s", acOptChroot, pcNewRoot, strerror(errno));
	}
	if ((char *)0 != pcNewDir && 0 > chdir(pcNewDir)) {
		fatal("chdir: %s: %s", pcNewDir, strerror(errno));
	}

	if ((const char *)0 != pcSetSession) {
#if USE_PAM
		auto struct pam_conv local_conv;
		auto int iStatus, iExit;
		auto pam_handle_t *pPCLocal;

		local_conv.appdata_ptr = (void *)cmd;
		local_conv.conv = OpConv;
		if (PAM_SUCCESS != (iStatus = pam_start(acDefPam, pcSetSession, &local_conv, &pPCLocal))) {
			RESTORE_STDERR();
			fatal("pam_start: %s: %s", pcSetSession, pam_strerror(pPCLocal, errno));
		}
		(void)pam_set_item(pPCLocal, PAM_TTY, acDevTty);
		(void)pam_set_item(pPCLocal, PAM_RHOST, acMyHost);
		(void)pam_set_item(pPCLocal, PAM_RUSER, pcPerp);
		switch (iStatus = pam_open_session(pPCLocal, PAM_SILENT)) {
		case PAM_SUCCESS:
			break;
		default:
			RESTORE_STDERR();
			fatal("%s: %s: %s", acOptSession, pcSetSession, pam_strerror(pPCLocal, iStatus));
			/*NOTREACHED*/
		}
		/* If you really need the session closed we have to fork,
		 * which I think is a stretch.  Sudo doesn't bother. -- ksb
		 */
		if ((char *)0 != pcEndSession) {
			switch ((wPid = fork())) {
			case -1:
				fatal("fork: %s", strerror(errno));
				/*NOTREACHED*/
			case 0:
				/* don't hold any input/output pipes
				 */
				if (0 == close(0))
					(void)open(acDevNull, O_RDWR, 0666);
				if (0 == close(1))
					(void)open(acDevNull, O_RDWR, 0666);
				iExit = 127;
				while (wPid != (wReap = wait(& iExit))) {
					if (-1 == wReap) {
						fatal("wait: %s", strerror(errno));
					}
				}
				if (0 != strcmp(pcPerp, pcEndSession))
					(void)pam_set_item(pPCLocal, PAM_USER, pcEndSession);
				iStatus = pam_close_session(pPCLocal, PAM_SILENT);
				(void)pam_end(pPCLocal, iStatus);

				/* PAM errors? The show is over, move along.
				 */
				exit(iExit);
			default:	/* child */
				break;
			}
		} else if (PAM_SUCCESS != (iStatus = pam_end(pPCLocal, iStatus))) {
			RESTORE_STDERR();
			fatal("pam_end: %s", pam_strerror(pPCLocal, iStatus));
		}
#else
		fatal("%s: no support on this host", acOptSession);
#endif
	}
#if OP_MAC_SUPPORT
	if ((char *)0 != pcNewMac) {
		auto mac_t macLabel;

		if (-1 == mac_prepare_process_label(& macLabel)) {
			fatal("-m: mac_prepare: %s", strerror(errno));
		}
		if (-1 == mac_from_text(&macLabel, pcNewMac)) {
			fatal("-m: %s: mac_from_text: %s", pcNewMac, strerror(errno));
		}
		if (-1 == mac_set_proc(macLabel)) {
			fatal("-m: %s: mac_set_proc: %s", pcNewMac, strerror(errno));
		}
	}
#endif
#if HAVE_setresuid
	if (setresuid(uReal, uEffective, uReal) < 0) {
		RESTORE_STDERR();
		fatal("setresuid: %ld,%ld,%ld: %s", (long)uReal, (long)uEffective, (long)uReal, strerror(errno));
	}
#else
	if (uReal == uEffective) {
		if (setuid(uReal) < 0) {
			RESTORE_STDERR();
			fatal("setuid: %ld: %s", (long)uReal, strerror(errno));
		}
	} else if (setreuid(uReal, uEffective) < 0) {
		RESTORE_STDERR();
		fatal("setreuid: %ld,%ld: %s", (long)uReal, (long)uEffective, strerror(errno));
	}
#endif
	syslog(((char *)0 == FindOpt(cmd, acOptNolog) ? LOG_INFO : LOG_NOTICE), "user %s SUCCEEDED to execute %s", pcPerp, pcCmd);
	if (0 == strcmp(pcExecPath, acEcho)) {
		static char acSpace[] = " ", acNL[] = "\n";
		register int fNFlag, iV;
		register struct iovec *pIO;

		if ((char *)0 != ppcNewArgs[1] && 0 == strcmp("-m", ppcNewArgs[1]))
			ppcNewArgs += 2, fNFlag = 1;
		else
			ppcNewArgs += 1, fNFlag = 0;

		for (i = 0; (char *)0 != ppcNewArgs[i]; ++i)
			/* count'm */;
		pIO = calloc(i*2+fNFlag, sizeof(struct iovec));
		iV = 0;
		for (i = 0; (char *)0 != ppcNewArgs[i]; ++i) {
			j = strlen(ppcNewArgs[i]);
			if (j > 1 && '\\' == ppcNewArgs[i][j-2] && 'c' ==  ppcNewArgs[i][j-1])
				j -= 2, fNFlag = 1;
			if ((struct iovec *)0 == pIO) {
				if ((i > 0 && -1 == write(1, acSpace, 1)) || -1 == write(1, ppcNewArgs[i], j))
					fatal("echo: write: %s", strerror(errno));
				continue;
			}
			if (i > 0) {
				pIO[iV].iov_base = acSpace;
				pIO[iV++].iov_len = 1;
			}
			pIO[iV].iov_base = (char *)ppcNewArgs[i];
			pIO[iV++].iov_len = j;
		}
		if (!fNFlag) {
			if ((struct iovec *)0 == pIO) {
				write(1, acNL, 1);
			} else {
				pIO[iV].iov_base = acNL;
				pIO[iV++].iov_len = 1;
			}
		}
		if ((struct iovec *)0 != pIO) {
			for (j = 0; j < iV; iV -= i, pIO += i) {
				i = iV > IOV_MAX ? IOV_MAX : iV;
				if (-1 == writev(1, pIO, i))
					fatal("echo: writev: %s", strerror(errno));
			}
		}
		return EX_OK;
	}
	if (aiNewIO[3] > -1) {
		for (i = getdtablesize()-1; i >= aiNewIO[3]; --i) {
			if (i == iFdOldErr)
				continue;
			close(i);
		}
	}
	execve(pcExecPath, (char **)ppcNewArgs, (char **)ppcNewEnv);
	ppcHold = environ;
	environ = (char **)ppcNewEnv;
	execvp(pcExecPath, (char **)ppcNewArgs);
	environ = ppcHold;
	RESTORE_STDERR();
#undef RESTORE_STDERR
	}

	/* Gads, after all that work we failed to execve!  We can't undo the
	 * syslog notice, but we should close the loop on missing binary files
	 * or bad perms in via log, assuming a human reads it.
	 */
	syslog(LOG_ERR, "%s:%d: execvp: %s: %s (user=%s)", cmd->pcfile, cmd->iline, pcExecPath, strerror(errno), pcPerp);
	fprintf(stderr, "%s: execvp: %s: %s\n", Progname, pcExecPath, strerror(errno));
	return EX_UNAVAILABLE;
}

/* The remains of the original main program from David Koblas, kinda
 */
static int
OldMain(const int argc, const char **argv)
{
	register int iFd, iArgs, iFdNull;
	register size_t iLen, iCredLen;
	register cmd_t *cmd;
	register struct passwd *pw;
	register char *pcEnv, *pcMem;
	auto const char *pcCred, *pcMay;
	auto const char *pcWrong;

	/* You can try, but we'll just open std{in,out,err} to /dev/null
	 */
	iFdNull = -1;
	for (iFd = 0; iFd < 3; ++iFd) {
		auto int iJunk;
		if (-1 == fcntl(iFd, F_GETFD, & iJunk)) {
			if (-1 == iFdNull)
				iFdNull = open(acDevNull, O_RDWR, 0666);
			else
				dup(iFdNull);
		}
	}

	/* We shouldn't ever get a NULL if mkcmd works
	 */
	if ((char *)0 == argv[0]) {
		fatal(acBadList);
	}
	if ((char *)0 == (pcEnv = getenv("USER")) || '\000' == *pcEnv) {
		pcEnv = getenv("LOGNAME");
	}
	if ((char *)0 != pcEnv && '\000' != *pcEnv && (struct passwd *)0 != (pw = getpwnam(pcEnv)) && pw->pw_uid == getuid()) {
		/* found them by their preferred name */
	} else if ((struct passwd *)0 == (pw = getpwuid(getuid()))) {
		fprintf(stderr, "%s: getpwuid: %ld: unmapped uid\n", Progname, (long)getuid());
		exit(EX_NOUSER);
	}
	if ((char *)0 == (pcPerp = strdup(pw->pw_name)) || (char *)0 == (pcPerpHome = strdup(pw->pw_dir)) || (char *)0 == (pcPerpShell = strdup(pw->pw_shell)))
		fatal("setup: %s", acBadAlloc);

	/* Set the globals for fatal error conditions
	 */
	if ((char *)0 != pcUid && (char *)0 != pcGid) {
		static char acTempl[] = " as %s:%s";

		iLen = ((sizeof(acTempl)+strlen(pcUid)+strlen(pcGid))|15)+1;
		pcMem = malloc(iLen);
		if ((char *)0 != pcMem)
			snprintf(pcMem, iLen, acTempl, pcUid, pcGid);
		pcCred = pcMem;
	} else if ((char *)0 != pcUid) {
		static char acTempl[] = " as login %s";

		iLen = ((sizeof(acTempl)+strlen(pcUid))|15)+1;
		pcMem = malloc(iLen);
		if ((char *)0 != pcMem)
			snprintf(pcMem, iLen, acTempl, pcUid);
		pcCred = pcMem;
	} else if ((char *)0 != pcGid) {
		static char acTempl[] = " as group %s";

		iLen = ((sizeof(acTempl)+strlen(pcGid))|15)+1;
		pcMem = malloc(iLen);
		if ((char *)0 != pcMem)
			snprintf(pcMem, iLen, acTempl, pcGid);
		pcCred = pcMem;
	} else {
		pcCred = "";
	}

	iCredLen = strlen(pcCred)+2;
	if ((char *)0 != pcSecFile) {
		iCredLen += strlen(pcSecFile)+4;	/* +strlen(" -f ") */
	}
	for (iArgs = 0; iArgs < argc; ++iArgs) {
		iCredLen += 1+strlen(argv[iArgs]);
	}
	iCredLen = (iCredLen|15)+17;
	if ((char *)0 == (pcMem = (char *)malloc(iCredLen))) {
		fatal(acBadAlloc);
	}
	pcMem[0] = '\000';
	for (iArgs = 0; iArgs < argc; ++iArgs) {
		(void) strcat(pcMem, " ");
		(void) strcat(pcMem, argv[iArgs]);
	}
	pcMem[0] = '\'';
	(void) strcat(pcMem, "'");
	(void) strcat(pcMem, pcCred);
	if ((char *)0 != pcSecFile) {
		(void) strcat(pcMem, " -f ");
		(void) strcat(pcMem, pcSecFile);
	}
	pcCmd = pcMem;

	pcWrong = acBadMnemonic;
	cmd = Find(argv[0], (long)argc, argv, & pcWrong);
	if ((cmd_t *)0 == cmd) {
		fatal("%s: %s", argv[0], pcWrong);
	}
	cmd = Build(cmd);

	if ((char *)0 == (pcMay = Verify(cmd, argc, argv, &pcWrong))) {
		fatal("%s: %s", argv[0], pcWrong);
	}
	exit(Go(cmd, argc, argv, pcMay));
	/*NOTREACHED*/
}

#if SENTINEL
/* A sentinel directory must be named for a group (and owned by it)	(ksb)
 */
static int
AnySentinel(const struct dirent *pDIDir)
{
	return (struct group *)0 != getgrnam(pDIDir->d_name);
}
#endif

static char
	*pcExtender,		/* ".cf" usually */
	*pcPrimary;		/* "access.cf" usually */

/* Find all the files with the same extender as the primary config.	(ksb)
 */
static int
AnyConfig(const struct dirent *pDIThis)
{
	register const char *pcDot;

	if ((const char *)0 != (pcDot = strrchr(pDIThis->d_name, '.'))) {
		if ((char *)0 == pcExtender)
			return 0;
		return 0 == strcmp(pcDot, pcExtender);
	}
	return (char *)0 == pcExtender;
}

/* Qsort so the first config file read is the primary one: this puts	(ksb)
 * the access.cf DEFAULT for all at the head of the list, if there is
 * one at the top of that file.
 */
static int
PickPrimary(const void *pvLeft, const void *pvRight)
{
	register const char *pcLeft, *pcRight;

	pcLeft = ((const struct dirent **)pvLeft)[0]->d_name;
	pcRight = ((const struct dirent **)pvRight)[0]->d_name;
	if ((const char *)0 != pcPrimary) {
		if (0 == strcmp(pcLeft, pcPrimary))
			return -1;
		if (0 == strcmp(pcRight, pcPrimary))
			return 1;
	}
	return strcmp(pcLeft, pcRight);
}

/* Read the whole config file via the parser.				(ksb)
 * Side-effect set pcAccess for $c/$C and Version.
 */
static int
ReadFile(const char *pcTemplate)
{
	register char *pcSlash, *pcFile;
	register int iCount, iErr, fd;
	auto int uPriv;
	auto struct dirent **ppDI;
	auto char acPath[MAXPATHLEN+4];
	auto struct stat stFile;
#if SENTINEL
	auto struct stat stSentDir;
#endif

	if (strlen(pcTemplate) > sizeof(acPath)) {
		syslog(LOG_ERR, "configuration path too long (%s)", pcTemplate);
		fatal("open: %s: %s", pcTemplate, strerror(ENAMETOOLONG));
		/*NOTREACHED*/
	}
	uPriv = geteuid();

	/* look for other *.cf in that directory -- ksb
	 */
	if ((char *)0 == (pcSlash = strrchr(strcpy(acPath, pcTemplate), '/')) || acPath == pcSlash) {
		syslog(LOG_ERR, "configuration filename missing slash: %s", acPath);
		fatal("Configuration file path should be absolute (%s)", acPath);
	}
	*pcSlash = '\000';
	pcPrimary = pcSlash+1;
	pcExtender = strrchr(pcPrimary, '.');
#if SENTINEL
	{
	/* Allow the superuser to make alias for op that manage groups.
	 * The group is represented by a directory next to access.cf that
	 * is named for a group entry in /etc/group, and must be grouped
	 * to that gid.  The configuration is read from that directory and
	 * the effective gid of op becomes that group (for $b/$B).
	 * Yes, the sentinel can be a symbolic link to a dir, but it cannot
	 * be named "OLD".  The new effective uid is the owner of the dir,
	 * or set to -1 to indicate no sentinel found.
	 */
	register struct group *grp;
	auto char acSentDir[MAXPATHLEN+4];

	stSentDir.st_uid = -1;
	if (0 == getuid() || 0 == geteuid() || getegid() != getgid()) {
		setgrent();
		iCount = scandir(acPath, & ppDI, (void *)AnySentinel, (void *)0);
		for (/* above */; iCount-- > 0; ++ppDI) {
			if (0 == strcmp(ppDI[0]->d_name, "OLD"))
				continue;
			snprintf(acSentDir, sizeof(acSentDir), "%s/%s", acPath, ppDI[0]->d_name);
			if (-1 == stat(acSentDir, &stSentDir) || (struct group *)0 == (grp = getgrnam(ppDI[0]->d_name)) || grp->gr_gid != stSentDir.st_gid || 0 != (stSentDir.st_mode&SENTINEL_MODE_MASK)) {
				if (fSanity) {
					fprintf(stderr, "%s: %s: sentinel directory permissions fail sanity check\n", Progname, acSentDir);
				}
				stSentDir.st_uid = -1;
				continue;
			}
			/* /usr/local/lib/op/op could even be a sentinel,  if
			 * there is a group named "op" -- don't do that.
			 */
			if (0 != strcmp(Progname, ppDI[0]->d_name)) {
				stSentDir.st_uid = -1;
				continue;
			}
#if HAVE_setresgid
			if (-1 == setresgid(-1, stSentDir.st_gid, stSentDir.st_gid))
				fatal("sentinel: setresgid: -1,%ld,%ld: %s", (long)stSentDir.st_gid, (long)stSentDir.st_gid, strerror(errno));
#else
			if (-1 == setegid(stSentDir.st_gid))
				fatal("sentinel: setegid: %ld: %s", (long)stSentDir.st_gid, strerror(errno));
#endif
			uPriv = stSentDir.st_uid;
			pcFile = acSentDir+strlen(acSentDir);
			*pcFile++ = '/';
			*pcFile = '\000';
			strncat(acSentDir, pcPrimary, sizeof(acSentDir));
			if (0 != strcmp(pcPrimary, pcFile))
				fatal("sentinel: path too long");
			pcSlash = strrchr(strcpy(acPath, acSentDir), '/');
			*pcSlash = '\000';
			pcPrimary = pcSlash+1;
			pcExtender = strrchr(pcPrimary, '.');
			break;
		}
		endgrent();
	}
	}
#endif
	iCount = scandir(acPath, & ppDI, (void *)AnyConfig, PickPrimary);
	if (iCount < 1 || 0 != strcmp(pcPrimary, ppDI[0]->d_name)) {
		*pcSlash = '/';
		syslog(LOG_ERR, "missing main configuration %s", acPath);
		fatal("stat: %s: %s", acPath, strerror(ENOENT));
	}
	if (uPriv == getuid())
		uPriv = -1;
	while (iCount-- > 0) {
		snprintf(pcSlash, &acPath[sizeof(acPath)]-pcSlash, "/%s", ppDI[0]->d_name);
		if ((char *)0 == (pcFile = strdup(acPath)))
			fatal("configuration: %s", acBadAlloc);
		if ((const char *)0 == pcAccess)
			pcAccess = pcFile;
		++ppDI;

		if (-1 == (fd = open(pcFile, O_RDONLY))) {
			iErr = errno;
			syslog(LOG_ERR, "open: %s: %s", pcFile, strerror(errno));
			fatal("open: %s: %s", pcFile, strerror(iErr));
		}
		if (-1 == fstat(fd, &stFile) ||
			(-1 != uPriv && stFile.st_uid != uPriv) ||
			((stFile.st_mode & 0037) != 0)) { /* no perm */
			syslog(LOG_ERR, "Permission problems on %s", pcFile);
			fatal("Permission problems on %s", pcFile);
		}

		OneFile(fd, pcFile);
		(void)close(fd);
	}
#if SENTINEL
	if (-1 != stSentDir.st_uid && -1 == seteuid(stSentDir.st_uid)) {
		fatal("sentinel: seteuid: %ld: %s", (long)stSentDir.st_uid, strerror(errno));
	}
#endif
	/* We don't free the scandir result, but we execve soon enough
	 */
	return 0;
}
