/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

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

#include "hostenv.h"
#include "mailer.h"
#include <ctype.h>
#include <signal.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 <mail.h>
#include "interpret.h"
#include "io.h"
#include NDIR_H

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

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

extern int run_relation(), run_trace();
extern int run_hostname(), run_whataddress(), run_erraddrlog();
extern struct conscell *sh_car(), *run_cadr(), *run_caddr(), *run_cadddr();
extern struct conscell *run_dblookup();
extern int run_stability(), run_daemon(), run_process(), run_rfc822(), run_db();
extern int run_grpmems(), run_praliases(), run_listaddrs(), run_homedir();
extern int run_822date(), run_filepriv(), run_runas(), run_gensym();
extern int run_uid2login(), run_login2uid(), run_basename(), run_recase();
extern int run_squirrel(), run_822syntax();
#if	defined(XMEM) && defined(CSRIMALLOC)
extern int run_malcontents();
#endif	/* CSRIMALLOC */
extern u_char *errors_to;

struct shCmd fnctns[] = {
{	"relation",	run_relation,		0,		0	},
{	DBLOOKUPNAME,	0,			run_dblookup,	SH_ARGV	},
/* The following are optional but are probably a good idea */
{	"db",		run_db,			0,		0	},
{	"trace",	run_trace,		0,		0	},
{	"untrace",	run_trace,		0,		0	},
{	"hostname",	run_hostname,		0,		0	},
{	"sender",	run_whataddress,	0,		0	},
{	"recipient",	run_whataddress,	0,		0	},
{	"erraddron",	run_erraddrlog,		0,		0	},
{	"channel",	0,			sh_car,		SH_ARGV	},
{	"host",		0,			run_cadr,	SH_ARGV	},
{	"user",		0,			run_caddr,	SH_ARGV	},
{	"attributes",	0,			run_cadddr,	SH_ARGV	},
{	"stability",	run_stability,		0,		0	},
{	"daemon",	run_daemon,		0,		0	},
{	"process",	run_process,		0,		0	},
{	"rfc822",	run_rfc822,		0,		0	},
{	"groupmembers",	run_grpmems,		0,		0	},
{	"printaliases",	run_praliases,		0,		0	},
{	"listaddresses",run_listaddrs,		0,		0	},
{	"homedirectory",run_homedir,		0,		0	},
{	"rfc822date",	run_822date,		0,		0	},
{	"filepriv",	run_filepriv,		0,		0	},
{	"runas",	run_runas,		0,		0	},
{	"gensym",	run_gensym,		0,		0	},
{	"uid2login",	run_uid2login,		0,		0	},
{	"login2uid",	run_login2uid,		0,		0	},
{	"basename",	run_basename,		0,		0	},
{	"recase",	run_recase,		0,		0	},
{	"squirrel",	run_squirrel,		0,		0	},
{	"rfc822syntax",	run_822syntax,		0,		0	},
#if	defined(XMEM) && defined(CSRIMALLOC)
{	"malcontents",	run_malcontents,	0,		0	},
#endif	/* CSRIMALLOC */
/* The rest have been added locally */
{ 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;

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

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

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

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

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

int
run_hostname(argc, argv)
	int argc;
	char *argv[];
{
	static char hostname[128];
	extern char *myhostname;
	extern int stickymem, getmyhostname();

	if (argc > 1) {
		int oval = stickymem;
		stickymem = MEM_PERM;
		myhostname = strnsave(argv[1], strlen(argv[1]));
		stickymem = oval;
	} else {
		/* can't fail... */
		(void) 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
 */

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

	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;
}

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

char *erraddrlog;

int
run_erraddrlog(argc, argv)
	int argc;
	char *argv[];
{
	switch (argc) {
	case 1:
		if (erraddrlog)
			(void) free(erraddrlog);
		erraddrlog = NULL;
		break;
	case 2:
		erraddrlog = smalloc(MEM_PERM, strlen(argv[1])+1);
		(void) strcpy(erraddrlog, argv[1]);
		break;
	default:
		fprintf(stderr, "Usage: %s [ /path ]\n", argv[0]);
		return 1;
	}
	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).
 */

struct conscell *
run_dblookup(avl, il)
	struct conscell *avl, *il;
{
	struct conscell *l;
	extern struct conscell *db();

	il = cdar(avl);
	if (il == NULL || !STRING(il) || cdr(il) != NULL) {
		fprintf(stderr, "Usage: %s key\n", car(avl)->string);
		return NULL;
	}
	if ((l = db(car(avl)->string, il->string)) == NULL)
		return NULL;
	return l;
}

struct conscell *
run_cadr(avl, il)
	struct conscell *avl, *il;
{
	struct conscell *tmp;

	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* car */
	/* setf preparation */
	if (car(il) == NULL) {
		if (il->prev) {
			il->prev = cdr(il->prev);
			il->pflags = 0;
		}
		return il;
	}
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	car(il)->pflags |= 01 | 04;
	return car(il);
}

struct conscell *
run_caddr(avl, il)
	struct conscell *avl, *il;
{
	struct conscell *tmp;

	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* car */
	/* setf preparation */
	if (car(il) == NULL) {
		if (il->prev) {
			il->prev = cdr(il->prev);
			il->pflags = 0;
		}
		return il;
	}
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	car(il)->pflags |= 01 | 04;
	return car(il);
}

struct conscell *
run_cadddr(avl, il)
	struct conscell *avl, *il;
{
	struct conscell *tmp;

	il = cdar(avl);
	if (il == NULL || STRING(il) || car(il) == NULL)
		return NULL;
	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* cdr */
	/* setf preparation */
	if (cdar(il)) {
		il->prev = cdar(il)->prev;
	} else if (car(il)->prev) {
		if (car(il)->pflags)
			il->prev = cdr(car(il)->prev);
		else
			il->prev = car(car(il)->prev);
	}
	il->pflags = 3;
	car(il) = cdar(il);

	/* car */
	/* setf preparation */
	if (car(il) == NULL) {
		if (il->prev) {
			il->prev = cdr(il->prev);
			il->pflags = 0;
		}
		return il;
	}
	car(il) = copycell(car(il));	/* don't modify malloc'ed memory! */
	cdar(il) = NULL;
	car(il)->pflags |= 01 | 04;
	return car(il);
}

extern int canexit, mustexit;

SIGNAL_TYPE
sig_exit()
{
	if (canexit) {
#ifdef	MALLOC_TRACE
		dbfree(); zshfree();
#endif	/* MALLOC_TRACE */
		die(0, "signal");
	}
	mustexit = 1;
	/* no need to reenable signal in USG, once will be enough */
}

static int gothup = 0;

SIGNAL_TYPE
sig_hup()
{
	gothup = 1;
	/* fprintf(stderr,"HUP\n"); */
	(void) signal(SIGHUP, sig_hup);
}

void
dohup()
{
	extern char *traps[];

	gothup = 0;
	if (traps[SIGHUP] != NULL)
		(void) eval(traps[SIGHUP], "trap", (char *)NULL);
}

/*
 * Run the Router in Daemon mode.
 */

/*
 * STABILITY option will make the router process incoming messages in
 * arrival (modtime) order, instead of randomly determined by position
 * in the router directory.  The scheme is to read in all the names,
 * and stat the files.  It would be possible to reuse the stat information
 * later, but I'm not convinced it is worthwhile since the later stat is
 * an fstat().  On the other hand, if we used splay tree insertion to
 * sort the entries, then the stat buffer could easily be reused in
 * makeLetter().
 *
 * SECURITY WARNING: iff makeLetter does not stat again, then there is
 * a window of opportunity for a Bad Guy to remove a file after it has
 * been stat'ed (with root privs), and before it has been processed.
 * This can be avoided partially by sticky-bitting the router directory,
 * and entirely by NOT saving the stat information we get here.
 */

struct de {
	int		f_name;
	struct stat	*f_stat;
};

int
decmp(a, b)
	register struct de *a, *b;
{
	return b->f_stat->st_mtime - a->f_stat->st_mtime;
}

static int desize, nbsize;
static struct de *dearray = NULL;
static char *nbarray = NULL;

void
rd_initstability()
{
	desize = 1;	/* max. number of directory entries */
	nbsize = 1;
	dearray = (struct de *)emalloc(desize * sizeof (struct de));
	nbarray = (char *)emalloc(nbsize * sizeof (char));
}

void
rd_endstability()
{
	if (dearray != NULL)
		free((char *)dearray);
	if (nbarray != NULL)
		free((char *)nbarray);
}

int
rd_doit(filename)
	char *filename;
{
	static u_int blen = 0;
	static char *buf;
	char *av[3];
	int len;
	extern int gensym, router_id, s_apply();
	extern void free_gensym();

	if (router_id) {
		len = strlen(filename);
		if (filename[len - 1] == ')' && strchr(filename, '(') != NULL)
			return 0;	/* an already locked message file */
		if (blen == 0) {
			blen = 100;
			buf = (char *)malloc(blen);
		}
		while (len + 12 > blen) {
			blen = 2 * blen;
			buf = (char *)realloc(buf, blen);
		}
		sprintf(buf, "%s(%d)", filename, router_id);
		if (erename(filename, buf) < 0)
			return 0;	/* something is wrong, erename() complains */
		filename = buf;
		/* message file is now "file(#)" and belongs to this process */
	}

	gensym = 1;
	av[0] = "process"; /* I think this needs to be here */
	av[1] = filename;
	av[2] = NULL;
	(void) s_apply(2, &av[0]);
	free_gensym();
	return 1;
}

int
rd_stability(dirp)
	DIR *dirp;
{
	register int i;
	int deindex, nbindex, namelen;
	struct NDIR_TYPE *dp;

	deindex = 0;
	nbindex = 0;
	/* collect the file names */
	while (dp = readdir(dirp)) {
		if (mustexit)
			break;
		if (dp->d_name[0] == '.')
			continue;

		namelen = strlen(dp->d_name);

		if (deindex >= desize) {
			desize *= 2;
			dearray = (struct de *)realloc((char *)dearray,
						desize * sizeof (struct de));
		}

		while (nbindex + namelen + 1 >= nbsize) {
			nbsize *= 2;
			nbarray = (char *)realloc(nbarray,
						  nbsize * sizeof (char));
		}

		/*
		 * The explicit alloc is done because alloc/dealloc
		 * of such small chunks should not affect fragmentation
		 * too much, and allocating large chunks would require
		 * management code and might still do bad things with
		 * the malloc algorithm.
		 */
		dearray[deindex].f_stat =
			(struct stat *)emalloc(sizeof (struct stat));
		if (stat(dp->d_name, dearray[deindex].f_stat) < 0) {
			perror(dp->d_name);
			free((char *)dearray[deindex].f_stat);
			continue;
		}
		dearray[deindex].f_name = nbindex;
		bcopy(dp->d_name, nbarray + nbindex, namelen+1);
		nbindex += namelen + 1;

		++deindex;
	}
	if (mustexit) {
		for (i = 0; i < deindex; ++i)
			free((char *)dearray[i].f_stat);
		return 0;
	}

	qsort((char *)dearray, deindex, sizeof dearray[0], decmp);

	for (i = 0; i < deindex; ++i)
		free((char *)dearray[i].f_stat);

	i = 0;
	while (deindex-- > 0) {
		if (mustexit)
			break;
		if (gothup) 
			dohup();
		i += rd_doit(nbarray + dearray[deindex].f_name);
	}
	return i;
}

int
rd_instability(dirp)
	DIR *dirp;
{
	struct NDIR_TYPE *dp;
	int i;

	i = 0;
	while (dp = readdir(dirp)) {
		if (mustexit)
			break;
		if (gothup)
			dohup();
		if (dp->d_name[0] != '.')
			i += rd_doit(dp->d_name);
	}
	return i;
}

int
run_stability(argc, argv)
	int argc;
	char *argv[];
{
	extern int stability, real_stability;

	switch (argc) {
	case 1:
		printf("%s %s\n", argv[0], stability ? "on" : "off");
		break;
	case 2:
		if (strcmp(argv[1], "on") == 0) {
			real_stability = 1;
		} else if (strcmp(argv[1], "off") == 0) {
			real_stability = 0;
		} else {
	default:
			fprintf(stderr, "Usage: %s [ on | off ]\n", argv[0]);
			return 1;
		}
		break;
	}
	return 0;
}

int
run_daemon(argc, argv)
	int argc;
	char *argv[];
{
	DIR *dirp;
	long loc;
	int nap, did_cnt;
	extern u_int sweepintvl;
	extern int stability, real_stability;
	extern void setfreefd();

	(void) signal(SIGTERM, sig_exit);	/* mustexit = 1 */
	dirp = opendir(".");	/* assert dirp != NULL ... */
	dirp->dd_size = 0;	/* stupid Berkeley bug workaround */
	loc = telldir(dirp);
	setfreefd();
	if (stability)
		rd_initstability();
	nap = 0;
	for (;;) {
		/*
		 * We do a seekdir() instead of opendir()/closedir()
		 * inside the loop, to avoid allocating and freeing
		 * an 8k chunk of memory all the time.  This can lead
		 * to memory fragmentation and thus growing VM.
		 */
		seekdir(dirp, loc);

		if (stability)
			did_cnt = rd_stability(dirp);
		else
			did_cnt = rd_instability(dirp);

		if (stability != real_stability) {
			stability = real_stability;
			if (stability == 0)
				rd_endstability();
			else
				rd_initstability();
		}

		if (mustexit)
			break;
		/*
		 * If we're busy, check again immediately.
		 * Otherwise slowly ramp the nap time back up.
		 * This speeds delivery at a small cost.
		 */
		if (did_cnt > 0) {
			nap = 0;
			continue;
		}
		if (nap < sweepintvl)
			nap++;
		canexit = 1;
		/*
		 * If a shell signal interrupts us here, there is
		 * potential for problems knowing which file descriptors
		 * are free.  One could add a setfreefd() to the trap
		 * routine, in that case.
		 */
		(void) sleep(nap);
		canexit = 0;
	}
	/*
	 * Major serious bug time here;  some closedir()'s
	 * free dirp before referring to dirp->dd_fd. GRRR.
	 * XX: remove this when bug is eradicated from System V's.
	 */
	(void) close(dirp->dd_fd);
	(void) closedir(dirp);
	return 0;
}

/*
 * Based on the name of a message file, figure out what to do with it.
 */

struct protosw {
	char *pattern;
	char *function;
} psw[] = {
/*{	"[0-9]*.x400",		"x400"		}, */
/*{	"[0-9]*.fax",		"fax"		}, */
/*{	"[0-9]*.uucp",		"uucp"		}, */
{	"[0-9]*",		"rfc822"	},
};


int
run_process(argc, argv)
	int argc;
	char *argv[];
{
	struct protosw *pswp;
	char *file;
	int r;
	extern int strmatch(), s_apply();

	if (argc != 2 || argv[1][0] == '\0') {
		fprintf(stderr, "Usage: %s messagefile\n", argv[0]);
		return PERR_USAGE;
	}
	file = emalloc(strlen(argv[1])+1);
	strcpy(file, argv[1]);

	r = 0;	/* by default, ignore it */
	for (pswp = &psw[0]; pswp < &psw[(sizeof psw / sizeof psw[0])]; ++pswp)
		if (strmatch(pswp->pattern, file)) {
			printf("process %s %s\n", pswp->function, file);
			argv[0] = pswp->function;
			r = s_apply(argc, argv);
			printf("done with %s\n", file);
			if (r)
				printf("status %d\n", r);
			break;
		}

	(void) free(file);
	return r;
}


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

int
run_grpmems(argc, argv)
	int argc;
	char *argv[];
{
	char **cpp;
	struct group *grp;
	extern struct group *getgrnam();

	if (argc != 2) {
		fprintf(stderr, "Usage: %s groupname\n", argv[0]);
		return 1;
	}
	if ((grp = getgrnam(argv[1])) == 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);
	return 0;
}

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

int
run_praliases(argc, argv)
	int argc;
	char *argv[];
{
	register struct header *h;
	struct envelope *e;
	char *cp;
	int errflg, count, status;
	long offset, size, prevsize, maxsize;
	FILE *indexfp;
	int c, verbose;
	char *indexfile, buf[8192], ibuf[BUFSIZ];
	struct siobuf *osiop = NULL;
	extern int optind, errno;
	extern int makeLetter();
	extern void hdr_errprint(), hdr_print();
	extern char *optarg;
	extern HeaderStamp hdr_type();

	verbose = 0; indexfile = NULL;
	optind = 0;
	errflg = 0;
	while ((c = getopt(argc, argv, "vo:")) != EOF) {
		switch (c) {
		case 'v':
			++verbose;
			break;
		case 'o':
			indexfile = optarg;
			break;
		default:
			++errflg;
			break;
		}
	}
	if (errflg || optind != argc - 1) {
		fprintf(stderr,
			"Usage: %s [ -v ] [ -o indexoutputfile ] aliasfile\n",
			argv[0]);
		return 1;
	}

	e = (struct envelope *)tmalloc(sizeof (struct envelope));
	if ((e->e_fp = fopen(argv[optind], "r")) == NULL) {
		c = errno;
		fprintf(stderr, "%s: open(\"%s\"): ", argv[0], argv[optind]);
		errno = c;
		perror("");
		status = PERR_BADOPEN;
	} else {
		(void) setvbuf(e->e_fp, buf, _IOFBF, sizeof buf);
		osiop = siofds[fileno(e->e_fp)];
		siofds[fileno(e->e_fp)] = NULL;
		e->e_file = argv[optind];
		status = makeLetter(e, 1);
		siofds[fileno(e->e_fp)] = osiop;
		(void) 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);
		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) {
			(void) 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) {
		for (cp = h->h_pname; cp && *cp; ++cp)
			if (isascii(*cp) && isupper(*cp))
				*cp = tolower(*cp);
	}

	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;
		}
		(void) 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) && strcmp(cp, h->h_pname) == 0) {
			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;
}

