/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *	Some functions Copyright 1991-2001 Matti Aarnio.
 */

/*
 * The routines in this file implement various C-coded functions that
 * are callable from the configuration file.
 */

#include "mailer.h"
#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <ctype.h>
#include <fcntl.h>
#include <sys/file.h>			/* O_RDONLY for run_praliases() */
#include <pwd.h>			/* for run_homedir() */
#include <grp.h>			/* for run_grpmems() */
#include <errno.h>
#include <sysexits.h>

#include "zmsignal.h"
#include "zsyslog.h"
#include "mail.h"
#include "interpret.h"
#include "io.h"
#include "libz.h"
#include "libc.h"

#include "prototypes.h"

#ifndef	_IOFBF
#define	_IOFBF	0
#endif	/* !_IOFBF */

extern conscell *s_value;


/* The builtin functions are declared and initialized here.  */

#define ARGCV __((int argc, const char *argv[]))
static int run_hostname    ARGCV;
static int run_whataddress ARGCV;
static int run_iserrormsg  ARGCV;
static int run_erraddrlog  ARGCV;

static conscell *run_dblookup __((conscell *avl, conscell *il));
extern conscell *sh_car       __((conscell *avl, conscell *il));
static conscell *run_cadr     __((conscell *avl, conscell *il));
static conscell *run_caddr    __((conscell *avl, conscell *il));
static conscell *run_cadddr   __((conscell *avl, conscell *il));
static conscell *run_listexpand   __((conscell *avl, conscell *il));
#if 0
static conscell *run_newattribute __((conscell *avl, conscell *il));
#endif

extern int run_doit      ARGCV;
extern int run_stability ARGCV;
extern int run_process   ARGCV;

static int run_grpmems   ARGCV;
#if 0 /* dead code */
static int run_praliases ARGCV;
#endif
static int zap_DSN_notify ARGCV;
static int post_zap_DSN_notify ARGCV;
static int run_homedir   ARGCV;
static int run_822date   ARGCV;
static int run_filepriv  ARGCV;
static int run_runas     ARGCV;
static int run_cat       ARGCV;
static int run_gensym    ARGCV;
static int run_uid2login ARGCV;
static int run_login2uid ARGCV;
static int run_basename  ARGCV;
static int run_recase    ARGCV;
static int run_squirrel  ARGCV;
static int run_822syntax ARGCV;
static int run_condquote ARGCV;
static int run_dequote   ARGCV;
static int run_syslog    ARGCV;

#if	defined(XMEM) && defined(CSRIMALLOC)
static int run_malcontents ARGCV;
#endif	/* CSRIMALLOC */

extern const char *traps[];
extern int nobody;
extern time_t time __((time_t *));
extern int routerdirloops;

#ifndef strchr
extern char *strchr(), *strrchr();
#endif

struct shCmd fnctns[] = {
{	"relation",	run_relation,	NULL,	NULL,	0	},
{	DBLOOKUPNAME,	NULL,	run_dblookup,	NULL,	SH_ARGV	},
/* The following are optional but are probably a good idea */
{	"db",		run_db,		NULL,	NULL,	0	},
{	"trace",	run_trace,	NULL,	NULL,	0	},
{	"untrace",	run_trace,	NULL,	NULL,	0	},
{	"hostname",	run_hostname,	NULL,	NULL,	0	},
{	"iserrormsg",	run_iserrormsg,	NULL,	NULL,	0	},
{	"sender",	run_whataddress,NULL,	NULL,	0	},
{	"recipient",	run_whataddress,NULL,	NULL,	0	},
{	"erraddron",	run_erraddrlog,	NULL,	NULL,	0	},
{	"channel",	NULL,		sh_car,	NULL,	SH_ARGV	},
{	"host",		NULL,	run_cadr,	NULL,	SH_ARGV	},
{	"user",		NULL,	run_caddr,	NULL,	SH_ARGV	},
{	"attributes",	NULL,	run_cadddr,	NULL,	SH_ARGV	},
{	"stability",	run_stability,	NULL,	NULL,	0	},
{	"stableprocess", run_doit,	NULL,	NULL,	0	},
{	"daemon",	run_daemon,	NULL,	NULL,	0	},
{	"process",	run_process,	NULL,	NULL,	0	},
{	"rfc822",	run_rfc822,	NULL,	NULL,	0	},
{	"groupmembers",	run_grpmems,	NULL,	NULL,	0	},
#if 0
{	"printaliases",	run_praliases,	NULL,	NULL,	0	},
#endif
{	"zapDSNnotify", zap_DSN_notify,	NULL,	NULL,	SH_ARGV	},
{	"postzapDSNnotify", post_zap_DSN_notify, NULL, NULL, SH_ARGV },
{	"listexpand",	NULL,	run_listexpand,	NULL,	SH_ARGV	},
#if 0
{	"newattribute",	NULL,	run_newattribute, NULL,	SH_ARGV	},
#endif
{	"homedirectory",run_homedir,	NULL,	NULL,	0	},
{	"rfc822date",	run_822date,	NULL,	NULL,	0	},
{	"filepriv",	run_filepriv,	NULL,	NULL,	SH_ARGV	},
{	"runas",	run_runas,	NULL,	NULL,	SH_ARGV	},
{	"cat",		run_cat,	NULL,	NULL,	SH_ARGV	},
{	"gensym",	run_gensym,	NULL,	NULL,	0	},
{	"uid2login",	run_uid2login,	NULL,	NULL,	0	},
{	"login2uid",	run_login2uid,	NULL,	NULL,	0	},
{	"basename",	run_basename,	NULL,	NULL,	0	},
{	"recase",	run_recase,	NULL,	NULL,	0	},
{	"squirrel",	run_squirrel,	NULL,	NULL,	0	},
{	"rfc822syntax",	run_822syntax,	NULL,	NULL,	0	},
{	"dequote",	run_dequote,	NULL,	NULL,	0	},
{	"condquote",	run_condquote,	NULL,	NULL,	0	},
{	"syslog",	run_syslog,	NULL,	NULL,	0	},
{	"logger",	run_syslog,	NULL,	NULL,	0	},
#if	defined(XMEM) && defined(CSRIMALLOC)
{	"malcontents",	run_malcontents,NULL,	NULL,	0	},
#endif	/* CSRIMALLOC */
/* The rest have been added locally */
{ NULL, NULL, NULL, NULL, 0 }
};

int		funclevel = 0;

int		D_sequencer = 0;
int		D_hdr_rewrite = 0;
int		D_router = 0;
int		D_functions = 0;
int		D_compare = 0;
int		D_matched = 0;
int		D_assign = 0;
int		D_final = 0;
int		D_db = 0;
int		D_alias = 0;
int		D_bind = 0;
int		D_resolv = 0;
int		D_alloc = 0;
int		D_regnarrate = 0;
extern int	D_rfc822;

static struct debugind {
	const char	*name;
	int		*indicator;
} buggers[] = {
	{	"assign",		&D_assign	},
	{	"bind",			&D_bind		},
	{	"compare",		&D_compare	},
	{	"db",			&D_db		},
	{	"except",		0		},
	{	"final",		&D_final	},
	{	"functions",		&D_functions	},
	{	"matched",		&D_matched	},
	{	"memory",		&D_alloc	},
	{	"on",			&D_functions	},	/* dup */
	{	"regexp",		&D_regnarrate	},
	{	"resolv",		&D_resolv	},
	{	"rewrite",		&D_hdr_rewrite	},
	{	"rfc822",		&D_rfc822	},
	{	"router",		&D_router	},
	{	"sequencer",		&D_sequencer	},
	{	NULL,			0		}
};

/* The builtin trace function. This is also used by command line debug specs */

int
run_trace(argc, argv)
	int argc;
	const char *argv[];
{
	struct debugind *dbi;
	int debug;
	const char *prog;
	int rc = 0;

	if (argc == 1) {
		fprintf(stderr, "Usage: %s all", argv[0]);
		for (dbi = &buggers[0]; dbi->name != NULL; ++dbi)
			fprintf(stderr, "|%s", dbi->name);
		putc('\n', stderr);
		return EX_USAGE;
	}
	prog = argv[0];
	debug = (strncmp(*argv, "un", 2) != 0);
	while (--argc > 0) {
		++argv;
		if (STREQ(*argv, "off")  ||  STREQ(*argv, "all")) {
			for (dbi = &buggers[0]; dbi->name != NULL; ++dbi)
			    if (dbi->indicator)
				*(dbi->indicator) = (**argv == (debug?'a':'o'));
			continue;
		} else {
			for (dbi = &buggers[0]; dbi->name != NULL; ++dbi) {
				if (STREQ(*argv, dbi->name)) {
					if (dbi->indicator == NULL)
					  debug = !debug; /* except */
					else
					  *(dbi->indicator) = debug;
					break;
				}
			}
		}
		if (dbi->name == NULL) {
			fprintf(stderr, "%s: unknown attribute: %s\n",
					prog, *argv);
			rc = EX_USAGE;
		}
	}
	return rc;
}


