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

/*
 * This file contains most of the RFC822-specific message manipulation.
 */

#include "mailer.h"
#include <sys/param.h>
#include <fcntl.h>
#include <sys/file.h>
#include <errno.h>
#include "mail.h"
#ifdef	HAVE_SYSLOG
#include <syslog.h>
#endif	/* HAVE_SYSLOG */
#include <unistd.h>

#include "prototypes.h"

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

#ifdef	HAVE_LOCKF
#ifdef	F_OK
#undef	F_OK
#endif	/* F_OK */
#ifdef	X_OK
#undef	X_OK
#endif	/* X_OK */
#ifdef	W_OK
#undef	W_OK
#endif	/* W_OK */
#ifdef	R_OK
#undef	R_OK
#endif	/* R_OK */

#endif	/* HAVE_LOCKF */

extern int hdr_status();
extern int efstat();
extern int elink();
extern int eunlink();
extern int erename();
extern int getline();
extern int nobody;
extern int s_equal();
extern long elseek();
extern long offset();
extern void initline();


static void	prctladdr __((struct conscell *info, FILE *fp, int cfflag, char *comment));
static void	prdsndata __((struct conscell *info, FILE *fp, int cfflag, char *comment));
static struct conscell	*find_errto __((struct conscell *list));

/*
 * Apply RFC822 parsing and processing to the message in the file in argv[1].
 */

int
run_rfc822(argc, argv)
	int argc;
	char *argv[];
{
	struct envelope *e;
	char *file, buf[8196];
	int status, c, errflg;
	memtypes oval;

	errflg = 0;
/*
	while ((c = getopt(argc, argv, "")) != EOF) {
		switch (c) {
		default:
			++errflg;
			break;
		}
	}
	if (errflg || optind != (argc - 1)) { 
*/
	if (argc != 2) {
		fprintf(stderr, "Usage: %s messagefile\n", argv[0]);
		return PERR_USAGE;
	}

	/* file = argv[optind]; */
	file = argv[1];

#ifdef	XMEM
	mal_contents(stdout);
#endif	/* XMEM */
	oval = stickymem;
	stickymem = MEM_TEMP;	/* per-message space */

	e = (struct envelope *)tmalloc(sizeof (struct envelope));

	if ((e->e_fp = fopen(file, "r")) == NULL) {
		fprintf(stderr, "router: cannot open %s\n", file);
		status = PERR_BADOPEN;
	} else {
/* XX: DEBUG STUFF! */
if (FILENO(e->e_fp) < 3) {
  fprintf(stderr,"RFC822: While opening mail-file '%s', got fd=%d  AARGH! (fds 0..2 should never close on us!)",
	  file, FILENO(e->e_fp));
  fflush(stderr);
  /* abort(); */
}
		status = PERR_OK;
		setvbuf(e->e_fp, buf, _IOFBF, sizeof buf);
		e->e_file = file;
		status = makeLetter(e, 0);
	}

	if (status == 0)
		/* passing the file is redundant but nice for backtraces */
		status = sequencer(e, file);

	switch (status) {
	case PERR_OK:		/* 0 */
		break;
	case PERR_BADOPEN:
		break;
	case PERR_BADCONTINUATION:	/* fatal */
		squirrel(e, "continuation line prior to header");
		break;
	case PERR_BADSUBMIT:		/* fatal */
		squirrel(e, "unrecognized envelope information");
		break;
	case PERR_LOOP:			/* fatal */
		squirrel(e, "possible looping message");
		break;
	case PERR_ENVELOPE:		/* fatal */
		reject(e, "envelope");
		break;
	case PERR_DEFERRED:
		defer(e);
		break;
	case PERR_HEADER:		/* fatal */
		reject(e, "header");
		break;
	case PERR_NORECIPIENTS:		/* fatal */
		reject(e, "norecipients");
		break;
	case PERR_NOSENDER:
		squirrel(e, "really truly Unknown Sender!");
		break;
	case PERR_CTRLFILE:
		defer(e);
		break;
	default:
		abort();
	}

	if (D_final > 0)
		dumpInfo(e);
	if (e->e_fp != NULL)
		fclose(e->e_fp);
	tfree(MEM_TEMP);
	stickymem = oval;
#ifdef	XMEM
	mal_contents(stdout);
#endif	/* XMEM */
	return status;
}

/*
 * Read, store, and parse the message control information (envelope + header).
 */

int
makeLetter(e, octothorp)
	register struct envelope *e;
	int octothorp;		/* does # at start of word start a comment? */
{
	register int	i;
	register u_char	*cp;
	struct header	*h;
	int		n, inheader;
	struct header	*ph, *nh;
	int badcontinuationflag = 0;
	int badsubmitflag = 0;

	e->e_eHeaders = 0;
	e->e_headers = 0;
	e->e_hdrOffset = 0;
	e->e_msgOffset = 0;
	e->e_nowtime = now = time(NULL);
	if (efstat(FILENO(e->e_fp), &(e->e_statbuf)) < 0) {
#ifdef	HAVE_ST_BLKSIZE
		e->e_statbuf.st_blksize = 0;
#endif	/* !HAVE_ST_BLKSIZE */
		e->e_statbuf.st_mtime = e->e_nowtime;
	}
	e->e_localtime = *(localtime(&(e->e_statbuf.st_mtime)));
#ifdef	HAVE_ST_BLKSIZE
	initline(e->e_statbuf.st_blksize);
#else	/* !HAVE_ST_BLKSIZE */
	initline(4096);
#endif	/* !HAVE_ST_BLKSIZE */

	inheader = 0;
	while ((n = getline(e->e_fp)) > !octothorp) {
		/* We do kludgy processing things in case the input
		   does have a CRLF at the end of the line.. */
		if (n > 1 &&
		    linebuf[n-2] == '\r' &&
		    linebuf[n-1] == '\n') {
			--n;
			linebuf[n-1] = '\n';
			if (n <= !octothorp) break;
		}
		/* Ok, now we can proceed with the original agenda.. */
		i = hdr_status(linebuf, linebuf, n);
		if (i > 0) {		/* a real message header */
			if (octothorp && linebuf[0] == '#')
				continue;
			/* record start of headers for posterity */
			if (!inheader)
				e->e_hdrOffset = offset(e->e_fp)-n;
			/* cons up a new one at top of header list */
			h = makeHeader(spt_headers, linebuf, i);
			h->h_next = e->e_headers;
			e->e_headers = h;
			h->h_lines = makeToken(linebuf+i+1, n-i-2);
			h->h_lines->t_type = Line;
			++inheader;
		} else if (i == 0) {	/* a continuation line */
			if (inheader && linebuf[0] == ':') {
				optsave(FYI_ILLHEADER, e);
				/* cons up a new one at top of header list */
				h = makeHeader(spt_headers,
					       (u_char*)"X-Null-Field", 12);
				h->h_next = e->e_headers;
				e->e_headers = h;
				h->h_lines = makeToken(linebuf+i+1, n-i-2);
				h->h_lines->t_type = Line;
			} else if (inheader && n > 1) {
				/* append to the header we just saw */
				struct token *t;
				t = e->e_headers->h_lines;
				while (t->t_next != NULL)
					t = t->t_next;
				t->t_next = makeToken(linebuf, n-1);
				t->t_next->t_type = Line;
			} else if (!octothorp) {
				/* return PERR_BADCONTINUATION */ ;
				badcontinuationflag = 1;
			}
		} else if (!inheader		/* envelope information */
			   && (*(cp=linebuf-i) == ' ' || *cp == '\t'
				|| *cp == '\n')) {
			if (octothorp && linebuf[0] == '#')
				continue;
			/* cons up a new one at top of envelope header list */
			h = makeHeader(spt_eheaders, linebuf, -i);
			h->h_next = e->e_eHeaders;
			e->e_eHeaders = h;
			h->h_lines = makeToken(linebuf-i+1, n > 1-i ? n+i-2: 0);
			h->h_lines->t_type = Line;
			switch (h->h_descriptor->class) {
				struct headerinfo *hd;
				HeaderSemantics osem;

			case normal:
			case Resent:
				/* error */
				fprintf(stderr,
					"%s: unknown envelope header: ",
					progname);
				fwrite((char *)linebuf, sizeof (char), -i, stderr);
				putc('\n', stderr);
				h->h_contents = hdr_scanparse(e, h, octothorp);
				h->h_stamp = hdr_type(h);
				/* return PERR_BADSUBMIT; */
				badsubmitflag = 1;
				break;
			case eFrom:		/* pity to break the elegance */
				hd = h->h_descriptor;
				osem = hd->semantics;
				hd->semantics = Mailbox;
				h->h_contents = hdr_scanparse(e, h, octothorp);
				h->h_stamp = hdr_type(h);
				hd->semantics = osem;
				break;
			default:		/* a real envelope header */
				h->h_contents = hdr_scanparse(e, h, octothorp);
				h->h_stamp = hdr_type(h);
				break;
			}
		} else if (!octothorp)
			break;
	}
	/* reverse the list of headers so we keep them in the original order */
	for (ph = NULL, h = e->e_eHeaders; h != NULL; h = nh) {
		nh = h->h_next;
		h->h_next = ph;
		if (h->h_descriptor->semantics == nilHeaderSemantics
		    || !hdr_nilp(h))
			ph = h;
	}
	e->e_eHeaders = ph;
	/* parse the message headers -- we already took care of envelope info */
	for (ph = NULL, h = e->e_headers; h != NULL; h = nh) {
		nh = h->h_next;
		h->h_next = ph;
		if (!octothorp && h->h_descriptor != NULL) {
			h->h_contents = hdr_scanparse(e, h, 0);
			h->h_stamp = hdr_type(h);
			if (!hdr_nilp(h))	/* excise null-valued headers */
				ph = h;
		} else
			ph = h;
	}
	e->e_headers = ph;
	/* record the start of the message body for posterity */
	e->e_msgOffset = offset(e->e_fp);

	if (badsubmitflag || badcontinuationflag)
	  squirrel(e, "unrecognized envelope information; entries discarded!");

	return 0;
}