int
run_listaddrs(argc, argv)
	int argc;
	char *argv[];
{
	struct header hs;
	struct envelope *e;
	struct address *ap;
	struct token **pt, *t;
	struct addr *pp;
	int c, n, errflag, stuff;
	char *comment, *erroraddress;
	u_char *s;
	struct siobuf *osiop;
	FILE *mfp, *fp;
	char buf[4096];
	extern HeaderStamp hdr_type();
	extern time_t now;
	extern char *optarg;
	extern void initline(), pureAddress(), hdr_errprint();
	extern int getline(), iserrmessage();

	errflag = 0;
	erroraddress = NULL;
	comment = "list";
	while ((c = getopt(argc, argv, "c:e:E:")) != EOF) {
		switch (c) {
		case 'c':
			comment = optarg;
			break;
		case 'e':
			erroraddress = optarg;
			break;
		case 'E':
			if (errors_to) free(errors_to);
			errors_to = malloc(strlen(optarg)+1);
			if (errors_to)
				strcpy(errors_to, optarg);
			break;
		default:
			++errflag;
			break;
		}
	}
	if (errflag) {
		fprintf(stderr,
			"Usage: %s [ -e error-address ] [ -E errors-to-address ] [ -c comment ]\n",
			argv[0]);
		return 1;
	}
	e = (struct envelope *)tmalloc(sizeof (struct envelope));
	e->e_nowtime = now;
	e->e_file = argv[0];
	/* we only need sensible e if its a time stamp header, which it ain't */
	hs.h_descriptor = &aliashdr;
	hs.h_pname = argv[0];

	/* create h->h_lines */
	/*
	 * It is best to maintain a line at a time as tokens, so errors will
	 * print out nicely.
	 */
	initline(4096);
	pt = &hs.h_lines;
	t = NULL;
	/*
	 * 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) {
		(void) fprintf(stderr, "%s: fdopen failed\n", argv[0]);
		return 1;
	}
	/* use a constant buffer to avoid memory leaks from stdio usage */
	(void) setvbuf(fp, buf, _IOFBF, sizeof buf);
	c = 0;
	while ((n = getline(fp)) > 0) {
		/*
		 * For sendmail compatibility, addresses may not cross line
		 * boundaries, and line boundary is just as good an address
		 * separator as comma is, sigh.
		 */
		if (linebuf[n-1] == '\n')
			--n;
		stuff = 0;
		for (s = linebuf; s < linebuf + n; ++s) {
			if (isascii(*s) && !isspace(*s))
				c = stuff = 1;
			if (*s == ',')
				c = 0;
		}
		if (!stuff)
			continue;
		if (c) {
			linebuf[n] = ',';
			++n;
		}
                /**
                 * Additional sendmail compability kludge ++haa
                 * If address starts with \, zap the \ character.
                 * This is jus to not to force people to edit their
                 * .forwards :-(  "\haa" works as expected  :-)
                 **/
                if (linebuf[0] == '\\') linebuf[0] = ' ';
                for (s=linebuf; s<linebuf+n; ++s) {
                  if (*s == ',' && *(s+1)=='\\') *(s+1)=' ';
                }
		/* end of sendmail compatibility kluge */
		*pt = t = makeToken(linebuf, n);
		t->t_type = Line;
		pt = &(t->t_next);
	}
#ifdef	USE_FILENOFUNC
	fp->_file = -1;		/* so the close will fail */
#else	/* !USE_FILENOFUNC */
	fileno(fp) = -1;	/* so the close will fail */
#endif	/* USE_FILENOFUNC */
	fclose(fp);

	if (t == NULL) {	/* if the file is empty, use error address */
		*pt = t = makeToken(erroraddress, strlen(erroraddress));
		t->t_type = Line;
		pt = &(t->t_next);
	}

	/* fix up any trailing comma (more sendmail compatibility kluges) */

	s = t->t_pname + TOKENLEN(t)-1;
	if (c && (*s == ',' || *s == '\n'))
		*s = '\0';
	*pt = NULL;

	hs.h_contents = hdr_scanparse(e, &hs, 1);
	hs.h_stamp = hdr_type(&hs);
	if (hs.h_stamp == BadHeader || hs.h_contents.a == NULL) {
		if (hs.h_stamp == BadHeader) {
			if (erroraddress != NULL) {
				if (!iserrmessage()
				    && (mfp = mail_open(MSG_RFC822)) != NULL) {
					osiop = siofds[fileno(mfp)];
					siofds[fileno(mfp)] = NULL;
					fprintf(mfp, "channel error\n");
					fprintf(mfp, "To: %s\n", erroraddress);
					fprintf(mfp, "Subject: Error in %s\n\n",
							comment);
					hdr_errprint(e, &hs, mfp, comment);
					siofds[fileno(mfp)] = osiop;
					(void) mail_close(mfp);
				}
				printf("%s\n", erroraddress);
			}
			hdr_errprint(e, &hs, stderr, comment);
		} else {/* if (hs.h_contents.a == NULL) */
			if (erroraddress != NULL)
				printf("%s\n", erroraddress);
			fprintf(stderr, "%s: null input\n", argv[0]);
		}
		return 1;
	}
	for (ap = hs.h_contents.a; ap != NULL; ap = ap->a_next) {
		for (pp = ap->a_tokens; pp != NULL; pp = pp->p_next)
			if (pp->p_type == anAddress)
				break;
		if (pp == NULL)
			continue;
		pureAddress(stdout, ap->a_tokens);
		/* (void) printAddress(stdout, ap->a_tokens, 0); */
		putc('\n', stdout);
	}
	/*
	 * XX: If the alias expansion was a mail group,
	 * we didn't print anything.
	 */
	return 0;
}