int gensym;
const char * const gs_name = "g%d";

static int
run_gensym(argc, argv)
	int argc;
	const char *argv[];
{
	printf(gs_name, gensym++);
	putchar('\n');
	return 0;
}

extern void free_gensym __((void));
void
free_gensym()
{
	int i;
	char buf[30];

	for (i=0; i<gensym; ++i) {
		sprintf(buf, gs_name, i);
		v_purge(buf);
	}
}


/* hostname -- get system hostname or set my idea of the hostname */

static int
run_hostname(argc, argv)
	int argc;
	const char *argv[];
{
	static char hostname[128];

	if (argc > 1) {
		memtypes oval = stickymem;
		stickymem = MEM_MALLOC;
		myhostname = strdup(argv[1]);
		stickymem = oval;
	} else {
		/* can't fail... */
		getmyhostname(hostname, sizeof hostname);
		printf("%s\n", hostname);
	}
	return 0;
}

/*
 * senderaddress/recipientaddress -- find out whether the current header
 * address being rewritten is a sender or a recipient address
 */

static int
run_whataddress(argc, argv)
	int argc;
	const char *argv[];
{
	static int toggle = 0;

	if (isSenderAddr == isRecpntAddr /* == 0 */) {
		fprintf(stderr,
	"Should not call '%s' outside header address rewriting function!\n",
			argv[0]);
		toggle = !toggle;
		return toggle;	/* pseudorandom :-) */
	} else if (argc > 1)
		fprintf(stderr, "Usage: %s\n", argv[0]);
	if (argv[0][0] == 's')		/* called as 'senderaddress' */
		return isSenderAddr ? 0 : 1;
	return isRecpntAddr ? 0 : 1;
}

/*
 * $(iserrormsg dummyargs)  returns a flag 
 */

static int
run_iserrormsg(argc, argv)
	int argc;
	const char *argv[];
{
	return !isErrorMsg;
}

/*
 * this is like accton(), but for logging errors in addresses.
 */

char *erraddrlog;

static int
run_erraddrlog(argc, argv)
	int argc;
	const char *argv[];
{
	switch (argc) {
	case 1:
		if (erraddrlog)
			free(erraddrlog);
		erraddrlog = NULL;
		break;
	case 2:
		erraddrlog = smalloc(MEM_PERM, strlen(argv[1])+1);
		strcpy(erraddrlog, argv[1]);
		break;
	default:
		fprintf(stderr, "Usage: %s [ /path ]\n", argv[0]);
		return EX_USAGE;
	}
	return 0;
}


/*
 * Interface to databases; the relation function arranges to attach this
 * function to all database function definitions.  It is called as
 *	database key
 * and is expected to act like a normal function (i.e. print value on stdout).
 */

static conscell *
run_dblookup(avl, il)
	conscell *avl, *il; /* Inputs gc protected */
{
	conscell *l;
	const char *argv30[30];
	int i;

	memset(argv30, 0, sizeof(argv30));

	il = cdar(avl);
	if (il == NULL || !STRING(il)) {
		fprintf(stderr, "Usage: %s key [up_to_19_substitution_elements_or_options]\n", car(avl)->string);
		return NULL;
	}
	i = 0;
	for (; il && i < 30-1 && STRING(il); il = cdr(il))
	  argv30[i++] = il->string;

	l = dblookup(car(avl)->string, i, argv30);
	if (l == NULL)
		return NULL;
	return l;
}

static conscell *
run_cadr(avl, il)
	conscell *avl, *il; /* Inputs gc protected */
{
	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	car(il) = cdar(il);

	/* car */
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	return car(il);
}

static conscell *
run_caddr(avl, il)
	conscell *avl, *il; /* Inputs gc protected */
{
	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	car(il) = cdar(il);

	/* cdr */
	car(il) = cdar(il);

	/* car */
	/* setf preparation */
	if (car(il) == NULL) {
		return il;
	}
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	return car(il);
}

static conscell *
run_cadddr(avl, il)
	conscell *avl, *il; /* Inputs gc protected */
{
	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	car(il) = cdar(il);

	/* cdr */
	car(il) = cdar(il);

	/* cdr */
	car(il) = cdar(il);

	/* car */
	/* setf preparation */
	if (car(il) == NULL) {
		return il;
	}
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	return car(il);
}


/*
 * Print a list of the members of a group.
 */

static int
run_grpmems(argc, argv)
	int argc;
	const char *argv[];
{
	const char **cpp;
	struct Zgroup *grp;

	if (argc != 2) {
		fprintf(stderr, "Usage: %s groupname\n", argv[0]);
		return EX_USAGE;
	}
	grp = zgetgrnam(argv[1]);
	if (grp == NULL) {
		fprintf(stderr, "%s: no group '%s'\n", argv[0], argv[1]);
		return 1;
	}
	for (cpp = grp->gr_mem; *cpp != NULL ; ++cpp)
		printf("%s\n", *cpp);
	endgrent();
	return 0;
}

static struct headerinfo aliashdr = {
	"aliases", AddressList, Recipient, normal
};

#if 0 /* dead code, 'zmailer newdb' does these differently... */
static int
run_praliases(argc, argv)
	int argc;
	const char *argv[];
{
	register struct header *h;
	struct envelope *e;
	const char *cp;
	int errflg, count, status;
	long offset, size, prevsize, maxsize;
	FILE *indexfp;
	int c, verbose;
	const char *indexfile;
	char buf[8192], ibuf[BUFSIZ];
	struct siobuf *osiop = NULL;
	int tabsep = 0;


	verbose = 0;
	indexfile = NULL;
	zoptind = 1;
	errflg = 0;
	while (1) {
		c = zgetopt(argc, (char*const*)argv, "vo:t");
		if (c == EOF)
			break;
		switch (c) {
		case 'v':
			++verbose;
			break;
		case 'o':
			indexfile = zoptarg;
			break;
		case 't':
			tabsep = 1;
			break;
		default:
			++errflg;
			break;
		}
	}
	if (errflg || zoptind != argc - 1) {
		fprintf(stderr,
			"Usage: %s [ -v ] [ -o indexoutputfile ] aliasfile\n",
			argv[0]);
		return EX_USAGE;
	}

	e = (struct envelope *)tmalloc(sizeof (struct envelope));
	if ((e->e_fp = fopen(argv[zoptind], "r")) == NULL) {
		c = errno;
		fprintf(stderr, "%s: open(\"%s\"): ", argv[0], argv[zoptind]);
		errno = c;
		perror("");
		status = PERR_BADOPEN;
	} else {
		setvbuf(e->e_fp, buf, _IOFBF, sizeof buf);
		osiop = siofds[FILENO(e->e_fp)];
		siofds[FILENO(e->e_fp)] = NULL;
		e->e_file = argv[zoptind];
		status = makeLetter(e, 1);	/* Parse the aliases database
						   as if all entries were of
						   same syntax as "To:" et.al.
						   on the normal email header*/
		siofds[FILENO(e->e_fp)] = osiop;
		fclose(e->e_fp);
		e->e_fp = NULL;
	}

	if (status != 0) {
		fprintf(stderr, "%s: format error!\n", argv[0]);
		return 2;
	}

	for (h = e->e_headers; h != NULL; h = h->h_next) {
		h->h_descriptor = &aliashdr;
		h->h_contents = hdr_scanparse(e, h, 1, 0);
		h->h_stamp = hdr_type(h);
		if (h->h_stamp == BadHeader) {
			hdr_errprint(e, h, stderr, "alias expansion");
			++errflg;
		} else if (h->h_contents.a == NULL) {
			fprintf(stderr, "%s: null alias '%s'\n", argv[0],
					h->h_pname);
			++errflg;
		}
	}

	if (errflg) {
		fprintf(stderr,
			"%s: aborted after detecting %d syntax error%s\n",
			argv[0], errflg, errflg == 1 ? "" : "s");
		return 3;
	}

	/* store all aliases in canonical lowercase */
	for (h = e->e_headers, count = 0; h != NULL; h = h->h_next, ++count) {
	  strlower((char*)h->h_pname);
	}

	if (count <= 0) {
		fprintf(stderr, "%s: no aliases found!\n", argv[0]);
		return 4;
	}

	if (indexfile != NULL) {
		if ((indexfp = fopen(indexfile, "w")) == NULL) {
			c = errno;
			fprintf(stderr, "%s: open(\"%s\"): ",
					argv[0], indexfile);
			errno = c;
			perror("");
			return 5;
		}
		setvbuf(indexfp, ibuf, _IOFBF, sizeof ibuf);
		osiop = siofds[FILENO(indexfp)];
		siofds[FILENO(indexfp)] = NULL;
	} else
		indexfp = NULL;
	maxsize = size = 0;
	cp = ":";	/* anything that can't be an alias */
	for (h = e->e_headers ; h != NULL ; h = h->h_next) {
		offset = 2 + strlen(h->h_pname);
		/* offset += offset >= 7 ? 1 : 8 - offset%8; */
		prevsize = size;
		size = ftell(stdout);
		if (size - prevsize > maxsize)
			maxsize = size - prevsize;
		if (*cp == *(h->h_pname) && STREQ(cp, h->h_pname)) {
			fprintf(stderr, "%s: multiple definitions of '%s'.\n",
					argv[0], h->h_pname);
			cp = ":";
		}
		prevsize = size + offset; /* prevsize is convenient scratch */
		if (indexfp != NULL) {
			if (fprintf(indexfp, "%s\t%ld\n",
					     h->h_pname, prevsize) == EOF)
				++errflg;
			if (errflg)
				break;
		}
		cp = h->h_pname;
		hdr_print(h, stdout);
	}
	if (verbose) {
		prevsize = size;
		size = ftell(stdout);
		if (size - prevsize > maxsize)
			maxsize = size - prevsize;
		fprintf(stderr,
			"%d aliases, longest %ld bytes, %ld bytes total\n",
			count, maxsize, size);
	}
	if (fflush(stdout) == EOF)
		++errflg;
	if (indexfp != NULL) {
		siofds[FILENO(indexfp)] = osiop;
		if (fclose(indexfp) == EOF)
			++errflg;
	}
	if (errflg)
		fprintf(stderr, "%s: I/O error while writing output!\n",
				argv[0]);
	return errflg;
}
#endif /* ... dead code */

