#ident "@(#)optim.c	1.4 'attmail mail(1) command'"
#ident	"@(#)mailx:optim.c	1.5.2.1"
/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)mailx:optim.c	1.5"

/*
 * mailx -- a modified version of a University of California at Berkeley
 *	mail program
 *
 * Network name modification routines.
 */

#include "rcv.h"
#include "configdefs.h"

static char		*arpafix();
static char		*lasthost();
static char		*makeremote();
static int		mstash();
static int		mtype();
static int		netlook();
static int		nettype();
static int		ntype();
static char		*revarpa();
static char		*tackon();
static struct xtrahash	*xlocate();
static int		yyinit();
static int		yylex();

/*
 * Map a name into the correct network "view" of the
 * name.  This is done by prepending the name with the
 * network address of the sender, then optimizing away
 * nonsense.
 */

char *
netmap(name, from)
	char name[], from[];
{
	char nbuf[BUFSIZ], ret[BUFSIZ];
	register char *cp, *oname;

	if (debug) fprintf(stderr, "netmap(name '%s', from '%s')\n", name, from);
	if (strlen(from) == 0)
		return(name);
	if (any('@', name) || any('%', name))
		return(arpafix(name, from));
	if (any('@', from) || any('%', from))
		return(unuucp(makeremote(name, from)));
	if (value("onehop") && (cp = strchr(name, '!')) && cp > name)
		strcpy(nbuf, name);
	else {
		from = tackon(host, from);
		*strrchr(from, '!') = 0;
		name = tackon(lasthost(from), name);
		while (((cp = lasthost(from)) != 0) && ishost(cp, name)) {
			oname = name;
			name = strchr(name, '!') + 1;
			if (cp == from) {
				from[strlen(from)] = '!';
				if (value("mustbang") && !strchr(name, '!'))
					name = oname;
				return(unuucp(name));
			}
			*--cp = 0;
		}
		from[strlen(from)] = '!';
		from = strchr(from, '!') + 1;
		sprintf(nbuf, "%s!%s", from, name);
	}
	strcpy(ret, nbuf);
	cp = revarpa(ret);
	if (debug) fprintf(stderr, "wind up with '%s'\n", name);
	if (!icequal(name, cp))
		return(unuucp(savestr(cp)));
	return(unuucp(name));
}

/*
 * Stick a host on the beginning of a uucp
 * address if it isn't there already.
 */
static char *
tackon(sys, rest)
	char *sys, *rest;
{
	while (*rest == '!')
		rest++;
	if (!ishost(sys, rest)) {
		char *r = salloc(strlen(sys) + strlen(rest) + 2);
		sprintf(r, "%s!%s", sys, rest);
		rest = r;
	}
	return rest;
}

/*
 * Check equality of the first host in a uucp address.
 */
ishost(sys, rest)
	char *sys, *rest;
{
	while (*sys && *sys == *rest)
		sys++, rest++;
	return(*sys == 0 && *rest == '!');
}

/*
 * Return last host in a uucp address.
 */
static char *
lasthost(addr)
	char *addr;
{
	char *r = strrchr(addr, '!');
	return r ? ++r : addr;
}

/*
 * Optionally translate an old format uucp name into a new one, e.g.
 * "mach1!mach2!user" becomes "user@mach2.UUCP".  This optional because
 * some information is necessarily lost (e.g. the route it got here
 * via) and if we don't have the host in our routing tables, we lose.
 */
char *
unuucp(name)
char *name;
{
	register char *np, *hp, *cp;
	char result[100];
	char tname[300];

	if (UnUUCP==0 &&
	    ((cp = value("conv"))==NOSTR || strcmp(cp, "internet")))
		return name;
	if (debug) fprintf(stderr, "unuucp(%s)\n", name);
	strcpy(tname, name);
	np = strrchr(tname, '!');
	if (np == NOSTR)
		return name;
	*np++ = 0;
	hp = strrchr(tname, '!');
	if (hp == NOSTR)
		hp = tname;
	else
		*hp++ = 0;
	cp = strchr(np, '@');
	if (cp == NOSTR)
		cp = strchr(np, '%');
	if (cp)
		*cp = 0;
	if (debug) fprintf(stderr, "host %s, name %s\n", hp, np);
	sprintf(result, "%s@%s.UUCP", np, hp);
	if (debug) fprintf(stderr, "unuucp returns %s\n", result);
	return savestr(result);
}

/*
 * Turn a network machine name into a unique character
 */
static int
netlook(machine, attnet)
	char machine[];
{
	register struct netmach *np;
	register char *cp, *cp2;
	char nbuf[BUFSIZ];

	/*
	 * Make into lower case.
	 */
	for (cp = machine, cp2 = nbuf;
	     *cp && cp2 < &nbuf[BUFSIZ-1];
	     *cp2++ = tolower(*cp++))
		/*nothing*/;
	*cp2 = 0;

	/*
	 * If a single letter machine, look through those first.
	 */

	if (strlen(nbuf) == 1)
		for (np = netmach; np->nt_mid != 0; np++)
			if (np->nt_mid == nbuf[0])
				return(nbuf[0]);

	/*
	 * Look for usual name
	 */

	for (np = netmach; np->nt_mid != 0; np++)
		if (strcmp(np->nt_machine, nbuf) == 0)
			return(np->nt_mid);

	/*
	 * Look in side hash table.
	 */

	return(mstash(nbuf, attnet));
}