void
dumpInfo(e)
	struct envelope *e;
{
	printf("Message header starts at byte %ld.\n", e->e_hdrOffset);
	printf("Message body starts at byte %ld.\n", e->e_msgOffset);
	printf("ENVELOPE:\n");
	dumpHeaders(e->e_eHeaders);
	printf("HEADERS:\n");
	dumpHeaders(e->e_headers);
}

void
dumpHeaders(h)
	struct header *h;
{
	for (; h != NULL; h = h->h_next)
		dumpHeader(h);
}

void
dumpHeader(h)
	struct header *h;
{
	struct token	*t;
	struct address	*a;
	struct addr	*p;

	if (h->h_stamp == BadHeader)
		hdr_errprint(NULL, h, stdout, "header");
	printf("%s\n", h->h_pname);
	if (h->h_descriptor == NULL)
		return;
	switch (h->h_descriptor->semantics) {
	case DateTime:
		printf("\tUNIX time %d, local time %s",
			h->h_contents.d, ctime(&(h->h_contents.d)));
		break;
	case nilHeaderSemantics:
		for (t = h->h_lines; t != NULL; t = t->t_next)
			printf("\t%s\n", formatToken(t));
		break;
	case Received:
		if (h->h_contents.r == NULL)
			break;
		if ((a = h->h_contents.r->r_from) != NULL) {
			printf("\tFrom:\n");
			for (p = a->a_tokens; p != NULL; p=p->p_next) {
				printf("\t%s:\n", formatAddr(p->p_type));
				for (t = p->p_tokens; t != NULL; t = t->t_next)
					printf("\t\t%s\n", formatToken(t));
			}
		}
		if ((a = h->h_contents.r->r_by) != NULL) {
			printf("\tBy:\n");
			for (p = a->a_tokens; p != NULL; p=p->p_next) {
				printf("\t%s:\n", formatAddr(p->p_type));
				for (t = p->p_tokens; t != NULL; t = t->t_next)
					printf("\t\t%s\n", formatToken(t));
			}
		}
		if ((t = h->h_contents.r->r_via) != NULL)
			printf("\tVia: %s\n", formatToken(t));
		for (t = h->h_contents.r->r_with; t != NULL; t = t->t_next)
			printf("\tWith: %s\n", formatToken(t));
		if ((t = h->h_contents.r->r_convert) != NULL)
			printf("\tConvert: %s\n", formatToken(t));
		printf("\tUNIX time %d, local time %s",
			h->h_contents.r->r_time,
			ctime(&(h->h_contents.r->r_time)));
		break;
	default:
		for (a = h->h_contents.a; a != NULL; a = a->a_next) {
			for (p = a->a_tokens; p != NULL; p=p->p_next) {
				printf("\t%s:\n", formatAddr(p->p_type));
				for (t = p->p_tokens; t != NULL; t = t->t_next)
					printf("\t\t%s\n", formatToken(t));
			}
			printf("--- end of address ---\n");
		}
		break;
	}
}


/*
 * The following two variables are set so header address rewriting functions
 * can find out whether they are dealing with a sender or recipient address.
 * The variables are accessed through C coded config file functions.
 */
int	isSenderAddr = 0;
int	isRecpntAddr = 0;


#define FindEnvelope(X)	\
	for (h = e->e_eHeaders; h != NULL; h = h->h_next) \
	    if (h->h_descriptor->class == X) \
		break;

#define RESENT_SIZE (sizeof "resent-" - 1)

#define	FindHeader(X,RESENT) \
	for (h = e->e_headers; h != NULL; h = h->h_next) \
	    if ((RESENT ? (h->h_descriptor->class == Resent) : \
		          (h->h_descriptor->class != Resent)   ) \
		&& h->h_descriptor->hdr_name != NULL \
		&& STREQ(h->h_descriptor->hdr_name+(RESENT?RESENT_SIZE:0), X))\
		    break;

/* we insert the new header just before this point */
#define InsertHeader(IH, EXPR) \
	{ for (ph = e->e_headers; ph != NULL; ph = ph->h_next) \
		if (ph->h_next == IH) \
			break; \
	nh = EXPR; \
	if (ph != NULL) { \
		nh->h_next = IH; \
		ph->h_next = nh; \
	} else { \
		nh->h_next = e->e_headers; \
		e->e_headers = nh; \
	} }

/* Make it possible to do real override of Errors-To: with  $(listaddress ...) */
char *errors_to = NULL;



/* Save the message in a directory for the postmaster to view it later */

void
squirrel(e, text)
	struct envelope *e;
	char *text;
{
	char *path;

#ifndef	USE_ALLOCA
	path = emalloc(5+strlen(POSTMANDIR)+10);
#else
	path = alloca(5+strlen(POSTMANDIR)+10);
#endif
	sprintf(path, "../%s/%d", POSTMANDIR, e->e_statbuf.st_ino);

	/* XX: We make a copy by link()ing file to two dirs, some
	       systems (Andrew-FS in mind) can't do it! Copying is
	       ok in their case.. */

	if (elink(e->e_file, path) < 0) {
		sprintf(path, "..%d", e->e_statbuf.st_ino);
		erename(e->e_file, path);
	} else {
		eunlink(e->e_file);
	}
	fprintf(stderr, "squirrel: %d saved for inspection: %s\n",
			e->e_statbuf.st_ino, text);
#ifdef	LOG_ERR
	syslog(LOG_ERR, "%d saved for inspection: %s\n",
			e->e_statbuf.st_ino, text);
#endif	/* LOG_ERR */
#ifndef	USE_ALLOCA
	free(path);
#endif
}

struct header *
erraddress(e)
	struct envelope *e;
{
	register struct header *h;
	struct header *best;
	int minval;
	char *cp, **cpp;

	best = NULL;
	minval = 10000;
	for (h = e->e_headers; h != NULL; h = h->h_next) {
		if ((e->e_resent != 0 && h->h_descriptor->class != Resent)
		    || h->h_descriptor->hdr_name == NULL
		    || h->h_stamp == BadHeader)
			continue;
		cp = h->h_descriptor->hdr_name + e->e_resent;
		/* char *err_prio_list[] = { "sender", "errors-to", 0 }; */
		for (cpp = err_prio_list; *cpp != NULL; ++cpp) {
			if (CISTREQ(*cpp, cp)
			    && (cpp - err_prio_list) < minval) {
				best = h;
				minval = cpp - err_prio_list;
			}
		}
	}
	if (best == NULL) {
		FindEnvelope(eFrom);	/* should never be NULL */
		/* but might still be problematic */
		if (h != NULL && h->h_stamp == BadHeader)
		  FindHeader("from",e->e_resent);
	} else
		h = best;
	if (h != NULL && h->h_stamp == BadHeader)
		h = NULL;
	if (h == NULL) {
		/* everything else failed, so use the owner of the file */
		if (!e->e_trusted)
			h = mkSender(e, uidpwnam(e->e_statbuf.st_uid), 1);
#if	0
		else
			h = mkSender(e, (u_char *)POSTMASTER, 1);
#endif
	}
	return h;
}