#ifdef DEBUG_FOPEN
void
report_fds(fp)
FILE *fp;
{
	int fdnro;
	int lastfd = getdtablesize();
	fd_set fdset;
	struct timeval tv;
	extern int errno;

	fprintf(fp,"File handles: %d/",lastfd);

	for (fdnro = 0; fdnro < lastfd; ++fdnro) {
	  char ss[4];
	  char *s = ss;
	  *s = 0;
	  tv.tv_sec = 0;
	  tv.tv_usec = 0;
	  FD_ZERO(&fdset);
	  FD_SET(fdnro,&fdset);
	  errno = 0;
	  if (select(lastfd,&fdset,NULL,NULL,&tv) != -1)
	    *s++ = 'r';
	  tv.tv_sec = 0;
	  tv.tv_usec = 0;
	  FD_ZERO(&fdset);
	  FD_SET(fdnro,&fdset);
	  if (select(lastfd,NULL,&fdset,NULL,&tv) != -1)
	    *s++ = 'w';
	  *s = 0;
	  if (ss[0] != 0)
	    fprintf(fp," %d%s",fdnro,ss);
	}
	fprintf(fp,"\n");
	fflush(fp);
}
#endif


/*
 *  zap_DSN_notify()
 *
 *  To be called immediately after a new nattr value has been generated,
 *  and thus a new storage variable for it exists!
 *
 *  scrubbedset=$(zapDSNnotify attrlistvarname [diagname finalsuccessrcpt])
 *

 +
 +  Damn...  The issues here are rather complicated.
 +  These routines will be suspended for a while pending further
 +  analysis.  [mea/10-sept-2000]
 +

 */
static int zap_DSN_notify(argc, argv)
     int argc;
     const char *argv[];
{
#if 1
	return 0;
#else
	conscell *l, *lc, **pl;
	conscell *l1, *tmp;
	int notifysuccess = 0;
	int notifytrace   = 0;
	int len;
	char *s;
	const char *onam             = argv[1];
	const char *diagname         = argv[2];
	const char *finalsuccessrcpt = argv[3];
	const char *diagaddr         = argv[4];

	if (!diagname)         finalsuccessrcpt = NULL;
	if (!finalsuccessrcpt) diagaddr         = NULL;

	l1 = v_find(onam);
	if (!l1) return 0;
	l = cdr(l1);
	lc = l1 = NULL;

	pl = &car(l);
	l = *pl;
	for (lc = l; lc && cdr(lc); pl = &cddr(lc),lc = *pl) {
	  if (!STRING(lc))
	    return 0; /* ?? */
	  if (STREQ("DSN",lc->string)) {
	    lc = cdr(lc);
	    if (!lc || !STRING(lc))
	      return 0;
	    break;
	  }
	}
	if (!lc) return 0; /* No DSN data */

	s = lc->string;

	if (D_sequencer)
	  fprintf(stderr," zapDSNnotify('%s') DSN='%s'  -> ", onam, s);

	while (*s) {
	  if (CISTREQN(s,"NOTIFY=",7)) {
	    char *p = s+7;
	    /* While non-blank (parameter) string */
	    while (*p && *p != ' ' && *p != '\t') {
	      if (*p == ',') {
		++p;
		continue;
	      }
	      if (CISTREQN(p,"SUCCESS",7)) {
		notifysuccess=1;
		if (p[7] == ',')
		  strcpy(p,p+8); /* ZAP the "SUCCESS," status! */
		else
		  strcpy(p,p+7); /* ZAP the "SUCCESS" status! */
		continue;
	      }
	      if (CISTREQN(p,"TRACE",5)) {
		notifytrace = 1;
		if (p[5] == ',')
		  strcpy(p,p+6); /* ZAP the "TRACE," status! */
		else
		  strcpy(p,p+5); /* ZAP the "TRACE" status! */
		continue;
	      }
	      ++p;
	    }
	    /* Ok,  Now we may have e.g.:
	         i1: NOTIFY=NEVER
	         o1: NOTIFY=NEVER
		 i2: NOTIFY=SUCCESS,DELAY,FAILURE
		 o2: NOTIFY=DELAY,FAILURE
		 i3: NOTIFY=DELAY,SUCCESS,FAILURE
		 o3: NOTIFY=DELAY,FAILURE
		 i4: NOTIFY=DELAY,FAILURE,SUCCESS
		 o4: NOTIFY=DELAY,FAILURE,
		 i5: NOTIFY=SUCCESS
		 o5: NOTIFY=
	       of which the o4 needs trailing comma cleanup,
	       and o5 zapping of the entire NOTIFY parameter. */

	    if (p > s && p[-1] == ',') /* Case 4 */
	      p[-1] = ' '; /* An extra comma to zap */

	    if (p > s && p[-1] == '=') /* Case 5 */ {
	      while (*p == ' ' || *p == '\t') ++p;
	      strcpy(s, p); /* That the entire NOTIFY= parameter */
	      continue;
	    }

	    /* Skip over trailing whitespace */
	    while (*p == ' ' || *p == '\t') ++p;
	    s = p;
	    continue; /* This is done, restart */
	  } /* end of  if ("NOTIFY=") */

	  /* Nothing recognized, scan over the non-blank string */
	  while (*s && *s != ' ' && *s != '\t') ++s;
	  /* And possible trailing whitespace */
	  while (      *s == ' ' || *s == '\t') ++s;
	}

	if (D_sequencer)
	  fprintf(stderr,"'%s'  rc=%d\n", lc->string, notifysuccess);

	if (!notifysuccess && !notifytrace) return 0; /* We are DONE! */

	/* Now depending are we handling a mailing-list expansion, or
	   an alias expansion (RFC 1891, 6.2.7.*) we either report
	   "delivered" to the list input address, OR "expanded"
	   for aliases and .forwards (single recipient ones should
	   *not* report anything but rewrite the recipient address,
	   but we slip at that..) */

	s = lc->string;
	len = strlen(s);

	if (diagname)         len += strlen(diagname)+2;
	if (finalsuccessrcpt) len += strlen(finalsuccessrcpt)+2;
	if (diagaddr)         len += strlen(diagaddr)+2;

	s = (char *)realloc(s, len+23);
	if (!s) return -1; /* Hardly happens, but... */

	lc->string = s;

	s += len;

	if (notifysuccess && notifytrace)
	  strcpy(s, " NTRACE=SUCCESS,TRACE");
	else if (notifysuccess)
	  strcpy(s, " NTRACE=SUCCESS");
	else if (notifytrace)
	  strcpy(s, " NTRACE=TRACE");

	if (notifysuccess) {
	  s += strlen(s);
	  if (diagname)         sprintf(s,";%s", diagname);
	  s += strlen(s);
	  if (finalsuccessrcpt) sprintf(s,";%s", finalsuccessrcpt);
	  s += strlen(s);
	  if (diagaddr)         sprintf(s,";%s", diagaddr);
	}

	return notifysuccess;
#endif
}


