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

#include "hostenv.h"
#include "mailer.h"

#include "prototypes.h"

struct conscell **return_valuep = 0;
struct conscell *s_value = 0;
int svstop = -1;
#define SVSTOPMAX 5 /* Is this too low ? */
struct conscell *svstack[SVSTOPMAX] = { 0 };

extern int l_apply __((u_char *fname, struct conscell *l));
int
l_apply(fname, l)
	u_char *fname;
	struct conscell *l;
{
	int retval;

	return_valuep = &svstack[++svstop];
	if (svstop >= SVSTOPMAX || svstop < 0) {
	  fprintf(stderr,"l_apply('%s',*l) ran out of svstack[]! (svstop=%d)",
		  fname,svstop);
	  abort(); /* Well, we analyze the core if this ever happens.. */
	}
	svstack[svstop] = NULL;
	retval = lapply((char *)fname, l);
	s_value = svstack[svstop];
	return_valuep = --svstop < 0 ? NULL : &svstack[svstop];
	return retval;
}


int
s_apply(argc, argv)
	int argc;
	char *argv[];
{
	int retval;

	return_valuep = &svstack[++svstop];
	if (svstop >= SVSTOPMAX || svstop < 0) {
	  fprintf(stderr,"s_apply() ran out of svstack[]! (svstop=%d)",
		  svstop);
	  abort(); /* Well, we analyze the core if this ever happens.. */
	}
	svstack[svstop] = NULL;
	retval = apply(argc, argv);
	s_value = svstack[svstop];
	return_valuep = --svstop < 0 ? NULL : &svstack[svstop];
	return retval;
}

int
n_apply(cpp, argc, argv)
	int argc;
	char **cpp, *argv[];
{
	int retval;

	sb_external(fileno(stdout));	/* set up for retrieval of stdout */
	retval = apply(argc, argv);
	*cpp = sb_retrieve(fileno(stdout));	/* safe alloc'ed memory */
	return retval;
}

/*
 * Call func with tokenlist t as the argument.
 * This is used to rewrite an address.
 */

static int s_rewrite __((u_char *func, struct token *t, u_char *argx));
static int
s_rewrite(func, t, argx)
	u_char *func, *argx;
	struct token *t;
{
	register u_char *cp, *bp;
	u_char *av[4];
	u_char buf[4096];		/* XX: this is NOT GOOD! */

	if (t == NULL)
		return 0;
	cp = buf;
	cp += printToken(buf, buf + sizeof buf, t, (struct token *)NULL, 0);
	if (t->t_next == NULL && t->t_type == String && buf[0] == '"') {
		*(cp-1) = '\0';
		cp = buf + 1;
		bp = buf;
		for (bp = buf, cp = buf + 1 ; *cp != '\0' ; ++cp) {
			if (*cp == '\\' && *(cp+1) != '\0')
				*bp++ = *++cp;
			else
				*bp++ = *cp;
		}
		*bp = '\0';
	}

	/* shell interface - we want stdout to show up here */
	av[0] = func;
	av[1] = buf;
	av[2] = argx;
	av[3] = NULL;
	return s_apply(argx == NULL ? 2 : 3, (char **)&av[0]);
}


/*
 * The transformed message header addresses must be merged with their
 * original format (including comments, etc). We want to change the 'look'
 * of a message header address as little as possible, so we merge the new
 * pure address with the old version. RFC822 mumbles something about all
 * addresses being transmitted in a canonical format (without comments
 * embedded in route-addr's for example), but since we aren't generating
 * anything the original UA wouldn't generate, that requirement is blithely
 * ignored.
 */

static struct addr *mergeAddress __((struct addr *pp, struct token *t));
static struct addr *
mergeAddress(pp, t)
	struct addr *pp;
	struct token *t;
{
	struct addr *ppp, *npp, *fpp;
	struct token *nt, *pt;