/* Send the message back to originator with user-friendly chastizing errors */

void
reject(e, msgfile)
	struct envelope *e;
	char *msgfile;
{
	register struct header *h;
	char *path, lastCh, nextToLastCh, *cp;
	int n;
	FILE *mfp, *fp;
	char buf[BUFSIZ], vbuf[BUFSIZ];

	fprintf(stderr, "Rejecting because of %s\n", msgfile);
	
	if ((h = erraddress(e)) == NULL) {
		squirrel(e, "No one to return an error to!");
		return;
	}
	/*
	 * turn all (possibly more than one) the addresses in whatever h
	 * is pointing at, into recipient addresses.
	 */
#ifndef	USE_ALLOCA
	path = emalloc(3+strlen(mailshare)+strlen(FORMSDIR)+strlen(msgfile));
#else
	path = alloca(3+strlen(mailshare)+strlen(FORMSDIR)+strlen(msgfile));
#endif
	sprintf(path, "%s/%s/%s", mailshare, FORMSDIR, msgfile);
	if ((fp = fopen(path, "r")) == NULL) {
		perror(path);
		squirrel(e, "Couldn't open reject form file");
#ifndef	USE_ALLOCA
		free(path);
#endif
		return;
	}
#ifndef	USE_ALLOCA
	free(path);
#endif
	if ((mfp = mail_open(MSG_RFC822)) == NULL) {
		squirrel(e, "Couldn't open reject message file");
		return;
	}
	/* who is it from? */
	cp = h->h_pname;
	h->h_pname = "To";
	hdr_print(h, mfp);
	/* XX: if forcing postmaster cc:, do it here */
	h->h_pname = cp;
	lastCh = nextToLastCh = '\0';
	setvbuf(fp, vbuf, _IOFBF, sizeof vbuf);
	while (fgets(buf,sizeof(buf),fp) != NULL) {
	  int rc;
	  n = strlen(buf);
	  if (strncmp("HDR",buf,3)==0 ||
	      strncmp("SUB",buf,3)==0) {
	    rc = fputs(buf+4,mfp);
	    n -= 4;
	  } else
	    rc = fputs(buf,mfp);
	  if (n > 1) {	  
	    nextToLastCh = buf[n-2];
	    lastCh = buf[n-1];
	  } else {
	    nextToLastCh = lastCh;
	    lastCh = buf[0];
	  }
	}
	fclose(fp);
	if (lastCh == '\n') {
		if (nextToLastCh != '\n')
			putc('\n', mfp);
	} else {
		putc('\n', mfp);
		putc('\n', mfp);
	}
	/* print the headers that had errors in them, with annotations */
	for (h = e->e_eHeaders; h != NULL; h = h->h_next)
		if (h->h_stamp == BadHeader) {
			hdr_errprint(e, h, mfp, "envelope");
			putc('\n', mfp);
		}
	for (h = e->e_headers; h != NULL; h = h->h_next)
		if (h->h_stamp == BadHeader) {
			hdr_errprint(e, h, mfp, "header");
			putc('\n', mfp);
		}
	fprintf(mfp, "\nThe entire original message file follows.\n");
	fprintf(mfp, "\n-----------------------------------------\n");
	rewind(e->e_fp);
	while ((n = fread(buf, 1, sizeof buf, e->e_fp)) > 0) {
		if (fwrite(buf, 1, n, mfp) != n) {
			squirrel(e, "Couldn't include rejected input message");
			fclose(fp);
			mail_abort(mfp);
			return;
		}
	}
	if (mail_close(mfp) < 0) {
		squirrel(e, "Couldn't close reject message file");
		fclose(fp);
		mail_abort(mfp);
	}
	eunlink(e->e_file);
}

/* Save a message file away because something went wrong during routing */

void
defer(e)
	struct envelope *e;
{
	int i;
	char *path;

	if (e->e_fp != NULL && files_gid >= 0) {
		fchown(FILENO(e->e_fp), -1, files_gid);
		fchmod(FILENO(e->e_fp),
			(0440|e->e_statbuf.st_mode) & 0660);
	}
#ifdef	USE_ALLOCA
	path = alloca(5+strlen(DEFERREDDIR)+strlen(e->e_file));
#else
	path = emalloc(5+strlen(DEFERREDDIR)+strlen(e->e_file));
#endif
	sprintf(path, "../%s/%s", DEFERREDDIR, e->e_file);
	unlink(path);

	/* XX: Deferrance uses  link()/unlink(), while rename() is equally
	       valid choice */

	/* try linking every few seconds for a minute */
	for (i = 0; elink(e->e_file, path) < 0 && i < 10 && errno != ENOENT;++i)
		sleep(6);
	if (i >= 10) {
#ifdef	LOG_ALERT
		syslog(LOG_ALERT, "cannot defer %s (%m)", e->e_file);
#endif	/* LOG_ALERT */
		fprintf(stderr, "cannot defer %s\n", e->e_file);
		/* XX: make sure file is not unlink()ed in main() */
	} else {
		if (!savefile)
			eunlink(e->e_file);
#ifdef	LOG_NOTICE
		syslog(LOG_NOTICE, "%s; %sing deferred",
			   e->e_messageid == NULL ? e->e_file : e->e_messageid,
			   deferit ? "schedul" : "rout");
#endif	/* LOG_NOTICE */
	}
#ifndef	USE_ALLOCA
	free(path);
#endif
}

#define dprintf	if (D_sequencer) printf

#define	QCHANNEL(x)	(x)->string
#define	QHOST(x)	(cdr(x))->string
#define	QUSER(x)	(cddr(x))->string
#define	QATTRIBUTES(x)	(cdr(cddr(x)))->string


/*
 * Construct a header line that contains a given sender name.
 */

struct header *
mkSender(e, name, flag)
	struct envelope *e;
	u_char *name;
	int flag;
{
	register struct header	*sh, *h;
	struct addr *pp, *qp, **ppp;
	struct conscell *l;
	int didbracket;
	struct spblk *spl;
	
	sh = (struct header *)tmalloc(sizeof (struct header));
	sh->h_pname = e->e_resent ? "Resent-From" : "From";
	sh->h_descriptor = senderDesc();
	sh->h_stamp = newHeader;
	sh->h_lines = 0;
	sh->h_next = 0;
	sh->h_contents.a = (struct address *)tmalloc(sizeof (struct address));
	sh->h_contents.a->a_tokens = NULL;
	sh->h_contents.a->a_next = NULL;
	sh->h_contents.a->a_stamp = newAddress;
	sh->h_contents.a->a_dsn   = NULL;
	ppp = &sh->h_contents.a->a_tokens;
	didbracket = 0;
	FindEnvelope(eFullname);
	if (h != NULL && h->h_contents.a != NULL) {
		sh->h_contents.a->a_tokens = h->h_contents.a->a_tokens;
		/* 'Full Name' */
		for (pp = h->h_contents.a->a_tokens;
		     pp != NULL; pp = pp->p_next) {
			qp = (struct addr *)tmalloc(sizeof (struct addr));
			*qp = *pp;
			*ppp = qp;
			ppp = &qp->p_next;
			if (pp->p_next == NULL)
				break;
		}
	} else if ((spl = sp_lookup(symbol(name), spt_fullnamemap)) != NULL) {
		*ppp = (struct addr *)tmalloc(sizeof (struct addr));
		pp = *ppp;
		ppp = &pp->p_next;
		pp->p_tokens = makeToken((u_char *)spl->data,
					 strlen((char *)spl->data));
		pp->p_tokens->t_type = Word;
		pp->p_type = aPhrase;
	}
	if (sh->h_contents.a->a_tokens != NULL) {	/* 'Full Name <' */
		*ppp = (struct addr *)tmalloc(sizeof (struct addr));
		pp = *ppp;
		ppp = &pp->p_next;
		pp->p_tokens = makeToken((u_char *)"<", 1);
		didbracket = 1;
		pp->p_tokens->t_type = Special;
		pp->p_type = aSpecial;
	}
	FindEnvelope(ePrettyLogin);
	if (h != NULL && h->h_contents.a == NULL)
		h = NULL;
	if (h != NULL && !flag && !e->e_trusted) {
		/* make sure the pretty login is valid. see thesender() */
		l = router(h->h_contents.a, e->e_statbuf.st_uid, "sender");
		if (l != NULL) {
			l = pickaddress(l);
			flag = (QUSER(l) == NULL || !nullhost(QHOST(l)));
			if (!flag)
				flag = !CISTREQ(QUSER(l), name);
		} else
			flag = 1;
	} else
		l = NULL;
	if (h != NULL && (!flag || e->e_trusted)) {
		/* 'Full Name <Pretty.Login' */
		if (l != NULL) {
			if (e->e_trusted)
				e->e_from_trusted = l;
			else
				e->e_from_resolved = l;
		}
		for (pp = h->h_contents.a->a_tokens;
		     pp != NULL; pp = pp->p_next) {
			qp = (struct addr *)tmalloc(sizeof (struct addr));
			*qp = *pp;
			*ppp = qp;
			ppp = &qp->p_next;
			if (pp->p_next == NULL)
				break;
		}
	} else {
		/* 'Full Name <login' */
		*ppp = (struct addr *)tmalloc(sizeof (struct addr));
		pp = *ppp;
		ppp = &pp->p_next;
		pp->p_tokens = makeToken(name, strlen((char *)name));
		pp->p_tokens->t_type = Atom;
		pp->p_type = anAddress;
		l = NULL;
	}
	if (didbracket) {
		/* 'Full Name <Pretty.Login>' */
		*ppp = (struct addr *)tmalloc(sizeof (struct addr));
		pp = *ppp;
		ppp = &pp->p_next;
		pp->p_tokens = makeToken((u_char *)">", 1);
		pp->p_tokens->t_type = Special;
		pp->p_type = aSpecial;
	}
	*ppp = NULL;
	if (l == NULL && !e->e_trusted) {
		l = router(sh->h_contents.a, e->e_statbuf.st_uid, "sender");
		if (l != NULL)
			e->e_from_trusted = pickaddress(l);
	}

#ifdef	notdef
	/* X: sprintf(buf, "%s <%s>", fullname, name); more or less */
	sh->h_lines = makeToken(name, strlen((char *)name));
	sh->h_lines->t_type = Line;
	sh->h_contents = hdr_scanparse(e, sh, 0);
#endif
	/* X: optimize: save this in a hashtable somewhere */
	return sh;
}