/*
 *  post_zap_DSN_notify()
 *
 *  To be called immediately after a new nattr value has been generated,
 *  and thus a new storage variable for it exists!
 *
 *  $(postzapDSNnotify chainvarname)
 *
 *  If the variable called  chainvarname  has more than one address
 *  entry -- ( ((x x x x))  ((x x x x)) ) -- then we leave this NTRACE=
 *  data to be as is.  If the variable has only ONE address ( ((x x x x)) )
 *  AND the ...............

 +
 +  Damn...  The issues here are rather complicated.
 +  These routines will be suspended for a while pending further
 +  analysis.  [mea/10-sept-2000]
 +

 */
static int post_zap_DSN_notify(argc, argv)
     int argc;
     const char *argv[];
{
#if 1
	return 0;
#else
	conscell *l, *lc, **pl;
	conscell *l1, *tmp, *l0;
	int notifysuccess = 0;
	int notifytrace   = 0;
	int len;
	char *s;
	const char *onam = argv[1];

	l0 = v_find(onam);
	if (!l0) return 0;

	l = cdr(l1);
	lc = l1 = NULL;

	pl = &car(l);
	l = *pl;
	for (lc = l; lc && cdr(lc); pl = &cddr(lc),lc = *pl) {
	  if (!STRING(lc))
	    return 0; /* ?? */
	  if (STREQ("DSN",lc->string)) {
	    lc = cdr(lc);
	    if (!lc || !STRING(lc))
	      return 0;
	    break;
	  }
	}
	if (!lc) return 0; /* No DSN data */

	s = lc->string;

	if (D_sequencer)
	  fprintf(stderr," postzapDSNnotify('%s') DSN='%s'  -> ", onam, s);

	while (*s) {
	  if (CISTREQN(s,"NOTIFY=",7)) {
	    char *p = s+7;
	    /* While non-blank (parameter) string */
	    while (*p && *p != ' ' && *p != '\t') {
	      if (*p == ',') {
		++p;
		continue;
	      }
	      if (CISTREQN(p,"SUCCESS",7)) {
		notifysuccess=1;
		if (p[7] == ',')
		  strcpy(p,p+8); /* ZAP the "SUCCESS," status! */
		else
		  strcpy(p,p+7); /* ZAP the "SUCCESS" status! */
		continue;
	      }
	      if (CISTREQN(p,"TRACE",5)) {
		notifytrace = 1;
		if (p[5] == ',')
		  strcpy(p,p+6); /* ZAP the "TRACE," status! */
		else
		  strcpy(p,p+5); /* ZAP the "TRACE" status! */
		continue;
	      }
	      ++p;
	    }
	    /* Ok,  Now we may have e.g.:
	         i1: NOTIFY=NEVER
	         o1: NOTIFY=NEVER
		 i2: NOTIFY=SUCCESS,DELAY,FAILURE
		 o2: NOTIFY=DELAY,FAILURE
		 i3: NOTIFY=DELAY,SUCCESS,FAILURE
		 o3: NOTIFY=DELAY,FAILURE
		 i4: NOTIFY=DELAY,FAILURE,SUCCESS
		 o4: NOTIFY=DELAY,FAILURE,
		 i5: NOTIFY=SUCCESS
		 o5: NOTIFY=
	       of which the o4 needs trailing comma cleanup,
	       and o5 zapping of the entire NOTIFY parameter. */

	    if (p > s && p[-1] == ',') /* Case 4 */
	      p[-1] = ' '; /* An extra comma to zap */

	    if (p > s && p[-1] == '=') /* Case 5 */ {
	      while (*p == ' ' || *p == '\t') ++p;
	      strcpy(s, p); /* That the entire NOTIFY= parameter */
	      continue;
	    }

	    /* Skip over trailing whitespace */
	    while (*p == ' ' || *p == '\t') ++p;
	    s = p;
	    continue; /* This is done, restart */
	  } /* end of  if ("NOTIFY=") */

	  /* Nothing recognized, scan over the non-blank string */
	  while (*s && *s != ' ' && *s != '\t') ++s;
	  /* And possible trailing whitespace */
	  while (      *s == ' ' || *s == '\t') ++s;
	}

	if (D_sequencer)
	  fprintf(stderr,"'%s'  rc=%d\n", lc->string, notifysuccess);

	if (!notifysuccess && !notifytrace) return 0; /* We are DONE! */

	/* Now depending are we handling a mailing-list expansion, or
	   an alias expansion (RFC 1894, 6.2.7.*) we either report
	   "delivered" to the list input address, OR "expanded"
	   for aliases and .forwards (single recipient ones should
	   *not* report anything but rewrite the recipient address,
	   but we slip at that..) */

	s = lc->string;
	len = strlen(s);

	s = (char *)realloc(s, len+23);
	if (!s) return -1; /* Hardly happens, but... */

	lc->string = s;

	s += len;

	if (notifysuccess && notifytrace)
	  strcpy(s, " NTRACE=SUCCESS,TRACE");
	else if (notifysuccess)
	  strcpy(s, " NTRACE=SUCCESS");
	else if (notifytrace)
	  strcpy(s, " NTRACE=TRACE");

	return notifysuccess;
#endif
}


/* listexpand()  -- do a bunch of tasks usually done with a group of
   functions, starting with  "listaddresses" ... */