	pt = NULL;
	for (ppp = fpp = NULL; pp != NULL; pp = pp->p_next, ppp = npp) {
		npp = (struct addr *)tmalloc(sizeof (struct addr));
		if (ppp != NULL)
			ppp->p_next = npp;
		else
			fpp = npp;
		if (pp->p_type != anAddress) {
			/* copy non-address portions unchanged */
			*npp = *pp;
		} else if (t != NULL) {
			/* copy the same number of tokens as was there */
			/* ... this is not terribly sophisticated ... */
			npp->p_type = anAddress;
			npp->p_tokens = t;
			for (nt = pp->p_tokens; nt != NULL && t != NULL;
						nt = nt->t_next, t = t->t_next)
				pt = t;
			if (t != NULL)
				pt->t_next = NULL;
		}
		npp->p_next = NULL;
	}
	if (t != NULL && pt != NULL)
		pt->t_next = t;
	return fpp;
}

/*
 * Rewrite a header nicely...
 */

struct header *
hdr_rewrite(name, h)
	u_char *name;
	struct header *h;
{
	register struct address *ap;
	register struct addr *pp;
	register struct token *t;
	struct address *nap, *pap;
	struct token *nt, *pt, *addrtokens;
	struct header *nh;
	struct conscell *l;
	u_char *cp, *eocp, *s, buf[4096]; 	/* XX */

	if (D_hdr_rewrite) {
		printf("---------------------------\n");
		printf("Sending this through %s:\n", (char *)name);
		dumpHeader(h);
	}
	nh = (struct header *)tmalloc(sizeof (struct header));
	*nh = *h;
	nh->h_contents.a = NULL;
	nh->h_next = NULL;
	pap = NULL;	/* shut up lint */
	for (ap = h->h_contents.a; ap != NULL; ap = ap->a_next) {
		addrtokens = NULL;
		pt = NULL;
		for (pp = ap->a_tokens; pp != NULL; pp = pp->p_next) {
			if (pp->p_type != anAddress)
				continue;
			for (t = pp->p_tokens; t != NULL; t = t->t_next) {
				nt = copyToken(t);
				if (pt != NULL)
					pt->t_next = nt;
				pt = nt;
				if (addrtokens == NULL)
					addrtokens = nt;
			}
		}
		if (addrtokens != NULL
		       && addrtokens->t_next == NULL
		       && addrtokens->t_type == String) {
			eocp = addrtokens->t_pname + TOKENLEN(addrtokens);
			for (cp = addrtokens->t_pname, s=buf; cp < eocp; ++cp) {
				if (*cp == '\\' && cp < eocp - 1 && *(cp+1) == '"')
					continue;
				*s++ = *cp;
			}
			*s = 0;
			addrtokens->t_pname = (u_char *)strsave((char *)buf);
		}
		nap = (struct address *)tmalloc(sizeof (struct address));
		nap->a_pname = NULL;
		nap->a_stamp = newAddress;
		/* don't bother initializing a_uid/a_mode here, no use */
		nap->a_next = NULL;
		nap->a_dsn  = NULL;
		deferit = 0;
		v_set(DEFER, "");
		if (addrtokens == NULL)
			s_value = NULL;
		else if (s_rewrite(name, addrtokens, (u_char *)NULL) != 0) {
			if (s_value != NULL) {
				s_free_tree(s_value);
				s_value = NULL;
			}
			if (deferit
			    && s_rewrite(DEFERHDR, addrtokens, (u_char *)NULL)
			    && s_value != NULL) {
				s_free_tree(s_value);
				s_value = NULL;
			}
		}
		if (s_value != NULL
		    && (LIST(s_value) || *(s_value->string) == '\0')) {
			s_free_tree(s_value);
			s_value = NULL;
		}
		if (s_value == NULL) {
			/* copy this address unchanged */
			nap->a_tokens = ap->a_tokens;
		} else {
			/* integrate result with original address form */
			l = s_copy_tree(s_value);
			s_free_tree(s_value);
			s_value = NULL;
			t = HDR_SCANNER(l->string);
			/* X: check for errors! */
			nap->a_tokens = mergeAddress(ap->a_tokens, t);
		}
		if (nh->h_contents.a == NULL)
			nh->h_contents.a = nap;
		else
			pap->a_next = nap;
		pap = nap;
	}
	if (D_hdr_rewrite) {
		printf("Resulting in this header:\n");
		dumpHeader(nh);
		hdr_print(nh, stdout);
	}
	return nh;
}

