view usr/src/cmd/sendmail/src/headers.c @ 3966:7aaa946a7259

6542343 upgrade sendmail to 8.14.1
author jbeck
date Wed, 04 Apr 2007 16:59:29 -0700
parents 8dfb1c11a5d7
children a17d5e4e8666
line wrap: on
line source

/*
 * Copyright (c) 1998-2004, 2006, 2007 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sendmail.h>
#include <sm/sendmail.h>

SM_RCSID("@(#)$Id: headers.c,v 8.310 2007/02/07 22:44:35 ca Exp $")

static HDR	*allocheader __P((char *, char *, int, SM_RPOOL_T *, bool));
static size_t	fix_mime_header __P((HDR *, ENVELOPE *));
static int	priencode __P((char *));
static bool	put_vanilla_header __P((HDR *, char *, MCI *));

/*
**  SETUPHEADERS -- initialize headers in symbol table
**
**	Parameters:
**		none
**
**	Returns:
**		none
*/

void
setupheaders()
{
	struct hdrinfo *hi;
	STAB *s;

	for (hi = HdrInfo; hi->hi_field != NULL; hi++)
	{
		s = stab(hi->hi_field, ST_HEADER, ST_ENTER);
		s->s_header.hi_flags = hi->hi_flags;
		s->s_header.hi_ruleset = NULL;
	}
}

/*
**  DOCHOMPHEADER -- process and save a header line.
**
**	Called by chompheader.
**
**	Parameters:
**		line -- header as a text line.
**		pflag -- flags for chompheader() (from sendmail.h)
**		hdrp -- a pointer to the place to save the header.
**		e -- the envelope including this header.
**
**	Returns:
**		flags for this header.
**
**	Side Effects:
**		The header is saved on the header list.
**		Contents of 'line' are destroyed.
*/

static struct hdrinfo	NormalHeader =	{ NULL, 0, NULL };
static unsigned long	dochompheader __P((char *, int, HDR **, ENVELOPE *));

static unsigned long
dochompheader(line, pflag, hdrp, e)
	char *line;
	int pflag;
	HDR **hdrp;
	ENVELOPE *e;
{
	unsigned char mid = '\0';
	register char *p;
	register HDR *h;
	HDR **hp;
	char *fname;
	char *fvalue;
	bool cond = false;
	bool dropfrom;
	bool headeronly;
	STAB *s;
	struct hdrinfo *hi;
	bool nullheader = false;
	BITMAP256 mopts;

	headeronly = hdrp != NULL;
	if (!headeronly)
		hdrp = &e->e_header;

	/* strip off options */
	clrbitmap(mopts);
	p = line;
	if (!bitset(pflag, CHHDR_USER) && *p == '?')
	{
		int c;
		register char *q;

		q = strchr(++p, '?');
		if (q == NULL)
			goto hse;

		*q = '\0';
		c = *p & 0377;

		/* possibly macro conditional */
		if (c == MACROEXPAND)
		{
			/* catch ?$? */
			if (*++p == '\0')
			{
				*q = '?';
				goto hse;
			}

			mid = (unsigned char) *p++;

			/* catch ?$abc? */
			if (*p != '\0')
			{
				*q = '?';
				goto hse;
			}
		}
		else if (*p == '$')
		{
			/* catch ?$? */
			if (*++p == '\0')
			{
				*q = '?';
				goto hse;
			}

			mid = (unsigned char) macid(p);
			if (bitset(0200, mid))
			{
				p += strlen(macname(mid)) + 2;
				SM_ASSERT(p <= q);
			}
			else
				p++;

			/* catch ?$abc? */
			if (*p != '\0')
			{
				*q = '?';
				goto hse;
			}
		}
		else
		{
			while (*p != '\0')
			{
				if (!isascii(*p))
				{
					*q = '?';
					goto hse;
				}

				setbitn(bitidx(*p), mopts);
				cond = true;
				p++;
			}
		}
		p = q + 1;
	}

	/* find canonical name */
	fname = p;
	while (isascii(*p) && isgraph(*p) && *p != ':')
		p++;
	fvalue = p;
	while (isascii(*p) && isspace(*p))
		p++;
	if (*p++ != ':' || fname == fvalue)
	{
hse:
		syserr("553 5.3.0 header syntax error, line \"%s\"", line);
		return 0;
	}
	*fvalue = '\0';
	fvalue = p;

	/* if the field is null, go ahead and use the default */
	while (isascii(*p) && isspace(*p))
		p++;
	if (*p == '\0')
		nullheader = true;

	/* security scan: long field names are end-of-header */
	if (strlen(fname) > 100)
		return H_EOH;

	/* check to see if it represents a ruleset call */
	if (bitset(pflag, CHHDR_DEF))
	{
		char hbuf[50];

		(void) expand(fvalue, hbuf, sizeof(hbuf), e);
		for (p = hbuf; isascii(*p) && isspace(*p); )
			p++;
		if ((*p++ & 0377) == CALLSUBR)
		{
			auto char *endp;
			bool strc;

			strc = *p == '+';	/* strip comments? */
			if (strc)
				++p;
			if (strtorwset(p, &endp, ST_ENTER) > 0)
			{
				*endp = '\0';
				s = stab(fname, ST_HEADER, ST_ENTER);
				if (LogLevel > 9 &&
				    s->s_header.hi_ruleset != NULL)
					sm_syslog(LOG_WARNING, NOQID,
						  "Warning: redefined ruleset for header=%s, old=%s, new=%s",
						  fname,
						  s->s_header.hi_ruleset, p);
				s->s_header.hi_ruleset = newstr(p);
				if (!strc)
					s->s_header.hi_flags |= H_STRIPCOMM;
			}
			return 0;
		}
	}

	/* see if it is a known type */
	s = stab(fname, ST_HEADER, ST_FIND);
	if (s != NULL)
		hi = &s->s_header;
	else
		hi = &NormalHeader;

	if (tTd(31, 9))
	{
		if (s == NULL)
			sm_dprintf("no header flags match\n");
		else
			sm_dprintf("header match, flags=%lx, ruleset=%s\n",
				   hi->hi_flags,
				   hi->hi_ruleset == NULL ? "<NULL>"
							  : hi->hi_ruleset);
	}

	/* see if this is a resent message */
	if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
	    bitset(H_RESENT, hi->hi_flags))
		e->e_flags |= EF_RESENT;

	/* if this is an Errors-To: header keep track of it now */
	if (UseErrorsTo && !bitset(pflag, CHHDR_DEF) && !headeronly &&
	    bitset(H_ERRORSTO, hi->hi_flags))
		(void) sendtolist(fvalue, NULLADDR, &e->e_errorqueue, 0, e);

	/* if this means "end of header" quit now */
	if (!headeronly && bitset(H_EOH, hi->hi_flags))
		return hi->hi_flags;

	/*
	**  Horrible hack to work around problem with Lotus Notes SMTP
	**  mail gateway, which generates From: headers with newlines in
	**  them and the <address> on the second line.  Although this is
	**  legal RFC 822, many MUAs don't handle this properly and thus
	**  never find the actual address.
	*/

	if (bitset(H_FROM, hi->hi_flags) && SingleLineFromHeader)
	{
		while ((p = strchr(fvalue, '\n')) != NULL)
			*p = ' ';
	}

	/*
	**  If there is a check ruleset, verify it against the header.
	*/

	if (bitset(pflag, CHHDR_CHECK))
	{
		int rscheckflags;
		char *rs;

		rscheckflags = RSF_COUNT;
		if (!bitset(hi->hi_flags, H_FROM|H_RCPT))
			rscheckflags |= RSF_UNSTRUCTURED;

		/* no ruleset? look for default */
		rs = hi->hi_ruleset;
		if (rs == NULL)
		{
			s = stab("*", ST_HEADER, ST_FIND);
			if (s != NULL)
			{
				rs = (&s->s_header)->hi_ruleset;
				if (bitset((&s->s_header)->hi_flags,
					   H_STRIPCOMM))
					rscheckflags |= RSF_RMCOMM;
			}
		}
		else if (bitset(hi->hi_flags, H_STRIPCOMM))
			rscheckflags |= RSF_RMCOMM;
		if (rs != NULL)
		{
			int l, k;
			char qval[MAXNAME];

			l = 0;
			qval[l++] = '"';

			/* - 3 to avoid problems with " at the end */
			/* should be sizeof(qval), not MAXNAME */
			for (k = 0; fvalue[k] != '\0' && l < MAXNAME - 3; k++)
			{
				switch (fvalue[k])
				{
				  /* XXX other control chars? */
				  case '\011': /* ht */
				  case '\012': /* nl */
				  case '\013': /* vt */
				  case '\014': /* np */
				  case '\015': /* cr */
					qval[l++] = ' ';
					break;
				  case '"':
					qval[l++] = '\\';
					/* FALLTHROUGH */
				  default:
					qval[l++] = fvalue[k];
					break;
				}
			}
			qval[l++] = '"';
			qval[l] = '\0';
			k += strlen(fvalue + k);
			if (k >= MAXNAME)
			{
				if (LogLevel > 9)
					sm_syslog(LOG_WARNING, e->e_id,
						  "Warning: truncated header '%s' before check with '%s' len=%d max=%d",
						  fname, rs, k, MAXNAME - 1);
			}
			macdefine(&e->e_macro, A_TEMP,
				macid("{currHeader}"), qval);
			macdefine(&e->e_macro, A_TEMP,
				macid("{hdr_name}"), fname);

			(void) sm_snprintf(qval, sizeof(qval), "%d", k);
			macdefine(&e->e_macro, A_TEMP, macid("{hdrlen}"), qval);
			if (bitset(H_FROM, hi->hi_flags))
				macdefine(&e->e_macro, A_PERM,
					macid("{addr_type}"), "h s");
			else if (bitset(H_RCPT, hi->hi_flags))
				macdefine(&e->e_macro, A_PERM,
					macid("{addr_type}"), "h r");
			else
				macdefine(&e->e_macro, A_PERM,
					macid("{addr_type}"), "h");
			(void) rscheck(rs, fvalue, NULL, e, rscheckflags, 3,
				       NULL, e->e_id, NULL);
		}
	}

	/*
	**  Drop explicit From: if same as what we would generate.
	**  This is to make MH (which doesn't always give a full name)
	**  insert the full name information in all circumstances.
	*/

	dropfrom = false;
	p = "resent-from";
	if (!bitset(EF_RESENT, e->e_flags))
		p += 7;
	if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
	    !bitset(EF_QUEUERUN, e->e_flags) && sm_strcasecmp(fname, p) == 0)
	{
		if (tTd(31, 2))
		{
			sm_dprintf("comparing header from (%s) against default (%s or %s)\n",
				fvalue, e->e_from.q_paddr, e->e_from.q_user);
		}
		if (e->e_from.q_paddr != NULL &&
		    e->e_from.q_mailer != NULL &&
		    bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) &&
		    (strcmp(fvalue, e->e_from.q_paddr) == 0 ||
		     strcmp(fvalue, e->e_from.q_user) == 0))
			dropfrom = true;
	}

	/* delete default value for this header */
	for (hp = hdrp; (h = *hp) != NULL; hp = &h->h_link)
	{
		if (sm_strcasecmp(fname, h->h_field) == 0 &&
		    !bitset(H_USER, h->h_flags) &&
		    !bitset(H_FORCE, h->h_flags))
		{
			if (nullheader)
			{
				/* user-supplied value was null */
				return 0;
			}
			if (dropfrom)
			{
				/* make this look like the user entered it */
				h->h_flags |= H_USER;
				return hi->hi_flags;
			}
			h->h_value = NULL;
			if (!cond)
			{
				/* copy conditions from default case */
				memmove((char *) mopts, (char *) h->h_mflags,
					sizeof(mopts));
			}
			h->h_macro = mid;
		}
	}

	/* create a new node */
	h = (HDR *) sm_rpool_malloc_x(e->e_rpool, sizeof(*h));
	h->h_field = sm_rpool_strdup_x(e->e_rpool, fname);
	h->h_value = sm_rpool_strdup_x(e->e_rpool, fvalue);
	h->h_link = NULL;
	memmove((char *) h->h_mflags, (char *) mopts, sizeof(mopts));
	h->h_macro = mid;
	*hp = h;
	h->h_flags = hi->hi_flags;
	if (bitset(pflag, CHHDR_USER) || bitset(pflag, CHHDR_QUEUE))
		h->h_flags |= H_USER;

	/* strip EOH flag if parsing MIME headers */
	if (headeronly)
		h->h_flags &= ~H_EOH;
	if (bitset(pflag, CHHDR_DEF))
		h->h_flags |= H_DEFAULT;
	if (cond || mid != '\0')
		h->h_flags |= H_CHECK;

	/* hack to see if this is a new format message */
	if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
	    bitset(H_RCPT|H_FROM, h->h_flags) &&
	    (strchr(fvalue, ',') != NULL || strchr(fvalue, '(') != NULL ||
	     strchr(fvalue, '<') != NULL || strchr(fvalue, ';') != NULL))
	{
		e->e_flags &= ~EF_OLDSTYLE;
	}

	return h->h_flags;
}