static conscell *
run_listexpand(avl, il)
	conscell *avl, *il;
{
	struct header hs;
	struct envelope *e;
	struct address *ap, *aroot = NULL, **atail = &aroot;
	token822 *t;
	conscell *al = NULL, *alp = NULL, *tmp = NULL;
	conscell *plustail = NULL, *domain = NULL;
	conscell *l, *lrc;
	char *localpart = NULL, *origaddr = NULL, *attributenam = NULL;
	int   c, n, errflag, stuff;
	volatile int cnt;
	const char *comment, *erroraddress;
	char *s, *s_end;
	int   privilege = -1;
	struct siobuf *osiop = NULL;
	FILE *mfp = NULL, *fp;
	char  buf[4096];
	char  DSNbuf[4096+200];
	int   fd2;
	char *olderrors = errors_to;
	char *notary = NULL;
	int   no_dsn = 0;
	int   okaddresses = 0;
	int   errcount = 0;
	int   linecnt = 0;
	int   euid = geteuid();
	GCVARS3;


	il = cdar(avl); /* The CDR (next) of the arg list.. */

	errflag = 0;
	erroraddress = NULL;
	comment = "list";
	zoptind = 1;

	while (il != NULL && STRING(il) && il->string[0] == '-' &&
	       cdr(il) != NULL && STRING(cdr(il))) {
	  switch( il->string[1] ) {
	    case 'c':
	      comment = (char*)cdr(il)->string;
	      if (strchr(comment,'\n') || strchr(comment,'\r'))
		errflag = 1;
	      break;
	    case 'p':
	      privilege = atoi((char*)cdr(il)->string);
	      break;
	    case 'e':
	      erroraddress = (char*)cdr(il)->string;
	      if (strchr(erroraddress,'\n') || strchr(erroraddress,'\r'))
		errflag = 1;
	      break;
	    case 'E':
	      if (errors_to != olderrors)
		free(errors_to);
	      errors_to = strdup((char*)cdr(il)->string);
	      if (strchr(errors_to,'\n') || strchr(errors_to,'\r'))
		errflag = 1;
	      break;
	    case 'N':
	      notary = (char *)cdr(il)->string;
	      if (strchr(notary,'\n') || strchr(notary,'\r'))
		errflag = 1;
	      if (STREQ(notary,"-")) no_dsn = 1;
	      break;
	    default:
	      errflag = 1;
	      break;
	  }
	  il = cddr(il); /* Skip TWO entries at the time! */
	}
	if (privilege < 0)
	  privilege = nobody;

	cnt = 0;
	tmp = il;
	for (; tmp != NULL; tmp = cdr(tmp)) ++cnt; /* Count arguments.. */

	if (errflag || cnt < 3 || cnt > 5 ||
	    !STRING(il) || !STRING(cdr(il)) || !STRING(cddr(il)) ) {
		fprintf(stderr,
			"Usage: %s [ -e error-address ] [ -E errors-to-address ] [-p privilege] [ -c comment ] [ -N notarystring ] $attribute $localpart $origaddr [$plustail [$domain]] [ [<] /file/path ]\n",
		car(avl)->string);
		if (errors_to != olderrors)
		  free(errors_to);
		errors_to = olderrors;
		return NULL;
	}

	attributenam = (char*)    (il)->string;
	localpart    = (char*) cdr(il)->string;
	origaddr     = (char*) cddr(il)->string;
	if (cdr(cddr(il))) {
	  plustail = cdr(cddr(il));
	  if (cddr(cddr(il)))
	    domain = cddr(cddr(il));
	}
	

	/* We (memory-)leak this stuff for a moment.. (but it is tmalloc()ed)*/
	e = (struct envelope *)tmalloc(sizeof (struct envelope));
	e->e_nowtime = now;
	e->e_file = (char*) car(avl)->string;
	/* we only need sensible `e' if its a time stamp header,
	   which it ain't, so "listaddress" is just fine for e_file .. */

	hs.h_descriptor = &aliashdr;
	hs.h_pname = (char*) car(avl)->string;

	initzline(4096);

	/*
	 * These hoops are so we can do this for both real stdin and
	 * the fake I/O stuff in ../libsh.
	 */
	if ((fp = fdopen(0, "r")) == NULL) {
		fprintf(stderr, "%s: fdopen failed\n", car(avl)->string);
#ifdef DEBUG_FOPEN
		report_fds(stderr);
#endif
		if (errors_to != olderrors)
		  free(errors_to);
		errors_to = olderrors;
		return NULL;
	}
	fd2 = dup(0);	/* Copy the file handle for later returning
			   AFTER an fclose() has closed the primary
			   copy.. */

	/* use a constant buffer to avoid memory leaks from stdio usage */
	setvbuf(fp, buf, _IOFBF, sizeof buf);
	c = 0;
	while ((n = zgetline(fp)) > 0) {
		++linecnt;
		/*
		 * For sendmail compatibility, addresses may not cross line
		 * boundaries, and line boundary is just as good an address
		 * separator as comma is, sigh.
		 * Also lines beginning with '#' are comments, and blank
		 * lines are (sortof) comments too.
		 */
		if (zlinebuf[n-1] == '\n')
			--n;
		if (zlinebuf[0] == '#')
			continue;

		stuff = 0;
		s_end = & zlinebuf[n];
		for (s = zlinebuf; s < s_end; ++s) {
			if (isascii(*s) && !isspace(*s))
				c = stuff = 1;
			if (*s == ',')
				c = 0;
		}
		if (!stuff)
			continue;
		if (c) {
			zlinebuf[n] = ',';
			++n;
		}

/* #ifdef SENDMAIL_COMPABILITY_KLUDGE */
		/**
		 * Additional sendmail compability kludge ++haa
		 * If address starts with \, zap the \ character.
		 * This is just to not to force people to edit their
		 * .forwards :-(  "\haa" works as expected  :-)
		 **/
		if (zlinebuf[0] == '\\') zlinebuf[0] = ' ';
		for (s = zlinebuf+1; s < s_end; ++s) {
		  if (s[-1] == ',' && s[0]=='\\') s[0] = ' ';
		}
		/* end of sendmail compatibility kluge */
/* #endif */

		/* create h->h_lines */
		/*
		 * It is best to maintain a line at a time as tokens,
		 * so errors will print out nicely.
		 */

		hs.h_lines = t = makeToken(zlinebuf, n);
		t->t_type = Line;

		/* fix up any trailing comma (more sendmail
		   compatibility kluges) */
		s = (char*)t->t_pname + TOKENLEN(t)-1;
		if (c && (*s == ',' || *s == '\n'))
		  *s = '\0';

		hs.h_contents = hdr_scanparse(e, &hs, 1, 1);
		hs.h_stamp = hdr_type(&hs);

		if (hs.h_stamp == BadHeader || hs.h_contents.a == NULL) {
		  ++errcount;
		  if (hs.h_stamp == BadHeader) {
		    if (erroraddress != NULL) {
		      if (!isErrChannel && !isErrorMsg) {
			if (mfp == NULL) {
			  /* We are likely running under 'runas ..'
			     Do reset now to ROOT, then to TRUSTED, ... */
			  runasrootuser();
			  runastrusteduser();
			  mfp = mail_open(MSG_RFC822);
			  runasrootuser();
			  if (mfp != NULL) {
			    osiop = siofds[FILENO(mfp)];
			    siofds[FILENO(mfp)] = NULL;
			    fprintf(mfp, "channel error\n");
			    fprintf(mfp, "errormsg\n");
			    fprintf(mfp, "to <%s>\n", erroraddress);
			    fprintf(mfp, "to <postoffice>\n");
			    fprintf(mfp, "env-end\n");
			    fprintf(mfp, "From: Error Channel <MAILER-DAEMON>\n");
			    fprintf(mfp, "To: %s\n", erroraddress);
			    fprintf(mfp, "Subject: Error in %s\n", comment);
			    fprintf(mfp, "Precedence: junk\n\n");
			    /* Print the report: */
			    fprintf(mfp,"Input file line number %d:\n",linecnt);
			    hdr_errprint(e, &hs, mfp, comment);
			  }
			} else { /* mfp != NULL */
			  fprintf(mfp,"Input file line number %d:\n",linecnt);
			  hdr_errprint(e, &hs, mfp, comment);
			}
		      }
#if 0
		      if (errcount == 1) /* At the first time only! */
			printf("%s\n", erroraddress);
#endif
		    }
		    fprintf(stderr,"Input file line number %d:\n",linecnt);
		    hdr_errprint(e, &hs, stderr, comment);
		  } else {		/* if (hs.h_contents.a == NULL) */
#if 0 /* Hmmm... We CAN have empty input lines! */
		    if (errcount == 1) {
		      /* Print only for the first intance.. */
		      if (erroraddress != NULL)
			printf("%s\n", erroraddress);
		      fprintf(stderr, "listexpand: null input on line %d of STDIN\n", linecnt);
		    }
#endif
		  }
		  continue;
		}

		*atail = hs.h_contents.a;
		while (*atail != NULL)
		  atail = &((*atail)->a_next);

		++okaddresses;
		
	}


	fclose(fp);	/* Now we discard the stdio buffers, but not the
			   fd number 0!  Actually we use a copy of it..	*/
	dup2(fd2,0);	/* Return the fd to descriptor 0..		*/
	close(fd2);	/* .. and discard the backup copy..		*/

	if (okaddresses == 0 && !isErrChannel && !isErrorMsg) {
		/* If the file is empty, use error address ...
		   Except when this is already an error mesage, say nothing! */

		if (erroraddress == NULL)
			erroraddress = "postmaster";

		hs.h_lines = t = makeToken(erroraddress, strlen(erroraddress));
		t->t_type = Line;

		/* fix up any trailing comma (more sendmail
		   compatibility kluges) */
		s = (char*)t->t_pname + TOKENLEN(t)-1;
		if (c && (*s == ',' || *s == '\n'))
		  *s = '\0';

		hs.h_contents = hdr_scanparse(e, &hs, 1, 1);
		hs.h_stamp = hdr_type(&hs);

		if (hs.h_stamp == BadHeader || hs.h_contents.a == NULL) {
		  /* OUTCH!  Even the "erroraddress" parameter is illegal! */
		  fprintf(stderr,"listexpand: input parameters bad, empty input, even 'erroraddress' bad!\n");
		} else {

		  *atail = hs.h_contents.a;
		  while (*atail != NULL)
		    atail = &((*atail)->a_next);
		}

		if (mfp == NULL) {
		  /* We are likely running under 'runas ..'
		     Do reset now to ROOT, then to TRUSTED, ... */
		  runasrootuser();
		  runastrusteduser();
		  mfp = mail_open(MSG_RFC822);
		  runasrootuser();
		  if (mfp != NULL) {
		    osiop = siofds[FILENO(mfp)];
		    siofds[FILENO(mfp)] = NULL;
		    fprintf(mfp, "channel error\n");
		    fprintf(mfp, "errormsg\n");
		    fprintf(mfp, "to <%s>\n", erroraddress);
		    fprintf(mfp, "to <postoffice>\n");
		    fprintf(mfp, "env-end\n");
		    fprintf(mfp, "From: Error Channel <MAILER-DAEMON>\n");
		    fprintf(mfp, "To: %s\n", erroraddress);
		    fprintf(mfp, "Subject: Error in %s\n", comment);
		    fprintf(mfp, "Precedence: junk\n\n");
		    /* Print the report: */
		    fprintf(mfp,"NO valid recipient addresses!\n");
		    fprintf(mfp,"Verify source file protection/ownership/access-path, and content.\n");
		    fprintf(mfp,"Current effective UID = %d\n", euid);
		  }
		} else { /* mfp != NULL */
		  fprintf(mfp,"\nNO valid recipient addresses!\n");
		  fprintf(mfp,"Verify source file protection/ownership/access-path, and content.\n");
		  fprintf(mfp,"Current effective UID = %d\n", euid);
		}
	}

	if (mfp != NULL) {
	  siofds[FILENO(mfp)] = osiop;
	  mail_close(mfp);
	}

	cnt = 0;

	al = l = lrc = NULL;
	GCPRO3(al, l, lrc);

	for (ap = aroot; ap != NULL; ap = ap->a_next) {
		int rc, slen;
		memtypes omem;
		char *s2, *se;
		struct addr *pp;

		for (pp = ap->a_tokens; pp != NULL; pp = pp->p_next)
			if (pp->p_type == anAddress)
				break;
		if (pp == NULL)
			continue;

		buf[0] = 0;
		pureAddressBuf(buf,sizeof(buf),pp);

		if (buf[0] == 0) continue; /* Burp ??? */

#define cdddr(x) cdr(cddr(x))

#define use_lapply 1
#ifdef use_lapply
		if (!no_dsn) {
		  *DSNbuf = 0;
		  if (notary != NULL)
		    strncpy(DSNbuf,notary,sizeof(DSNbuf)-30);
		  DSNbuf[sizeof(DSNbuf)-30] = 0; /* Be brutal, and chop
						    off the tail, if it is
						    too long.. */
		  s2 = strlen(DSNbuf)+DSNbuf;
		  if (s2 > DSNbuf)
		    strcpy(s2++," ");
		  strcpy(s2,"ORCPT=rfc822;");
		  s2 += strlen(s2);
		  se = DSNbuf + sizeof(DSNbuf)-1; /* BUF-end */
		  s = buf;
		  while (*s) {
		    c = *s;
		    if ('!' <= c && c <= '~' && c != '+' && c != '=') {
		      if (s2 < se)
			*s2++ = c;
		    } else if (s2 < se) {
		      sprintf(s2,"+%02X",c);
		      s2 += 3;
		    }
		    ++s;
		  }
		  *s2 = 0;
		  s = newattribute_2(attributenam,"DSN",DSNbuf);
		} else {
		  s = attributenam;
		}

		omem = stickymem;
		/* stickymem = MEM_MALLOC; */

		/* The set of parameters for the rrouter() script
		   function are:
		   - address
		   - origaddress
		   - Attribute variable name
		   - plustail
		   - domain
		   (The last two were added in June-1998)
		*/

		slen = strlen(buf);
		l         = newstring(dupnstr(buf, slen), slen);
		slen = strlen(origaddr);
		cdr(l)    = newstring(dupnstr(origaddr, slen), slen);
		slen = strlen(s);
		cddr(l)   = newstring(dupnstr(s, slen), slen);
		if (plustail != NULL) {
		  cdr(cddr(l))  = s_copy_chain(plustail);
		  if (domain != NULL)
		    cddr(cddr(l)) = s_copy_chain(domain);
		}
		l = ncons(l);

		stickymem = omem;

		deferit = 0;
		v_set(DEFER, "");

		rc = l_apply("rrouter", l);
		lrc = s_value;
		s_value = NULL;

		omem = stickymem;
		/* stickymem = MEM_MALLOC;
		   s_free_tree(l); */ /* We can clean up the input list */
		stickymem = omem;
#else
		lrc = router(ap, privilege, "recipient");
		rc = (lrc == NULL);
#endif
#if 0 /* XX: This HOLD handling didn't work ?? */
		if (deferit && (d = v_find(DEFER))) {
		  /* s_free_tree(lrc); */
		  lrc = NULL;
		  l = conststring("hold", 4);
		  cdr(l)   = copycell(cdr(d));
		  slen = strlen(buf);
		  cddr(l)  = newstring(dupnstr(buf, slen), slen);
		  cdddr(l) = car(attributes);
		  l = ncons(l);
		  l = ncons(l);
		} else
		  ;
#endif
		if (rc != 0 || lrc == NULL || !LIST(lrc)) {
		  /* $(rrouter xx xx xx)  returned something invalid.. */
#ifdef use_lapply
		  /* s_free_tree(lrc); */
#endif
		  lrc = NULL;
		  continue;
		} else {
		  /*
		   * We expect router to either return
		   * (local - user attributes) or (((local - user attributes)))
		   * or ( ((local - user attr)) ((local - another attr)) )
		   */
		  if (car(lrc) == NULL) {
		    /* duplicate removal trapped it. Empty list! */
		    continue;
		  }
		  if (LIST(car(lrc))) {
		    if (!LIST(caar(lrc)) || !STRING(caaar(lrc))) {
		      fprintf(stderr,
			      "%s: '%s' returned invalid 2-level list: ",
			      progname, ROUTER);
		      s_grind(lrc, stderr);
#ifdef use_lapply
		      /* s_free_tree(lrc); */
#endif
		      lrc = NULL;
		      if (errors_to != olderrors)
			free(errors_to);
		      errors_to = olderrors;
		      UNGCPRO3;
		      return NULL;
		    }
		    l = s_copy_chain(lrc);
		  } else {
		    l = s_copy_chain(lrc);
		    l = ncons(l);
		    l = ncons(l);
		  }

#ifdef use_lapply
		  /* s_free_tree(lrc); */
#endif
		  lrc = NULL;
		}

		/* Now the  "(conscell *) l" contains a route,
		   it is a time to put them together.. */
		/* l ->  ( ((chan param param2 attrs)) )  */
		if (al == NULL) {
		  al  = l;		/* The head anchor */
		  alp = car(al);	/* address list END node ptr */
		  while (cdr(alp) != NULL) {
		    alp = cdr(alp);
		    ++cnt;
		  }
		} else {
		  /*
		     (
		      ((chan param param2 attrs))
		      ((chan param param2 attrs))
		     )
		   */
		  cdr(alp) = car(l);	/* Attach the new list to the old */
		  while (cdr(alp) != NULL) {
		    alp = cdr(alp);	/* .. and traverse to its end ... */
		    ++cnt;
		  }
		}

		/* Debugging.. */
		/* buf[len] = '\n';
		   buf[len+1] = 0;
		   fwrite(buf,1,len+1,stdout); */
	}

#if 0
	if (al == NULL) { /* ERROR! NO ADDRESSES! */
	  int slen;
	  al = conststring("error", 5);
	  cdr(al)  = conststring("expansion", 9);
	  slen = strlen(localpart);
	  cddr(al) = newstring(dupnstr(localpart, slen), slen);
	  al = ncons(al);
	  al = ncons(al);
	}
#endif
	UNGCPRO3;

	if (errors_to != olderrors)
	  free(errors_to);
	errors_to = olderrors;
	return al;

} /* end-of: run_listexpand() */