/*
 * Deal with arpa net addresses.  The way this is done is strange.
 * In particular, if the destination arpa net host is not Berkeley,
 * then the address is correct as stands.  Otherwise, we strip off
 * the trailing @Berkeley, then cook up a phony person for it to
 * be from and optimize the result.
 */
static char *
arpafix(name, from)
	char name[];
	char from[];
{
	register char *cp;
	register int arpamach;
	char newname[BUFSIZ];

	if (debug) {
		fprintf(stderr, "arpafix(%s, %s)\n", name, from);
	}
	cp = strrchr(name, '@');
	if (cp == NOSTR)
		cp = strrchr(name, '%');
	if (cp == NOSTR) {
		fprintf(stderr, "Somethings amiss -- no @ or %% in arpafix\n");
		return(name);
	}
	cp++;
	arpamach = netlook(cp, '@');
	if (debug) fprintf(stderr, "cp '%s', arpamach %o, nettypes arpamach %o LOCAL %o\n", cp, arpamach, nettype(arpamach), nettype(LOCAL));
	if (arpamach == 0) {
		if (debug)
			fprintf(stderr, "machine %s unknown, uses: %s\n", cp, name);
		return(name);
	}
	if (((nettype(arpamach) & nettype(LOCAL)) & ~AN) == 0) {
		if (debug)
			fprintf(stderr, "machine %s known but remote, uses: %s\n",
			    cp, name);
		return(name);
	}
	strcpy(newname, name);
	cp = strrchr(newname, '@');
	if (cp == NOSTR)
		cp = strrchr(newname, '%');
	*cp = 0;
	if (debug) fprintf(stderr, "local address, return '%s'\n", newname);
	return(savestr(newname));
}

/*
 * We have name with no @'s in it, and from with @'s.
 * Assume that name is meaningful only on the site in from.
 */
static char *
makeremote(name, from)
	char name[];
	char from[];
{
	register char *cp;
	static char rbuf[200];

	if (debug) fprintf(stderr, "makeremote(%s, %s) returns ", name, from);
	strcpy(rbuf, name);
	cp = strrchr(from, '@');
	if (cp == NOSTR)
		cp = strrchr(from, '%');
	strcat(rbuf, cp);
	if (debug) fprintf(stderr, "%s\n", rbuf);
	return rbuf;
}

/*
 * Take a network machine descriptor and find the types of connected
 * nets and return it.
 */
static int
nettype(mid)
{
	register struct netmach *np;

	if (mid & 0200)
		return(mtype(mid));
	for (np = netmach; np->nt_mid != 0; np++)
		if (np->nt_mid == mid)
			return(np->nt_type);
	return(0);
}

/*
 * Hashing routines to salt away machines seen scanning
 * networks paths that we don't know about.
 */

#define	XHSIZE		19		/* Size of extra hash table */
#define	NXMID		(XHSIZE*3/4)	/* Max extra machines */

struct xtrahash {
	char	*xh_name;		/* Name of machine */
	short	xh_mid;			/* Machine ID */
	short	xh_attnet;		/* Attached networks */
} xtrahash[XHSIZE];

static struct xtrahash	*xtab[XHSIZE];		/* F: mid-->machine name */

static short	midfree;			/* Next free machine id */

/*
 * Initialize the extra host hash table.
 * Called by sreset.
 */
void
minit()
{
	register struct xtrahash *xp, **tp;

	midfree = 0;
	tp = &xtab[0];
	for (xp = &xtrahash[0]; xp < &xtrahash[XHSIZE]; xp++) {
		xp->xh_name = NOSTR;
		xp->xh_mid = 0;
		xp->xh_attnet = 0;
		*tp++ = (struct xtrahash *) 0;
	}
}

/*
 * Stash a net name in the extra host hash table.
 * If a new entry is put in the hash table, deduce what
 * net the machine is attached to from the net character.
 *
 * If the machine is already known, add the given attached
 * net to those already known.
 */
static int
mstash(name, attnet)
	char name[];
{
	register struct xtrahash *xp;
	int x;

	xp = xlocate(name);
	if (xp == (struct xtrahash *) 0) {
		printf("Ran out of machine id spots\n");
		return(0);
	}
	if (xp->xh_name == NOSTR) {
		if (midfree >= XHSIZE) {
			printf("Out of machine ids\n");
			return(0);
		}
		xtab[midfree] = xp;
		xp->xh_name = savestr(name);
		xp->xh_mid = 0200 + midfree++;
	}
	x = ntype(attnet);
	if (x == 0)
		xp->xh_attnet |= AN;
	else
		xp->xh_attnet |= x;
	return(xp->xh_mid);
}

