/*
 *	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"

conscell **return_valuep = NULL;
conscell *s_value        = NULL;

int
l_apply(fname, l)
	const char *fname;
	conscell *l;
{
	int retval;
	conscell *retvp = NULL;
	conscell **oretvp = return_valuep;
	GCVARS2;

	GCPRO2(l, retvp);

	return_valuep = &retvp;

	retval = lapply(fname, l);
	s_value = retvp;

	return_valuep = oretvp;
	UNGCPRO2;
	return retval;
}


int
s_apply(argc, argv)
	int argc;
	const char *argv[];
{
	int retval;
	conscell *retvp = NULL;
	conscell **oretvp = return_valuep;
	GCVARS1;

	GCPRO1(retvp);

	return_valuep = &retvp;

	retval = apply(argc, argv);
	s_value = retvp;
	return_valuep = oretvp;
	UNGCPRO1;
	return retval;
}

int
n_apply(cpp, argc, argv)
	int argc;
	char **cpp;
	const char *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 __((const char *func, token822 *t,
			 const char *sender, const char *argx));

static int
s_rewrite(func, t, sender, argx)
	const char *func, *sender, *argx;
	token822 *t;
{
	register char *cp, *bp;
	const char *av[4];
	char *buf = malloc(4000);
	int bufspc = 4000;
	int rc;

	if (t == NULL)
		return 0;
	cp = buf;
	cp += printdToken(&buf, &bufspc, t, (token822 *)NULL, 0);
	/* Was it a quote-containing string ?  If so, strip the quotes,
	   and undo back-slash quoting */
	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;
	rc = s_apply(argx == NULL ? 2 : 3, av);
	free(buf);
	return rc;
}