/*
**  CHOMPHEADER -- process and save a header line.
**
**	Called by collect, readcf, and readqf to deal with header lines.
**	This is just a wrapper for dochompheader().
**
**	Parameters:
**		line -- header as a text line.
**		pflag -- flags for chompheader() (from sendmail.h)
**		hdrp -- a pointer to the place to save the header.
**		e -- the envelope including this header.
**
**	Returns:
**		flags for this header.
**
**	Side Effects:
**		The header is saved on the header list.
**		Contents of 'line' are destroyed.
*/


unsigned long
chompheader(line, pflag, hdrp, e)
	char *line;
	int pflag;
	HDR **hdrp;
	register ENVELOPE *e;
{
	unsigned long rval;

	if (tTd(31, 6))
	{
		sm_dprintf("chompheader: ");
		xputs(sm_debug_file(), line);
		sm_dprintf("\n");
	}

	/* quote this if user (not config file) input */
	if (bitset(pflag, CHHDR_USER))
	{
		char xbuf[MAXLINE];
		char *xbp = NULL;
		int xbufs;

		xbufs = sizeof(xbuf);
		xbp = quote_internal_chars(line, xbuf, &xbufs);
		if (tTd(31, 7))
		{
			sm_dprintf("chompheader: quoted: ");
			xputs(sm_debug_file(), xbp);
			sm_dprintf("\n");
		}
		rval = dochompheader(xbp, pflag, hdrp, e);
		if (xbp != xbuf)
			sm_free(xbp);
	}
	else
		rval = dochompheader(line, pflag, hdrp, e);

	return rval;
}

/*
**  ALLOCHEADER -- allocate a header entry
**
**	Parameters:
**		field -- the name of the header field (will not be copied).
**		value -- the value of the field (will be copied).
**		flags -- flags to add to h_flags.
**		rp -- resource pool for allocations
**		space -- add leading space?
**
**	Returns:
**		Pointer to a newly allocated and populated HDR.
**
**	Notes:
**		o field and value must be in internal format, i.e.,
**		metacharacters must be "quoted", see quote_internal_chars().
**		o maybe add more flags to decide:
**		  - what to copy (field/value)
**		  - whether to convert value to an internal format
*/