/*
 * Search for the given name in the hash table
 * and return the pointer to it if found, or to the first
 * empty slot if not found.
 *
 * If no free slots can be found, return 0.
 */

static struct xtrahash *
xlocate(name)
	char name[];
{
	register int h, q, i;
	register char *cp;
	register struct xtrahash *xp;

	for (h = 0, cp = name; *cp; h = (h << 2) + *cp++)
		;
	if (h < 0 && (h = -h) < 0)
		h = 0;
	h = h % XHSIZE;
	cp = name;
	for (i = 0, q = 0; q < XHSIZE; i++, q = i * i) {
		xp = &xtrahash[(h + q) % XHSIZE];
		if (xp->xh_name == NOSTR)
			return(xp);
		if (strcmp(cp, xp->xh_name) == 0)
			return(xp);
		if (h - q < 0)
			q += XHSIZE;
		xp = &xtrahash[(h - q) % XHSIZE];
		if (xp->xh_name == NOSTR)
			return(xp);
		if (strcmp(cp, xp->xh_name) == 0)
			return(xp);
	}
	return((struct xtrahash *) 0);
}

/*
 * Return the bit mask of net's that the given extra host machine
 * id has so far.
 */
static int
mtype(mid)
{
	register int m;

	if ((mid & 0200) == 0)
		return(0);
	m = mid & 0177;
	if (m >= midfree) {
		printf("Use made of undefined machine id\n");
		return(0);
	}
	return(xtab[m]->xh_attnet);
}

/*
 * Return the network of the separator --
 *	AN for arpa net
 *	BN for Bell labs net	(e.g. UUCP, NOT Berknet)
 *	SN for Schmidt net	(Berknet)
 *	0 if we don't know.
 */
static int
ntype(nc)
	register int nc;
{
	register struct ntypetab *np;

	for (np = ntypetab; np->nt_char != 0; np++)
		if (np->nt_char == nc)
			return(np->nt_bcode);
	return(0);
}


/*
 * Code to twist around arpa net names.
 */

#define WORD 257			/* Token for a string */

static	char netbuf[256];
static	char *yylval;

/*
 * Reverse all of the arpa net addresses in the given name to
 * be of the form "host @ user" instead of "user @ host"
 * This function is its own inverse.
 */

static char *
revarpa(str)
	char str[];
{

	if (yyinit(str) < 0)
		return(NOSTR);
	if (name())
		return(NOSTR);
	if (strcmp(str, netbuf) == 0)
		return(str);
	return(savestr(netbuf));
}

/*
 * Parse (by recursive descent) network names, using the following grammar:
 *	name:
 *		term {':' term}
 *		term {'^' term}
 *		term {'!' term}
 *		term '@' name
 *		term '%' name
 *
 *	term:
 *		string of characters.
 */

name()
{
	register int t;
	register char *cp;

	for (;;) {
		t = yylex();
		if (t != WORD)
			return(-1);
		cp = yylval;
		t = yylex();
		switch (t) {
		case 0:
			strcat(netbuf, cp);
			return(0);

		case '@':
		case '%':
			if (name())
				return(-1);
			strcat(netbuf, "@");
			strcat(netbuf, cp);
			return(0);

		case WORD:
			return(-1);

		default:
			strcat(netbuf, cp);
			stradd(netbuf, t);
		}
	}
}

/*
 * Scanner for network names.
 */

static	char *charp;			/* Current input pointer */
static	int nexttok;			/* Salted away next token */

/*
 * Initialize the network name scanner.
 */
static int
yyinit(str)
	char str[];
{
	static char lexbuf[BUFSIZ];

	netbuf[0] = 0;
	if (strlen(str) >= sizeof lexbuf - 1)
		return(-1);
	nexttok = 0;
	strcpy(lexbuf, str);
	charp = lexbuf;
	return(0);
}

/*
 * Scan and return a single token.
 * yylval is set to point to a scanned string.
 */
static int
yylex()
{
	register char *cp, *dot;
	register int s;

	if (nexttok) {
		s = nexttok;
		nexttok = 0;
		return(s);
	}
	cp = charp;
	while (*cp && isspace(*cp))
		cp++;
	if (*cp == 0)
		return(0);
	if (any(*cp, metanet)) {
		charp = cp+1;
		return(*cp);
	}
	dot = cp;
	while (*cp && !any(*cp, metanet) && !any(*cp, " \t"))
		cp++;
	if (any(*cp, metanet))
		nexttok = *cp;
	if (*cp == 0)
		charp = cp;
	else
		charp = cp+1;
	*cp = 0;
	yylval = dot;
	return(WORD);
}

/*
 * Add a single character onto a string.
 */

void
stradd(str, c)
	register char *str;
	register int c;
{
	while (*str)
		str++;
	*str++ = (char)c;
	*str = '\0';
}