int
run_homedir(argc, argv)
	int argc;
	char *argv[];
{
	struct passwd *pw;
	int changed;
	char *cp;
	extern struct passwd *getpwnam();

	if (argc != 2) {
		fprintf(stderr, "Usage: %s name\n", argv[0]);
		return 1;
	}
	if ((pw = getpwnam(argv[1])) == NULL) {
		for (cp = argv[1], changed = 0 ; *cp != '\0'; ++cp) {
			if (isascii(*cp) && isupper(*cp)) {
				*cp = tolower(*cp);
				++changed;
			}
		}
		if (!changed || (changed && (pw = getpwnam(argv[1])) == NULL))
			return 2;
	}
	printf("%s\n", pw->pw_dir);
	return 0;
}

int
run_822date(argc, argv)
	int argc;
	char *argv[];
{
	time_t now;
	extern time_t time();
	extern char *rfc822date();

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

int
run_filepriv(argc, argv)
	int argc;
	char *argv[];
{
	int id;
	struct stat stbuf;
	char *file, *cp, *dir;
	FILE *fp;
	extern int nobody;

	if (argc == 1 || argc > 3) {
		fprintf(stderr, "Usage: %s pathname [ uid ]\n", argv[0]);
		return 1;
	}
	file = argv[1];
	if (argc == 3 && isascii(argv[2][0]) && isdigit(argv[2][0]))
		id = atoi(argv[2]);
	else {
		if ((fp = fopen(file, "r")) == NULL) {
			/* if we can't open it, don't trust it */
			perror(file);
			(void) fprintf(stderr,
				"%s: cannot fopen(\"%s\")!\n", argv[0], file);
			return 2;
		}
		if (fstat(fileno(fp), &stbuf) < 0) {
			(void) fprintf(stderr,
				"%s: cannot fstat(\"%s\")!\n", argv[0], file);
			(void) fclose(fp);
			return 3;
		}
		fclose(fp);
		if ((stbuf.st_mode & S_IFMT) != S_IFREG
		    || ((stbuf.st_mode & 022) && !(stbuf.st_mode & S_ISVTX))) {
			/*
			 * If it is a symlink or special or directory
			 * or writable by non-owner, don't trust it
			 */
			printf("%d\n", nobody);
			return 0;
		}
		id = stbuf.st_uid;
	}
	if ((cp = strrchr(file, '/')) == NULL) {
		printf("%d\n", id);
		return 0;
	} else if (cp == file)	/* root path */
		++cp;
	dir = emalloc(cp - file + 1);
	bcopy(file, dir, cp - file);
	dir[cp - file] = '\0';

	if (stat(dir, &stbuf) < 0 || (stbuf.st_mode & S_IFMT) != S_IFDIR) {
		fprintf(stderr, "%s: not a directory: \"%s\"!\n", argv[0], dir);
		(void) free(dir);
		return 4;
	}
	(void) free(dir);
	if (((stbuf.st_mode & 022) && !(stbuf.st_mode & S_ISVTX))
	    || (stbuf.st_uid != 0 && stbuf.st_uid != id))
		id = nobody;
	printf("%d\n", id);
	return 0;
}

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

	if (argc < 3) {
		fprintf(stderr, "Usage: %s user function [args...]\n", argv[0]);
		return 1;
	}
	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 (
#ifdef	USE_SETREUID
			setreuid(0, uid)
#else	/* SVID */
#ifdef	USE_SETEUID
			seteuid(uid)
#else	/* ! USE_SETEUID */
			setuid(uid)	/* Only reversible under recent SVID! */
#endif	/* USE_SETEUID */
#endif	/* USE_SETREUID */
			< 0) {
			(void) fprintf(stderr, "%s: setuid(%d): %s\n",
					       argv[0], uid, strerror(errno));
			return 1;
		}
	}
	/* must be builtin or defined function */
	r = s_apply(argc - 2, argv + 2);
	if (myuid == 0 && uid != 0) {
		if (
#ifdef	USE_SETREUID
			setreuid(0, 0)
#else	/* SVID */
#ifdef	USE_SETREUID
			seteuid(0)
#else	/* ! USE_SETEUID */
			setuid(0)	/* Only works under recent SVID! */
#endif	/* USE_SETREUID */
#endif	/* USE_SETREUID */
			< 0)
			abort();
	}

	return r;
}

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

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

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

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

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

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

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

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