static HDR *
allocheader(field, value, flags, rp, space)
	char *field;
	char *value;
	int flags;
	SM_RPOOL_T *rp;
	bool space;
{
	HDR *h;
	STAB *s;

	/* find info struct */
	s = stab(field, ST_HEADER, ST_FIND);

	/* allocate space for new header */
	h = (HDR *) sm_rpool_malloc_x(rp, sizeof(*h));
	h->h_field = field;
	if (space)
	{
		size_t l;
		char *n;

		l = strlen(value);
		SM_ASSERT(l + 2 > l);
		n = sm_rpool_malloc_x(rp, l + 2);
		n[0] = ' ';
		n[1] = '\0';
		sm_strlcpy(n + 1, value, l + 1);
		h->h_value = n;
	}
	else
		h->h_value = sm_rpool_strdup_x(rp, value);
	h->h_flags = flags;
	if (s != NULL)
		h->h_flags |= s->s_header.hi_flags;
	clrbitmap(h->h_mflags);
	h->h_macro = '\0';

	return h;
}

/*
**  ADDHEADER -- add a header entry to the end of the queue.
**
**	This bypasses the special checking of chompheader.
**
**	Parameters:
**		field -- the name of the header field (will not be copied).
**		value -- the value of the field (will be copied).
**		flags -- flags to add to h_flags.
**		e -- envelope.
**		space -- add leading space?
**
**	Returns:
**		none.
**
**	Side Effects:
**		adds the field on the list of headers for this envelope.
**
**	Notes: field and value must be in internal format, i.e.,
**		metacharacters must be "quoted", see quote_internal_chars().
*/

void
addheader(field, value, flags, e, space)
	char *field;
	char *value;
	int flags;
	ENVELOPE *e;
	bool space;
{
	register HDR *h;
	HDR **hp;
	HDR **hdrlist = &e->e_header;

	/* find current place in list -- keep back pointer? */
	for (hp = hdrlist; (h = *hp) != NULL; hp = &h->h_link)
	{
		if (sm_strcasecmp(field, h->h_field) == 0)
			break;
	}

	/* allocate space for new header */
	h = allocheader(field, value, flags, e->e_rpool, space);
	h->h_link = *hp;
	*hp = h;
}

/*
**  INSHEADER -- insert a header entry at the specified index
**	This bypasses the special checking of chompheader.
**
**	Parameters:
**		idx -- index into the header list at which to insert
**		field -- the name of the header field (will be copied).
**		value -- the value of the field (will be copied).
**		flags -- flags to add to h_flags.
**		e -- envelope.
**		space -- add leading space?
**
**	Returns:
**		none.
**
**	Side Effects:
**		inserts the field on the list of headers for this envelope.
**
**	Notes:
**		- field and value must be in internal format, i.e.,
**		metacharacters must be "quoted", see quote_internal_chars().
**		- the header list contains headers that might not be
**		sent "out" (see putheader(): "skip"), hence there is no
**		reliable way to insert a header at an exact position
**		(except at the front or end).
*/

void
insheader(idx, field, value, flags, e, space)
	int idx;
	char *field;
	char *value;
	int flags;
	ENVELOPE *e;
	bool space;
{
	HDR *h, *srch, *last = NULL;

	/* allocate space for new header */
	h = allocheader(field, value, flags, e->e_rpool, space);

	/* find insertion position */
	for (srch = e->e_header; srch != NULL && idx > 0;
	     srch = srch->h_link, idx--)
		last = srch;

	if (e->e_header == NULL)
	{
		e->e_header = h;
		h->h_link = NULL;
	}
	else if (srch == NULL)
	{
		SM_ASSERT(last != NULL);
		last->h_link = h;
		h->h_link = NULL;
	}
	else
	{
		h->h_link = srch->h_link;
		srch->h_link = h;
	}
}

/*
**  HVALUE -- return value of a header.
**
**	Only "real" fields (i.e., ones that have not been supplied
**	as a default) are used.
**
**	Parameters:
**		field -- the field name.
**		header -- the header list.
**
**	Returns:
**		pointer to the value part (internal format).
**		NULL if not found.
**
**	Side Effects:
**		none.
*/

char *
hvalue(field, header)
	char *field;
	HDR *header;
{
	register HDR *h;

	for (h = header; h != NULL; h = h->h_link)
	{
		if (!bitset(H_DEFAULT, h->h_flags) &&
		    sm_strcasecmp(h->h_field, field) == 0)
			return h->h_value;
	}
	return NULL;
}

/*
**  ISHEADER -- predicate telling if argument is a header.
**
**	A line is a header if it has a single word followed by
**	optional white space followed by a colon.
**
**	Header fields beginning with two dashes, although technically
**	permitted by RFC822, are automatically rejected in order
**	to make MIME work out.  Without this we could have a technically
**	legal header such as ``--"foo:bar"'' that would also be a legal
**	MIME separator.
**
**	Parameters:
**		h -- string to check for possible headerness.
**
**	Returns:
**		true if h is a header.
**		false otherwise.
**
**	Side Effects:
**		none.
*/

bool
isheader(h)
	char *h;
{
	char *s;

	s = h;
	if (s[0] == '-' && s[1] == '-')
		return false;

	while (*s > ' ' && *s != ':' && *s != '\0')
		s++;

	if (h == s)
		return false;

	/* following technically violates RFC822 */
	while (isascii(*s) && isspace(*s))
		s++;

	return (*s == ':');
}

/*
**  EATHEADER -- run through the stored header and extract info.
**
**	Parameters:
**		e -- the envelope to process.
**		full -- if set, do full processing (e.g., compute
**			message priority).  This should not be set
**			when reading a queue file because some info
**			needed to compute the priority is wrong.
**		log -- call logsender()?
**
**	Returns:
**		none.
**
**	Side Effects:
**		Sets a bunch of global variables from information
**			in the collected header.
*/