static int
run_homedir(argc, argv)
	int argc;
	const char *argv[];
{
	struct Zpasswd *pw;
	char *b;
	int err;

	if (argc != 2) {
		fprintf(stderr, "Usage: %s name\n", argv[0]);
		return EX_USAGE;
	}

	pw = zgetpwnam(argv[1]);
	err = errno;
	if (pw == NULL) {
		strlower((char*)argv[1]);
		pw = zgetpwnam(argv[1]);
		err = errno;
		if (pw == NULL) {
		  if (err == 0)      return 2;
		  ++deferit;

		  b = malloc(strlen(argv[1])+10);
		  sprintf(b, "HOME:%s", argv[1]);
		  v_set(DEFER, b);
		  free(b);

		  return 3;
		}
	}
	printf("%s\n", pw->pw_dir);
	return 0;
}

static int
run_822date(argc, argv)
	int argc;
	const char *argv[];
{
	time_t dnow;

	time(&dnow);
	if (argc == 2 && STREQ(argv[1], "-s"))
		printf("%ld\n", dnow);
	else
		printf("%s", rfc822date(&dnow));
	return 0;
}


static int
run_filepriv(argc, argv)
	int argc;
	const char *argv[];
{
	const char *argv0 = argv[0];
	int id;
	struct stat stbuf;
	long fmode = -1;
	long dmode = -1;
	const char *file, *cp;
	char *dir;
	int maxperm = 0666 ^ filepriv_mask_reg; /* XOR.. */
	int fd;

	if (argc > 2 && argv[1][0] == '-' && argv[1][1] == 'M') {
	  ++argv;
	  --argc;
	  maxperm = (int)strtol(argv[1],NULL,8); /* Octal! */
	  ++argv;
	  --argc;
	}
	if (argc == 1 || argc > 3 || (maxperm & (~0664)) != 0) {
		fprintf(stderr, "Usage: %s [-M maxperm] pathname [ uid ]\n", argv0);
		if (maxperm & (~0664))
		  fprintf(stderr, "       maxperm must be 664 or stricter!\n");
		return EX_USAGE;
	}
	file = argv[1];
	if (argc == 3 && isascii(argv[2][0]) && isdigit(argv[2][0])) {
		fmode = S_IFREG|0400;
		id = atoi(argv[2]);
	} else {
		fd = open(file, O_RDONLY, 0);
		if (fd < 0) {
			/* if we can't open it, don't trust it */
			perror(file);
			fprintf(stderr,
				"%s: cannot open(\"%s\")!\n", argv0, file);
			return 2;
		}
		if (fstat(fd, &stbuf) < 0) {
			fprintf(stderr, "%s: cannot fstat(\"%s\")!\n",
				argv0, file);
			close(fd);
			return 3;
		}
		fmode = stbuf.st_mode;
		close(fd);
		id = stbuf.st_uid;
	}
	cp = strrchr(file, '/');
	if (cp == NULL) {
		printf("%d\n", id);
		return 0;
	} else if (cp == file)	/* root path */
		++cp;
#ifdef	USE_ALLOCA
	dir = (char*)alloca(cp - file + 1);
#else
	dir = (char*)emalloc(cp - file + 1);
#endif
	memcpy(dir, file, cp - file);
	dir[cp - file] = '\0';

	/* Use stat(2), we fold symlinks to their final dirs(whatever) */
	if (stat(dir, &stbuf) < 0 || !S_ISDIR(stbuf.st_mode)) {
		fprintf(stderr, "%s: not a directory: \"%s\"!\n",
			argv0, dir);
#ifndef	USE_ALLOCA
		free(dir);
#endif
		return 4;
	}
#ifndef	USE_ALLOCA
	free(dir);
#endif

	dmode = stbuf.st_mode;

	if (!S_ISDIR(dmode)) {
	  /* Directory is not directory ?? */
	  id = nobody;
	}

	if (id != nobody) {
	  if (!S_ISREG(fmode))
	    /* File is not a regular file ?? */
	    id = nobody;
	}

	/*
	 * If it is a  special or directory or writable
	 * by non-owner (modulo permission), don't trust it!
	 * BUT ONLY if the directory where it is has X bits
	 * for group or others!
	 */

	/* Group and World accessibility of the residence directory
	   defines what bits to analyze.
	*/

	switch (dmode & 00011) {
	case 0000:	/* No Group access, no World access */
	  if ((fmode & 07700) & ~maxperm) /* Verify only User bits */
	    id = nobody;
	  break;
	case 0010:	/* Group access, no World access */
	  if ((fmode & 07770) & ~maxperm) /* Verify User and Group bits */
	    id = nobody;
	  break;
	case 0001:	/* No Group access, but yes World ?! */
	case 0011:	/* Group and World accesses */
	  if ((fmode & 07777) & ~maxperm) /* Verify all bits */
	    id = nobody;
	  break;
	}

	if ((dmode & filepriv_mask_dir) && /*If world/group writable*/
	    !(dmode & S_ISVTX))	/* and not sticky, OR */
	  id = nobody;				/* Don't trust */
	if (id != nobody &&
	    stbuf.st_uid != 0 && stbuf.st_uid != id)/*dir owned by root/user*/
	  id = nobody;				/* Don't trust */

	printf("%d\n", id);
	return 0;
}