int
run_recase(argc, argv)
	int argc;
	char *argv[];
{
	char *cp;
	int c, flag, errflg, action;
	extern int optind;

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

	switch (action) {
	case 'u':
		for (cp = argv[optind]; *cp != '\0'; ++cp)
			if (isascii(*cp) && islower(*cp))
				*cp = toupper(*cp);
		break;
	case 'l':
		for (cp = argv[optind]; *cp != '\0'; ++cp)
			if (isascii(*cp) && isupper(*cp))
				*cp = tolower(*cp);
		break;
	case 'p':
		flag = 1;
		for (cp = argv[optind]; *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[optind]);
	return 0;
}

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


struct {
	short	fyitype;
	short	fyisave;
	char	*fyiname;
	char	*fyitext;
} fyitable[] = {
{ FYI_BREAKIN, 1, "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)
				squirrel(e, fyitable[i].fyitext);
			fprintf(stderr, "*** %s\n", fyitable[i].fyitext);
			return;
		}
	}
}

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

	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 (cistrcmp(argv[i], fyitable[j].fyiname) == 0) {
				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 1;
	}
	return 0;
}

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

int
run_822syntax(argc, argv)
	int argc;
	char *argv[];
{
	struct header hs;
	struct envelope es;
	char buf[4096];
	extern time_t now;
	extern HeaderStamp hdr_type();
	extern void hdr_errprint();

	if (argc != 2)
		return 2;
	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);
	hs.h_stamp = hdr_type(&hs);
	if (hs.h_stamp == BadHeader) {
		hdr_errprint(&es, &hs, stderr, "RFC822/976");
		return 1;
	} else if (hs.h_contents.a == NULL)
		return 1;
	return 0;
}