void
setenvinfo(e)
	struct envelope *e;
{
	struct header *h;
	struct conscell *pl, *plhead, *tmp;
	char buf[20];

	/* include header size ("headersize"), message size ("size"),
	   message body size ("bodysize"), now ("now"), resent ("resent")
	   trusted ("trusted"), message file name ("file"), message id
	   ("message-id") */

#define	CONSTSTR(s)	cdr(pl) = conststring(s); pl = cdr(pl)
#define	NEWSTR(s)	cdr(pl) = newstring(s); pl = cdr(pl)
#define	NEWSTRD(d)	sprintf(buf, "%ld", (long)(d)); \
			cdr(pl) = newstring((u_char *)strsave(buf)); \
			pl = cdr(pl)

	pl = plhead = conststring((u_char *)"file");
	CONSTSTR((u_char *)e->e_file);

	if (e->e_messageid != NULL) {
		CONSTSTR((u_char *)"message-id");
		CONSTSTR((u_char *)e->e_messageid);
	}

	CONSTSTR((u_char *)"uid");
	NEWSTRD(e->e_statbuf.st_uid);

	CONSTSTR((u_char *)"gid");
	NEWSTRD(e->e_statbuf.st_gid);

	CONSTSTR((u_char *)"size");
	NEWSTRD(e->e_statbuf.st_size - e->e_hdrOffset);

	CONSTSTR((u_char *)"headersize");
	NEWSTRD(e->e_msgOffset - e->e_hdrOffset);

	CONSTSTR((u_char *)"bodysize");
	NEWSTRD(e->e_statbuf.st_size - e->e_msgOffset);

	CONSTSTR((u_char *)"now");
	NEWSTRD(e->e_nowtime);

	CONSTSTR((u_char *)"delay");
	NEWSTRD(e->e_nowtime - e->e_statbuf.st_mtime);

	CONSTSTR((u_char *)"resent");
	CONSTSTR(e->e_resent ? (u_char *)"yes" : (u_char *)"no");

	CONSTSTR((u_char *)"trusted");
	CONSTSTR(e->e_trusted ? (u_char *)"yes" : (u_char *)"no");

	/* for every non-address envelope header, include
	   header-name header-value pair in property list */

	for (h = e->e_eHeaders; h != NULL; h = h->h_next) {
		if (h->h_descriptor->user_type != nilUserType)
			continue;
		CONSTSTR((u_char *)h->h_descriptor->hdr_name);
		if (h->h_lines == NULL || *h->h_lines->t_pname == '\0') {
			CONSTSTR((u_char *)h->h_descriptor->hdr_name);
		} else {
			CONSTSTR(h->h_lines->t_pname);
		}
	}
	cdr(pl) = NULL;
	plhead = ncons(plhead);
	v_setl("envelopeinfo", plhead);
}

/*
 * The router function must return three values,
 *	a (channel, host, user) triple.
 *
 * If we get a deferral while routing, call a deferral
 * function to deal with it.
 */