void
eatheader(e, full, log)
	register ENVELOPE *e;
	bool full;
	bool log;
{
	register HDR *h;
	register char *p;
	int hopcnt = 0;
	char buf[MAXLINE];

	/*
	**  Set up macros for possible expansion in headers.
	*/

	macdefine(&e->e_macro, A_PERM, 'f', e->e_sender);
	macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
	if (e->e_origrcpt != NULL && *e->e_origrcpt != '\0')
		macdefine(&e->e_macro, A_PERM, 'u', e->e_origrcpt);
	else
		macdefine(&e->e_macro, A_PERM, 'u', NULL);

	/* full name of from person */
	p = hvalue("full-name", e->e_header);
	if (p != NULL)
	{
		if (!rfc822_string(p))
		{
			/*
			**  Quote a full name with special characters
			**  as a comment so crackaddr() doesn't destroy
			**  the name portion of the address.
			*/

			p = addquotes(p, e->e_rpool);
		}
		macdefine(&e->e_macro, A_PERM, 'x', p);
	}

	if (tTd(32, 1))
		sm_dprintf("----- collected header -----\n");
	e->e_msgid = NULL;
	for (h = e->e_header; h != NULL; h = h->h_link)
	{
		if (tTd(32, 1))
			sm_dprintf("%s:", h->h_field);
		if (h->h_value == NULL)
		{
			if (tTd(32, 1))
				sm_dprintf("<NULL>\n");
			continue;
		}

		/* do early binding */
		if (bitset(H_DEFAULT, h->h_flags) &&
		    !bitset(H_BINDLATE, h->h_flags))
		{
			if (tTd(32, 1))
			{
				sm_dprintf("(");
				xputs(sm_debug_file(), h->h_value);
				sm_dprintf(") ");
			}
			expand(h->h_value, buf, sizeof(buf), e);
			if (buf[0] != '\0' &&
			    (buf[0] != ' ' || buf[1] != '\0'))
			{
				if (bitset(H_FROM, h->h_flags))
					expand(crackaddr(buf, e),
					       buf, sizeof(buf), e);
				h->h_value = sm_rpool_strdup_x(e->e_rpool, buf);
				h->h_flags &= ~H_DEFAULT;
			}
		}
		if (tTd(32, 1))
		{
			xputs(sm_debug_file(), h->h_value);
			sm_dprintf("\n");
		}

		/* count the number of times it has been processed */
		if (bitset(H_TRACE, h->h_flags))
			hopcnt++;

		/* send to this person if we so desire */
		if (GrabTo && bitset(H_RCPT, h->h_flags) &&
		    !bitset(H_DEFAULT, h->h_flags) &&
		    (!bitset(EF_RESENT, e->e_flags) ||
		     bitset(H_RESENT, h->h_flags)))
		{
#if 0
			int saveflags = e->e_flags;
#endif /* 0 */

			(void) sendtolist(denlstring(h->h_value, true, false),
					  NULLADDR, &e->e_sendqueue, 0, e);

#if 0
			/*
			**  Change functionality so a fatal error on an
			**  address doesn't affect the entire envelope.
			*/

			/* delete fatal errors generated by this address */
			if (!bitset(EF_FATALERRS, saveflags))
				e->e_flags &= ~EF_FATALERRS;
#endif /* 0 */
		}

		/* save the message-id for logging */
		p = "resent-message-id";
		if (!bitset(EF_RESENT, e->e_flags))
			p += 7;
		if (sm_strcasecmp(h->h_field, p) == 0)
		{
			e->e_msgid = h->h_value;
			while (isascii(*e->e_msgid) && isspace(*e->e_msgid))
				e->e_msgid++;
			macdefine(&e->e_macro, A_PERM, macid("{msg_id}"),
				  e->e_msgid);
		}
	}
	if (tTd(32, 1))
		sm_dprintf("----------------------------\n");

	/* if we are just verifying (that is, sendmail -t -bv), drop out now */
	if (OpMode == MD_VERIFY)
		return;

	/* store hop count */
	if (hopcnt > e->e_hopcount)
	{
		e->e_hopcount = hopcnt;
		(void) sm_snprintf(buf, sizeof(buf), "%d", e->e_hopcount);
		macdefine(&e->e_macro, A_TEMP, 'c', buf);
	}

	/* message priority */
	p = hvalue("precedence", e->e_header);
	if (p != NULL)
		e->e_class = priencode(p);
	if (e->e_class < 0)
		e->e_timeoutclass = TOC_NONURGENT;
	else if (e->e_class > 0)
		e->e_timeoutclass = TOC_URGENT;
	if (full)
	{
		e->e_msgpriority = e->e_msgsize
				 - e->e_class * WkClassFact
				 + e->e_nrcpts * WkRecipFact;
	}

	/* check for DSN to properly set e_timeoutclass */
	p = hvalue("content-type", e->e_header);
	if (p != NULL)
	{
		bool oldsupr;
		char **pvp;
		char pvpbuf[MAXLINE];
		extern unsigned char MimeTokenTab[256];

		/* tokenize header */
		oldsupr = SuprErrs;
		SuprErrs = true;
		pvp = prescan(p, '\0', pvpbuf, sizeof(pvpbuf), NULL,
			      MimeTokenTab, false);
		SuprErrs = oldsupr;

		/* Check if multipart/report */
		if (pvp != NULL && pvp[0] != NULL &&
		    pvp[1] != NULL && pvp[2] != NULL &&
		    sm_strcasecmp(*pvp++, "multipart") == 0 &&
		    strcmp(*pvp++, "/") == 0 &&
		    sm_strcasecmp(*pvp++, "report") == 0)
		{
			/* Look for report-type=delivery-status */
			while (*pvp != NULL)
			{
				/* skip to semicolon separator */
				while (*pvp != NULL && strcmp(*pvp, ";") != 0)
					pvp++;

				/* skip semicolon */
				if (*pvp++ == NULL || *pvp == NULL)
					break;

				/* look for report-type */
				if (sm_strcasecmp(*pvp++, "report-type") != 0)
					continue;

				/* skip equal */
				if (*pvp == NULL || strcmp(*pvp, "=") != 0)
					continue;

				/* check value */
				if (*++pvp != NULL &&
				    sm_strcasecmp(*pvp,
						  "delivery-status") == 0)
					e->e_timeoutclass = TOC_DSN;

				/* found report-type, no need to continue */
				break;
			}
		}
	}

	/* message timeout priority */
	p = hvalue("priority", e->e_header);
	if (p != NULL)
	{
		/* (this should be in the configuration file) */
		if (sm_strcasecmp(p, "urgent") == 0)
			e->e_timeoutclass = TOC_URGENT;
		else if (sm_strcasecmp(p, "normal") == 0)
			e->e_timeoutclass = TOC_NORMAL;
		else if (sm_strcasecmp(p, "non-urgent") == 0)
			e->e_timeoutclass = TOC_NONURGENT;
		else if (bitset(EF_RESPONSE, e->e_flags))
			e->e_timeoutclass = TOC_DSN;
	}
	else if (bitset(EF_RESPONSE, e->e_flags))
		e->e_timeoutclass = TOC_DSN;

	/* date message originated */
	p = hvalue("posted-date", e->e_header);
	if (p == NULL)
		p = hvalue("date", e->e_header);
	if (p != NULL)
		macdefine(&e->e_macro, A_PERM, 'a', p);

	/* check to see if this is a MIME message */
	if ((e->e_bodytype != NULL &&
	     sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0) ||
	    hvalue("MIME-Version", e->e_header) != NULL)
	{
		e->e_flags |= EF_IS_MIME;
		if (HasEightBits)
			e->e_bodytype = "8BITMIME";
	}
	else if ((p = hvalue("Content-Type", e->e_header)) != NULL)
	{
		/* this may be an RFC 1049 message */
		p = strpbrk(p, ";/");
		if (p == NULL || *p == ';')
		{
			/* yep, it is */
			e->e_flags |= EF_DONT_MIME;
		}
	}

	/*
	**  From person in antiquated ARPANET mode
	**	required by UK Grey Book e-mail gateways (sigh)
	*/

	if (OpMode == MD_ARPAFTP)
	{
		register struct hdrinfo *hi;

		for (hi = HdrInfo; hi->hi_field != NULL; hi++)
		{
			if (bitset(H_FROM, hi->hi_flags) &&
			    (!bitset(H_RESENT, hi->hi_flags) ||
			     bitset(EF_RESENT, e->e_flags)) &&
			    (p = hvalue(hi->hi_field, e->e_header)) != NULL)
				break;
		}
		if (hi->hi_field != NULL)
		{
			if (tTd(32, 2))
				sm_dprintf("eatheader: setsender(*%s == %s)\n",
					hi->hi_field, p);
			setsender(p, e, NULL, '\0', true);
		}
	}

	/*
	**  Log collection information.
	*/

	if (log && bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
	{
		logsender(e, e->e_msgid);
		e->e_flags &= ~EF_LOGSENDER;
	}
}

/*
**  LOGSENDER -- log sender information
**
**	Parameters:
**		e -- the envelope to log
**		msgid -- the message id
**
**	Returns:
**		none
*/

void
logsender(e, msgid)
	register ENVELOPE *e;
	char *msgid;
{
	char *name;
	register char *sbp;
	register char *p;
	char hbuf[MAXNAME + 1];
	char sbuf[MAXLINE + 1];
	char mbuf[MAXNAME + 1];

	/* don't allow newlines in the message-id */
	/* XXX do we still need this? sm_syslog() replaces control chars */
	if (msgid != NULL)
	{
		size_t l;

		l = strlen(msgid);
		if (l > sizeof(mbuf) - 1)
			l = sizeof(mbuf) - 1;
		memmove(mbuf, msgid, l);
		mbuf[l] = '\0';
		p = mbuf;
		while ((p = strchr(p, '\n')) != NULL)
			*p++ = ' ';
	}

	if (bitset(EF_RESPONSE, e->e_flags))
		name = "[RESPONSE]";
	else if ((name = macvalue('_', e)) != NULL)
		/* EMPTY */
		;
	else if (RealHostName == NULL)
		name = "localhost";
	else if (RealHostName[0] == '[')
		name = RealHostName;
	else
	{
		name = hbuf;
		(void) sm_snprintf(hbuf, sizeof(hbuf), "%.80s", RealHostName);
		if (RealHostAddr.sa.sa_family != 0)
		{
			p = &hbuf[strlen(hbuf)];
			(void) sm_snprintf(p, SPACELEFT(hbuf, p),
					   " (%.100s)",
					   anynet_ntoa(&RealHostAddr));
		}
	}

	/* some versions of syslog only take 5 printf args */
#if (SYSLOG_BUFSIZE) >= 256
	sbp = sbuf;
	(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
		"from=%.200s, size=%ld, class=%d, nrcpts=%d",
		e->e_from.q_paddr == NULL ? "<NONE>" : e->e_from.q_paddr,
		e->e_msgsize, e->e_class, e->e_nrcpts);
	sbp += strlen(sbp);
	if (msgid != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				", msgid=%.100s", mbuf);
		sbp += strlen(sbp);
	}
	if (e->e_bodytype != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				", bodytype=%.20s", e->e_bodytype);
		sbp += strlen(sbp);
	}
	p = macvalue('r', e);
	if (p != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				", proto=%.20s", p);
		sbp += strlen(sbp);
	}
	p = macvalue(macid("{daemon_name}"), e);
	if (p != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				", daemon=%.20s", p);
		sbp += strlen(sbp);
	}
	sm_syslog(LOG_INFO, e->e_id, "%.850s, relay=%s", sbuf, name);