static int
run_runas(argc, argv)
	int argc;
	const char *argv[];
{
	int uid, r;
	const char *cp;
	static int initeduid = 0;
	static short myuid = -1;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s user function [args...]\n", argv[0]);
		return EX_USAGE;
	}
	cp = argv[1];
	if (*cp == '-')
		uid = -1, ++cp;
	else
		uid = 1;
	if (isdigit(*cp))	/* what if user id is "3com" ? */
		uid *= atoi(cp);
	else			/* look up login name and get uid */
		uid = login_to_uid(cp);

	if (!initeduid)
		myuid = geteuid();
	if (myuid == 0 && uid != 0) {
		if (SETEUID(uid) < 0) {
			fprintf(stderr, "%s: seteuid(%d): %s\n",
				argv[0], uid, strerror(errno));
			return 1;
		}
	}
	/* must be builtin or defined function */
	r = s_apply(argc - 2, argv + 2); /* within:  run_runas() */
	if (myuid == 0 && uid != 0) {
		if (SETEUID(0) < 0)
			abort(); /* user-identity change failed! */
	}

	return r;
}

static int
run_cat(argc, argv)
	int argc;
	const char *argv[];
{
	FILE *fp;
	char buf[8192];
	int i;
	struct stat stbuf;

	if (argc < 2) {
		fprintf(stderr, "Usage: %s [filenames ...]\n", argv[0]);
		return EX_USAGE;
	}

	for ( ;argv[1] != NULL; ++argv) {
	  /* Must be a regular file (via a symlink, though!), no
	     pipes, sockets, devices... */
	  if (stat(argv[1], &stbuf) == 0 && S_ISREG(stbuf.st_mode) &&
	      (fp = fopen(argv[1], "r"))) {
	    for (;!ferror(fp) && !feof(fp);) {
	      i = fread(buf, 1, sizeof(buf), fp);
	      if (i > 0) {
		int j = 0;
		while (j < i)
		  j += fwrite(buf + j, 1, i - j, stdout);
	      } else
		break;
	    }
	    fclose(fp);
	  }
	}
	return 0;
}


static int
run_uid2login(argc, argv)
	int argc;
	const char *argv[];
{
	if (argc != 2 || !isdigit(argv[1][0])) {
		fprintf(stderr, "Usage: %s uid\n", argv[0]);
		return EX_USAGE;
	}
	printf("%s\n", uidpwnam(atoi(argv[1])));
	return 0;
}

static int
run_login2uid(argc, argv)
	int argc;
	const char *argv[];
{
	if (argc != 2) {
		fprintf(stderr, "Usage: %s login\n", argv[0]);
		return EX_USAGE;
	}
	printf("%d\n", login_to_uid(argv[1]));
	return 0;
}

static int
run_basename(argc, argv)
	int argc;
	const char *argv[];
{
	const char *cp;
	int len;

	if (argc == 1) {
		fprintf(stderr, "Usage: %s pathname suffix-to-strip\n",
				argv[0]);
		return EX_USAGE;
	}
	cp = strrchr(argv[1], '/');
	if (cp == NULL)
		cp = argv[1];
	else
		++cp;
	if (argc > 2 && (len = strlen(cp) - strlen(argv[2])) > 0) {
		if (STREQ(cp + len, argv[2])) {
			while (len-- > 0)
				putchar(*cp++);
			putchar('\n');
			return 0;
		}
	}
	printf("%s\n", cp);
	return 0;
}