/*
 * Construct a trace header line.
 */

struct header *
mkTrace(e)
	struct envelope *e;
{
	register struct header	*h, *th;
	struct addr *na;
	struct address *ap;
	static struct headerinfo	traceDesc =
		{ "received", Received, nilUserType, normal };
	
	th = (struct header *)tmalloc(sizeof (struct header));
	th->h_pname = "Received";
	th->h_descriptor = &traceDesc;
	th->h_stamp = newHeader;
	th->h_next = 0;
	th->h_contents.r = (struct received *)tmalloc(sizeof (struct received));
	FindEnvelope(eRcvdFrom);
	/* from */
	if (h != NULL)
		th->h_contents.r->r_from = h->h_contents.a;
	else
		th->h_contents.r->r_from = NULL;
	/* by */
	if (myhostname != NULL) {
		na = (struct addr *)tmalloc(sizeof (struct addr));
		/* prepend now -- reverse later */
		na->p_tokens = makeToken((u_char *)myhostname,
					 strlen(myhostname));
		na->p_next = NULL;
		na->p_type = anAddress;
		ap = (struct address *)tmalloc(sizeof (struct address));
		ap->a_tokens = na;
		ap->a_next = NULL;
		/* we don't need to fill in the other structure elements */
		/* but, just in case... */
		ap->a_stamp = newAddress;
		ap->a_dsn  = NULL;
		th->h_contents.r->r_by = ap;
	} else
		th->h_contents.r->r_by = NULL;
	/* via */
	FindEnvelope(eVia);
	if (h != NULL && h->h_contents.a->a_tokens != NULL) {
		th->h_contents.r->r_via = h->h_contents.a->a_tokens->p_tokens;
	} else
		th->h_contents.r->r_via = NULL;
	/* with */
	FindEnvelope(eWith);
	if (h != NULL && h->h_contents.a->a_tokens != NULL) {
		th->h_contents.r->r_with = h->h_contents.a->a_tokens->p_tokens;
	} else
		th->h_contents.r->r_with = NULL;
	/* id */
	na = (struct addr *)tmalloc(sizeof (struct addr));
	na->p_tokens = makeToken((u_char *)e->e_file, strlen(e->e_file));
	na->p_next = NULL;
	na->p_type = anAddress;	/* really a message id */
	ap = (struct address *)tmalloc(sizeof (struct address));
	ap->a_tokens = na;
	ap->a_next = NULL;
	ap->a_dsn  = NULL;
	ap->a_stamp = newAddress;
	th->h_contents.r->r_id = ap;
	/* for */
	th->h_contents.r->r_for = NULL;
	/* time */
	th->h_contents.r->r_time = e->e_nowtime;
	/* looks like:sprintf(buf, "from %s by %s via %s with %s id %s; %s"); */
	return th;
}

struct rwmatrix {
	struct rwmatrix	*next;
	struct rwmatrix	*down;
	struct conscell *info;	/* rewrite, sender, or recipient list info */
	struct notary   *dsn;
	struct conscell *errto;
	union {
		int	number;	/* XOR address id number for recipients */
		struct header *h;	/* for rewritings */
	} urw;
};


static struct rwmatrix *
rwalloc(rwpp)
	struct rwmatrix **rwpp;
{
	struct rwmatrix *p;

	p = (struct rwmatrix *)tmalloc(sizeof (struct rwmatrix));
	p->down = NULL;
	p->dsn  = NULL;
	p->errto = NULL;
	p->urw.number = 0;			/* unused but why not */
	if (*rwpp != NULL)
		p->next = *rwpp;
	else
		p->next = NULL;
	*rwpp = p;
	return p;
}

int
nullhost(s)
	u_char *s;
{
	return (s == NULL || *s == '\0' || strcmp((char *)s, "-") == 0);
	/* actually we should also check for localhostness, but lets not
	   get carried away... */
}

struct conscell *
pickaddress(l)
	struct conscell *l;
{
	struct conscell *la, *lx, *p;

	/*
	 * Given an AND-XOR tree returned from the router, pick one address
	 * quad to become *the* resolved address.
	 */

	for (p = NULL, la = car(l); la != NULL && LIST(la) ; la = cdr(la)) {
		/* AND addresses; i.e. exploded address list or one address */
		for (lx = car(la); lx != NULL && LIST(lx) ; lx = cdr(lx)) {
			if (STRING(cdar(lx)) && nullhost(cdar(lx)->string)) {
				p = lx;
				break;
			}
		}
	}
	if (p == NULL)
		p = caar(l);
	if (!LIST(p))
		return NULL;
	return car(p);
}

int
thesender(e, a)
	struct envelope *e;
	struct address *a;
{
	struct conscell *l;

	l = router(a, e->e_statbuf.st_uid, "sender");
	if (l == NULL)
		return 0;
	e->e_from_resolved = pickaddress(l);

	if (QUSER(e->e_from_resolved) == NULL
	    || !nullhost(QHOST(e->e_from_resolved)))
		return 0;
	/*
	 * We assume here, that local mailbox id's correspond to user
	 * login names. If this is not the case, trp->user needs to be
	 * canonicalized at this point... the problem is that the algorithm
	 * must be the same here and in the local mail delivery program
	 * which is outside the router. Maybe that should be a C library
	 * routine, but for now, ignore such complications. This check is
	 * only done if the message file wasn't created by a trusted user,
	 * so in practise this is less of a problem than it might appear to be.
	 */
	return CISTREQ(QUSER(e->e_from_resolved),uidpwnam(e->e_statbuf.st_uid));
}

struct conscell *
makequad()
{
	struct conscell *l, *tmp;

	l = conststring(NULL);
	cdr(l) = conststring(NULL);
	cddr(l) = conststring(NULL);
	cdr(cddr(l)) = conststring(NULL);
	return l;
}

static struct envelope *qate;

int
iserrmessage()
{
	return (qate != NULL
		&& qate->e_from_trusted != NULL
		&& CISTREQ(QCHANNEL(qate->e_from_trusted), "error"));
}

/*
 * The sequencer takes care of doing the right things with the right headers
 * in the right order. It implements the semantics of message processing.
 */

