Sendmail source for the 3B1/7300 (part 5/8)

David H. Brierley dave at galaxia.Newport.RI.US
Sat Feb 25 12:28:59 AEST 1989


----- cut here and feed to /bin/sh -----
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 8)."
# Contents:  src/headers.c src/readcf.c
# Wrapped by dave at galaxia on Fri Feb 24 20:24:05 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'src/headers.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/headers.c'\"
else
echo shar: Extracting \"'src/headers.c'\" \(16858 characters\)
sed "s/^X//" >'src/headers.c' <<'END_OF_FILE'
X/*
X**  Sendmail
X**  Copyright (c) 1983  Eric P. Allman
X**  Berkeley, California
X**
X**  Copyright (c) 1983 Regents of the University of California.
X**  All rights reserved.  The Berkeley software License Agreement
X**  specifies the terms and conditions for redistribution.
X*/
X
X#ifndef lint
Xstatic char	SccsId[] = "@(#)headers.c	5.7 (Berkeley) 9/21/85";
X#endif not lint
X
X# include <errno.h>
X# include "sendmail.h"
X
X/*
X**  CHOMPHEADER -- process and save a header line.
X**
X**	Called by collect and by readcf to deal with header lines.
X**
X**	Parameters:
X**		line -- header as a text line.
X**		def -- if set, this is a default value.
X**
X**	Returns:
X**		flags for this header.
X**
X**	Side Effects:
X**		The header is saved on the header list.
X**		Contents of 'line' are destroyed.
X*/
X
Xchompheader(line, def)
X	char *line;
X	bool def;
X{
X	register char *p;
X	register HDR *h;
X	HDR **hp;
X	char *fname;
X	char *fvalue;
X	struct hdrinfo *hi;
X	bool cond = FALSE;
X	BITMAP mopts;
X	extern char *crackaddr();
X
X# ifdef DEBUG
X	if (tTd(31, 6))
X		printf("chompheader: %s\n", line);
X# endif DEBUG
X
X	/* strip off options */
X	clrbitmap(mopts);
X	p = line;
X	if (*p == '?')
X	{
X		/* have some */
X		register char *q = index(p + 1, *p);
X		
X		if (q != NULL)
X		{
X			*q++ = '\0';
X			while (*++p != '\0')
X				setbitn(*p, mopts);
X			p = q;
X		}
X		else
X			syserr("chompheader: syntax error, line \"%s\"", line);
X		cond = TRUE;
X	}
X
X	/* find canonical name */
X	fname = p;
X	p = index(p, ':');
X	if (p == NULL)
X	{
X		syserr("chompheader: syntax error, line \"%s\"", line);
X		return (0);
X	}
X	fvalue = &p[1];
X	while (isspace(*--p))
X		continue;
X	*++p = '\0';
X	makelower(fname);
X
X	/* strip field value on front */
X	if (*fvalue == ' ')
X		fvalue++;
X
X	/* see if it is a known type */
X	for (hi = HdrInfo; hi->hi_field != NULL; hi++)
X	{
X		if (strcmp(hi->hi_field, fname) == 0)
X			break;
X	}
X
X	/* see if this is a resent message */
X	if (!def && bitset(H_RESENT, hi->hi_flags))
X		CurEnv->e_flags |= EF_RESENT;
X
X	/* if this means "end of header" quit now */
X	if (bitset(H_EOH, hi->hi_flags))
X		return (hi->hi_flags);
X
X	/* drop explicit From: if same as what we would generate -- for MH */
X	p = "resent-from";
X	if (!bitset(EF_RESENT, CurEnv->e_flags))
X		p += 7;
X	if (!def && !QueueRun && strcmp(fname, p) == 0)
X	{
X		if (CurEnv->e_from.q_paddr != NULL &&
X		    strcmp(fvalue, CurEnv->e_from.q_paddr) == 0)
X			return (hi->hi_flags);
X	}
X
X	/* delete default value for this header */
X	for (hp = &CurEnv->e_header; (h = *hp) != NULL; hp = &h->h_link)
X	{
X		if (strcmp(fname, h->h_field) == 0 &&
X		    bitset(H_DEFAULT, h->h_flags) &&
X		    !bitset(H_FORCE, h->h_flags))
X			h->h_value = NULL;
X	}
X
X	/* create a new node */
X	h = (HDR *) xalloc(sizeof *h);
X	h->h_field = newstr(fname);
X	h->h_value = NULL;
X	h->h_link = NULL;
X	bcopy((char *) mopts, (char *) h->h_mflags, sizeof mopts);
X	*hp = h;
X	h->h_flags = hi->hi_flags;
X	if (def)
X		h->h_flags |= H_DEFAULT;
X	if (cond)
X		h->h_flags |= H_CHECK;
X	if (h->h_value != NULL)
X		free((char *) h->h_value);
X	h->h_value = newstr(fvalue);
X
X	/* hack to see if this is a new format message */
X	if (!def && bitset(H_RCPT|H_FROM, h->h_flags) &&
X	    (index(fvalue, ',') != NULL || index(fvalue, '(') != NULL ||
X	     index(fvalue, '<') != NULL || index(fvalue, ';') != NULL))
X	{
X		CurEnv->e_flags &= ~EF_OLDSTYLE;
X	}
X
X	return (h->h_flags);
X}
X/*
X**  ADDHEADER -- add a header entry to the end of the queue.
X**
X**	This bypasses the special checking of chompheader.
X**
X**	Parameters:
X**		field -- the name of the header field.
X**		value -- the value of the field.  It must be lower-cased.
X**		e -- the envelope to add them to.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		adds the field on the list of headers for this envelope.
X*/
X
Xaddheader(field, value, e)
X	char *field;
X	char *value;
X	ENVELOPE *e;
X{
X	register HDR *h;
X	register struct hdrinfo *hi;
X	HDR **hp;
X
X	/* find info struct */
X	for (hi = HdrInfo; hi->hi_field != NULL; hi++)
X	{
X		if (strcmp(field, hi->hi_field) == 0)
X			break;
X	}
X
X	/* find current place in list -- keep back pointer? */
X	for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link)
X	{
X		if (strcmp(field, h->h_field) == 0)
X			break;
X	}
X
X	/* allocate space for new header */
X	h = (HDR *) xalloc(sizeof *h);
X	h->h_field = field;
X	h->h_value = newstr(value);
X	h->h_link = *hp;
X	h->h_flags = hi->hi_flags | H_DEFAULT;
X	clrbitmap(h->h_mflags);
X	*hp = h;
X}
X/*
X**  HVALUE -- return value of a header.
X**
X**	Only "real" fields (i.e., ones that have not been supplied
X**	as a default) are used.
X**
X**	Parameters:
X**		field -- the field name.
X**
X**	Returns:
X**		pointer to the value part.
X**		NULL if not found.
X**
X**	Side Effects:
X**		none.
X*/
X
Xchar *
Xhvalue(field)
X	char *field;
X{
X	register HDR *h;
X
X	for (h = CurEnv->e_header; h != NULL; h = h->h_link)
X	{
X		if (!bitset(H_DEFAULT, h->h_flags) && strcmp(h->h_field, field) == 0)
X			return (h->h_value);
X	}
X	return (NULL);
X}
X/*
X**  ISHEADER -- predicate telling if argument is a header.
X**
X**	A line is a header if it has a single word followed by
X**	optional white space followed by a colon.
X**
X**	Parameters:
X**		s -- string to check for possible headerness.
X**
X**	Returns:
X**		TRUE if s is a header.
X**		FALSE otherwise.
X**
X**	Side Effects:
X**		none.
X*/
X
Xbool
Xisheader(s)
X	register char *s;
X{
X	while (*s > ' ' && *s != ':' && *s != '\0')
X		s++;
X
X	/* following technically violates RFC822 */
X	while (isspace(*s))
X		s++;
X
X	return (*s == ':');
X}
X/*
X**  EATHEADER -- run through the stored header and extract info.
X**
X**	Parameters:
X**		e -- the envelope to process.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		Sets a bunch of global variables from information
X**			in the collected header.
X**		Aborts the message if the hop count is exceeded.
X*/
X
Xeatheader(e)
X	register ENVELOPE *e;
X{
X	register HDR *h;
X	register char *p;
X	int hopcnt = 0;
X
X#ifdef DEBUG
X	if (tTd(32, 1))
X		printf("----- collected header -----\n");
X#endif DEBUG
X	for (h = e->e_header; h != NULL; h = h->h_link)
X	{
X#ifdef DEBUG
X		extern char *capitalize();
X
X		if (tTd(32, 1))
X			printf("%s: %s\n", capitalize(h->h_field), h->h_value);
X#endif DEBUG
X		/* count the number of times it has been processed */
X		if (bitset(H_TRACE, h->h_flags))
X			hopcnt++;
X
X		/* send to this person if we so desire */
X		if (GrabTo && bitset(H_RCPT, h->h_flags) &&
X		    !bitset(H_DEFAULT, h->h_flags) &&
X		    (!bitset(EF_RESENT, CurEnv->e_flags) || bitset(H_RESENT, h->h_flags)))
X		{
X			sendtolist(h->h_value, (ADDRESS *) NULL, &CurEnv->e_sendqueue);
X		}
X
X		/* log the message-id */
X#ifdef LOG
X		if (!QueueRun && LogLevel > 8 && h->h_value != NULL &&
X		    strcmp(h->h_field, "message-id") == 0)
X		{
X			char buf[MAXNAME];
X
X			p = h->h_value;
X			if (bitset(H_DEFAULT, h->h_flags))
X			{
X				expand(p, buf, &buf[sizeof buf], e);
X				p = buf;
X			}
X			syslog(LOG_INFO, "%s: message-id=%s", e->e_id, p);
X		}
X#endif LOG
X	}
X#ifdef DEBUG
X	if (tTd(32, 1))
X		printf("----------------------------\n");
X#endif DEBUG
X
X	/* store hop count */
X	if (hopcnt > e->e_hopcount)
X		e->e_hopcount = hopcnt;
X
X	/* message priority */
X	p = hvalue("precedence");
X	if (p != NULL)
X		e->e_class = priencode(p);
X	if (!QueueRun)
X		e->e_msgpriority = e->e_msgsize
X				 - e->e_class * WkClassFact
X				 + e->e_nrcpts * WkRecipFact;
X
X	/* return receipt to */
X	p = hvalue("return-receipt-to");
X	if (p != NULL)
X		e->e_receiptto = p;
X
X	/* errors to */
X	p = hvalue("errors-to");
X	if (p != NULL)
X		sendtolist(p, (ADDRESS *) NULL, &e->e_errorqueue);
X
X	/* from person */
X	if (OpMode == MD_ARPAFTP)
X	{
X		register struct hdrinfo *hi = HdrInfo;
X
X		for (p = NULL; p == NULL && hi->hi_field != NULL; hi++)
X		{
X			if (bitset(H_FROM, hi->hi_flags))
X				p = hvalue(hi->hi_field);
X		}
X		if (p != NULL)
X			setsender(p);
X	}
X
X	/* full name of from person */
X	p = hvalue("full-name");
X	if (p != NULL)
X		define('x', p, e);
X
X	/* date message originated */
X	p = hvalue("posted-date");
X	if (p == NULL)
X		p = hvalue("date");
X	if (p != NULL)
X	{
X		define('a', p, e);
X		/* we don't have a good way to do canonical conversion ....
X		define('d', newstr(arpatounix(p)), e);
X		.... so we will ignore the problem for the time being */
X	}
X
X	/*
X	**  Log collection information.
X	*/
X
X# ifdef LOG
X	if (!QueueRun && LogLevel > 1)
X	{
X		syslog(LOG_INFO, "%s: from=%s, size=%ld, class=%d\n",
X		       CurEnv->e_id, CurEnv->e_from.q_paddr, CurEnv->e_msgsize,
X		       CurEnv->e_class);
X	}
X# endif LOG
X}
X/*
X**  PRIENCODE -- encode external priority names into internal values.
X**
X**	Parameters:
X**		p -- priority in ascii.
X**
X**	Returns:
X**		priority as a numeric level.
X**
X**	Side Effects:
X**		none.
X*/
X
Xpriencode(p)
X	char *p;
X{
X	register int i;
X	extern bool sameword();
X
X	for (i = 0; i < NumPriorities; i++)
X	{
X		if (sameword(p, Priorities[i].pri_name))
X			return (Priorities[i].pri_val);
X	}
X
X	/* unknown priority */
X	return (0);
X}
X/*
X**  CRACKADDR -- parse an address and turn it into a macro
X**
X**	This doesn't actually parse the address -- it just extracts
X**	it and replaces it with "$g".  The parse is totally ad hoc
X**	and isn't even guaranteed to leave something syntactically
X**	identical to what it started with.  However, it does leave
X**	something semantically identical.
X**
X**	The process is kind of strange.  There are a number of
X**	interesting cases:
X**		1.  comment <address> comment	==> comment <$g> comment
X**		2.  address			==> address
X**		3.  address (comment)		==> $g (comment)
X**		4.  (comment) address		==> (comment) $g
X**	And then there are the hard cases....
X**		5.  add (comment) ress		==> $g (comment)
X**		6.  comment <address (comment)>	==> comment <$g (comment)>
X**		7.    .... etc ....
X**
X**	Parameters:
X**		addr -- the address to be cracked.
X**
X**	Returns:
X**		a pointer to the new version.
X**
X**	Side Effects:
X**		none.
X**
X**	Warning:
X**		The return value is saved in local storage and should
X**		be copied if it is to be reused.
X*/
X
Xchar *
Xcrackaddr(addr)
X	register char *addr;
X{
X	register char *p;
X	register int i;
X	static char buf[MAXNAME];
X	char *rhs;
X	bool gotaddr;
X	register char *bp;
X
X# ifdef DEBUG
X	if (tTd(33, 1))
X		printf("crackaddr(%s)\n", addr);
X# endif DEBUG
X
X	(void) strcpy(buf, "");
X	rhs = NULL;
X
X	/* strip leading spaces */
X	while (*addr != '\0' && isspace(*addr))
X		addr++;
X
X	/*
X	**  See if we have anything in angle brackets.  If so, that is
X	**  the address part, and the rest is the comment.
X	*/
X
X	p = index(addr, '<');
X	if (p != NULL)
X	{
X		/* copy the beginning of the addr field to the buffer */
X		*p = '\0';
X		(void) strcpy(buf, addr);
X		(void) strcat(buf, "<");
X		*p++ = '<';
X
X		/* skip spaces */
X		while (isspace(*p))
X			p++;
X
X		/* find the matching right angle bracket */
X		addr = p;
X		for (i = 0; *p != '\0'; p++)
X		{
X			switch (*p)
X			{
X			  case '<':
X				i++;
X				break;
X
X			  case '>':
X				i--;
X				break;
X			}
X			if (i < 0)
X				break;
X		}
X
X		/* p now points to the closing quote (or a null byte) */
X		if (*p != '\0')
X		{
X			/* make rhs point to the extra stuff at the end */
X			rhs = p;
X			*p++ = '\0';
X		}
X	}
X
X	/*
X	**  Now parse the real address part.  "addr" points to the (null
X	**  terminated) version of what we are inerested in; rhs points
X	**  to the extra stuff at the end of the line, if any.
X	*/
X
X	p = addr;
X
X	/* now strip out comments */
X	bp = &buf[strlen(buf)];
X	gotaddr = FALSE;
X	for (; *p != '\0'; p++)
X	{
X		if (*p == '(')
X		{
X			/* copy to matching close paren */
X			*bp++ = *p++;
X			for (i = 0; *p != '\0'; p++)
X			{
X				*bp++ = *p;
X				switch (*p)
X				{
X				  case '(':
X					i++;
X					break;
X
X				  case ')':
X					i--;
X					break;
X				}
X				if (i < 0)
X					break;
X			}
X			continue;
X		}
X
X		/*
X		**  If this is the first "real" character we have seen,
X		**  then we put the "$g" in the buffer now.
X		*/
X
X		if (isspace(*p))
X			*bp++ = *p;
X		else if (!gotaddr)
X		{
X			(void) strcpy(bp, "\001g");
X			bp += 2;
X			gotaddr = TRUE;
X		}
X	}
X
X	/* hack, hack.... strip trailing blanks */
X	do
X	{
X		*bp-- = '\0';
X	} while (isspace(*bp));
X	bp++;
X
X	/* put any right hand side back on */
X	if (rhs != NULL)
X	{
X		*rhs = '>';
X		(void) strcpy(bp, rhs);
X	}
X
X# ifdef DEBUG
X	if (tTd(33, 1))
X		printf("crackaddr=>`%s'\n", buf);
X# endif DEBUG
X
X	return (buf);
X}
X/*
X**  PUTHEADER -- put the header part of a message from the in-core copy
X**
X**	Parameters:
X**		fp -- file to put it on.
X**		m -- mailer to use.
X**		e -- envelope to use.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		none.
X*/
X
Xputheader(fp, m, e)
X	register FILE *fp;
X	register MAILER *m;
X	register ENVELOPE *e;
X{
X	char buf[BUFSIZ];
X	register HDR *h;
X	extern char *arpadate();
X	extern char *capitalize();
X	char obuf[MAXLINE];
X
X	for (h = e->e_header; h != NULL; h = h->h_link)
X	{
X		register char *p;
X		extern bool bitintersect();
X
X		if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
X		    !bitintersect(h->h_mflags, m->m_flags))
X			continue;
X
X		/* handle Resent-... headers specially */
X		if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
X			continue;
X
X		p = h->h_value;
X		if (bitset(H_DEFAULT, h->h_flags))
X		{
X			/* macro expand value if generated internally */
X			expand(p, buf, &buf[sizeof buf], e);
X			p = buf;
X			if (p == NULL || *p == '\0')
X				continue;
X		}
X
X		if (bitset(H_FROM|H_RCPT, h->h_flags))
X		{
X			/* address field */
X			bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
X
X			if (bitset(H_FROM, h->h_flags))
X				oldstyle = FALSE;
X			commaize(h, p, fp, oldstyle, m);
X		}
X		else
X		{
X			/* vanilla header line */
X			register char *nlp;
X
X			(void) sprintf(obuf, "%s: ", capitalize(h->h_field));
X			while ((nlp = index(p, '\n')) != NULL)
X			{
X				*nlp = '\0';
X				(void) strcat(obuf, p);
X				*nlp = '\n';
X				putline(obuf, fp, m);
X				p = ++nlp;
X				obuf[0] = '\0';
X			}
X			(void) strcat(obuf, p);
X			putline(obuf, fp, m);
X		}
X	}
X}
X/*
X**  COMMAIZE -- output a header field, making a comma-translated list.
X**
X**	Parameters:
X**		h -- the header field to output.
X**		p -- the value to put in it.
X**		fp -- file to put it to.
X**		oldstyle -- TRUE if this is an old style header.
X**		m -- a pointer to the mailer descriptor.  If NULL,
X**			don't transform the name at all.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		outputs "p" to file "fp".
X*/
X
Xcommaize(h, p, fp, oldstyle, m)
X	register HDR *h;
X	register char *p;
X	FILE *fp;
X	bool oldstyle;
X	register MAILER *m;
X{
X	register char *obp;
X	int opos;
X	bool firstone = TRUE;
X	char obuf[MAXLINE + 3];
X
X	/*
X	**  Output the address list translated by the
X	**  mailer and with commas.
X	*/
X
X# ifdef DEBUG
X	if (tTd(14, 2))
X		printf("commaize(%s: %s)\n", h->h_field, p);
X# endif DEBUG
X
X	obp = obuf;
X	(void) sprintf(obp, "%s: ", capitalize(h->h_field));
X	opos = strlen(h->h_field) + 2;
X	obp += opos;
X
X	/*
X	**  Run through the list of values.
X	*/
X
X	while (*p != '\0')
X	{
X		register char *name;
X		char savechar;
X		extern char *remotename();
X		extern char *DelimChar;		/* defined in prescan */
X
X		/*
X		**  Find the end of the name.  New style names
X		**  end with a comma, old style names end with
X		**  a space character.  However, spaces do not
X		**  necessarily delimit an old-style name -- at
X		**  signs mean keep going.
X		*/
X
X		/* find end of name */
X		while (isspace(*p) || *p == ',')
X			p++;
X		name = p;
X		for (;;)
X		{
X			char *oldp;
X			char pvpbuf[PSBUFSIZE];
X			extern bool isatword();
X			extern char **prescan();
X
X			(void) prescan(p, oldstyle ? ' ' : ',', pvpbuf);
X			p = DelimChar;
X
X			/* look to see if we have an at sign */
X			oldp = p;
X			while (*p != '\0' && isspace(*p))
X				p++;
X
X			if (*p != '@' && !isatword(p))
X			{
X				p = oldp;
X				break;
X			}
X			p += *p == '@' ? 1 : 2;
X			while (*p != '\0' && isspace(*p))
X				p++;
X		}
X		/* at the end of one complete name */
X
X		/* strip off trailing white space */
X		while (p >= name && (isspace(*p) || *p == ',' || *p == '\0'))
X			p--;
X		if (++p == name)
X			continue;
X		savechar = *p;
X		*p = '\0';
X
X		/* translate the name to be relative */
X		name = remotename(name, m, bitset(H_FROM, h->h_flags), FALSE);
X		if (*name == '\0')
X		{
X			*p = savechar;
X			continue;
X		}
X
X		/* output the name with nice formatting */
X		opos += qstrlen(name);
X		if (!firstone)
X			opos += 2;
X		if (opos > 78 && !firstone)
X		{
X			(void) strcpy(obp, ",\n");
X			putline(obuf, fp, m);
X			obp = obuf;
X			(void) sprintf(obp, "        ");
X			opos = strlen(obp);
X			obp += opos;
X			opos += qstrlen(name);
X		}
X		else if (!firstone)
X		{
X			(void) sprintf(obp, ", ");
X			obp += 2;
X		}
X
X		/* strip off quote bits as we output */
X		while (*name != '\0' && obp < &obuf[MAXLINE])
X		{
X			if (bitset(0200, *name))
X				*obp++ = '\\';
X			*obp++ = *name++ & ~0200;
X		}
X		firstone = FALSE;
X		*p = savechar;
X	}
X	(void) strcpy(obp, "\n");
X	putline(obuf, fp, m);
X}
X/*
X**  ISATWORD -- tell if the word we are pointing to is "at".
X**
X**	Parameters:
X**		p -- word to check.
X**
X**	Returns:
X**		TRUE -- if p is the word at.
X**		FALSE -- otherwise.
X**
X**	Side Effects:
X**		none.
X*/
X
Xbool
Xisatword(p)
X	register char *p;
X{
X	extern char lower();
X
X	if (lower(p[0]) == 'a' && lower(p[1]) == 't' &&
X	    p[2] != '\0' && isspace(p[2]))
X		return (TRUE);
X	return (FALSE);
X}
END_OF_FILE
if test 16858 -ne `wc -c <'src/headers.c'`; then
    echo shar: \"'src/headers.c'\" unpacked with wrong size!