struct conscell *
router(a, uid, type)
	struct address *a;
	int uid;
	char *type;
{
	register struct token *t, *tt;
	int r;
	struct token *last;
	struct addr *p;
	struct conscell *l, *lc, *tmp;
	char buf[30], gsbuf[30];
	struct notary *DSN = NULL;

	if (a == NULL)
		return NULL;
	t = last = NULL;
	DSN = a->a_dsn;
	for (p = a->a_tokens; p != NULL; p = p->p_next)
		if (p->p_type == anAddress) {
			/* link up all address tokens together */
			for (tt = p->p_tokens; tt != NULL; tt = tt->t_next) {
				if (t == NULL) {
					t = copyToken(tt);
					last = t;
				} else {
					last->t_next = copyToken(tt);
					last = last->t_next;
				}
			}
		}
	if (D_router) {
		printf("Routing:\n");
		for (tt = t; tt != NULL; tt = tt->t_next)
			printf("\t\t%s\n", formatToken(tt));
	}
	if (t == NULL)
		return NULL;
	if (t->t_pname[0] == '<' && TOKENLEN(t) == 1 && t->t_next == NULL)
		abort();

	/* assemble the default attribute list: (privilege <uid>) */
	l = conststring((u_char *)"privilege");
	sprintf(buf, "%d", uid);
	cdr(l) = newstring((u_char *)buf);
	lc = cdr(l);
	if (type != NULL) {
		cdr(lc) = conststring((u_char *)"type");
		lc = cdr(lc);
		cdr(lc) = newstring((u_char *) type );
		lc = cdr(l);
	}
	if (DSN != NULL && DSN->dsn != NULL) {
		cdr(lc) = conststring((u_char *)"DSN");
		lc = cdr(lc);
		cdr(lc) = conststring((u_char *)DSN->dsn);
		lc = cdr(lc);
	}
	/* See if some "errorsto" definition is available.. */
	if (errors_to != NULL) {
		cdr(lc) = conststring((u_char *)"ERR");
		lc = cdr(lc);
		cdr(lc) = newstring((u_char *)strsave(errors_to));
		lc = cdr(lc);
	}
	l = ncons(l);
	sprintf(gsbuf, gs_name, gensym++);
	/* g0 (name in gsbuf) will be freed by free_gensym() later */
	v_setl(gsbuf, l);

	deferit = 0;
	v_set(DEFER, "");
	r = s_rewrite(ROUTER, t, (u_char *)gsbuf);
#if 0
	if (deferit) {
		s_free_tree(s_value);
		s_value = NULL;
		r = s_rewrite(DEFERENV, t, (u_char *)gsbuf);
	}
#endif
	if (r != 0 || s_value == NULL || !LIST(s_value)) {
		/* router returned something invalid */
		s_free_tree(s_value);
		s_value = NULL;
		return NULL;
	}

	/*
	 * We expect router to either return
	 * (local - user attributes) or (((local - user attributes)))
	 */
	if (LIST(car(s_value))) {
		if (!LIST(caar(s_value)) || !STRING(caaar(s_value))) {
			fprintf(stderr,
				"%s: '%s' returned invalid 2-level list: ",
				progname, ROUTER);
			s_grind(s_value, stderr);
			s_free_tree(s_value);
			s_value = NULL;
			return NULL;
		}
		l = s_copy_tree(s_value);
	} else {
		l = s_copy_tree(s_value);
		l = ncons(l);
		l = ncons(l);
	}

	s_free_tree(s_value);
	s_value = NULL;

	return l;
}

/*
 * Crossbar switch. That's the closest metaphor I can think of that describes
 * what this function actually does --- which is looking at the sender and
 * recipient addresses, or more precisely the (channel, host, user) triples,
 * and munging them both appropriately using whatever criteria it wishes.
 *
 * The crossbar configuration file function should return 7 values, as
 * shown below. The first six are its munged calling parameters, the
 * seventh if non-null is the name of another configuration file function
 * which will be called for munging the message header addresses.
 * The munged parameters will eventually find their way onto the envelopes.
 */

struct conscell *
crossbar(from, to)
	struct conscell *from, *to;
{
	struct conscell *l, *tmp;

	l = ncons(from);
	cdar(l) = to;
	s_set_prev(l, car(l));

	if (l_apply(CROSSBAR, l) != 0 || s_value == NULL) {
		if (s_value != NULL)
			s_free_tree(s_value);	/* superfluous? */
		return NULL;
	}

	/*
	 * We expect to see something like
	 * (rewrite (fc fh fu) (tc th tu)) or
	 * ((address-rewrite header-rewrite) (fc fh fu) (tc th tu))
	 * back from the crossbar function.
	 */

	l = s_copy_tree(s_value);
	s_free_tree(s_value);
	s_value = NULL;

	return l;
}