int
sequencer(e, file)
	struct envelope *e;
	char *file;
{
	struct header *h, *ph, *nh, *oh, *msgidh, **hp;
	struct address *a, *ap;
	struct addr *p;
	char *ofpname, *path, *qpath;
	struct conscell *l, *routed_addresses, *sender, *to;
	struct rwmatrix *rwhead, *nsp, *rwp = NULL, *rcp = NULL;
	struct token *t = NULL;
	int idnumber, nxor, i;
	int def_uid, header_error, perr;
	FILE *ofp, *vfp;
	char vbuf[2048], verbosefile[1024];
	char *envid = NULL;


	errors_to = NULL; /* to be gotten rid off.. */

	if (e == NULL) {
		/* No envelope ??? */
		return PERR_OK;
	}

	dprintf("Parse envelope and message header\n");
	e->e_messageid = NULL;
	perr = 0;
	if (myhostname) {	/* appease honeyman et al groupies */
		dprintf("Stamp it with a trace header\n");
		if ((h = mkTrace(e)) != NULL) {
			h->h_next = e->e_headers;
			e->e_headers = h;
		}
	}
	/* gross loop checking */
	for (h = e->e_headers, i = 0; h != NULL; h = h->h_next) {
		if (h->h_descriptor->semantics == Received
		    || (h->h_descriptor->semantics == nilHeaderSemantics
			&& CISTREQ(h->h_pname, "Received"))) {
			if (++i >= maxReceived) {
				fprintf(stderr,
				     "%s: looping message, squirreled away!\n",
					progname);
				perr = PERR_LOOP;
				squirrel(e,"looping message, exceeded maxReceived");
				/* XXX: Should do "loop-count-exceeded"! */
				break;
			}
		}
	}
	dprintf("Determine if message is a Resent-* type thing\n");
	e->e_resent = 0;
	for (h = e->e_headers; h != NULL; h = h->h_next)
		if (h->h_descriptor->class == Resent) {
			e->e_resent = 1;
			break;
		}
	dprintf("It is%s...\n", e->e_resent ? "" : "n't");

	dprintf("Sender authentication\n");
	e->e_trusted = isgoodguy(e->e_statbuf.st_uid);
	dprintf("Sender is%s trusted\n", e->e_trusted ? "" : "n't");
	dprintf("Generate an error message if an error occurred in parsing\n");
	for (h = e->e_eHeaders; h != NULL; h = h->h_next)
		if (h->h_stamp == BadHeader)
			break;
	if (h != NULL && h->h_stamp == BadHeader) {
		/*
		 * There's an error in the envelope; this implies a system
		 * problem that must be brought to the attention of the
		 * postmaster ASAP. Hopefully the postmaster will resubmit
		 * the message when the problem is fixed, so we don't tell
		 * the originator that anything went wrong.
		 */
		defer(e);
		/* Often the defer() becomes resubmited via cron, but lets
		   hope the postmaster will see things manually.. */
		return PERR_ENVELOPE;
	}
	e->e_from_trusted = makequad();
	e->e_from_resolved = makequad();

	if (myhostname) {	/* we care about message id's */
		dprintf("Make sure Message-Id exists, for loop control\n");
		/* it may be  message-id, or resent-message-id */
		FindHeader("message-id",e->e_resent);
		if (h == NULL) /* Ok, wasn't, how about resent-message-id ? */
		  FindHeader("message-id",!e->e_resent);
		if (h == NULL) {
			/* the time used must be the same as for trace header */
			InsertHeader(h, mkMessageId(e, e->e_nowtime));
			/* or: e->e_statbuf.st_mtime */
			dprintf("We added one\n");
			msgidh = nh;
		} else
			msgidh = h;
		if (msgidh->h_contents.a != NULL
		    && msgidh->h_contents.a->a_tokens != NULL)
			e->e_messageid =
				saveAddress(msgidh->h_contents.a->a_tokens);
	}

	/* put pertinent message information in the zsh environment */
	setenvinfo(e);

	FindEnvelope(eEnvid);
	envid = NULL;
	if (h != NULL && h->h_contents.a != NULL
	    && h->h_contents.a->a_pname != NULL) {
		envid = (char*)h->h_contents.a->a_pname;
	}
	FindEnvelope(eFrom);
	if (h == NULL) {
		dprintf("A sender was NOT specified in the envelope\n");
		for (h = e->e_headers; h != NULL; h = h->h_next)
			if (h->h_descriptor->user_type == Sender
			    && (e->e_resent == 0
				|| h->h_descriptor->class == Resent))
				break;
		if (h != NULL && e->e_trusted) {
			dprintf("Use the Sender: or From: field from header\n");
			h = copySender(e);
		} else {
			dprintf("Generate a sender based on owner of file\n");
			h = mkSender(e, uidpwnam(e->e_statbuf.st_uid), 0);
		}
		if (h == NULL)
			h = mkSender(e, (u_char *)POSTMASTER, 1);
		/* assert h != NULL */
		h->h_next = e->e_eHeaders;
		e->e_eHeaders = h;
	} else if (!e->e_trusted
		   || ((ap = h->h_contents.a) != NULL &&
		       ap->a_tokens != NULL &&
		       ap->a_tokens->p_next == NULL &&
		       ap->a_tokens->p_type == anAddress &&
		       (t = ap->a_tokens->p_tokens) != NULL &&
		       t->t_next == NULL)) {
		dprintf("A sender was specified in the envelope\n");
		if (!e->e_trusted) {
			dprintf("Replace the sender based on owner of file\n");
			h = mkSender(e, uidpwnam(e->e_statbuf.st_uid), 0);
		} else {
			dprintf("Provide a full name for the originator\n");
			/* ensure there is a fullnamemap entry */
			login_to_uid(t->t_pname);
			h = mkSender(e, t->t_pname, 0);
		}
		h->h_next = e->e_eHeaders;
		e->e_eHeaders = h;
		/*
		 * No need to delete other header since there can only
		 * be one sender in the message envelope and in other
		 * places in the code we will use the first one found.
		 */
	}

	for (h = e->e_headers; h != NULL; h = h->h_next)
		if (h->h_stamp == BadHeader)
			break;
	if (h != NULL && h->h_stamp == BadHeader) {
		/*
		 * There's an error in the message header; we save the message
		 * for the future amusement of the postmaster, and also send
		 * back a nasty note to the originator.
		 */
		optsave(FYI_BADHEADER, e);
		/* continue on with address rewriting - emit warning there */
		/* return e; */
		header_error = 1;
	} else
		header_error = 0;
	dprintf("Originating channel determination\n");
	def_uid = nobody;

	if (e->e_trusted) {
		FindEnvelope(eChannel);
		if (h != NULL) {
			dprintf("A channel was specified\n");
			if ((ap = h->h_contents.a) != NULL
			    && (p = ap->a_tokens) != NULL
			    && p->p_tokens != NULL) {
				t = p->p_tokens;
				QCHANNEL(e->e_from_trusted) =
					(u_char *)strnsave((char *)t->t_pname,
							   TOKENLEN(t));
			}
			if (QCHANNEL(e->e_from_trusted) == NULL) {
				/*
				 * the mailer is supposed to know about
				 * all valid channel identifiers. Gripe.
				 */
				optsave(FYI_NOCHANNEL, e);
			}
		}
		FindEnvelope(eRcvdFrom);
		if (h != NULL)		/* a previous host was specified */
			QHOST(e->e_from_trusted) = h->h_contents.a->a_pname;
		FindEnvelope(eUser);
		if (h != NULL)		/* a previous user was specified */
			QUSER(e->e_from_trusted) = h->h_contents.a->a_pname;
		if (QCHANNEL(e->e_from_trusted) == NULL
		    || QHOST(e->e_from_trusted) == NULL
		    || QUSER(e->e_from_trusted) == NULL) {
			FindEnvelope(eFrom);
			/* X: assert h != NULL */
			if (h == NULL || h->h_stamp == BadHeader) {
			  /* XX: perhaps should be other way around? */
			  FindHeader("sender",e->e_resent);
			  if (h == NULL || h->h_stamp == BadHeader)
			    FindHeader("from",e->e_resent);
			  if (h == NULL || h->h_stamp == BadHeader)
			    FindHeader("sender",!e->e_resent);
			  if (h == NULL || h->h_stamp == BadHeader)
			    FindHeader("from",!e->e_resent);
			  if (h == NULL || h->h_stamp == BadHeader)
			    h = mkSender(e,uidpwnam(e->e_statbuf.st_uid),0);
			}
			if (h == NULL)
				abort();
			l = router(h->h_contents.a,
				   e->e_statbuf.st_uid, "sender");
			if (l != NULL) {
				/*
				 * In case the router returns several addresses,
				 * we pick one at random to use for sender priv
				 * determination.
				 */
				e->e_from_resolved = pickaddress(l);
			}
		}
		if (nullhost(QHOST(e->e_from_resolved)) &&
		    nullhost(QHOST(e->e_from_trusted))) {
			/* local user */
			FindEnvelope(eExternal);
			if (h != NULL || (e->e_statbuf.st_mode & 022)) {
				optsave(FYI_BREAKIN, e);
			} else if (QUSER(e->e_from_resolved) != NULL)
				def_uid =
					login_to_uid(QUSER(e->e_from_resolved));
			else if (QUSER(e->e_from_trusted) != NULL)
				def_uid =
					login_to_uid(QUSER(e->e_from_trusted));
		}
	} else {
		dprintf("We know sender is local and one of the peons\n");
		def_uid = e->e_statbuf.st_uid;
		FindHeader("from",e->e_resent);
		nh = h;
		FindHeader("sender",e->e_resent);
		if (h != NULL && nh == NULL) {
			dprintf("A Sender w/o a From is bad; fixed\n");
			h->h_descriptor = senderDesc();
			set_pname(e, h, "From");
			nh = h;
			h = NULL;
		}
		if (h != NULL && h->h_contents.a != NULL) {
			/* a Sender: was given */
			if (!thesender(e, h->h_contents.a)) {
				/* but it is fake, so correct it */
				dprintf("The Sender: is not the sender\n");
				set_pname(e, h, "Fake-Sender");
			} else /* it is correct and we don't care about From: */
				h = NULL;
		} else if (nh != NULL && nh->h_contents.a != NULL) {
			/* only a From: was given */
			if (!thesender(e, nh->h_contents.a)) {
			  /* but it is fake, so add a Sender: */
			  dprintf("The From: is not the sender\n");
			  if (h != NULL) {
			    /* use our empty Sender: */
			    ph = mkSender(e,uidpwnam(e->e_statbuf.st_uid),0);
			    h->h_contents.a = ph->h_contents.a;
			    h = NULL;
			  } else
			    h = nh;
			} else
			  h = NULL;
		}
		if (h != NULL) {
		  InsertHeader(h,mkSender(e, uidpwnam(e->e_statbuf.st_uid),0));
		  set_pname(e, nh, "Sender");
		}
	}
	if (QCHANNEL(e->e_from_trusted) == NULL)
		QCHANNEL(e->e_from_trusted) = QCHANNEL(e->e_from_resolved);
	if (QHOST(e->e_from_trusted) == NULL)
		QHOST(e->e_from_trusted) = QHOST(e->e_from_resolved);
	if (QUSER(e->e_from_trusted) == NULL)
		QUSER(e->e_from_trusted) = QUSER(e->e_from_resolved);
	if (QATTRIBUTES(e->e_from_trusted) == NULL)
		QATTRIBUTES(e->e_from_trusted) =QATTRIBUTES(e->e_from_resolved);
	qate = e;

	dprintf("Recipient determination\n");
	FindEnvelope(eTo);
	if (h == NULL) {
		dprintf("No recipient(s) specified in the envelope\n");
		if (header_error) {
			dprintf("Due to header error, we ignore message\n");
			return PERR_HEADER;
		}
		ph = e->e_eHeaders;
		dprintf("Add all To:,Cc:,Bcc: to envelope recipients\n");
		oh = NULL;
		for (h = e->e_headers; h != NULL; oh = h, h = h->h_next) {
			if (h->h_descriptor->user_type != Recipient
			    || (e->e_resent != 0
				&& h->h_descriptor->class != Resent))
				continue;
			for(a = h->h_contents.a; a!=NULL; a = a->a_next)
				for (p=a->a_tokens;p!=NULL; p=p->p_next)
					if (p->p_type == anAddress)
						goto ok;
			continue;
		ok:
			nh = copyRecipient(h);
			nh->h_next = e->e_eHeaders;
			e->e_eHeaders = nh;
		}
		dprintf("Are we supposed to be psychic?\n");
		if (ph == e->e_eHeaders)
			return PERR_NORECIPIENTS;
	}
	dprintf("Nuke Bcc headers if any\n");
	oh = NULL;
	for (h = e->e_headers; h != NULL; oh = h, h = h->h_next) {
	  if (h->h_descriptor->hdr_name == NULL)
	    continue;
	  if ((h->h_descriptor->class == normal
	       && CISTREQ(h->h_descriptor->hdr_name,"bcc"))
	      || (h->h_descriptor->class == Resent
		  && CISTREQ(h->h_descriptor->hdr_name, "resent-bcc"))) {
	    if (oh == NULL)
	      e->e_headers = h->h_next;
	    else
	      oh->h_next = h->h_next;
	  }
	}

	/*
	 * Log the message after we find the envelope From address, otherwise
	 * log entries might have empty sender fields.
	 */
	if (e->e_messageid != NULL)
		logmessage(e);

	if (header_error) {
		/* only send back if local originator, until we fix gweansm */
		if (def_uid != nobody) {
			reject(e, "headerwarn");
			printf("def_uid = %d\n", def_uid);
		}
		dprintf("Emit warning headers\n");
		for (ph = NULL, h = e->e_headers; h != NULL; ph=h,h=h->h_next) {
			if (h->h_stamp == BadHeader) {
				if (ph == NULL)
					e->e_headers = hdr_warning(h);
				else
					ph->h_next = hdr_warning(h);
			}
		}
	}
	dprintf("Make sure a From: line exists...");
	FindHeader("from",e->e_resent);
	if (h == NULL) {
		FindEnvelope(eFrom);
		if (h == NULL) {	/* panic... can't happen... */
			dprintf(" none available!\n");
			optsave(FYI_NOSENDER, e);
		} else {
			/* assume we only care about local users */
			nh = (struct header *)tmalloc(sizeof (struct header));
			*nh = *h;
			set_pname(e, nh, "From");
			FindHeader("to",e->e_resent);
			if (h == NULL) {
			  for (h = e->e_headers; h != NULL; h = h->h_next)
			    if (CISTREQ(h->h_pname, "subject"))
			      break;
			}
			InsertHeader(h, nh);
			dprintf(" nope (added)!\n");
		}
	} else
		dprintf(" yup!\n");
	/* Any 'To:' in the headers ? */
	FindHeader("to",e->e_resent);
	if (h == NULL)
	  FindHeader("to",!e->e_resent);
	if (h == NULL) {
#if 1
		dprintf("No 'To:' -header, creating our own\n");
		InsertHeader(h, mkToHeader(e,"unlisted-recipients:; (no To-header on input)"));
#else
		dprintf("Insert the To: header lines\n");
		for (h = e->e_headers; h != NULL; h = h->h_next)
			if (CISTREQ(h->h_pname, "subject"))
				break;
		if (h == NULL) {
			FindHeader("from",e->e_resent);
			if (h != NULL)
				h = h->h_next;
		}
		ph = h;
		for (h = e->e_headers; h != NULL; h = h->h_next)
			if (h->h_next == ph)
				break;
		ph = h;	/* assert ph != NULL */
		for (h = e->e_eHeaders; h != NULL; h = h->h_next) {
			if (h->h_descriptor->class == eTo) {
				nh = (struct header *)tmalloc(sizeof (struct header));
				*nh = *h;
				set_pname(e, nh, "To");
				nh->h_next = ph->h_next;
				ph->h_next = nh;
				/* so we add them in reverse order... */
			}
		}
#endif
	}
#ifdef notdef
	rewrite all addresses in the message according to the
	incoming-rewriting rules for the originating channel.
#endif
	dprintf("Route recipient addresses\n");
	routed_addresses = NULL;

	if (errors_to) free(errors_to);
	errors_to = NULL;

	for (h = e->e_eHeaders; h != NULL; h = h->h_next) {
		static struct notary *DSN;
		if (h->h_descriptor->class != eTo &&
		    h->h_descriptor->class != eToDSN) {
			DSN = NULL;
			continue;
		}
		if (D_sequencer) dumpHeader(h);
		if (h->h_descriptor->class == eToDSN) {
			DSN = NULL;
			if (h->h_contents.a == NULL ||
			    h->h_contents.a->a_pname == NULL ||
			    h->h_contents.a->a_pname[0] == 0) {
			  /* HUH!  How come ? "todsn" w/o data ?? */
			  continue;
			}
			DSN = (struct notary *)tmalloc(sizeof(struct notary));
			DSN->envid = envid;
			DSN->dsn = (char*)h->h_contents.a->a_pname;
			continue;
		}
		if (DSN != NULL) {
			h->h_contents.a->a_dsn = DSN;
			DSN = NULL;
		}
		for (a = h->h_contents.a; a != NULL; a = a->a_next) {
			l = router(a, def_uid, "recipient");
			if (l == NULL)
				continue;

			if (routed_addresses == NULL) {
				struct conscell *tmp;
				routed_addresses = ncons(car(l));
			} else {
				cdr(s_last(car(l))) = car(routed_addresses);
				car(routed_addresses) = car(l);
			}
			/* freecell(l) */
		}
	}

	dprintf("Crossbar applied to all (sender,routed-address) pairs\n");

	{
	  struct conscell *tmp;
	  sender = ncons(e->e_from_trusted);
	}
#if 0
	if (LIST(sender) && LIST(car(sender)))
		sender = caar(sender);
#endif

	if (routed_addresses == NULL)	/* they were probably all deferred */ {
		printf("No routed addresses -> deferred\n");
		return PERR_DEFERRED;
	}

	rwhead = NULL;
	for (idnumber = 0, l = car(routed_addresses); l != NULL ; l = cdr(l)) {
		/* first level is ANDs */
		++idnumber, nxor = 0;
		for (to = car(l); to != NULL; to = cdr(to)) {
			struct conscell *x, *tmp, *errto;

			/* secondlevel is XORs */
#if 0
			printf("crossbar sender: ");
			s_grind(sender, stdout);
			putchar('\n');
			printf("crossbar to: ");
			s_grind(to, stdout);
			putchar('\n');
#endif

			if ((x = crossbar(sender, to)) == NULL)
				continue;
#if 0
			printf("crossbar returns: ");
			s_grind(x, stdout);
			putchar('\n');
#endif
			++nxor;
			tmp   = copycell(car(x));
			errto = find_errto(cdar(x));
			cdr(tmp) = NULL;
			for (rwp = rwhead; rwp != NULL; rwp = rwp->next)
				if (s_equal(rwp->info, tmp) &&
				    s_equal(rwp->errto,errto))
					break;
			if (rwp == NULL) {
				rwp = rwalloc(&rwhead);
				rwp->info = tmp;	/* rewritings */
				rwp->errto = errto;
			}
			tmp = cddar(x);
			cddar(x) = NULL;
			for (nsp = rwp->down; nsp != NULL; nsp = nsp->next)
				if (s_equal(nsp->info, cdar(x)) &&
				    s_equal(nsp->errto,errto))
					break;
			if (nsp == NULL) {
				nsp = rwalloc(&rwp->down);
				nsp->info = cdar(x);	/* new sender */
				nsp->errto = errto;
			}
#if 0
			else {
				printf("==comparing: ");
				s_grind(nsp->info, stdout);
				printf("\n\tand: ");
				s_grind(cdar(x), stdout);
				printf("\n");
				fflush(stdout);
			}
#endif
			rcp = rwalloc(&nsp->down);
			rcp->info = tmp;		/* new recipient */
			rcp->urw.number = idnumber;
			rcp->errto = NULL;
#if 0
			for (rwp = rwhead; rwp != NULL; rwp = rwp->next) {
				printf("**");
				s_grind(rwp->info, stdout);
				printf("\n");
				fflush(stdout);
				for (nsp = rwp->down; nsp != NULL; nsp = nsp->next) {
					struct rwmatrix *rp;
					printf("**\t");
					s_grind(nsp->info, stdout);
					printf("\n");
					fflush(stdout);
					for (rp = nsp->down; rp != NULL;
					     rp = rp->next) {
						printf("**\t\t");
						s_grind(rp->info, stdout);
						printf("\n");
						fflush(stdout);
					}
				}
			}
#endif
		}
		if (nxor <= 1) {
			--idnumber;
			if (nxor == 1)
				rcp->urw.number = 0; /* 0 means no XOR addrs */
		}
	}

	isSenderAddr = isRecpntAddr = 0;
	dprintf("Make sure Date, From, To, are in the header\n");
	FindHeader("date",e->e_resent);
	if (h == NULL)
	  InsertHeader(h, mkDate(e->e_resent, e->e_statbuf.st_mtime));

	dprintf("Rewrite message headers\n");
	for (rwp = rwhead; rwp != NULL; rwp = rwp->next) {
		hp = &rwp->urw.h;
		for (h = e->e_headers; h != NULL; h = h->h_next) {
			isSenderAddr = h->h_descriptor->user_type == Sender;
			isRecpntAddr = h->h_descriptor->user_type == Recipient;
			if (isSenderAddr || isRecpntAddr) {
				if (!STRING(rwp->info))	/* just addresses */
					continue;
				*hp = hdr_rewrite(rwp->info->string, h);
				hp = &(*hp)->h_next;
			}
		}
		*hp = NULL;
	}
 
	if (rwhead == NULL)
	  return PERR_NORECIPIENTS;

	dprintf("Emit specification to the transport system\n");
#ifdef	USE_ALLOCA
	ofpname = (char *)alloca((u_int)(strlen(file)+9));
#else
	ofpname = (char *)emalloc((u_int)(strlen(file)+9));
#endif
	/* Guaranteed unique within this machine */
	sprintf(ofpname,".%s.%d",file,getpid());
	if ((ofp = fopen(ofpname, "w+")) == NULL) {
#ifndef USE_ALLOCA
		free(ofpname);
#endif
		printf("Creation of control file failed\n");
		return PERR_CTRLFILE;
	}
	setvbuf(ofp, vbuf, _IOFBF, sizeof vbuf);
	if (files_gid >= 0) {
		fchown(FILENO(ofp), e->e_statbuf.st_uid, files_gid);
		fchmod(FILENO(ofp), 0460);
	}
	fprintf(ofp, "%c%c%s\n",
		_CF_MESSAGEID, _CFTAG_NORMAL, file);
	fprintf(ofp, "%c%c%d\n",
		_CF_BODYOFFSET, _CFTAG_NORMAL, e->e_msgOffset);
	if (envid != NULL)
		fprintf(ofp, "%c%c%s\n",
			_CF_DSNENVID, _CFTAG_NORMAL, envid);
	if (e->e_messageid != NULL)
		fprintf(ofp, "%c%c%s\n",
			_CF_LOGIDENT, _CFTAG_NORMAL, e->e_messageid);
	/* else { we don't want to log anything } */
	/*
	 * If this message came from an error channel, then
	 * do NOT produce an error message if something goes
	 * wrong. It can quickly lead to Bad Things happening
	 * to your disk space.
	 */

	if (!iserrmessage()) {
#if 1
                if (errors_to != NULL) {        /* [mea@utu.fi] Stupid, but workable.. */
                        putc(_CF_ERRORADDR, ofp);
                        putc(_CFTAG_NORMAL, ofp);
                        fprintf(ofp,"%s\n",errors_to);
                        free(errors_to);
                        errors_to = NULL;
                } else
#endif
		if ((h = erraddress(e)) != NULL) {
			putc(_CF_ERRORADDR, ofp);
			putc(_CFTAG_NORMAL, ofp);
			for (ap = h->h_contents.a;
			     ap != NULL; ap = ap->a_next) {
				if (ap != h->h_contents.a)
					putc(' ', ofp);
				printAddress(ofp, ap->a_tokens, 0);
				if (ap->a_next != NULL)
					putc(',', ofp);
			}
			putc('\n', ofp);
		}
	}
	/*
	 * If this message might obsolete another we need to tell
	 * the scheduler.
	 */

	for (h = e->e_headers; h != NULL; h = h->h_next) {
		if (h->h_descriptor->hdr_name == NULL
		    || !CISTREQ(h->h_descriptor->hdr_name, "obsoletes"))
			continue;
		for (ap = h->h_contents.a; ap != NULL; ap = ap->a_next) {
			putc(_CF_OBSOLETES, ofp);
			putc(_CFTAG_NORMAL, ofp);
			printAddress(ofp, ap->a_tokens, 0);
			putc('\n', ofp);
		}
	}

	/*
	 * If this message might trigger scheduler to give target (smtp),
	 * we must tell the scheduler.
	 */

	for (h = e->e_headers; h != NULL; h = h->h_next) {
		if (h->h_descriptor->hdr_name == NULL
		    || !CISTREQ(h->h_descriptor->hdr_name, "turnme"))
			continue;
		for (ap = h->h_contents.a; ap != NULL; ap = ap->a_next) {
			putc(_CF_TURNME, ofp);
			putc(_CFTAG_NORMAL, ofp);
			printAddress(ofp, ap->a_tokens, 0);
			putc('\n', ofp);
		}
	}

	FindEnvelope(eVerbose);
	if (h != NULL
	    && h->h_contents.a != NULL && h->h_contents.a->a_tokens != NULL) {
		if (h->h_contents.a->a_tokens->p_tokens != NULL &&
		    h->h_contents.a->a_tokens->p_tokens->t_type == String)
			h->h_contents.a->a_tokens->p_tokens->t_type = Atom;
		printToken(verbosefile, verbosefile + sizeof verbosefile,
			   h->h_contents.a->a_tokens->p_tokens,
			   (struct token *)NULL, 0);
		/*
		 * We have to be careful how we open this file, since one might
		 * imagine someone trying to append to /etc/passwd using this
		 * stuff.  The only safe way is to open it with the permissions
		 * of the owner of the message file.
		 */
		setreuid(0, e->e_statbuf.st_uid);

		if ((vfp = fopen(verbosefile, "a")) != NULL) {
			fseek(vfp, 0L, 2);
			setvbuf(vfp, NULL, _IOLBF, 0);
			fprintf(vfp, "router processed message %s\n", file);
			fprintf(ofp, "%c%c%s\n", _CF_VERBOSE, _CFTAG_NORMAL,
				verbosefile);
		}
		setreuid(0, 0);
	} else
		vfp = NULL;

	for (rwp = rwhead; rwp != NULL; rwp = rwp->next) {

		if (!iserrmessage()  &&  rwp->errto != NULL) {
			/* print envelope sender address */
                        putc(_CF_ERRORADDR, ofp);
                        putc(_CFTAG_NORMAL, ofp);
                        fprintf(ofp,"%s\n",rwp->errto);
		}

		for (nsp = rwp->down; nsp != NULL; nsp = nsp->next) {

			fprintf(ofp, "%c%c", _CF_SENDER, _CFTAG_NORMAL);
			prctladdr(nsp->info, ofp, _CF_SENDER, "sender");
			putc('\n', ofp);
			if (vfp != NULL) {
				fprintf(vfp, "%c%c",
					_CF_SENDER, _CFTAG_NORMAL);
				prctladdr(nsp->info, vfp,
					  _CF_SENDER, "sender");
				putc('\n', vfp);
			}
			/* print recipient addresses */
			for (rcp = nsp->down; rcp != NULL; rcp = rcp->next) {
				if (rcp->urw.number > 0)
					putc(_CF_XORECIPIENT, ofp);
				else
					putc(_CF_RECIPIENT, ofp);
				putc(_CFTAG_NORMAL, ofp);
				fprintf(ofp,"%*s",_CFTAG_RCPTPIDSIZE,"");
				if (rcp->urw.number > 0)
					fprintf(ofp, "%d ", rcp->urw.number);
				prctladdr(rcp->info, ofp,
					  _CF_RECIPIENT, "recipient");
				putc('\n', ofp);
				/* XX: DSN data output ! */
				prdsndata(rcp->info, ofp,
					  _CF_RCPTNOTARY, "recipient");
				if (vfp != NULL) {
					if (rcp->urw.number > 0)
						fprintf(vfp, "%c%c%d ",
							_CF_XORECIPIENT,
							_CFTAG_NORMAL,
							rcp->urw.number);
					else
						fprintf(vfp, "%c%c%*s",
							_CF_RECIPIENT,
							_CFTAG_NORMAL,
							_CFTAG_RCPTPIDSIZE,"");
					prctladdr(rcp->info, vfp,
						  _CF_RECIPIENT, "recipient");
					putc('\n', vfp);
				}
			}
		}
		/* print header */
		putc(_CF_MSGHEADERS, ofp);
		putc('\n', ofp);
		if (vfp != NULL)
			fprintf(vfp, "headers rewritten using '%s' function:\n",
				     rwp->info->string);
		/* print the header, replacing all To:, Cc:, fields with
		   the corresponding fields as stored with the rewrite set. */
		nh = rwp->urw.h;
		for (h = e->e_headers; h != NULL; h = h->h_next) {
			if (nh != NULL
			    && (h->h_descriptor->user_type == Sender
			     || h->h_descriptor->user_type == Recipient)) {
				hdr_print(nh, ofp);
				if (vfp != NULL)
					hdr_print(nh, vfp);
				nh = nh->h_next;
			} else {
				hdr_print(h, ofp);
				if (vfp != NULL)
					hdr_print(h, vfp);
			}
		}
		putc('\n', ofp);
		if (vfp != NULL)
			putc('\n', vfp);
	}

	if (vfp != NULL) {
		fprintf(vfp, "router done processing %s\n", file);
		fclose(vfp);
	}
	
	if (e->e_fp != NULL && files_gid >= 0) {
		fchown(FILENO(e->e_fp), -1, files_gid);
		fchmod(FILENO(e->e_fp),
		       (0440|e->e_statbuf.st_mode) & 0660);
	}
	/* all is nirvana -- link the input file to somewhere safe */
#ifdef	USE_ALLOCA
	qpath = alloca(5+strlen(QUEUEDIR)+strlen(file));
#else
	qpath = emalloc(5+strlen(QUEUEDIR)+strlen(file));
#endif
	sprintf(qpath, "../%s/%s", QUEUEDIR, file);
	if ((fclose(ofp) == EOF) || (erename(file, qpath) < 0)) {
		unlink(qpath);
		unlink(ofpname);
#ifndef	USE_ALLOCA
		free(ofpname);
		free(qpath);
#endif
		return PERR_CTRLFILE;
	}

#ifndef USE_ALLOCA
	path = emalloc(5+strlen(TRANSPORTDIR)+strlen(file));
#else
	/* This actually reallocs more space from stack, but then it
	   is just stack space and will disappear.. */
	path = alloca(5+strlen(TRANSPORTDIR)+strlen(file));
#endif
	sprintf(path, "../%s/%s", TRANSPORTDIR, file);
	unlink(path);	/* Should actually always fail.. */
	if (erename(ofpname, path) < 0) {
		unlink(qpath);
		unlink(path);
#ifndef	USE_ALLOCA
		free(ofpname);
		free(qpath);
		free(path);
#endif
		return PERR_CTRLFILE;
	}

#ifndef	USE_ALLOCA
	free(ofpname);
	free(qpath);
	free(path);
#endif

	/* we did it! */
	return PERR_OK;
}