fi
# end of 'src/headers.c'
fi
if test -f 'src/readcf.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/readcf.c'\"
else
echo shar: Extracting \"'src/readcf.c'\" \(18332 characters\)
sed "s/^X//" >'src/readcf.c' <<'END_OF_FILE'
X/*
X**  Sendmail
X**  Copyright (c) 1983  Eric P. Allman
X**  Berkeley, California
X**
X**  Copyright (c) 1983 Regents of the University of California.
X**  All rights reserved.  The Berkeley software License Agreement
X**  specifies the terms and conditions for redistribution.
X*/
X
X#ifndef lint
Xstatic char	SccsId[] = "@(#)readcf.c	5.10 (Berkeley) 1/11/86";
X#endif not lint
X
X# include "sendmail.h"
X
X/*
X**  READCF -- read control file.
X**
X**	This routine reads the control file and builds the internal
X**	form.
X**
X**	The file is formatted as a sequence of lines, each taken
X**	atomically.  The first character of each line describes how
X**	the line is to be interpreted.  The lines are:
X**		Dxval		Define macro x to have value val.
X**		Cxword		Put word into class x.
X**		Fxfile [fmt]	Read file for lines to put into
X**				class x.  Use scanf string 'fmt'
X**				or "%s" if not present.  Fmt should
X**				only produce one string-valued result.
X**		Hname: value	Define header with field-name 'name'
X**				and value as specified; this will be
X**				macro expanded immediately before
X**				use.
X**		Sn		Use rewriting set n.
X**		Rlhs rhs	Rewrite addresses that match lhs to
X**				be rhs.
X**		Mn arg=val...	Define mailer.  n is the internal name.
X**				Args specify mailer parameters.
X**		Oxvalue		Set option x to value.
X**		Pname=value	Set precedence name to value.
X**
X**	Parameters:
X**		cfname -- control file name.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		Builds several internal tables.
X*/
X
Xreadcf(cfname)
X	char *cfname;
X{
X	FILE *cf;
X	int ruleset = 0;
X	char *q;
X	char **pv;
X	struct rewrite *rwp = NULL;
X	char buf[MAXLINE];
X	register char *p;
X	extern char **prescan();
X	extern char **copyplist();
X	char exbuf[MAXLINE];
X	char pvpbuf[PSBUFSIZE];
X	extern char *fgetfolded();
X	extern char *munchstring();
X
X	cf = fopen(cfname, "r");
X	if (cf == NULL)
X	{
X		syserr("cannot open %s", cfname);
X		exit(EX_OSFILE);
X	}
X
X	FileName = cfname;
X	LineNumber = 0;
X	while (fgetfolded(buf, sizeof buf, cf) != NULL)
X	{
X		/* map $ into \001 (ASCII SOH) for macro expansion */
X		for (p = buf; *p != '\0'; p++)
X		{
X			if (*p != '$')
X				continue;
X
X			if (p[1] == '$')
X			{
X				/* actual dollar sign.... */
X				(void) strcpy(p, p + 1);
X				continue;
X			}
X
X			/* convert to macro expansion character */
X			*p = '\001';
X		}
X
X		/* interpret this line */
X		switch (buf[0])
X		{
X		  case '\0':
X		  case '#':		/* comment */
X			break;
X
X		  case 'R':		/* rewriting rule */
X			for (p = &buf[1]; *p != '\0' && *p != '\t'; p++)
X				continue;
X
X			if (*p == '\0')
X			{
X				syserr("invalid rewrite line \"%s\"", buf);
X				break;
X			}
X
X			/* allocate space for the rule header */
X			if (rwp == NULL)
X			{
X				RewriteRules[ruleset] = rwp =
X					(struct rewrite *) xalloc(sizeof *rwp);
X			}
X			else
X			{
X				rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
X				rwp = rwp->r_next;
X			}
X			rwp->r_next = NULL;
X
X			/* expand and save the LHS */
X			*p = '\0';
X			expand(&buf[1], exbuf, &exbuf[sizeof exbuf], CurEnv);
X			rwp->r_lhs = prescan(exbuf, '\t', pvpbuf);
X			if (rwp->r_lhs != NULL)
X				rwp->r_lhs = copyplist(rwp->r_lhs, TRUE);
X
X			/* expand and save the RHS */
X			while (*++p == '\t')
X				continue;
X			q = p;
X			while (*p != '\0' && *p != '\t')
X				p++;
X			*p = '\0';
X			expand(q, exbuf, &exbuf[sizeof exbuf], CurEnv);
X			rwp->r_rhs = prescan(exbuf, '\t', pvpbuf);
X			if (rwp->r_rhs != NULL)
X				rwp->r_rhs = copyplist(rwp->r_rhs, TRUE);
X			break;
X
X		  case 'S':		/* select rewriting set */
X			ruleset = atoi(&buf[1]);
X			if (ruleset >= MAXRWSETS || ruleset < 0)
X			{
X				syserr("bad ruleset %d (%d max)", ruleset, MAXRWSETS);
X				ruleset = 0;
X			}
X			rwp = NULL;
X			break;
X
X		  case 'D':		/* macro definition */
X			define(buf[1], newstr(munchstring(&buf[2])), CurEnv);
X			break;
X
X		  case 'H':		/* required header line */
X			(void) chompheader(&buf[1], TRUE);
X			break;
X
X		  case 'C':		/* word class */
X		  case 'F':		/* word class from file */
X			/* read list of words from argument or file */
X			if (buf[0] == 'F')
X			{
X				/* read from file */
X				for (p = &buf[2]; *p != '\0' && !isspace(*p); p++)
X					continue;
X				if (*p == '\0')
X					p = "%s";
X				else
X				{
X					*p = '\0';
X					while (isspace(*++p))
X						continue;
X				}
X				fileclass(buf[1], &buf[2], p);
X				break;
X			}
X
X			/* scan the list of words and set class for all */
X			for (p = &buf[2]; *p != '\0'; )
X			{
X				register char *wd;
X				char delim;
X
X				while (*p != '\0' && isspace(*p))
X					p++;
X				wd = p;
X				while (*p != '\0' && !isspace(*p))
X					p++;
X				delim = *p;
X				*p = '\0';
X				if (wd[0] != '\0')
X					setclass(buf[1], wd);
X				*p = delim;
X			}
X			break;
X
X		  case 'M':		/* define mailer */
X			makemailer(&buf[1]);
X			break;
X
X		  case 'O':		/* set option */
X			setoption(buf[1], &buf[2], TRUE, FALSE);
X			break;
X
X		  case 'P':		/* set precedence */
X			if (NumPriorities >= MAXPRIORITIES)
X			{
X				toomany('P', MAXPRIORITIES);
X				break;
X			}
X			for (p = &buf[1]; *p != '\0' && *p != '=' && *p != '\t'; p++)
X				continue;
X			if (*p == '\0')
X				goto badline;
X			*p = '\0';
X			Priorities[NumPriorities].pri_name = newstr(&buf[1]);
X			Priorities[NumPriorities].pri_val = atoi(++p);
X			NumPriorities++;
X			break;
X
X		  case 'T':		/* trusted user(s) */
X			p = &buf[1];
X			while (*p != '\0')
X			{
X				while (isspace(*p))
X					p++;
X				q = p;
X				while (*p != '\0' && !isspace(*p))
X					p++;
X				if (*p != '\0')
X					*p++ = '\0';
X				if (*q == '\0')
X					continue;
X				for (pv = TrustedUsers; *pv != NULL; pv++)
X					continue;
X				if (pv >= &TrustedUsers[MAXTRUST])
X				{
X					toomany('T', MAXTRUST);
X					break;
X				}
X				*pv = newstr(q);
X			}
X			break;
X
X		  default:
X		  badline:
X			syserr("unknown control line \"%s\"", buf);
X		}
X	}
X	FileName = NULL;
X}
X/*
X**  TOOMANY -- signal too many of some option
X**
X**	Parameters:
X**		id -- the id of the error line
X**		maxcnt -- the maximum possible values
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		gives a syserr.
X*/
X
Xtoomany(id, maxcnt)
X	char id;
X	int maxcnt;
X{
X	syserr("too many %c lines, %d max", id, maxcnt);
X}
X/*
X**  FILECLASS -- read members of a class from a file
X**
X**	Parameters:
X**		class -- class to define.
X**		filename -- name of file to read.
X**		fmt -- scanf string to use for match.
X**
X**	Returns:
X**		none
X**
X**	Side Effects:
X**
X**		puts all lines in filename that match a scanf into
X**			the named class.
X*/
X
Xfileclass(class, filename, fmt)
X	int class;
X	char *filename;
X	char *fmt;
X{
X	FILE *f;
X	char buf[MAXLINE];
X
X	f = fopen(filename, "r");
X	if (f == NULL)
X	{
X		syserr("cannot open %s", filename);
X		return;
X	}
X
X	while (fgets(buf, sizeof buf, f) != NULL)
X	{
X		register STAB *s;
X		register char *p;
X# ifdef SCANF
X		char wordbuf[MAXNAME+1];
X
X		if (sscanf(buf, fmt, wordbuf) != 1)
X			continue;
X		p = wordbuf;
X# else SCANF
X		p = buf;
X# endif SCANF
X
X		/*
X		**  Break up the match into words.
X		*/
X
X		while (*p != '\0')
X		{
X			register char *q;
X
X			/* strip leading spaces */
X			while (isspace(*p))
X				p++;
X			if (*p == '\0')
X				break;
X
X			/* find the end of the word */
X			q = p;
X			while (*p != '\0' && !isspace(*p))
X				p++;
X			if (*p != '\0')
X				*p++ = '\0';
X
X			/* enter the word in the symbol table */
X			s = stab(q, ST_CLASS, ST_ENTER);
X			setbitn(class, s->s_class);
X		}
X	}
X
X	(void) fclose(f);
X}
X/*
X**  MAKEMAILER -- define a new mailer.
X**
X**	Parameters:
X**		line -- description of mailer.  This is in labeled
X**			fields.  The fields are:
X**			   P -- the path to the mailer
X**			   F -- the flags associated with the mailer
X**			   A -- the argv for this mailer
X**			   S -- the sender rewriting set
X**			   R -- the recipient rewriting set
X**			   E -- the eol string
X**			The first word is the canonical name of the mailer.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		enters the mailer into the mailer table.
X*/
X
Xmakemailer(line)
X	char *line;
X{
X	register char *p;
X	register struct mailer *m;
X	register STAB *s;
X	int i;
X	char fcode;
X	extern int NextMailer;
X	extern char **makeargv();
X	extern char *munchstring();
X	extern char *DelimChar;
X	extern long atol();
X
X	/* allocate a mailer and set up defaults */
X	m = (struct mailer *) xalloc(sizeof *m);
X	bzero((char *) m, sizeof *m);
X	m->m_mno = NextMailer;
X	m->m_eol = "\n";
X
X	/* collect the mailer name */
X	for (p = line; *p != '\0' && *p != ',' && !isspace(*p); p++)
X		continue;
X	if (*p != '\0')
X		*p++ = '\0';
X	m->m_name = newstr(line);
X
X	/* now scan through and assign info from the fields */
X	while (*p != '\0')
X	{
X		while (*p != '\0' && (*p == ',' || isspace(*p)))
X			p++;
X
X		/* p now points to field code */
X		fcode = *p;
X		while (*p != '\0' && *p != '=' && *p != ',')
X			p++;
X		if (*p++ != '=')
X		{
X			syserr("`=' expected");
X			return;
X		}
X		while (isspace(*p))
X			p++;
X
X		/* p now points to the field body */
X		p = munchstring(p);
X
X		/* install the field into the mailer struct */
X		switch (fcode)
X		{
X		  case 'P':		/* pathname */
X			m->m_mailer = newstr(p);
X			break;
X
X		  case 'F':		/* flags */
X			for (; *p != '\0'; p++)
X				setbitn(*p, m->m_flags);
X			break;
X
X		  case 'S':		/* sender rewriting ruleset */
X		  case 'R':		/* recipient rewriting ruleset */
X			i = atoi(p);
X			if (i < 0 || i >= MAXRWSETS)
X			{
X				syserr("invalid rewrite set, %d max", MAXRWSETS);
X				return;
X			}
X			if (fcode == 'S')
X				m->m_s_rwset = i;
X			else
X				m->m_r_rwset = i;
X			break;
X
X		  case 'E':		/* end of line string */
X			m->m_eol = newstr(p);
X			break;
X
X		  case 'A':		/* argument vector */
X			m->m_argv = makeargv(p);
X			break;
X
X		  case 'M':		/* maximum message size */
X			m->m_maxsize = atol(p);
X			break;
X		}
X
X		p = DelimChar;
X	}
X
X	/* now store the mailer away */
X	if (NextMailer >= MAXMAILERS)
X	{
X		syserr("too many mailers defined (%d max)", MAXMAILERS);
X		return;
X	}
X	Mailer[NextMailer++] = m;
X	s = stab(m->m_name, ST_MAILER, ST_ENTER);
X	s->s_mailer = m;
X}
X/*
X**  MUNCHSTRING -- translate a string into internal form.
X**
X**	Parameters:
X**		p -- the string to munch.
X**
X**	Returns:
X**		the munched string.
X**
X**	Side Effects:
X**		Sets "DelimChar" to point to the string that caused us
X**		to stop.
X*/
X
Xchar *
Xmunchstring(p)
X	register char *p;
X{
X	register char *q;
X	bool backslash = FALSE;
X	bool quotemode = FALSE;
X	static char buf[MAXLINE];
X	extern char *DelimChar;
X
X	for (q = buf; *p != '\0'; p++)
X	{
X		if (backslash)
X		{
X			/* everything is roughly literal */
X			backslash = FALSE;
X			switch (*p)
X			{
X			  case 'r':		/* carriage return */
X				*q++ = '\r';
X				continue;
X
X			  case 'n':		/* newline */
X				*q++ = '\n';
X				continue;
X
X			  case 'f':		/* form feed */
X				*q++ = '\f';
X				continue;
X
X			  case 'b':		/* backspace */
X				*q++ = '\b';
X				continue;
X			}
X			*q++ = *p;
X		}
X		else
X		{
X			if (*p == '\\')
X				backslash = TRUE;
X			else if (*p == '"')
X				quotemode = !quotemode;
X			else if (quotemode || *p != ',')
X				*q++ = *p;
X			else
X				break;
X		}
X	}
X
X	DelimChar = p;
X	*q++ = '\0';
X	return (buf);
X}
X/*
X**  MAKEARGV -- break up a string into words
X**
X**	Parameters:
X**		p -- the string to break up.
X**
X**	Returns:
X**		a char **argv (dynamically allocated)
X**
X**	Side Effects:
X**		munges p.
X*/
X
Xchar **
Xmakeargv(p)
X	register char *p;
X{
X	char *q;
X	int i;
X	char **avp;
X	char *argv[MAXPV + 1];
X
X	/* take apart the words */
X	i = 0;
X	while (*p != '\0' && i < MAXPV)
X	{
X		q = p;
X		while (*p != '\0' && !isspace(*p))
X			p++;
X		while (isspace(*p))
X			*p++ = '\0';
X		argv[i++] = newstr(q);
X	}
X	argv[i++] = NULL;
X
X	/* now make a copy of the argv */
X	avp = (char **) xalloc(sizeof *avp * i);
X	bcopy((char *) argv, (char *) avp, sizeof *avp * i);
X
X	return (avp);
X}
X/*
X**  PRINTRULES -- print rewrite rules (for debugging)
X**
X**	Parameters:
X**		none.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		prints rewrite rules.
X*/
X
X# ifdef DEBUG
X
Xprintrules()
X{
X	register struct rewrite *rwp;
X	register int ruleset;
X
X	for (ruleset = 0; ruleset < 10; ruleset++)
X	{
X		if (RewriteRules[ruleset] == NULL)
X			continue;
X		printf("\n----Rule Set %d:", ruleset);
X
X		for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
X		{
X			printf("\nLHS:");
X			printav(rwp->r_lhs);
X			printf("RHS:");
X			printav(rwp->r_rhs);
X		}
X	}
X}
X
X# endif DEBUG
X/*
X**  SETOPTION -- set global processing option
X**
X**	Parameters:
X**		opt -- option name.
X**		val -- option value (as a text string).
X**		safe -- set if this came from a configuration file.
X**			Some options (if set from the command line) will
X**			reset the user id to avoid security problems.
X**		sticky -- if set, don't let other setoptions override
X**			this value.
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		Sets options as implied by the arguments.
X*/
X
Xstatic BITMAP	StickyOpt;		/* set if option is stuck */
Xextern char	*NetName;		/* name of home (local) network */
X# ifdef SMTP
X# ifdef WIZ
Xextern char	*WizWord;		/* the stored wizard password */
X# endif WIZ
X# endif SMTP
X
Xsetoption(opt, val, safe, sticky)
X	char opt;
X	char *val;
X	bool safe;
X	bool sticky;
X{
X	extern bool atobool();
X	extern time_t convtime();
X	extern int QueueLA;
X	extern int RefuseLA;
X	extern bool trusteduser();
X	extern char *username();
X
X# ifdef DEBUG
X	if (tTd(37, 1))
X		printf("setoption %c=%s", opt, val);
X# endif DEBUG
X
X	/*
X	**  See if this option is preset for us.
X	*/
X
X	if (bitnset(opt, StickyOpt))
X	{
X# ifdef DEBUG
X		if (tTd(37, 1))
X			printf(" (ignored)\n");
X# endif DEBUG
X		return;
X	}
X
X	/*
X	**  Check to see if this option can be specified by this user.
X	*/
X
X	if (!safe && getruid() == 0)
X		safe = TRUE;
X	if (!safe && index("deiLmorsv", opt) == NULL)
X	{
X# ifdef DEBUG
X		if (tTd(37, 1))
X			printf(" (unsafe)");
X# endif DEBUG
X		if (getruid() != geteuid())
X		{
X			printf("(Resetting uid)\n");
X			(void) setgid(getgid());
X			(void) setuid(getuid());
X		}
X	}
X#ifdef DEBUG
X	else if (tTd(37, 1))
X		printf("\n");
X#endif DEBUG
X
X	switch (opt)
X	{
X	  case 'A':		/* set default alias file */
X		if (val[0] == '\0')
X			AliasFile = "aliases";
X		else
X			AliasFile = newstr(val);
X		break;
X
X	  case 'a':		/* look N minutes for "@:@" in alias file */
X		if (val[0] == '\0')
X			SafeAlias = 5;
X		else
X			SafeAlias = atoi(val);
X		break;
X
X	  case 'B':		/* substitution for blank character */
X		SpaceSub = val[0];
X		if (SpaceSub == '\0')
X			SpaceSub = ' ';
X		break;
X
X	  case 'c':		/* don't connect to "expensive" mailers */
X		NoConnect = atobool(val);
X		break;
X
X	  case 'C':		/* checkpoint after N connections */
X		CheckPointLimit = atoi(val);
X		break;
X
X	  case 'd':		/* delivery mode */
X		switch (*val)
X		{
X		  case '\0':
X			SendMode = SM_DELIVER;
X			break;
X
X		  case SM_QUEUE:	/* queue only */
X#ifndef QUEUE
X			syserr("need QUEUE to set -odqueue");
X#endif QUEUE
X			/* fall through..... */
X
X		  case SM_DELIVER:	/* do everything */
X		  case SM_FORK:		/* fork after verification */
X			SendMode = *val;
X			break;
X
X		  default:
X			syserr("Unknown delivery mode %c", *val);
X			exit(EX_USAGE);
X		}
X		break;
X
X	  case 'D':		/* rebuild alias database as needed */
X		AutoRebuild = atobool(val);
X		break;
X
X	  case 'e':		/* set error processing mode */
X		switch (*val)
X		{
X		  case EM_QUIET:	/* be silent about it */
X		  case EM_MAIL:		/* mail back */
X		  case EM_BERKNET:	/* do berknet error processing */
X		  case EM_WRITE:	/* write back (or mail) */
X			HoldErrs = TRUE;
X			/* fall through... */
X
X		  case EM_PRINT:	/* print errors normally (default) */
X			ErrorMode = *val;
X			break;
X		}
X		break;
X
X	  case 'F':		/* file mode */
X		FileMode = atooct(val) & 0777;
X		break;
X
X	  case 'f':		/* save Unix-style From lines on front */
X		SaveFrom = atobool(val);
X		break;
X
X	  case 'g':		/* default gid */
X		DefGid = atoi(val);
X		break;
X
X	  case 'H':		/* help file */
X		if (val[0] == '\0')
X			HelpFile = "sendmail.hf";
X		else
X			HelpFile = newstr(val);
X		break;
X
X	  case 'i':		/* ignore dot lines in message */
X		IgnrDot = atobool(val);
X		break;
X
X	  case 'L':		/* log level */
X		LogLevel = atoi(val);
X		break;
X
X	  case 'M':		/* define macro */
X		define(val[0], newstr(&val[1]), CurEnv);
X		sticky = FALSE;
X		break;
X
X	  case 'm':		/* send to me too */
X		MeToo = atobool(val);
X		break;
X
X	  case 'n':		/* validate RHS in newaliases */
X		CheckAliases = atobool(val);
X		break;
X
X# ifdef DAEMON
X	  case 'N':		/* home (local?) network name */
X		NetName = newstr(val);
X		break;
X# endif DAEMON
X
X	  case 'o':		/* assume old style headers */
X		if (atobool(val))
X			CurEnv->e_flags |= EF_OLDSTYLE;
X		else
X			CurEnv->e_flags &= ~EF_OLDSTYLE;
X		break;
X
X	  case 'P':		/* postmaster copy address for returned mail */
X		PostMasterCopy = newstr(val);
X		break;
X
X	  case 'q':		/* slope of queue only function */
X		QueueFactor = atoi(val);
X		break;
X
X	  case 'Q':		/* queue directory */
X		if (val[0] == '\0')
X			QueueDir = "mqueue";
X		else
X			QueueDir = newstr(val);
X		break;
X
X	  case 'r':		/* read timeout */
X		ReadTimeout = convtime(val);
X		break;
X
X	  case 'S':		/* status file */
X		if (val[0] == '\0')
X			StatFile = "sendmail.st";
X		else
X			StatFile = newstr(val);
X		break;
X
X	  case 's':		/* be super safe, even if expensive */
X		SuperSafe = atobool(val);
X		break;
X
X	  case 'T':		/* queue timeout */
X		TimeOut = convtime(val);
X		break;
X
X	  case 't':		/* time zone name */
X# ifdef V6
X		StdTimezone = newstr(val);
X		DstTimezone = index(StdTimeZone, ',');
X		if (DstTimezone == NULL)
X			syserr("bad time zone spec");
X		else
X			*DstTimezone++ = '\0';
X# endif V6
X		break;
X
X	  case 'u':		/* set default uid */
X		DefUid = atoi(val);
X		break;
X
X	  case 'v':		/* run in verbose mode */
X		Verbose = atobool(val);
X		break;
X
X# ifdef SMTP
X# ifdef WIZ
X	  case 'W':		/* set the wizards password */
X		WizWord = newstr(val);
X		break;
X# endif WIZ
X# endif SMTP
X
X	  case 'x':		/* load avg at which to auto-queue msgs */
X		QueueLA = atoi(val);
X		break;
X
X	  case 'X':		/* load avg at which to auto-reject connections */
X		RefuseLA = atoi(val);
X		break;
X
X	  case 'y':		/* work recipient factor */
X		WkRecipFact = atoi(val);
X		break;
X
X	  case 'Y':		/* fork jobs during queue runs */
X		ForkQueueRuns = atobool(val);
X		break;
X
X	  case 'z':		/* work message class factor */
X		WkClassFact = atoi(val);
X		break;
X
X	  case 'Z':		/* work time factor */
X		WkTimeFact = atoi(val);
X		break;
X
X	  default:
X		break;
X	}
X	if (sticky)
X		setbitn(opt, StickyOpt);
X	return;
X}
X/*
X**  SETCLASS -- set a word into a class
X**
X**	Parameters:
X**		class -- the class to put the word in.
X**		word -- the word to enter
X**
X**	Returns:
X**		none.
X**
X**	Side Effects:
X**		puts the word into the symbol table.
X*/
X
Xsetclass(class, word)
X	int class;
X	char *word;
X{
X	register STAB *s;
X
X	s = stab(word, ST_CLASS, ST_ENTER);
X	setbitn(class, s->s_class);
X}
END_OF_FILE
if test 18332 -ne `wc -c <'src/readcf.c'`; then
    echo shar: \"'src/readcf.c'\" unpacked with wrong size!
fi
# end of 'src/readcf.c'
fi
echo shar: End of archive 5 \(of 8\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 8 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
David H. Brierley
Home: dave at galaxia.Newport.RI.US   {rayssd,xanth,lazlo,jclyde}!galaxia!dave
Work: dhb at rayssd.ray.com           {sun,decuac,gatech,necntc,ukma}!rayssd!dhb



More information about the Unix-pc.sources mailing list