#else /* (SYSLOG_BUFSIZE) >= 256 */

	sm_syslog(LOG_INFO, e->e_id,
		  "from=%s",
		  e->e_from.q_paddr == NULL ? "<NONE>"
					    : shortenstring(e->e_from.q_paddr,
							    83));
	sm_syslog(LOG_INFO, e->e_id,
		  "size=%ld, class=%ld, nrcpts=%d",
		  e->e_msgsize, e->e_class, e->e_nrcpts);
	if (msgid != NULL)
		sm_syslog(LOG_INFO, e->e_id,
			  "msgid=%s",
			  shortenstring(mbuf, 83));
	sbp = sbuf;
	*sbp = '\0';
	if (e->e_bodytype != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				"bodytype=%.20s, ", e->e_bodytype);
		sbp += strlen(sbp);
	}
	p = macvalue('r', e);
	if (p != NULL)
	{
		(void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
				"proto=%.20s, ", p);
		sbp += strlen(sbp);
	}
	sm_syslog(LOG_INFO, e->e_id,
		  "%.400srelay=%s", sbuf, name);
#endif /* (SYSLOG_BUFSIZE) >= 256 */
}

/*
**  PRIENCODE -- encode external priority names into internal values.
**
**	Parameters:
**		p -- priority in ascii.
**
**	Returns:
**		priority as a numeric level.
**
**	Side Effects:
**		none.
*/

static int
priencode(p)
	char *p;
{
	register int i;

	for (i = 0; i < NumPriorities; i++)
	{
		if (sm_strcasecmp(p, Priorities[i].pri_name) == 0)
			return Priorities[i].pri_val;
	}

	/* unknown priority */
	return 0;
}

/*
**  CRACKADDR -- parse an address and turn it into a macro
**
**	This doesn't actually parse the address -- it just extracts
**	it and replaces it with "$g".  The parse is totally ad hoc
**	and isn't even guaranteed to leave something syntactically
**	identical to what it started with.  However, it does leave
**	something semantically identical if possible, else at least
**	syntactically correct.
**
**	For example, it changes "Real Name <real@example.com> (Comment)"
**	to "Real Name <$g> (Comment)".
**
**	This algorithm has been cleaned up to handle a wider range
**	of cases -- notably quoted and backslash escaped strings.
**	This modification makes it substantially better at preserving
**	the original syntax.
**
**	Parameters:
**		addr -- the address to be cracked.
**		e -- the current envelope.
**
**	Returns:
**		a pointer to the new version.
**
**	Side Effects:
**		none.
**
**	Warning:
**		The return value is saved in local storage and should
**		be copied if it is to be reused.
*/

#define SM_HAVE_ROOM		((bp < buflim) && (buflim <= bufend))

/*
**  Append a character to bp if we have room.
**  If not, punt and return $g.
*/

#define SM_APPEND_CHAR(c)					\
	do							\
	{							\
		if (SM_HAVE_ROOM)				\
			*bp++ = (c);				\
		else						\
			goto returng;				\
	} while (0)

#if MAXNAME < 10
ERROR MAXNAME must be at least 10
#endif /* MAXNAME < 10 */