static void
prctladdr(info, fp, cfflag, comment)
	struct conscell *info;
	FILE *fp;
	int cfflag;
	char *comment;
{
	int i = 0;
	register struct conscell *l, *x;

	for (l = car(info); l != NULL; l = cdr(l)) {
		++i;
		if (STRING(l)) {
			if (cdr(l) == NULL
			    && (x = v_find(l->string)) != NULL
			    && (x = cdr(x)) != NULL
			    && LIST(x)) {
				for (x = car(x); x != NULL; x = cddr(x)) {
					if (STRING(x)
					    && strcmp((char *)x->string,
						      "privilege") == 0) {
						x = cdr(x);
						break;
					}
				}
				/* if x == NULL, no privilege was specified */
			} else
				x = l;
			if (x != NULL) {
				if (x->string == NULL || *x->string == '\0')
					putc('-', fp);
				else
					fprintf(fp, "%s", x->string);
			}
			if (cdr(l))
				putc(' ', fp);
		} else
			fprintf(stderr, "Malformed %s\n", comment);
	}
}

static void
prdsndata(info, fp, cfflag, comment)
	struct conscell *info;
	FILE *fp;
	int cfflag;
	char *comment;
{
	int i = 0;
	register struct conscell *l, *x = NULL;

	for (l = car(info); l != NULL; l = cdr(l)) {
	  ++i;
	  if (STRING(l)) {
	    if (cdr(l) == NULL
		&& (x = v_find(l->string)) != NULL
		&& (x = cdr(x)) != NULL
		&& LIST(x)) {
	      for (x = car(x); x != NULL; x = cddr(x)) {
		if (STRING(x) && strcmp((char *)x->string,"DSN") == 0) {
		  x = cdr(x);
		  break;
		}
	      }
	      /* if x == NULL, no DSN was specified */
	    }
	  } else
	    fprintf(stderr, "Malformed DSN %s\n", comment);
	}
	if (x != NULL)
	  if (*x->string != '\0')
	    fprintf(fp, "%c %s\n", cfflag, x->string);
}


static struct conscell *
find_errto(info)
struct conscell *info;
{
	int i = 0;
	register struct conscell *l, *x = NULL;

	for (l = car(info); l != NULL; l = cdr(l)) {
	  ++i;
	  if (STRING(l)) {
	    if (cdr(l) == NULL
		&& (x = v_find(l->string)) != NULL
		&& (x = cdr(x)) != NULL
		&& LIST(x)) {
	      for (x = car(x); x != NULL; x = cddr(x)) {
		if (STRING(x) && strcmp((char *)x->string,"ERR") == 0) {
		  x = cdr(x);
		  break;
		}
	      }
	      return x;
	      /* if x == NULL, no Errors-To -expansion was specified */
	    }
	  } else
	    return NULL; /* A list ??  Shall not be! */
	}
	return x;
}