static int
run_syslog(argc, argv)
	int argc;
	const char *argv[];
{
	int c;
	int prio = LOG_INFO;
	int errflg = 0;
	zoptind = 1;

	while ((c = zgetopt(argc, (char*const*)argv, "p:")) != EOF) {
		switch (c) {
		case 'p':	/* priority */
			if(STREQ(zoptarg, "debug")) {
				prio = LOG_DEBUG;
			} else if(STREQ(zoptarg, "info")) {
				prio = LOG_INFO;
			} else if(STREQ(zoptarg, "notice")) {
				prio = LOG_NOTICE;
			} else if(STREQ(zoptarg, "warning")) {
				prio = LOG_WARNING;
			} else if(STREQ(zoptarg, "err")) {
				prio = LOG_ERR;
			} else if(STREQ(zoptarg, "crit")) {
				prio = LOG_CRIT;
			} else if(STREQ(zoptarg, "alert")) {
				prio = LOG_ALERT;
			} else if(STREQ(zoptarg, "emerg")) {
				prio = LOG_EMERG;
			} else {
				++errflg;
			}
			break;
		default:
			++errflg;
			break;
		}
	}

	if (errflg || zoptind != argc - 1) {
		fprintf(stderr, "Usage: %s [-p prio] string\n", argv[0]);
		return EX_USAGE;
	}
	zsyslog((prio, "%s", argv[zoptind]));
	return 0;
}

static int
run_recase(argc, argv)
	int argc;
	const char *argv[];
{
	char *cp;
	int c, flag, errflg, action = 0;

	zoptind = 1;
	errflg = 0;

	while (1) {
		c = zgetopt(argc, (char*const*)argv, "ulp");
		if (c == EOF)
			break;
		switch (c) {
		case 'u':	/* up-case */
		case 'l':	/* low-case */
		case 'p':	/* prettify */
			action = c;
			break;
		default:
			++errflg;
			break;
		}
	}
	if (errflg || zoptind != argc - 1) {
		fprintf(stderr, "Usage: %s [ -u | -l | -p ] -- string\n",
				argv[0]);
		return EX_USAGE;
	}

	switch (action) {
	case 'u':
		strupper((char*)argv[zoptind]);
		break;
	case 'l':
		strlower((char*)argv[zoptind]);
		break;
	case 'p':
		flag = 1;
		for (cp = (char*)argv[zoptind]; *cp != '\0'; ++cp) {
			if (isascii(*cp) && isalnum(*cp)) {
				if (flag && islower(*cp))
					*cp = toupper(*cp);
				else if (!flag && isupper(*cp))
					*cp = tolower(*cp);
				flag = 0;
			} else
				flag = 1;
		}
		break;
	}
	printf("%s\n", argv[zoptind]);
	return 0;
}

#if	defined(XMEM) && defined(CSRIMALLOC)
static int
run_malcontents(argc, argv)
	int argc;
	const char *argv[];
{
	mal_contents(stdout);
}
#endif	/* CSRIMALLOC */


static struct {
	short		 fyitype;
	short		 fyisave;
	const char	*fyiname;
	const char	*fyitext;
} fyitable[] = {
{ FYI_BREAKIN, 0, "breakin",	"external message claims local origin!"	},
{ FYI_BADHEADER, 0, "badheader","message header syntax error!"		},
{ FYI_ILLHEADER, 0, "illheader","null header field name!"		},
{ FYI_NOCHANNEL, 0, "nochannel","a null string input channel was specified" },
{ FYI_NOSENDER, 0, "nosender",	"no sender could be determined!"	},
};

void
optsave(type, e)
	int type;
	struct envelope *e;
{
	int i;

	for (i = 0; i < (sizeof fyitable / sizeof fyitable[0]); ++i) {
		if (type == fyitable[i].fyitype) {
			if (fyitable[i].fyisave) {
				char name[20];
				sprintf(name,"_%s", fyitable[i].fyiname);
				squirrel(e, name, fyitable[i].fyitext);
			}
			fprintf(stderr, "*** %s\n", fyitable[i].fyitext);
			return;
		}
	}
}

static int
run_squirrel(argc, argv)
	int argc;
	const char *argv[];
{
	int i, j, errflag, flag;

	errflag = 0;
	for (i = 1; i < argc; ++i) {
		if (argv[i][0] == '-') {
			argv[i] = argv[i]+1;
			flag = 0;
		} else
			flag = 1;
		for (j = 0; j < (sizeof fyitable / sizeof fyitable[0]); ++j) {
			if (CISTREQ(argv[i], fyitable[j].fyiname)) {
				fyitable[j].fyisave = flag;
				j = -1;
				break;
			}
		}
		if (j != -1)
			++errflag;
	}
	if (errflag || argc == 1) {
		fprintf(stderr, "Usage: %s [", argv[0]);
		for (j = 0; j < (sizeof fyitable / sizeof fyitable[0]); ++j) {
			if (j > 0)
				fprintf(stderr, " |");
			if (fyitable[j].fyisave)
				fprintf(stderr, " -%s", fyitable[j].fyiname);
			else
				fprintf(stderr, " %s", fyitable[j].fyiname);
		}
		fprintf(stderr, " ]\n");
		return EX_USAGE;
	}
	return 0;
}

static struct headerinfo addrhdr = {
	"route-addr", RouteAddress, Recipient, normal
};

static int
run_822syntax(argc, argv)
	int argc;
	const char *argv[];
{
	struct header hs;
	struct envelope es;
	/*char buf[4096];*/

	if (argc != 2)
		return EX_USAGE;
	es.e_nowtime = now;
	es.e_file = argv[0];
	hs.h_descriptor = &addrhdr;
	hs.h_pname = argv[1];
	hs.h_lines = makeToken(argv[1], strlen(argv[1]));
	hs.h_lines->t_type = Line;
	hs.h_lines->t_next = NULL;
	hs.h_contents = hdr_scanparse(&es, &hs, 1, 0);
	hs.h_stamp = hdr_type(&hs);
	if (hs.h_stamp == BadHeader) {
		hdr_errprint(&es, &hs, stderr, "RFC822/976/2822");
		return 1;
	} else if (hs.h_contents.a == NULL)
		return 1;
	return 0;
}


static int
run_condquote_(argc, argv, condq)
	int argc, condq;
	const char *argv[];
{
	const char *s;
	int mustquote = 0;
	int c, quoted;
	int spc = 0;
	int errflg = 0;
	const char *appstr = NULL;

	extern int rfc822_mustquote __((const char *, const int));

	/* We remove quotes when they are not needed, and add them when
	   they really are needed! */

	zoptind = 1;
	while (1) {
	  c = zgetopt(argc, (char*const*)argv, "s:a:");
	    
	  if (c == EOF) break;
	  switch (c) {
	  case 's':
	    spc = *zoptarg; /* First char only */
	    break;
	  case 'a':
	    appstr = zoptarg;
	    break;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || zoptind != argc - 1) {
	  fprintf(stderr,
		  "Usage: %s [ -s SPCCHR ] [ -a APPENDSTR ] string\n",
		  argv[0]);
	  return EX_USAGE;
	}

	s = argv[zoptind];

	mustquote = rfc822_mustquote(s, spc);
	/* A bitset:
	   0001  Has Quotes
	   0002  Ended while inside a quote
	   0004  Has characters which must be quoted
	*/


	if (!condq || mustquote == 1) {
	  /* Well, we can actually DEQUOTE the thing just fine! */
	  quoted = 0;
	  for (; *s; ++s) {
	    c = *s;
	    if (c == ' ' && spc) {
	      putchar(spc);
	      quoted = 0;
	      continue;
	    }
	    if (quoted) {
	      putchar(c);
	      quoted = 0;
	      continue;
	    }
	    if (c == '\\') {
	      /* A quoted pair! */
	      quoted = 1;
	      putchar(c);
	      c = *++s;
	    } else if (c == '"') {
	      continue; /* Drop it! */
	    } else
	      putchar(c);
	  }
	} else if (mustquote > 1 && !(mustquote & 1) && condq) {
	  /* Has things needing quotes, but no quotes in place! */
	  putchar('"');
	  for (; *s; ++s) {
	    c = *s;
	    if (c == ' ' && spc)
	      putchar(spc);
	    else if (c == '"') {
	      putchar('\\');
	      putchar(c);
	    } else
	      putchar(c);
	  }
	  putchar('"');
	} else {
	  /* The original one.. */
	  for (; *s; ++s) {
	    c = *s;
	    if (c == ' ' && spc)
	      putchar(spc);
	    else if (c == '\\') {
	      putchar(c);
	    } else
	      putchar(c);
	  }
	}
	if (appstr)
	  puts(appstr);

	return 0;
}

static int
run_condquote(argc, argv)
	int argc;
	const char *argv[];
{
  return run_condquote_(argc,argv,1);
}

static int
run_dequote(argc, argv)
	int argc;
	const char *argv[];
{
  return run_condquote_(argc,argv,0);
}


syntax highlighted by Code2HTML, v. 0.9.1