char *
crackaddr(addr, e)
	register char *addr;
	ENVELOPE *e;
{
	register char *p;
	register char c;
	int cmtlev;			/* comment level in input string */
	int realcmtlev;			/* comment level in output string */
	int anglelev;			/* angle level in input string */
	int copylev;			/* 0 == in address, >0 copying */
	int bracklev;			/* bracket level for IPv6 addr check */
	bool addangle;			/* put closing angle in output */
	bool qmode;			/* quoting in original string? */
	bool realqmode;			/* quoting in output string? */
	bool putgmac = false;		/* already wrote $g */
	bool quoteit = false;		/* need to quote next character */
	bool gotangle = false;		/* found first '<' */
	bool gotcolon = false;		/* found a ':' */
	register char *bp;
	char *buflim;
	char *bufhead;
	char *addrhead;
	char *bufend;
	static char buf[MAXNAME + 1];

	if (tTd(33, 1))
		sm_dprintf("crackaddr(%s)\n", addr);

	buflim = bufend = &buf[sizeof(buf) - 1];
	bp = bufhead = buf;

	/* skip over leading spaces but preserve them */
	while (*addr != '\0' && isascii(*addr) && isspace(*addr))
	{
		SM_APPEND_CHAR(*addr);
		addr++;
	}
	bufhead = bp;

	/*
	**  Start by assuming we have no angle brackets.  This will be
	**  adjusted later if we find them.
	*/

	p = addrhead = addr;
	copylev = anglelev = cmtlev = realcmtlev = 0;
	bracklev = 0;
	qmode = realqmode = addangle = false;

	while ((c = *p++) != '\0')
	{
		/*
		**  Try to keep legal syntax using spare buffer space
		**  (maintained by buflim).
		*/

		if (copylev > 0)
			SM_APPEND_CHAR(c);

		/* check for backslash escapes */
		if (c == '\\')
		{
			/* arrange to quote the address */
			if (cmtlev <= 0 && !qmode)
				quoteit = true;

			if ((c = *p++) == '\0')
			{
				/* too far */
				p--;
				goto putg;
			}
			if (copylev > 0)
				SM_APPEND_CHAR(c);
			goto putg;
		}

		/* check for quoted strings */
		if (c == '"' && cmtlev <= 0)
		{
			qmode = !qmode;
			if (copylev > 0 && SM_HAVE_ROOM)
			{
				if (realqmode)
					buflim--;
				else
					buflim++;
				realqmode = !realqmode;
			}
			continue;
		}
		if (qmode)
			goto putg;

		/* check for comments */
		if (c == '(')
		{
			cmtlev++;

			/* allow space for closing paren */
			if (SM_HAVE_ROOM)
			{
				buflim--;
				realcmtlev++;
				if (copylev++ <= 0)
				{
					if (bp != bufhead)
						SM_APPEND_CHAR(' ');
					SM_APPEND_CHAR(c);
				}
			}
		}
		if (cmtlev > 0)
		{
			if (c == ')')
			{
				cmtlev--;
				copylev--;
				if (SM_HAVE_ROOM)
				{
					realcmtlev--;
					buflim++;
				}
			}
			continue;
		}
		else if (c == ')')
		{
			/* syntax error: unmatched ) */
			if (copylev > 0 && SM_HAVE_ROOM && bp > bufhead)
				bp--;
		}

		/* count nesting on [ ... ] (for IPv6 domain literals) */
		if (c == '[')
			bracklev++;
		else if (c == ']')
			bracklev--;

		/* check for group: list; syntax */
		if (c == ':' && anglelev <= 0 && bracklev <= 0 &&
		    !gotcolon && !ColonOkInAddr)
		{
			register char *q;

			/*
			**  Check for DECnet phase IV ``::'' (host::user)
			**  or DECnet phase V ``:.'' syntaxes.  The latter
			**  covers ``user@DEC:.tay.myhost'' and
			**  ``DEC:.tay.myhost::user'' syntaxes (bletch).
			*/

			if (*p == ':' || *p == '.')
			{
				if (cmtlev <= 0 && !qmode)
					quoteit = true;
				if (copylev > 0)
				{
					SM_APPEND_CHAR(c);
					SM_APPEND_CHAR(*p);
				}
				p++;
				goto putg;
			}

			gotcolon = true;

			bp = bufhead;
			if (quoteit)
			{
				SM_APPEND_CHAR('"');

				/* back up over the ':' and any spaces */
				--p;
				while (p > addr &&
				       isascii(*--p) && isspace(*p))
					continue;
				p++;
			}
			for (q = addrhead; q < p; )
			{
				c = *q++;
				if (quoteit && c == '"')
					SM_APPEND_CHAR('\\');
				SM_APPEND_CHAR(c);
			}
			if (quoteit)
			{
				if (bp == &bufhead[1])
					bp--;
				else
					SM_APPEND_CHAR('"');
				while ((c = *p++) != ':')
					SM_APPEND_CHAR(c);
				SM_APPEND_CHAR(c);
			}

			/* any trailing white space is part of group: */
			while (isascii(*p) && isspace(*p))
			{
				SM_APPEND_CHAR(*p);
				p++;
			}
			copylev = 0;
			putgmac = quoteit = false;
			bufhead = bp;
			addrhead = p;
			continue;
		}

		if (c == ';' && copylev <= 0 && !ColonOkInAddr)
			SM_APPEND_CHAR(c);

		/* check for characters that may have to be quoted */
		if (strchr(MustQuoteChars, c) != NULL)
		{
			/*
			**  If these occur as the phrase part of a <>
			**  construct, but are not inside of () or already
			**  quoted, they will have to be quoted.  Note that
			**  now (but don't actually do the quoting).
			*/

			if (cmtlev <= 0 && !qmode)
				quoteit = true;
		}

		/* check for angle brackets */
		if (c == '<')
		{
			register char *q;

			/* assume first of two angles is bogus */
			if (gotangle)
				quoteit = true;
			gotangle = true;

			/* oops -- have to change our mind */
			anglelev = 1;
			if (SM_HAVE_ROOM)
			{
				if (!addangle)
					buflim--;
				addangle = true;
			}

			bp = bufhead;
			if (quoteit)
			{
				SM_APPEND_CHAR('"');

				/* back up over the '<' and any spaces */
				--p;
				while (p > addr &&
				       isascii(*--p) && isspace(*p))
					continue;
				p++;
			}
			for (q = addrhead; q < p; )
			{
				c = *q++;
				if (quoteit && c == '"')
				{
					SM_APPEND_CHAR('\\');
					SM_APPEND_CHAR(c);
				}
				else
					SM_APPEND_CHAR(c);
			}
			if (quoteit)
			{
				if (bp == &buf[1])
					bp--;
				else
					SM_APPEND_CHAR('"');
				while ((c = *p++) != '<')
					SM_APPEND_CHAR(c);
				SM_APPEND_CHAR(c);
			}
			copylev = 0;
			putgmac = quoteit = false;
			continue;
		}

		if (c == '>')
		{
			if (anglelev > 0)
			{
				anglelev--;
				if (SM_HAVE_ROOM)
				{
					if (addangle)
						buflim++;
					addangle = false;
				}
			}
			else if (SM_HAVE_ROOM)
			{
				/* syntax error: unmatched > */
				if (copylev > 0 && bp > bufhead)
					bp--;
				quoteit = true;
				continue;
			}
			if (copylev++ <= 0)
				SM_APPEND_CHAR(c);
			continue;
		}

		/* must be a real address character */
	putg:
		if (copylev <= 0 && !putgmac)
		{
			if (bp > buf && bp[-1] == ')')
				SM_APPEND_CHAR(' ');
			SM_APPEND_CHAR(MACROEXPAND);
			SM_APPEND_CHAR('g');
			putgmac = true;
		}
	}

	/* repair any syntactic damage */
	if (realqmode && bp < bufend)
		*bp++ = '"';
	while (realcmtlev-- > 0 && bp < bufend)
		*bp++ = ')';
	if (addangle && bp < bufend)
		*bp++ = '>';
	*bp = '\0';
	if (bp < bufend)
		goto success;

 returng:
	/* String too long, punt */
	buf[0] = '<';
	buf[1] = MACROEXPAND;
	buf[2]= 'g';
	buf[3] = '>';
	buf[4]= '\0';
	sm_syslog(LOG_ALERT, e->e_id,
		  "Dropped invalid comments from header address");

 success:
	if (tTd(33, 1))
	{
		sm_dprintf("crackaddr=>`");
		xputs(sm_debug_file(), buf);
		sm_dprintf("'\n");
	}
	return buf;
}

/*
**  PUTHEADER -- put the header part of a message from the in-core copy
**
**	Parameters:
**		mci -- the connection information.
**		hdr -- the header to put.
**		e -- envelope to use.
**		flags -- MIME conversion flags.
**
**	Returns:
**		true iff header part was written successfully
**
**	Side Effects:
**		none.
*/