/*
 * 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, token822 *t));

static struct addr *
mergeAddress(pp, t)
	struct addr *pp;
	token822 *t;
{
	struct addr *ppp, *npp, *fpp;
	token822 *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)
	const char *name;
	struct header *h;
{
	register struct address *ap;
	register struct addr *pp;
	register token822 *t;
	struct address *nap = NULL, *pap;
	token822 *nt, *pt, *addrtokens;
	struct header *nh;
	const char *cp, *eocp;
	char *s, buf[4096], *eobuf; 	/* XX */

	eobuf = buf + sizeof(buf)-1;

	if (D_hdr_rewrite) {
		printf("---------------------------\n");
		printf("Sending this through %s:\n", 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;
				if (s < eobuf)
				  *s++ = *cp;
			}
			*s = 0;
			addrtokens->t_pname = strsave(buf);
		}
		nap = (struct address *)tmalloc(sizeof (struct address));
		nap->a_pname = NULL;
		nap->a_stamp = newAddress;
		nap->a_tokens = NULL;
		/* don't bother initializing a_uid/a_mode here, no use */
		nap->a_next = NULL;
		nap->a_dsn  = NULL;
		deferit = 0;
		v_set(DEFER, "");
		/*
		 * Header rewrite routines "intramachine", "null", and
		 * "internet" in script  crossbar.cf  can take header
		 * name (for debug purposes).
		 */
		if (addrtokens == NULL)
			s_value = NULL;
		else if (s_rewrite(name, addrtokens, NULL, h->h_pname) != 0) {
			if (s_value != NULL) {
			  /* s_free_tree(s_value); */
			  s_value = NULL;
			}
			if (deferit
			    && s_rewrite(DEFERHDR, addrtokens, NULL, h->h_pname)
			    && 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 */

			const char *cs = s_value->cstring;
			char *s;
			memtypes osticky = stickymem;
			stickymem = MEM_TEMP;

			/* This really does need long-term storage! */
			s = tmalloc(strlen(cs)+1);
			strcpy(s, cs);
			/* t = HDR_SCANNER(cs); */
			t = scan822((const char**)&s, strlen(s),
				    '!', '%', 0, &ap->a_tokens->p_tokens);

			stickymem = osticky;

			/* 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;
	conscell *pl, *plhead;
	char buf[20];
	GCVARS1;
	int slen;

	/* 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)	slen = strlen(s); cdr(pl) = conststring(s, slen); pl = cdr(pl)
#define	NEWSTR(s)	slen = strlen(s); cdr(pl) = newstring(s, slen);   pl = cdr(pl)
#define	NEWDUPSTR(s)	slen = strlen(s); cdr(pl) = newstring(dupnstr(s, slen), slen);   pl = cdr(pl)
#define	NEWSTRD(d)	sprintf(buf, "%ld", (long)(d)); NEWDUPSTR(buf)

	pl = plhead = conststring("file", 4);
	GCPRO1(plhead);

	CONSTSTR(e->e_file);

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

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

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

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

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

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

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

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

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

	CONSTSTR("trusted");
	CONSTSTR(e->e_trusted ? "yes" : "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(h->h_descriptor->hdr_name);
		if (h->h_lines == NULL || *h->h_lines->t_pname == '\0') {
			CONSTSTR(h->h_descriptor->hdr_name);
		} else {
			CONSTSTR(h->h_lines->t_pname);
		}
	}
	cdr(pl) = NULL;
	plhead = ncons(plhead);
	v_setl("envelopeinfo", plhead);
	UNGCPRO1;
}

static char gsbuf[30];
/*
 *  newattribute_2()
 */
char *newattribute_2(onam,nam,val)
     const char *onam, *nam, *val;
{
	conscell *l, *lc, *tmp, **pl;
	conscell	*l1;
	GCVARS4;
	int slen;

	l1 = v_find(onam);
	if (!l1)
	  return NULL;
	l = copycell(cdr(l1));
	lc = tmp = l1 = NULL;
	GCPRO4(l, lc, tmp, l1);

	cdr(l) = NULL;
	car(l) = s_copy_chain(car(l));
	pl = &car(l);
	l1 = *pl;
	for (lc = l1; lc && cdr(lc); pl = &cddr(lc),lc = *pl) {
	  if (!STRING(lc)) {
	    UNGCPRO4;
	    return NULL; /* ?? */
	  }
	  if (STREQ(nam,lc->string)) {
	    if (!cdr(lc)) {
	      UNGCPRO4;
	      return NULL;
	    }
	    *pl = cddr(lc) /* Skip this in chain */;
	  }
	}

	/* Prepend in reverse order */
	slen = strlen(val);
	tmp = newstring(dupnstr(val, slen), slen);
	cdr(tmp) = car(l);
	car(l) = tmp;
	slen = strlen(nam);
	tmp = newstring(dupnstr(nam, slen), slen);
	cdr(tmp) = car(l);
	car(l) = tmp;

	sprintf(gsbuf, gs_name, gensym++);
	/* gX (name in gsbuf) will be freed by free_gensym() later */
	v_setl(gsbuf, l);
	UNGCPRO4;
	return gsbuf;
}

/*
 * Build gensym
 */
static char  *build_gensym __((int, const char*, const char*, const char*, const char*, const char*, const char*));

static char *
build_gensym(uid, type, DSNstr, DSNret, DSNenv, errorsto, sender)
     int uid;
     const char *type, *DSNstr, *DSNret, *DSNenv, *errorsto, *sender;
{
	char buf[20];
	conscell *l, *pl;
	GCVARS1;
	int slen;

	/* assemble the default attribute list: (privilege <uid>) */
	l = conststring("privilege", 9);
	GCPRO1(l);
	sprintf(buf, "%d", uid);
	pl = l;
	NEWDUPSTR(buf);
	if (type) {
		CONSTSTR("type");
		CONSTSTR(type); /* Always a constant string */
	}
	if (DSNstr) {
		CONSTSTR("DSN");
		NEWDUPSTR(DSNstr);
	}
	if (DSNret) {
		CONSTSTR("DSNr");
		NEWDUPSTR(DSNret);
	}
	if (DSNenv) {
		CONSTSTR("DSNe");
		NEWDUPSTR(DSNenv);
	}
	/* See if some "errorsto" definition is available.. */
	if (errorsto) {
		CONSTSTR("ERR");
		NEWDUPSTR(errorsto);
	}
	/* See if some "sender" definition is available.. */
	if (sender) {
		CONSTSTR("sender");
		NEWDUPSTR(sender);
	}
	cdr(pl) = NULL; /* not needed in reality */
	l = ncons(l);
	sprintf(gsbuf, gs_name, gensym++);
	/* gX (name in gsbuf) will be freed by free_gensym() later */
	v_setl(gsbuf, l);
	UNGCPRO1;
	return gsbuf;
}

/*
 * 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.
 */

conscell *
router(a, uid, type, senderstr)
	struct address *a;
	int uid;
	const char *type, *senderstr;
{
	register token822 *t, *tt;
	int r;
	token822 *last;
	struct addr *p;
	conscell *l;
	const char *gsym;
	struct notary *DSN = NULL;
	const char *DSNstr;
	const char *DSNret;
	const char *DSNenv;
	GCVARS1;

	if (a == NULL)
		return NULL;
	t = last = NULL;
	DSN = a->a_dsn;
	DSNstr = DSNret = DSNenv = NULL;
	if (DSN) {
	  DSNstr = DSN->dsn;
	  DSNret = DSN->ret;
	  DSNenv = DSN->envid;
	}
	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();

	gsym = build_gensym(uid, type, DSNstr, DSNret, DSNenv, errors_to, senderstr);

	deferit = 0;
	v_set(DEFER, "");
	r = s_rewrite(ROUTER, t, NULL, gsym);
#if 0
	if (deferit) {
	  /* s_free_tree(s_value); */
	  s_value = NULL;
	  r = s_rewrite(DEFERENV, t, NULL, gsym);
	}
#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)))
	 */
	l = NULL;
	GCPRO1(l);
	if (car(s_value) && 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;
			UNGCPRO1;
			return NULL;
		}
		l = s_copy_chain(s_value);
	} else {
		l = s_copy_chain(s_value);
		l = ncons(l);
		l = ncons(l);
	}

	/* s_free_tree(s_value); */
	s_value = NULL;
	UNGCPRO1;

	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.
 */

conscell *
crossbar(from, to)
	conscell *from, *to;
{
	conscell *l = NULL;
	GCVARS3;

	GCPRO3(l, from, to);

	l = copycell(from);
	l = ncons(l);

	cdar(l) = to;

	if (l_apply(CROSSBAR, l) != 0 || s_value == NULL) {
	  s_value = NULL;
	  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_value;
	s_value = NULL;
	UNGCPRO3;

	return l;
}


syntax highlighted by Code2HTML, v. 0.9.1