bool
putheader(mci, hdr, e, flags)
	register MCI *mci;
	HDR *hdr;
	register ENVELOPE *e;
	int flags;
{
	register HDR *h;
	char buf[SM_MAX(MAXLINE,BUFSIZ)];
	char obuf[MAXLINE];

	if (tTd(34, 1))
		sm_dprintf("--- putheader, mailer = %s ---\n",
			mci->mci_mailer->m_name);

	/*
	**  If we're in MIME mode, we're not really in the header of the
	**  message, just the header of one of the parts of the body of
	**  the message.  Therefore MCIF_INHEADER should not be turned on.
	*/

	if (!bitset(MCIF_INMIME, mci->mci_flags))
		mci->mci_flags |= MCIF_INHEADER;

	for (h = hdr; h != NULL; h = h->h_link)
	{
		register char *p = h->h_value;
		char *q;

		if (tTd(34, 11))
		{
			sm_dprintf("  %s:", h->h_field);
			xputs(sm_debug_file(), p);
		}

		/* Skip empty headers */
		if (h->h_value == NULL)
			continue;

		/* heuristic shortening of MIME fields to avoid MUA overflows */
		if (MaxMimeFieldLength > 0 &&
		    wordinclass(h->h_field,
				macid("{checkMIMEFieldHeaders}")))
		{
			size_t len;

			len = fix_mime_header(h, e);
			if (len > 0)
			{
				sm_syslog(LOG_ALERT, e->e_id,
					  "Truncated MIME %s header due to field size (length = %ld) (possible attack)",
					  h->h_field, (unsigned long) len);
				if (tTd(34, 11))
					sm_dprintf("  truncated MIME %s header due to field size  (length = %ld) (possible attack)\n",
						   h->h_field,
						   (unsigned long) len);
			}
		}

		if (MaxMimeHeaderLength > 0 &&
		    wordinclass(h->h_field,
				macid("{checkMIMETextHeaders}")))
		{
			size_t len;

			len = strlen(h->h_value);
			if (len > (size_t) MaxMimeHeaderLength)
			{
				h->h_value[MaxMimeHeaderLength - 1] = '\0';
				sm_syslog(LOG_ALERT, e->e_id,
					  "Truncated long MIME %s header (length = %ld) (possible attack)",
					  h->h_field, (unsigned long) len);
				if (tTd(34, 11))
					sm_dprintf("  truncated long MIME %s header (length = %ld) (possible attack)\n",
						   h->h_field,
						   (unsigned long) len);
			}
		}

		if (MaxMimeHeaderLength > 0 &&
		    wordinclass(h->h_field,
				macid("{checkMIMEHeaders}")))
		{
			size_t len;

			len = strlen(h->h_value);
			if (shorten_rfc822_string(h->h_value,
						  MaxMimeHeaderLength))
			{
				if (len < MaxMimeHeaderLength)
				{
					/* we only rebalanced a bogus header */
					sm_syslog(LOG_ALERT, e->e_id,
						  "Fixed MIME %s header (possible attack)",
						  h->h_field);
					if (tTd(34, 11))
						sm_dprintf("  fixed MIME %s header (possible attack)\n",
							   h->h_field);
				}
				else
				{
					/* we actually shortened header */
					sm_syslog(LOG_ALERT, e->e_id,
						  "Truncated long MIME %s header (length = %ld) (possible attack)",
						  h->h_field,
						  (unsigned long) len);
					if (tTd(34, 11))
						sm_dprintf("  truncated long MIME %s header (length = %ld) (possible attack)\n",
							   h->h_field,
							   (unsigned long) len);
				}
			}
		}

		/*
		**  Suppress Content-Transfer-Encoding: if we are MIMEing
		**  and we are potentially converting from 8 bit to 7 bit
		**  MIME.  If converting, add a new CTE header in
		**  mime8to7().
		*/

		if (bitset(H_CTE, h->h_flags) &&
		    bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
			   mci->mci_flags) &&
		    !bitset(M87F_NO8TO7, flags))
		{
			if (tTd(34, 11))
				sm_dprintf(" (skipped (content-transfer-encoding))\n");
			continue;
		}

		if (bitset(MCIF_INMIME, mci->mci_flags))
		{
			if (tTd(34, 11))
				sm_dprintf("\n");
			if (!put_vanilla_header(h, p, mci))
				goto writeerr;
			continue;
		}

		if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
		    !bitintersect(h->h_mflags, mci->mci_mailer->m_flags) &&
		    (h->h_macro == '\0' ||
		     (q = macvalue(bitidx(h->h_macro), e)) == NULL ||
		     *q == '\0'))
		{
			if (tTd(34, 11))
				sm_dprintf(" (skipped)\n");
			continue;
		}

		/* handle Resent-... headers specially */
		if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
		{
			if (tTd(34, 11))
				sm_dprintf(" (skipped (resent))\n");
			continue;
		}

		/* suppress return receipts if requested */
		if (bitset(H_RECEIPTTO, h->h_flags) &&
		    (RrtImpliesDsn || bitset(EF_NORECEIPT, e->e_flags)))
		{
			if (tTd(34, 11))
				sm_dprintf(" (skipped (receipt))\n");
			continue;
		}

		/* macro expand value if generated internally */
		if (bitset(H_DEFAULT, h->h_flags) ||
		    bitset(H_BINDLATE, h->h_flags))
		{
			expand(p, buf, sizeof(buf), e);
			p = buf;
			if (*p == '\0')
			{
				if (tTd(34, 11))
					sm_dprintf(" (skipped -- null value)\n");
				continue;
			}
		}

		if (bitset(H_BCC, h->h_flags))
		{
			/* Bcc: field -- either truncate or delete */
			if (bitset(EF_DELETE_BCC, e->e_flags))
			{
				if (tTd(34, 11))
					sm_dprintf(" (skipped -- bcc)\n");
			}
			else
			{
				/* no other recipient headers: truncate value */
				(void) sm_strlcpyn(obuf, sizeof(obuf), 2,
						   h->h_field, ":");
				if (!putline(obuf, mci))
					goto writeerr;
			}
			continue;
		}

		if (tTd(34, 11))
			sm_dprintf("\n");

		if (bitset(H_FROM|H_RCPT, h->h_flags))
		{
			/* address field */
			bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);

			if (bitset(H_FROM, h->h_flags))
				oldstyle = false;
			commaize(h, p, oldstyle, mci, e);
		}
		else
		{
			if (!put_vanilla_header(h, p, mci))
				goto writeerr;
		}
	}

	/*
	**  If we are converting this to a MIME message, add the
	**  MIME headers (but not in MIME mode!).
	*/

#if MIME8TO7
	if (bitset(MM_MIME8BIT, MimeMode) &&
	    bitset(EF_HAS8BIT, e->e_flags) &&
	    !bitset(EF_DONT_MIME, e->e_flags) &&
	    !bitnset(M_8BITS, mci->mci_mailer->m_flags) &&
	    !bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME, mci->mci_flags) &&
	    hvalue("MIME-Version", e->e_header) == NULL)
	{
		if (!putline("MIME-Version: 1.0", mci))
			goto writeerr;
		if (hvalue("Content-Type", e->e_header) == NULL)
		{
			(void) sm_snprintf(obuf, sizeof(obuf),
					"Content-Type: text/plain; charset=%s",
					defcharset(e));
			if (!putline(obuf, mci))
				goto writeerr;
		}
		if (hvalue("Content-Transfer-Encoding", e->e_header) == NULL
		    && !putline("Content-Transfer-Encoding: 8bit", mci))
			goto writeerr;
	}
#endif /* MIME8TO7 */
	return true;

  writeerr:
	return false;
}

/*
**  PUT_VANILLA_HEADER -- output a fairly ordinary header
**
**	Parameters:
**		h -- the structure describing this header
**		v -- the value of this header
**		mci -- the connection info for output
**
**	Returns:
**		true iff header was written successfully
*/

static bool
put_vanilla_header(h, v, mci)
	HDR *h;
	char *v;
	MCI *mci;
{
	register char *nlp;
	register char *obp;
	int putflags;
	char obuf[MAXLINE + 256];	/* additional length for h_field */

	putflags = PXLF_HEADER | PXLF_STRIPMQUOTE;
	if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
		putflags |= PXLF_STRIP8BIT;
	(void) sm_snprintf(obuf, sizeof(obuf), "%.200s:", h->h_field);
	obp = obuf + strlen(obuf);
	while ((nlp = strchr(v, '\n')) != NULL)
	{
		int l;

		l = nlp - v;

		/*
		**  XXX This is broken for SPACELEFT()==0
		**  However, SPACELEFT() is always > 0 unless MAXLINE==1.
		*/

		if (SPACELEFT(obuf, obp) - 1 < (size_t) l)
			l = SPACELEFT(obuf, obp) - 1;

		(void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s", l, v);
		if (!putxline(obuf, strlen(obuf), mci, putflags))
			goto writeerr;
		v += l + 1;
		obp = obuf;
		if (*v != ' ' && *v != '\t')
			*obp++ = ' ';
	}

	/* XXX This is broken for SPACELEFT()==0 */
	(void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s",
			   (int) (SPACELEFT(obuf, obp) - 1), v);
	return putxline(obuf, strlen(obuf), mci, putflags);

  writeerr:
	return false;
}

/*
**  COMMAIZE -- output a header field, making a comma-translated list.
**
**	Parameters:
**		h -- the header field to output.
**		p -- the value to put in it.
**		oldstyle -- true if this is an old style header.
**		mci -- the connection information.
**		e -- the envelope containing the message.
**
**	Returns:
**		true iff header field was written successfully
**
**	Side Effects:
**		outputs "p" to "mci".
*/

bool
commaize(h, p, oldstyle, mci, e)
	register HDR *h;
	register char *p;
	bool oldstyle;
	register MCI *mci;
	register ENVELOPE *e;
{
	register char *obp;
	int opos, omax, spaces;
	bool firstone = true;
	int putflags = PXLF_HEADER | PXLF_STRIPMQUOTE;
	char **res;
	char obuf[MAXLINE + 3];

	/*
	**  Output the address list translated by the
	**  mailer and with commas.
	*/

	if (tTd(14, 2))
		sm_dprintf("commaize(%s:%s)\n", h->h_field, p);

	if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
		putflags |= PXLF_STRIP8BIT;

	obp = obuf;
	(void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.200s:", h->h_field);
	/* opos = strlen(obp); instead of the next 3 lines? */
	opos = strlen(h->h_field) + 1;
	if (opos > 201)
		opos = 201;
	obp += opos;

	spaces = 0;
	while (*p != '\0' && isascii(*p) && isspace(*p))
	{
		++spaces;
		++p;
	}
	if (spaces > 0)
	{
		SM_ASSERT(sizeof(obuf) > opos  * 2);

		/*
		**  Restrict number of spaces to half the length of buffer
		**  so the header field body can be put in here too.
		**  Note: this is a hack...
		*/

		if (spaces > sizeof(obuf) / 2)
			spaces = sizeof(obuf) / 2;
		(void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%*s", spaces,
				"");
		opos += spaces;
		obp += spaces;
		SM_ASSERT(obp < &obuf[MAXLINE]);
	}

	omax = mci->mci_mailer->m_linelimit - 2;
	if (omax < 0 || omax > 78)
		omax = 78;

	/*
	**  Run through the list of values.
	*/

	while (*p != '\0')
	{
		register char *name;
		register int c;
		char savechar;
		int flags;
		auto int status;

		/*
		**  Find the end of the name.  New style names
		**  end with a comma, old style names end with
		**  a space character.  However, spaces do not
		**  necessarily delimit an old-style name -- at
		**  signs mean keep going.
		*/

		/* find end of name */
		while ((isascii(*p) && isspace(*p)) || *p == ',')
			p++;
		name = p;
		res = NULL;
		for (;;)
		{
			auto char *oldp;
			char pvpbuf[PSBUFSIZE];

			res = prescan(p, oldstyle ? ' ' : ',', pvpbuf,
				      sizeof(pvpbuf), &oldp, ExtTokenTab, false);
			p = oldp;
#if _FFR_IGNORE_BOGUS_ADDR
			/* ignore addresses that can't be parsed */
			if (res == NULL)
			{
				name = p;
				continue;
			}
#endif /* _FFR_IGNORE_BOGUS_ADDR */

			/* look to see if we have an at sign */
			while (*p != '\0' && isascii(*p) && isspace(*p))
				p++;

			if (*p != '@')
			{
				p = oldp;
				break;
			}
			++p;
			while (*p != '\0' && isascii(*p) && isspace(*p))
				p++;
		}
		/* at the end of one complete name */

		/* strip off trailing white space */
		while (p >= name &&
		       ((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
			p--;
		if (++p == name)
			continue;

		/*
		**  if prescan() failed go a bit backwards; this is a hack,
		**  there should be some better error recovery.
		*/

		if (res == NULL && p > name &&
		    !((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
			--p;
		savechar = *p;
		*p = '\0';

		/* translate the name to be relative */
		flags = RF_HEADERADDR|RF_ADDDOMAIN;
		if (bitset(H_FROM, h->h_flags))
			flags |= RF_SENDERADDR;
#if USERDB
		else if (e->e_from.q_mailer != NULL &&
			 bitnset(M_UDBRECIPIENT, e->e_from.q_mailer->m_flags))
		{
			char *q;

			q = udbsender(name, e->e_rpool);
			if (q != NULL)
				name = q;
		}
#endif /* USERDB */
		status = EX_OK;
		name = remotename(name, mci->mci_mailer, flags, &status, e);
		if (*name == '\0')
		{
			*p = savechar;
			continue;
		}
		name = denlstring(name, false, true);

		/* output the name with nice formatting */
		opos += strlen(name);
		if (!firstone)
			opos += 2;
		if (opos > omax && !firstone)
		{
			(void) sm_strlcpy(obp, ",\n", SPACELEFT(obuf, obp));
			if (!putxline(obuf, strlen(obuf), mci, putflags))
				goto writeerr;
			obp = obuf;
			(void) sm_strlcpy(obp, "        ", sizeof(obuf));
			opos = strlen(obp);
			obp += opos;
			opos += strlen(name);
		}
		else if (!firstone)
		{
			(void) sm_strlcpy(obp, ", ", SPACELEFT(obuf, obp));
			obp += 2;
		}

		while ((c = *name++) != '\0' && obp < &obuf[MAXLINE])
			*obp++ = c;
		firstone = false;
		*p = savechar;
	}
	if (obp < &obuf[sizeof(obuf)])
		*obp = '\0';
	else
		obuf[sizeof(obuf) - 1] = '\0';
	return putxline(obuf, strlen(obuf), mci, putflags);

  writeerr:
	return false;
}

/*
**  COPYHEADER -- copy header list
**
**	This routine is the equivalent of newstr for header lists
**
**	Parameters:
**		header -- list of header structures to copy.
**		rpool -- resource pool, or NULL
**
**	Returns:
**		a copy of 'header'.
**
**	Side Effects:
**		none.
*/

HDR *
copyheader(header, rpool)
	register HDR *header;
	SM_RPOOL_T *rpool;
{
	register HDR *newhdr;
	HDR *ret;
	register HDR **tail = &ret;

	while (header != NULL)
	{
		newhdr = (HDR *) sm_rpool_malloc_x(rpool, sizeof(*newhdr));
		STRUCTCOPY(*header, *newhdr);
		*tail = newhdr;
		tail = &newhdr->h_link;
		header = header->h_link;
	}
	*tail = NULL;

	return ret;
}

/*
**  FIX_MIME_HEADER -- possibly truncate/rebalance parameters in a MIME header
**
**	Run through all of the parameters of a MIME header and
**	possibly truncate and rebalance the parameter according
**	to MaxMimeFieldLength.
**
**	Parameters:
**		h -- the header to truncate/rebalance
**		e -- the current envelope
**
**	Returns:
**		length of last offending field, 0 if all ok.
**
**	Side Effects:
**		string modified in place
*/

static size_t
fix_mime_header(h, e)
	HDR *h;
	ENVELOPE *e;
{
	char *begin = h->h_value;
	char *end;
	size_t len = 0;
	size_t retlen = 0;

	if (begin == NULL || *begin == '\0')
		return 0;

	/* Split on each ';' */
	/* find_character() never returns NULL */
	while ((end = find_character(begin, ';')) != NULL)
	{
		char save = *end;
		char *bp;

		*end = '\0';

		len = strlen(begin);

		/* Shorten individual parameter */
		if (shorten_rfc822_string(begin, MaxMimeFieldLength))
		{
			if (len < MaxMimeFieldLength)
			{
				/* we only rebalanced a bogus field */
				sm_syslog(LOG_ALERT, e->e_id,
					  "Fixed MIME %s header field (possible attack)",
					  h->h_field);
				if (tTd(34, 11))
					sm_dprintf("  fixed MIME %s header field (possible attack)\n",
						   h->h_field);
			}
			else
			{
				/* we actually shortened the header */
				retlen = len;
			}
		}

		/* Collapse the possibly shortened string with rest */
		bp = begin + strlen(begin);
		if (bp != end)
		{
			char *ep = end;

			*end = save;
			end = bp;

			/* copy character by character due to overlap */
			while (*ep != '\0')
				*bp++ = *ep++;
			*bp = '\0';
		}
		else
			*end = save;
		if (*end == '\0')
			break;

		/* Move past ';' */
		begin = end + 1;
	}
	return retlen;
}