/*
 * Copyright (c) 1998-2000 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.
 *
 */

#include <sendmail.h>

#ifndef lint
# if SMTP
static char id[] = "@(#)$Id: usersmtp.c,v 1.1.1.1 2000/06/10 00:40:56 wsanchez Exp $ (with SMTP)";
# else /* SMTP */
static char id[] = "@(#)$Id: usersmtp.c,v 1.1.1.1 2000/06/10 00:40:56 wsanchez Exp $ (without SMTP)";
# endif /* SMTP */
#endif /* ! lint */

#include <sysexits.h>

#if SMTP

static void	datatimeout __P((void));
static void	esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static void	helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));

/*
**  USERSMTP -- run SMTP protocol from the user end.
**
**	This protocol is described in RFC821.
*/

# define REPLYTYPE(r)	((r) / 100)		/* first digit of reply code */
# define REPLYCLASS(r)	(((r) / 10) % 10)	/* second digit of reply code */
# define SMTPCLOSING	421			/* "Service Shutting Down" */

#define ENHSCN(e, d)	(e) == NULL ? (d) : newstr(e)

static char	SmtpMsgBuffer[MAXLINE];		/* buffer for commands */
static char	SmtpReplyBuffer[MAXLINE];	/* buffer for replies */
static bool	SmtpNeedIntro;		/* need "while talking" in transcript */
/*
**  SMTPINIT -- initialize SMTP.
**
**	Opens the connection and sends the initial protocol.
**
**	Parameters:
**		m -- mailer to create connection to.
**		mci -- the mailer connection info.
**		e -- the envelope.
**		onlyhelo -- send only helo command?
**
**	Returns:
**		none.
**
**	Side Effects:
**		creates connection and sends initial protocol.
*/

void
smtpinit(m, mci, e, onlyhelo)
	MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
	bool onlyhelo;
{
	register int r;
	register char *p;
	register char *hn;
	char *enhsc;

	enhsc = NULL;
	if (tTd(18, 1))
	{
		dprintf("smtpinit ");
		mci_dump(mci, FALSE);
	}

	/*
	**  Open the connection to the mailer.
	*/

	SmtpError[0] = '\0';
	CurHostName = mci->mci_host;		/* XXX UGLY XXX */
	if (CurHostName == NULL)
		CurHostName = MyHostName;
	SmtpNeedIntro = TRUE;
	switch (mci->mci_state)
	{
	  case MCIS_ACTIVE:
		/* need to clear old information */
		smtprset(m, mci, e);
		/* FALLTHROUGH */

	  case MCIS_OPEN:
		if (!onlyhelo)
			return;
		break;

	  case MCIS_ERROR:
	  case MCIS_QUITING:
	  case MCIS_SSD:
		/* shouldn't happen */
		smtpquit(m, mci, e);
		/* FALLTHROUGH */

	  case MCIS_CLOSED:
		syserr("451 4.4.0 smtpinit: state CLOSED");
		return;

	  case MCIS_OPENING:
		break;
	}
	if (onlyhelo)
		goto helo;

	mci->mci_state = MCIS_OPENING;

	/*
	**  Get the greeting message.
	**	This should appear spontaneously.  Give it five minutes to
	**	happen.
	*/

	SmtpPhase = mci->mci_phase = "client greeting";
	sm_setproctitle(TRUE, e, "%s %s: %s",
			qid_printname(e), CurHostName, mci->mci_phase);
	r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL);
	if (r < 0)
		goto tempfail1;
	if (REPLYTYPE(r) == 4)
		goto tempfail2;
	if (REPLYTYPE(r) != 2)
		goto unavailable;

	/*
	**  Send the HELO command.
	**	My mother taught me to always introduce myself.
	*/

helo:
	if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
		mci->mci_flags |= MCIF_ESMTP;
	hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;

tryhelo:
	if (bitnset(M_LMTP, m->m_flags))
	{
		smtpmessage("LHLO %s", m, mci, hn);
		SmtpPhase = mci->mci_phase = "client LHLO";
	}
	else if (bitset(MCIF_ESMTP, mci->mci_flags))
	{
		smtpmessage("EHLO %s", m, mci, hn);
		SmtpPhase = mci->mci_phase = "client EHLO";
	}
	else
	{
		smtpmessage("HELO %s", m, mci, hn);
		SmtpPhase = mci->mci_phase = "client HELO";
	}
	sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
			CurHostName, mci->mci_phase);
	r = reply(m, mci, e, TimeOuts.to_helo, helo_options, NULL);
	if (r < 0)
		goto tempfail1;
	else if (REPLYTYPE(r) == 5)
	{
		if (bitset(MCIF_ESMTP, mci->mci_flags) &&
		    !bitnset(M_LMTP, m->m_flags))
		{
			/* try old SMTP instead */
			mci->mci_flags &= ~MCIF_ESMTP;
			goto tryhelo;
		}
		goto unavailable;
	}
	else if (REPLYTYPE(r) != 2)
		goto tempfail2;

	/*
	**  Check to see if we actually ended up talking to ourself.
	**  This means we didn't know about an alias or MX, or we managed
	**  to connect to an echo server.
	*/

	p = strchr(&SmtpReplyBuffer[4], ' ');
	if (p != NULL)
		*p = '\0';
	if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
	    !bitnset(M_LMTP, m->m_flags) &&
	    strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
	{
		syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
			CurHostName);
		mci_setstat(mci, EX_CONFIG, "5.3.5", "system config error");
		mci->mci_errno = 0;
		smtpquit(m, mci, e);
		return;
	}

	/*
	**  If this is expected to be another sendmail, send some internal
	**  commands.
	*/

	if (bitnset(M_INTERNAL, m->m_flags))
	{
		/* tell it to be verbose */
		smtpmessage("VERB", m, mci);
		r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc);
		if (r < 0)
			goto tempfail1;
	}

	if (mci->mci_state != MCIS_CLOSED)
	{
		mci->mci_state = MCIS_OPEN;
		return;
	}

	/* got a 421 error code during startup */

  tempfail1:
	if (mci->mci_errno == 0)
		mci->mci_errno = errno;
	mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
	if (mci->mci_state != MCIS_CLOSED)
		smtpquit(m, mci, e);
	return;

  tempfail2:
	if (mci->mci_errno == 0)
		mci->mci_errno = errno;
	/* XXX should use code from other end iff ENHANCEDSTATUSCODES */
	mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
		    SmtpReplyBuffer);
	if (mci->mci_state != MCIS_CLOSED)
		smtpquit(m, mci, e);
	return;

  unavailable:
	mci->mci_errno = errno;
	mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
	smtpquit(m, mci, e);
	return;
}
/*
**  ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
**
**	Parameters:
**		line -- the response line.
**		firstline -- set if this is the first line of the reply.
**		m -- the mailer.
**		mci -- the mailer connection info.
**		e -- the envelope.
**
**	Returns:
**		none.
*/

static void
esmtp_check(line, firstline, m, mci, e)
	char *line;
	bool firstline;
	MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
{
	if (strstr(line, "ESMTP") != NULL)
		mci->mci_flags |= MCIF_ESMTP;
	if (strstr(line, "8BIT-OK") != NULL)
		mci->mci_flags |= MCIF_8BITOK;
}
/*
**  HELO_OPTIONS -- process the options on a HELO line.
**
**	Parameters:
**		line -- the response line.
**		firstline -- set if this is the first line of the reply.
**		m -- the mailer.
**		mci -- the mailer connection info.
**		e -- the envelope.
**
**	Returns:
**		none.
*/

static void
helo_options(line, firstline, m, mci, e)
	char *line;
	bool firstline;
	MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
{
	register char *p;

	if (firstline)
		return;

	if (strlen(line) < (SIZE_T) 5)
		return;
	line += 4;
	p = strpbrk(line, " =");
	if (p != NULL)
		*p++ = '\0';
	if (strcasecmp(line, "size") == 0)
	{
		mci->mci_flags |= MCIF_SIZE;
		if (p != NULL)
			mci->mci_maxsize = atol(p);
	}
	else if (strcasecmp(line, "8bitmime") == 0)
	{
		mci->mci_flags |= MCIF_8BITMIME;
		mci->mci_flags &= ~MCIF_7BIT;
	}
	else if (strcasecmp(line, "expn") == 0)
		mci->mci_flags |= MCIF_EXPN;
	else if (strcasecmp(line, "dsn") == 0)
		mci->mci_flags |= MCIF_DSN;
	else if (strcasecmp(line, "enhancedstatuscodes") == 0)
		mci->mci_flags |= MCIF_ENHSTAT;
# if SASL
	else if (strcasecmp(line, "auth") == 0)
	{
		if (p == NULL || *p == '\0')
		{
			/* no parameter? */
			mci->mci_saslcap = NULL;
		}
		else
		{
			int l;

			if (mci->mci_saslcap != NULL)
				free(mci->mci_saslcap);
			l = strlen(p) + 1;
			mci->mci_saslcap = (char *)malloc(l);

			/* XXX this may be leaked */
			if (mci->mci_saslcap != NULL)
			{
				(void) strlcpy(mci->mci_saslcap, p, l);
				mci->mci_flags |= MCIF_AUTH;
			}
		}
	}
# endif /* SASL */
}
# if SASL

/*
**  GETSASLDATA -- process the challenges from the SASL protocol
**
**	This gets the relevant sasl response data out of the reply
**	from the server
**
**	Parameters:
**		line -- the response line.
**		firstline -- set if this is the first line of the reply.
**		m -- the mailer.
**		mci -- the mailer connection info.
**		e -- the envelope.
**
**	Returns:
**		none.
*/

void
getsasldata(line, firstline, m, mci, e)
	char *line;
	bool firstline;
	MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
{
	int len;
	char *out;
	int result;

	/* if not a continue we don't care about it */
	if ((strlen(line) <= 4) ||
	    (line[0] != '3') ||
	    (line[1] != '3') ||
	    (line[2] != '4'))
	{
		mci->mci_sasl_string = NULL;
		return;
	}

	/* forget about "334 " */
	line += 4;
	len = strlen(line);

	out = xalloc(len + 1);
	result = sasl_decode64(line, len, out, (u_int *)&len);
	if (result != SASL_OK)
	{
		len = 0;
		*out = '\0';
	}
	if (mci->mci_sasl_string != NULL)
	{
		if (mci->mci_sasl_string_len <= len)
		{
			free(mci->mci_sasl_string);
			mci->mci_sasl_string = xalloc(len + 1);
		}
	}
	else
		mci->mci_sasl_string = xalloc(len + 1);
	/* XXX this is probably leaked */
	memcpy(mci->mci_sasl_string, out, len);
	mci->mci_sasl_string[len] = '\0';
	mci->mci_sasl_string_len = len;
	free(out);
	return;
}

/*
**  READAUTH -- read auth value from a file
**
**	Parameters:
**		l -- line to define.
**		filename -- name of file to read.
**		safe -- if set, this is a safe read.
**
**	Returns:
**		line from file
**
**	Side Effects:
**		overwrites local static buffer. The caller should copy
**		the result.
**
*/

/* lines in authinfo file */
# define SASL_USER	1
# define SASL_AUTHID	2
# define SASL_PASSWORD	3
# define SASL_DEFREALM	4
# define SASL_MECH	5

static char *sasl_info_name[] = {
	"",
	"user id",
	"authorization id",
	"password",
	"realm",
	"mechanism"
};

static char *
readauth(l, filename, safe)
	int l;
	char *filename;
	bool safe;
{
	FILE *f;
	long sff;
	pid_t pid;
	int lc;
	static char buf[MAXLINE];

	if (filename == NULL || filename[0] == '\0')
		return "";
#if !_FFR_ALLOW_SASLINFO
	/*
	**  make sure we don't use a program that is not
	**  accesible to the user who specified a different authinfo file.
	**  However, currently we don't pass this info (authinfo file
	**  specified by user) around, so we just turn off program access.
	*/
	if (filename[0] == '|')
	{
		auto int fd;
		int i;
		char *p;
		char *argv[MAXPV + 1];

		i = 0;
		for (p = strtok(&filename[1], " \t"); p != NULL;
		     p = strtok(NULL, " \t"))
		{
			if (i >= MAXPV)
				break;
			argv[i++] = p;
		}
		argv[i] = NULL;
		pid = prog_open(argv, &fd, CurEnv);
		if (pid < 0)
			f = NULL;
		else
			f = fdopen(fd, "r");
	}
	else
#endif /* !_FFR_ALLOW_SASLINFO */
	{
		pid = -1;
		sff = SFF_REGONLY | SFF_SAFEDIRPATH | SFF_NOWLINK
		      | SFF_NOGWFILES | SFF_NOWWFILES | SFF_NORFILES;
		if (DontLockReadFiles)
			sff |= SFF_NOLOCK;
#if _FFR_ALLOW_SASLINFO
		/*
		**  XXX: make sure we don't read or open files that are not
		**  accesible to the user who specified a different authinfo
		**  file.
		*/
		sff |= SFF_MUSTOWN;
#else /* _FFR_ALLOW_SASLINFO */
		if (safe)
			sff |= SFF_OPENASROOT;
#endif /* _FFR_ALLOW_SASLINFO */

		f = safefopen(filename, O_RDONLY, 0, sff);
	}
	if (f == NULL)
	{
		syserr("readauth: cannot open %s", filename);
		return "";
	}

	lc = 0;
	while (lc < l && fgets(buf, sizeof buf, f) != NULL)
	{
		if (buf[0] != '#')
			lc++;
	}

	(void) fclose(f);
	if (pid > 0)
		(void) waitfor(pid);
	if (lc < l)
	{
		if (LogLevel >= 9)
			sm_syslog(LOG_WARNING, NOQID, "SASL: error: can't read %s from %s",
			  sasl_info_name[l], filename);
		return "";
	}
	lc = strlen(buf) - 1;
	if (lc >= 0)
		buf[lc] = '\0';
	if (tTd(95, 6))
		dprintf("readauth(%s, %d) = '%s'\n", filename, l, buf);
	return buf;
}

#  ifndef __attribute__
#   define __attribute__(x)
#  endif /* ! __attribute__ */

static int getsimple	__P((void *, int, const char **, unsigned *));
static int getsecret	__P((sasl_conn_t *, void *, int, sasl_secret_t **));
static int saslgetrealm	__P((void *, int, const char **, const char **));

static sasl_callback_t callbacks[] = {
	{	SASL_CB_GETREALM,	&saslgetrealm,	NULL	},
# define CB_GETREALM_IDX	0
	{	SASL_CB_PASS,		&getsecret,	NULL	},
# define CB_PASS_IDX	1
	{	SASL_CB_USER,		&getsimple,	NULL	},
# define CB_USER_IDX	2
	{	SASL_CB_AUTHNAME,	&getsimple,	NULL	},
# define CB_AUTHNAME_IDX	3
	{	SASL_CB_VERIFYFILE,	&safesaslfile,	NULL	},
	{	SASL_CB_LIST_END,	NULL,		NULL	}
};

/*
**  GETSIMPLE -- callback to get userid or authid
**
**	Parameters:
**		context -- unused
**		id -- what to do
**		result -- (pointer to) result
**		len -- (pointer to) length of result
**
**	Returns:
**		OK/failure values
*/

static int
getsimple(context, id, result, len)
	void *context __attribute__((unused));
	int id;
	const char **result;
	unsigned *len;
{
	char *h;
#  if SASL > 10509
	int addrealm;
	static int addedrealm = FALSE;
#  endif /* SASL > 10509 */
	static char *user = NULL;
	static char *authid = NULL;

	if (result == NULL)
		return SASL_BADPARAM;

	switch (id) {
	  case SASL_CB_USER:
		if (user == NULL)
		{
			h = readauth(SASL_USER, SASLInfo, TRUE);
			user = newstr(h);
		}
		*result = user;
		if (tTd(95, 5))
			dprintf("AUTH username '%s'\n", *result);
		if (len != NULL)
			*len = user ? strlen(user) : 0;
		break;

	  case SASL_CB_AUTHNAME:
#  if SASL > 10509
		/* XXX maybe other mechanisms too?! */
		addrealm = context != NULL &&
			   strcasecmp(context, "CRAM-MD5") == 0;
		if (addedrealm != addrealm && authid != NULL)
		{
			free(authid);
			authid = NULL;
			addedrealm = addrealm;
		}
#  endif /* SASL > 10509 */
		if (authid == NULL)
		{
			h = readauth(SASL_AUTHID, SASLInfo, TRUE);
#  if SASL > 10509
			if (addrealm && strchr(h, '@') == NULL)
			{
				size_t l;
				char *realm;

				realm = callbacks[CB_GETREALM_IDX].context;
				l = strlen(h) + strlen(realm) + 2;
				authid = xalloc(l);
				snprintf(authid, l, "%s@%s", h, realm);
			}
			else
#  endif /* SASL > 10509 */
				authid = newstr(h);
		}
		*result = authid;
		if (tTd(95, 5))
			dprintf("AUTH authid '%s'\n", *result);
		if (len != NULL)
			*len = authid ? strlen(authid) : 0;
		break;

	  case SASL_CB_LANGUAGE:
		*result = NULL;
		if (len != NULL)
			*len = 0;
		break;

	  default:
		return SASL_BADPARAM;
	}
	return SASL_OK;
}

/*
**  GETSECRET -- callback to get password
**
**	Parameters:
**		conn -- connection information
**		context -- unused
**		id -- what to do
**		psecret -- (pointer to) result
**
**	Returns:
**		OK/failure values
*/

static int
getsecret(conn, context, id, psecret)
	sasl_conn_t *conn;
	void *context __attribute__((unused));
	int id;
	sasl_secret_t **psecret;
{
	char *h;
	int len;
	static char *authpass = NULL;

	if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
		return SASL_BADPARAM;

	if (authpass == NULL)
	{
		h = readauth(SASL_PASSWORD, SASLInfo, TRUE);
		authpass = newstr(h);
	}
	len = strlen(authpass);
	*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len + 1);
	if (*psecret == NULL)
		return SASL_FAIL;
	(void) strlcpy((*psecret)->data, authpass, len + 1);
	(*psecret)->len = len;
	return SASL_OK;
}

/*
**  SAFESASLFILE -- callback for sasl: is file safe?
**
**	Parameters:
**		context -- pointer to context between invocations (unused)
**		file -- name of file to check
**		type -- type of file to check
**
**	Returns:
**		SASL_OK: file can be used
**		SASL_CONTINUE: don't use file
**		SASL_FAIL: failure (not used here)
**
*/
int
# if SASL > 10515
safesaslfile(context, file, type)
# else /* SASL > 10515 */
safesaslfile(context, file)
# endif /* SASL > 10515 */
	void *context;
	char *file;
# if SASL > 10515
	int type;
# endif /* SASL > 10515 */
{
	long sff;
	int r;
	char *p;

	if (file == NULL || *file == '\0')
		return SASL_OK;
	sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOGWFILES|SFF_NOWWFILES|SFF_ROOTOK;
	if ((p = strrchr(file, '/')) == NULL)
		p = file;
	else
		++p;

# if SASL <= 10515
	/* everything beside libs and .conf files must not be readable */
	r = strlen(p);
	if ((r <= 3 || strncmp(p, "lib", 3) != 0) &&
	    (r <= 5 || strncmp(p + r - 5, ".conf", 5) != 0)
#  if _FFR_UNSAFE_SASL
	    && !bitnset(DBS_GROUPREADABLESASLFILE, DontBlameSendmail)
#  endif /* _FFR_UNSAFE_SASL */
	   )
		sff |= SFF_NORFILES;
# else /* SASL > 10515 */
	/* files containing passwords should be not readable */
	if (type == SASL_VRFY_PASSWD)
	{
#  if _FFR_UNSAFE_SASL
		if (bitnset(DBS_GROUPREADABLESASLFILE, DontBlameSendmail))
			sff |= SFF_NOWRFILES;
		else
#  endif /* _FFR_UNSAFE_SASL */
			sff |= SFF_NORFILES;
	}
# endif /* SASL <= 10515 */

	if ((r = safefile(file, RunAsUid, RunAsGid, RunAsUserName, sff,
			  S_IRUSR, NULL)) == 0)
		return SASL_OK;
	if (LogLevel >= 11 || (r != ENOENT && LogLevel >= 9))
		sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
			  file, errstring(r));
	return SASL_CONTINUE;
}

/*
**  SASLGETREALM -- return the realm for SASL
**
**	return the realm for the client
**
**	Parameters:
**		context -- context shared between invocations
**			here: realm to return
**		availrealms -- list of available realms
**			{realm, realm, ...}
**		result -- pointer to result
**
**	Returns:
**		failure/success
*/
static int
saslgetrealm(context, id, availrealms, result)
	void *context;
	int id;
	const char **availrealms;
	const char **result;
{
	if (LogLevel > 12)
		sm_syslog(LOG_INFO, NOQID, "saslgetrealm: realm %s available realms %s",
			  context,
			  availrealms == NULL ? "<No Realms>" : *availrealms);
	if (context == NULL)
		return SASL_FAIL;

	/* check whether context is in list? */
	if (availrealms != NULL)
	{
		if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
		    NULL)
		{
			if (LogLevel > 8)
				sm_syslog(LOG_ERR, NOQID,
					  "saslgetrealm: realm %s not in list %s",
					  context, *availrealms);
			return SASL_FAIL;
		}
	}
	*result = (char *)context;
	return SASL_OK;
}
/*
**  ITEMINLIST -- does item appear in list?
**
**	Check whether item appears in list (which must be separated by a
**	character in delim) as a "word", i.e. it must appear at the begin
**	of the list or after a space, and it must end with a space or the
**	end of the list.
**
**	Parameters:
**		item -- item to search.
**		list -- list of items.
**		delim -- list of delimiters.
**
**	Returns:
**		pointer to occurrence (NULL if not found).
*/

char *
iteminlist(item, list, delim)
	char *item;
	char *list;
	char *delim;
{
	char *s;
	int len;

	if (list == NULL || *list == '\0')
		return NULL;
	if (item == NULL || *item == '\0')
		return NULL;
	s = list;
	len = strlen(item);
	while (s != NULL && *s != '\0')
	{
		if (strncasecmp(s, item, len) == 0 &&
		    (s[len] == '\0' || strchr(delim, s[len]) != NULL))
			return s;
		s = strpbrk(s, delim);
		if (s != NULL)
			while (*++s == ' ')
				continue;
	}
	return NULL;
}
/*
**  REMOVEMECH -- remove item [rem] from list [list]
**
**	Parameters:
**		rem -- item to remove
**		list -- list of items
**
**	Returns:
**		pointer to new list (NULL in case of error).
*/

char *
removemech(rem, list)
	char *rem;
	char *list;
{
	char *ret;
	char *needle;
	int len;

	if (list == NULL)
		return NULL;
	if (rem == NULL || *rem == '\0')
	{
		/* take out what? */
		return NULL;
	}

	/* find the item in the list */
	if ((needle = iteminlist(rem, list, " ")) == NULL)
	{
		/* not in there: return original */
		return list;
	}

	/* length of string without rem */
	len = strlen(list) - strlen(rem);
	if (len == 0)
	{
		ret = xalloc(1);  /* XXX leaked */
		*ret = '\0';
		return ret;
	}
	ret = xalloc(len);  /* XXX leaked */
	memset(ret, '\0', len);

	/* copy from start to removed item */
	memcpy(ret, list, needle - list);

	/* length of rest of string past removed item */
	len = strlen(needle) - strlen(rem) - 1;
	if (len > 0)
	{
		/* not last item -- copy into string */
		memcpy(ret + (needle - list),
		       list + (needle - list) + strlen(rem) + 1,
		       len);
	}
	else
		ret[(needle - list) - 1] = '\0';
	return ret;
}
/*
**  INTERSECT -- create the intersection between two lists
**
**	Parameters:
**		s1, s2 -- lists of items (separated by single blanks).
**
**	Returns:
**		the intersection of both lists.
*/

char *
intersect(s1, s2)
	char *s1, *s2;
{
	char *hr, *h1, *h, *res;
	int l1, l2, rl;

	if (s1 == NULL || s2 == NULL)	/* NULL string(s) -> NULL result */
		return NULL;
	l1 = strlen(s1);
	l2 = strlen(s2);
	rl = min(l1, l2);
	res = (char *)malloc(rl + 1);
	if (res == NULL)
		return NULL;
	*res = '\0';
	if (rl == 0)	/* at least one string empty? */
		return res;
	hr = res;
	h1 = s1;
	h = s1;

	/* walk through s1 */
	while (h != NULL && *h1 != '\0')
	{
		/* is there something after the current word? */
		if ((h = strchr(h1, ' ')) != NULL)
			*h = '\0';
		l1 = strlen(h1);

		/* does the current word appear in s2 ? */
		if (iteminlist(h1, s2, " ") != NULL)
		{
			/* add a blank if not first item */
			if (hr != res)
				*hr++ = ' ';

			/* copy the item */
			memcpy(hr, h1, l1);

			/* advance pointer in result list */
			hr += l1;
			*hr = '\0';
		}
		if (h != NULL)
		{
			/* there are more items */
			*h = ' ';
			h1 = h + 1;
		}
	}
	return res;
}
/*
**  ATTEMPTAUTH -- try to AUTHenticate using one mechanism
**
**	Parameters:
**		m -- the mailer.
**		mci -- the mailer connection structure.
**		e -- the envelope (including the sender to specify).
**		mechused - filled in with mechanism used
**
**	Returns:
**		EX_OK/EX_TEMPFAIL
*/

int
attemptauth(m, mci, e, mechused)
	MAILER *m;
	MCI *mci;
	ENVELOPE *e;
	char **mechused;
{
	int saslresult, smtpresult;
	sasl_external_properties_t ssf;
	sasl_interact_t *client_interact = NULL;
	char *out;
	unsigned int outlen;
	static char *mechusing;
	sasl_security_properties_t ssp;
	char in64[MAXOUTLEN];
# if NETINET
	extern SOCKADDR CurHostAddr;
# endif /* NETINET */

	*mechused = NULL;
	if (mci->mci_conn != NULL)
		free(mci->mci_conn);
	mci->mci_conn = NULL;

	/* make a new client sasl connection */
	saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
								 : "smtp",
				     CurHostName, NULL, 0, &mci->mci_conn);

	/* set properties */
	(void) memset(&ssp, '\0', sizeof ssp);
	saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
	if (saslresult != SASL_OK)
		return EX_TEMPFAIL;

	/* external security strength factor; we have none so zero */
	ssf.ssf = 0;
	ssf.auth_id = NULL;
	saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
	if (saslresult != SASL_OK)
		return EX_TEMPFAIL;

# if NETINET
	/* set local/remote ipv4 addresses */
	if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
	{
		SOCKADDR_LEN_T addrsize;
		struct sockaddr_in saddr_l;

		if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
				 (struct sockaddr_in *) &CurHostAddr)
		    != SASL_OK)
			return EX_TEMPFAIL;
		addrsize = sizeof(struct sockaddr_in);
		if (getsockname(fileno(mci->mci_out),
				(struct sockaddr *) &saddr_l, &addrsize) != 0)
		{
			if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
					 &saddr_l) != SASL_OK)
				return EX_TEMPFAIL;
		}
	}
# endif /* NETINET */

	/* start client side of sasl */
	saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
				       NULL, &client_interact,
				       &out, &outlen,
				       (const char **)&mechusing);
	callbacks[CB_AUTHNAME_IDX].context = mechusing;

	if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
	{
		return EX_TEMPFAIL;
	}

	*mechused = mechusing;

	/* send the info across the wire */
	if (outlen > 0)
	{
		saslresult = sasl_encode64(out, outlen, in64, MAXOUTLEN, NULL);
		if (saslresult != SASL_OK) /* internal error */
		{
			if (LogLevel > 8)
				sm_syslog(LOG_ERR, e->e_id,
					"encode64 for AUTH failed");
			return EX_TEMPFAIL;
		}
		smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
	}
	else
	{
		smtpmessage("AUTH %s", m, mci, mechusing);
	}

	/* get the reply */
	smtpresult = reply(m, mci, e, TimeOuts.to_datafinal, getsasldata, NULL);
	/* which timeout? XXX */

	for (;;)
	{
		/* check return code from server */
		if (smtpresult == 235)
		{
			define(macid("{auth_type}", NULL),
			       newstr(mechusing), e);
			if (LogLevel > 9)
				sm_syslog(LOG_INFO, NOQID,
					  "SASL: outgoing connection to %.64s: mech=%.16s",
					  mci->mci_host, mechusing);
			return EX_OK;
		}
		if (smtpresult == -1)
			return EX_IOERR;
		if (smtpresult != 334)
			return EX_TEMPFAIL;

		saslresult = sasl_client_step(mci->mci_conn,
					      mci->mci_sasl_string,
					      mci->mci_sasl_string_len,
					      &client_interact,
					      &out, &outlen);

		if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
		{
			if (tTd(95, 5))
				dprintf("AUTH FAIL: %s (%d)\n",
					sasl_errstring(saslresult, NULL, NULL),
					saslresult);

			/* fail deliberately, see RFC 2254 4. */
			smtpmessage("*", m, mci);

			/*
			**  but we should only fail for this authentication
			**  mechanism; how to do that?
			*/

			smtpresult = reply(m, mci, e, TimeOuts.to_datafinal,
					   getsasldata, NULL);
			return EX_TEMPFAIL;
		}

		if (outlen > 0)
		{
			saslresult = sasl_encode64(out, outlen, in64,
						   MAXOUTLEN, NULL);
			if (saslresult != SASL_OK)
			{
				/* give an error reply to the other side! */
				smtpmessage("*", m, mci);
				return EX_TEMPFAIL;
			}
		}
		else
			in64[0] = '\0';
		smtpmessage(in64, m, mci);
		smtpresult = reply(m, mci, e, TimeOuts.to_datafinal,
				   getsasldata, NULL);
		/* which timeout? XXX */
	}
	/* NOTREACHED */
}

/*
**  SMTPAUTH -- try to AUTHenticate
**
**	This will try mechanisms in the order the sasl library decided until:
**	- there are no more mechanisms
**	- a mechanism succeeds
**	- the sasl library fails initializing
**
**	Parameters:
**		m -- the mailer.
**		mci -- the mailer connection info.
**		e -- the envelope.
**
**	Returns:
**		EX_OK/EX_TEMPFAIL
*/

int
smtpauth(m, mci, e)
	MAILER *m;
	MCI *mci;
	ENVELOPE *e;
{
	int result;
	char *mechused;
	char *h;
	static char *defrealm = NULL;
	static char *mechs = NULL;

	mci->mci_sasl_auth = FALSE;
	if (defrealm == NULL)
	{
		h = readauth(SASL_DEFREALM, SASLInfo, TRUE);
		if (h != NULL && *h != '\0')
			defrealm = newstr(h);
	}
	if (defrealm == NULL || *defrealm == '\0')
		defrealm = newstr(macvalue('j', CurEnv));
	callbacks[CB_GETREALM_IDX].context = defrealm;

# if _FFR_DEFAUTHINFO_MECHS
	if (mechs == NULL)
	{
		h = readauth(SASL_MECH, SASLInfo, TRUE);
		if (h != NULL && *h != '\0')
			mechs = newstr(h);
	}
# endif /* _FFR_DEFAUTHINFO_MECHS */
	if (mechs == NULL || *mechs == '\0')
		mechs = AuthMechanisms;
	mci->mci_saslcap = intersect(mechs, mci->mci_saslcap);

	/* initialize sasl client library */
	result = sasl_client_init(callbacks);
	if (result != SASL_OK)
		return EX_TEMPFAIL;
	do {
		result = attemptauth(m, mci, e, &mechused);
		if (result == EX_OK)
			mci->mci_sasl_auth = TRUE;
		else if (result == EX_TEMPFAIL)
		{
			mci->mci_saslcap = removemech(mechused,
						      mci->mci_saslcap);
			if (mci->mci_saslcap == NULL ||
			    *(mci->mci_saslcap) == '\0')
				return EX_TEMPFAIL;
		}
		else	/* all others for now */
			return EX_TEMPFAIL;
	} while (result != EX_OK);
	return result;
}
# endif /* SASL */

/*
**  SMTPMAILFROM -- send MAIL command
**
**	Parameters:
**		m -- the mailer.
**		mci -- the mailer connection structure.
**		e -- the envelope (including the sender to specify).
*/

int
smtpmailfrom(m, mci, e)
	MAILER *m;
	MCI *mci;
	ENVELOPE *e;
{
	int r;
	char *bufp;
	char *bodytype;
	char buf[MAXNAME + 1];
	char optbuf[MAXLINE];
	char *enhsc;

	if (tTd(18, 2))
		dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
	enhsc = NULL;

	/* set up appropriate options to include */
	if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
	{
		snprintf(optbuf, sizeof optbuf, " SIZE=%ld", e->e_msgsize);
		bufp = &optbuf[strlen(optbuf)];
	}
	else
	{
		optbuf[0] = '\0';
		bufp = optbuf;
	}

	bodytype = e->e_bodytype;
	if (bitset(MCIF_8BITMIME, mci->mci_flags))
	{
		if (bodytype == NULL &&
		    bitset(MM_MIME8BIT, MimeMode) &&
		    bitset(EF_HAS8BIT, e->e_flags) &&
		    !bitset(EF_DONT_MIME, e->e_flags) &&
		    !bitnset(M_8BITS, m->m_flags))
			bodytype = "8BITMIME";
		if (bodytype != NULL &&
		    SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
		{
			snprintf(bufp, SPACELEFT(optbuf, bufp),
				 " BODY=%s", bodytype);
			bufp += strlen(bufp);
		}
	}
	else if (bitnset(M_8BITS, m->m_flags) ||
		 !bitset(EF_HAS8BIT, e->e_flags) ||
		 bitset(MCIF_8BITOK, mci->mci_flags))
	{
		/* EMPTY */
		/* just pass it through */
	}
# if MIME8TO7
	else if (bitset(MM_CVTMIME, MimeMode) &&
		 !bitset(EF_DONT_MIME, e->e_flags) &&
		 (!bitset(MM_PASS8BIT, MimeMode) ||
		  bitset(EF_IS_MIME, e->e_flags)))
	{
		/* must convert from 8bit MIME format to 7bit encoded */
		mci->mci_flags |= MCIF_CVT8TO7;
	}
# endif /* MIME8TO7 */
	else if (!bitset(MM_PASS8BIT, MimeMode))
	{
		/* cannot just send a 8-bit version */
		extern char MsgBuf[];

		usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
		mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
		return EX_DATAERR;
	}

	if (bitset(MCIF_DSN, mci->mci_flags))
	{
		if (e->e_envid != NULL &&
		    SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
		{
			snprintf(bufp, SPACELEFT(optbuf, bufp),
				 " ENVID=%s", e->e_envid);
			bufp += strlen(bufp);
		}

		/* RET= parameter */
		if (bitset(EF_RET_PARAM, e->e_flags) &&
		    SPACELEFT(optbuf, bufp) > 9)
		{
			snprintf(bufp, SPACELEFT(optbuf, bufp),
				 " RET=%s",
				 bitset(EF_NO_BODY_RETN, e->e_flags) ?
					"HDRS" : "FULL");
			bufp += strlen(bufp);
		}
	}

	if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
	    SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
# if SASL
	     && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
# endif /* SASL */
	    )
	{
		snprintf(bufp, SPACELEFT(optbuf, bufp),
			 " AUTH=%s", e->e_auth_param);
		bufp += strlen(bufp);
	}

	/*
	**  Send the MAIL command.
	**	Designates the sender.
	*/

	mci->mci_state = MCIS_ACTIVE;

	if (bitset(EF_RESPONSE, e->e_flags) &&
	    !bitnset(M_NO_NULL_FROM, m->m_flags))
		buf[0] = '\0';
	else
		expand("\201g", buf, sizeof buf, e);
	if (buf[0] == '<')
	{
		/* strip off <angle brackets> (put back on below) */
		bufp = &buf[strlen(buf) - 1];
		if (*bufp == '>')
			*bufp = '\0';
		bufp = &buf[1];
	}
	else
		bufp = buf;
	if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
	    !bitnset(M_FROMPATH, m->m_flags))
	{
		smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
	}
	else
	{
		smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
			*bufp == '@' ? ',' : ':', bufp, optbuf);
	}
	SmtpPhase = mci->mci_phase = "client MAIL";
	sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
			CurHostName, mci->mci_phase);
	r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc);
	if (r < 0)
	{
		/* communications failure */
		mci->mci_errno = errno;
		mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}
	else if (r == 421)
	{
		/* service shutting down */
		mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
			    SmtpReplyBuffer);
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}
	else if (REPLYTYPE(r) == 4)
	{
		mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
			    SmtpReplyBuffer);
		return EX_TEMPFAIL;
	}
	else if (REPLYTYPE(r) == 2)
	{
		return EX_OK;
	}
	else if (r == 501)
	{
		/* syntax error in arguments */
		mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
			    SmtpReplyBuffer);
		return EX_DATAERR;
	}
	else if (r == 553)
	{
		/* mailbox name not allowed */
		mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
			    SmtpReplyBuffer);
		return EX_DATAERR;
	}
	else if (r == 552)
	{
		/* exceeded storage allocation */
		mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
			    SmtpReplyBuffer);
		if (bitset(MCIF_SIZE, mci->mci_flags))
			e->e_flags |= EF_NO_BODY_RETN;
		return EX_UNAVAILABLE;
	}
	else if (REPLYTYPE(r) == 5)
	{
		/* unknown error */
		mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
			    SmtpReplyBuffer);
		return EX_UNAVAILABLE;
	}

	if (LogLevel > 1)
	{
		sm_syslog(LOG_CRIT, e->e_id,
			  "%.100s: SMTP MAIL protocol error: %s",
			  CurHostName,
			  shortenstring(SmtpReplyBuffer, 403));
	}

	/* protocol error -- close up */
	mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
		    SmtpReplyBuffer);
	smtpquit(m, mci, e);
	return EX_PROTOCOL;
}
/*
**  SMTPRCPT -- designate recipient.
**
**	Parameters:
**		to -- address of recipient.
**		m -- the mailer we are sending to.
**		mci -- the connection info for this transaction.
**		e -- the envelope for this transaction.
**
**	Returns:
**		exit status corresponding to recipient status.
**
**	Side Effects:
**		Sends the mail via SMTP.
*/

int
smtprcpt(to, m, mci, e)
	ADDRESS *to;
	register MAILER *m;
	MCI *mci;
	ENVELOPE *e;
{
	register int r;
	char *bufp;
	char optbuf[MAXLINE];
	char *enhsc;

	enhsc = NULL;
	optbuf[0] = '\0';
	bufp = optbuf;

	/*
	**  warning: in the following it is assumed that the free space
	**  in bufp is sizeof optbuf
	*/
	if (bitset(MCIF_DSN, mci->mci_flags))
	{
		/* NOTIFY= parameter */
		if (bitset(QHASNOTIFY, to->q_flags) &&
		    bitset(QPRIMARY, to->q_flags) &&
		    !bitnset(M_LOCALMAILER, m->m_flags))
		{
			bool firstone = TRUE;

			(void) strlcat(bufp, " NOTIFY=", sizeof optbuf);
			if (bitset(QPINGONSUCCESS, to->q_flags))
			{
				(void) strlcat(bufp, "SUCCESS", sizeof optbuf);
				firstone = FALSE;
			}
			if (bitset(QPINGONFAILURE, to->q_flags))
			{
				if (!firstone)
					(void) strlcat(bufp, ",",
						       sizeof optbuf);
				(void) strlcat(bufp, "FAILURE", sizeof optbuf);
				firstone = FALSE;
			}
			if (bitset(QPINGONDELAY, to->q_flags))
			{
				if (!firstone)
					(void) strlcat(bufp, ",",
						       sizeof optbuf);
				(void) strlcat(bufp, "DELAY", sizeof optbuf);
				firstone = FALSE;
			}
			if (firstone)
				(void) strlcat(bufp, "NEVER", sizeof optbuf);
			bufp += strlen(bufp);
		}

		/* ORCPT= parameter */
		if (to->q_orcpt != NULL &&
		    SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
		{
			snprintf(bufp, SPACELEFT(optbuf, bufp),
				 " ORCPT=%s", to->q_orcpt);
			bufp += strlen(bufp);
		}
	}

	smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);

	SmtpPhase = mci->mci_phase = "client RCPT";
	sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
			CurHostName, mci->mci_phase);
	r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc);
	to->q_rstatus = newstr(SmtpReplyBuffer);
	to->q_status = ENHSCN(enhsc, smtptodsn(r));
	if (!bitnset(M_LMTP, m->m_flags))
		to->q_statmta = mci->mci_host;
	if (r < 0 || REPLYTYPE(r) == 4)
		return EX_TEMPFAIL;
	else if (REPLYTYPE(r) == 2)
		return EX_OK;
	else if (r == 550)
	{
		to->q_status = ENHSCN(enhsc, "5.1.1");
		return EX_NOUSER;
	}
	else if (r == 551)
	{
		to->q_status = ENHSCN(enhsc, "5.1.6");
		return EX_NOUSER;
	}
	else if (r == 553)
	{
		to->q_status = ENHSCN(enhsc, "5.1.3");
		return EX_NOUSER;
	}
	else if (REPLYTYPE(r) == 5)
	{
		return EX_UNAVAILABLE;
	}

	if (LogLevel > 1)
	{
		sm_syslog(LOG_CRIT, e->e_id,
			  "%.100s: SMTP RCPT protocol error: %s",
			  CurHostName,
			  shortenstring(SmtpReplyBuffer, 403));
	}

	mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
		    SmtpReplyBuffer);
	return EX_PROTOCOL;
}
/*
**  SMTPDATA -- send the data and clean up the transaction.
**
**	Parameters:
**		m -- mailer being sent to.
**		mci -- the mailer connection information.
**		e -- the envelope for this message.
**
**	Returns:
**		exit status corresponding to DATA command.
**
**	Side Effects:
**		none.
*/

static jmp_buf	CtxDataTimeout;

int
smtpdata(m, mci, e)
	MAILER *m;
	register MCI *mci;
	register ENVELOPE *e;
{
	register int r;
	register EVENT *ev;
	int rstat;
	int xstat;
	time_t timeout;
	char *enhsc;

	enhsc = NULL;
	/*
	**  Send the data.
	**	First send the command and check that it is ok.
	**	Then send the data.
	**	Follow it up with a dot to terminate.
	**	Finally get the results of the transaction.
	*/

	/* send the command and check ok to proceed */
	smtpmessage("DATA", m, mci);
	SmtpPhase = mci->mci_phase = "client DATA 354";
	sm_setproctitle(TRUE, e, "%s %s: %s",
			qid_printname(e), CurHostName, mci->mci_phase);
	r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc);
	if (r < 0 || REPLYTYPE(r) == 4)
	{
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}
	else if (REPLYTYPE(r) == 5)
	{
		smtprset(m, mci, e);
		return EX_UNAVAILABLE;
	}
	else if (REPLYTYPE(r) != 3)
	{
		if (LogLevel > 1)
		{
			sm_syslog(LOG_CRIT, e->e_id,
				  "%.100s: SMTP DATA-1 protocol error: %s",
				  CurHostName,
				  shortenstring(SmtpReplyBuffer, 403));
		}
		smtprset(m, mci, e);
		mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
			    SmtpReplyBuffer);
		return EX_PROTOCOL;
	}

	/*
	**  Set timeout around data writes.  Make it at least large
	**  enough for DNS timeouts on all recipients plus some fudge
	**  factor.  The main thing is that it should not be infinite.
	*/

	if (setjmp(CtxDataTimeout) != 0)
	{
		mci->mci_errno = errno;
		mci->mci_state = MCIS_ERROR;
		mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);

		/*
		**  If putbody() couldn't finish due to a timeout,
		**  rewind it here in the timeout handler.  See
		**  comments at the end of putbody() for reasoning.
		*/

		if (e->e_dfp != NULL)
			(void) bfrewind(e->e_dfp);

		errno = mci->mci_errno;
		syserr("451 4.4.1 timeout writing message to %s", CurHostName);
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}

	if (tTd(18, 101))
	{
		/* simulate a DATA timeout */
		timeout = 1;
	}
	else
		timeout = DATA_PROGRESS_TIMEOUT;

	ev = setevent(timeout, datatimeout, 0);

	if (tTd(18, 101))
	{
		/* simulate a DATA timeout */
		(void) sleep(1);
	}

	/*
	**  Output the actual message.
	*/

	(*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
	(*e->e_putbody)(mci, e, NULL);

	/*
	**  Cleanup after sending message.
	*/

	clrevent(ev);

# if _FFR_CATCH_BROKEN_MTAS
	{
		fd_set readfds;
		struct timeval timeout;

		FD_ZERO(&readfds);
		FD_SET(fileno(mci->mci_in), &readfds);
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;
		if (select(fileno(mci->mci_in) + 1, FDSET_CAST &readfds,
			   NULL, NULL, &timeout) > 0 &&
		    FD_ISSET(fileno(mci->mci_in), &readfds))
		{
			/* terminate the message */
			fprintf(mci->mci_out, ".%s", m->m_eol);
			if (TrafficLogFile != NULL)
				fprintf(TrafficLogFile, "%05d >>> .\n",
					(int) getpid());
			if (Verbose)
				nmessage(">>> .");

			mci->mci_errno = EIO;
			mci->mci_state = MCIS_ERROR;
			mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
			smtpquit(m, mci, e);
			return EX_PROTOCOL;
		}
	}
# endif /* _FFR_CATCH_BROKEN_MTAS */

	if (ferror(mci->mci_out))
	{
		/* error during processing -- don't send the dot */
		mci->mci_errno = EIO;
		mci->mci_state = MCIS_ERROR;
		mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
		smtpquit(m, mci, e);
		return EX_IOERR;
	}

	/* terminate the message */
	fprintf(mci->mci_out, ".%s", m->m_eol);
	if (TrafficLogFile != NULL)
		fprintf(TrafficLogFile, "%05d >>> .\n", (int) getpid());
	if (Verbose)
		nmessage(">>> .");

	/* check for the results of the transaction */
	SmtpPhase = mci->mci_phase = "client DATA status";
	sm_setproctitle(TRUE, e, "%s %s: %s", qid_printname(e),
			CurHostName, mci->mci_phase);
	if (bitnset(M_LMTP, m->m_flags))
		return EX_OK;
	r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
	if (r < 0)
	{
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}
	mci->mci_state = MCIS_OPEN;
	xstat = EX_NOTSTICKY;
	if (r == 452)
		rstat = EX_TEMPFAIL;
	else if (REPLYTYPE(r) == 4)
		rstat = xstat = EX_TEMPFAIL;
	else if (REPLYCLASS(r) != 5)
		rstat = xstat = EX_PROTOCOL;
	else if (REPLYTYPE(r) == 2)
		rstat = xstat = EX_OK;
	else if (REPLYTYPE(r) == 5)
		rstat = EX_UNAVAILABLE;
	else
		rstat = EX_PROTOCOL;
	mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
		    SmtpReplyBuffer);
	if (e->e_statmsg != NULL)
		free(e->e_statmsg);
	if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
	    (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
		r += 5;
	else
		r = 4;
	e->e_statmsg = newstr(&SmtpReplyBuffer[r]);
	if (rstat != EX_PROTOCOL)
		return rstat;
	if (LogLevel > 1)
	{
		sm_syslog(LOG_CRIT, e->e_id,
			  "%.100s: SMTP DATA-2 protocol error: %s",
			  CurHostName,
			  shortenstring(SmtpReplyBuffer, 403));
	}
	return rstat;
}


static void
datatimeout()
{
	if (DataProgress)
	{
		time_t timeout;
		register EVENT *ev;

		/* check back again later */
		if (tTd(18, 101))
		{
			/* simulate a DATA timeout */
			timeout = 1;
		}
		else
			timeout = DATA_PROGRESS_TIMEOUT;

		DataProgress = FALSE;
		ev = setevent(timeout, datatimeout, 0);
	}
	else
	{
		/* no progress, give up */
		longjmp(CtxDataTimeout, 1);
	}
}
/*
**  SMTPGETSTAT -- get status code from DATA in LMTP
**
**	Parameters:
**		m -- the mailer to which we are sending the message.
**		mci -- the mailer connection structure.
**		e -- the current envelope.
**
**	Returns:
**		The exit status corresponding to the reply code.
*/

int
smtpgetstat(m, mci, e)
	MAILER *m;
	MCI *mci;
	ENVELOPE *e;
{
	int r;
	int status;
	char *enhsc;

	enhsc = NULL;
	/* check for the results of the transaction */
	r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
	if (r < 0)
	{
		smtpquit(m, mci, e);
		return EX_TEMPFAIL;
	}
	if (REPLYTYPE(r) == 4)
		status = EX_TEMPFAIL;
	else if (REPLYCLASS(r) != 5)
		status = EX_PROTOCOL;
	else if (REPLYTYPE(r) == 2)
		status = EX_OK;
	else if (REPLYTYPE(r) == 5)
		status = EX_UNAVAILABLE;
	else
		status = EX_PROTOCOL;
	if (e->e_statmsg != NULL)
		free(e->e_statmsg);
	if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
	    (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
		r += 5;
	else
		r = 4;
	e->e_statmsg = newstr(&SmtpReplyBuffer[r]);
	mci_setstat(mci, status, ENHSCN(enhsc, smtptodsn(r)),
		    SmtpReplyBuffer);
	if (LogLevel > 1 && status == EX_PROTOCOL)
	{
		sm_syslog(LOG_CRIT, e->e_id,
			  "%.100s: SMTP DATA-3 protocol error: %s",
			  CurHostName,
			  shortenstring(SmtpReplyBuffer, 403));
	}
	return status;
}
/*
**  SMTPQUIT -- close the SMTP connection.
**
**	Parameters:
**		m -- a pointer to the mailer.
**		mci -- the mailer connection information.
**		e -- the current envelope.
**
**	Returns:
**		none.
**
**	Side Effects:
**		sends the final protocol and closes the connection.
*/

void
smtpquit(m, mci, e)
	register MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
{
	bool oldSuprErrs = SuprErrs;
	int rcode;

	CurHostName = mci->mci_host;		/* XXX UGLY XXX */
	if (CurHostName == NULL)
		CurHostName = MyHostName;

	/*
	**	Suppress errors here -- we may be processing a different
	**	job when we do the quit connection, and we don't want the
	**	new job to be penalized for something that isn't it's
	**	problem.
	*/

	SuprErrs = TRUE;

	/* send the quit message if we haven't gotten I/O error */
	if (mci->mci_state != MCIS_ERROR &&
	    mci->mci_state != MCIS_QUITING)
	{
		int origstate = mci->mci_state;

		SmtpPhase = "client QUIT";
		mci->mci_state = MCIS_QUITING;
		smtpmessage("QUIT", m, mci);
		(void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL);
		SuprErrs = oldSuprErrs;
		if (mci->mci_state == MCIS_CLOSED ||
		    origstate == MCIS_CLOSED)
			return;
	}

	/* now actually close the connection and pick up the zombie */
	rcode = endmailer(mci, e, NULL);
	if (rcode != EX_OK)
	{
		char *mailer = NULL;

		if (mci->mci_mailer != NULL &&
		    mci->mci_mailer->m_name != NULL)
			mailer = mci->mci_mailer->m_name;

		/* look for naughty mailers */
		sm_syslog(LOG_ERR, e->e_id,
			  "smtpquit: mailer%s%s exited with exit value %d\n",
			  mailer == NULL ? "" : " ",
			  mailer == NULL ? "" : mailer,
			  rcode);
	}

	SuprErrs = oldSuprErrs;
}
/*
**  SMTPRSET -- send a RSET (reset) command
*/

void
smtprset(m, mci, e)
	register MAILER *m;
	register MCI *mci;
	ENVELOPE *e;
{
	int r;

	CurHostName = mci->mci_host;		/* XXX UGLY XXX */
	if (CurHostName == NULL)
		CurHostName = MyHostName;

	SmtpPhase = "client RSET";
	smtpmessage("RSET", m, mci);
	r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL);
	if (r < 0)
		mci->mci_state = MCIS_ERROR;
	else
	{
		/*
		**  Any response is deemed to be acceptable.
		**  The standard does not state the proper action
		**  to take when a value other than 250 is received.
		*/

		mci->mci_state = MCIS_OPEN;
		return;
	}
	smtpquit(m, mci, e);
}
/*
**  SMTPPROBE -- check the connection state
*/

int
smtpprobe(mci)
	register MCI *mci;
{
	int r;
	MAILER *m = mci->mci_mailer;
	ENVELOPE *e;
	extern ENVELOPE BlankEnvelope;

	CurHostName = mci->mci_host;		/* XXX UGLY XXX */
	if (CurHostName == NULL)
		CurHostName = MyHostName;

	e = &BlankEnvelope;
	SmtpPhase = "client probe";
	smtpmessage("RSET", m, mci);
	r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL);
	if (r < 0 || REPLYTYPE(r) != 2)
		smtpquit(m, mci, e);
	return r;
}
/*
**  REPLY -- read arpanet reply
**
**	Parameters:
**		m -- the mailer we are reading the reply from.
**		mci -- the mailer connection info structure.
**		e -- the current envelope.
**		timeout -- the timeout for reads.
**		pfunc -- processing function called on each line of response.
**			If null, no special processing is done.
**
**	Returns:
**		reply code it reads.
**
**	Side Effects:
**		flushes the mail file.
*/

int
reply(m, mci, e, timeout, pfunc, enhstat)
	MAILER *m;
	MCI *mci;
	ENVELOPE *e;
	time_t timeout;
	void (*pfunc)();
	char **enhstat;
{
	register char *bufp;
	register int r;
	bool firstline = TRUE;
	char junkbuf[MAXLINE];
	static char enhstatcode[ENHSCLEN];
	int save_errno;

	if (mci->mci_out != NULL)
		(void) fflush(mci->mci_out);

	if (tTd(18, 1))
		dprintf("reply\n");

	/*
	**  Read the input line, being careful not to hang.
	*/

	bufp = SmtpReplyBuffer;
	for (;;)
	{
		register char *p;

		/* actually do the read */
		if (e->e_xfp != NULL)
			(void) fflush(e->e_xfp);	/* for debugging */

		/* if we are in the process of closing just give the code */
		if (mci->mci_state == MCIS_CLOSED)
			return SMTPCLOSING;

		if (mci->mci_out != NULL)
			(void) fflush(mci->mci_out);

		/* get the line from the other side */
		p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
		mci->mci_lastuse = curtime();

		if (p == NULL)
		{
			bool oldholderrs;
			extern char MsgBuf[];

			/* if the remote end closed early, fake an error */
			if (errno == 0)
# ifdef ECONNRESET
				errno = ECONNRESET;
# else /* ECONNRESET */
				errno = EPIPE;
# endif /* ECONNRESET */

			mci->mci_errno = errno;
			oldholderrs = HoldErrs;
			HoldErrs = TRUE;
			usrerr("451 4.4.1 reply: read error from %s",
			       CurHostName == NULL ? "NO_HOST" : CurHostName);

			/* errors on QUIT should not be persistent */
			if (strncmp(SmtpMsgBuffer, "QUIT", 4) != 0)
				mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);

			/* if debugging, pause so we can see state */
			if (tTd(18, 100))
				(void) pause();
			mci->mci_state = MCIS_ERROR;
			save_errno = errno;
			smtpquit(m, mci, e);
# if XDEBUG
			{
				char wbuf[MAXLINE];
				int wbufleft = sizeof wbuf;

				p = wbuf;
				if (e->e_to != NULL)
				{
					int plen;

					snprintf(p, wbufleft, "%s... ",
						shortenstring(e->e_to, MAXSHORTSTR));
					plen = strlen(p);
					p += plen;
					wbufleft -= plen;
				}
				snprintf(p, wbufleft, "reply(%.100s) during %s",
					 CurHostName == NULL ? "NO_HOST" : CurHostName,
					 SmtpPhase);
				checkfd012(wbuf);
			}
# endif /* XDEBUG */
			errno = save_errno;
			HoldErrs = oldholderrs;
			return -1;
		}
		fixcrlf(bufp, TRUE);

		/* EHLO failure is not a real error */
		if (e->e_xfp != NULL && (bufp[0] == '4' ||
		    (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
		{
			/* serious error -- log the previous command */
			if (SmtpNeedIntro)
			{
				/* inform user who we are chatting with */
				fprintf(CurEnv->e_xfp,
					"... while talking to %s:\n",
					CurHostName == NULL ? "NO_HOST" : CurHostName);
				SmtpNeedIntro = FALSE;
			}
			if (SmtpMsgBuffer[0] != '\0')
				fprintf(e->e_xfp, ">>> %s\n", SmtpMsgBuffer);
			SmtpMsgBuffer[0] = '\0';

			/* now log the message as from the other side */
			fprintf(e->e_xfp, "<<< %s\n", bufp);
		}

		/* display the input for verbose mode */
		if (Verbose)
			nmessage("050 %s", bufp);

		/* ignore improperly formatted input */
		if (!ISSMTPREPLY(bufp))
			continue;

		if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
		    enhstat != NULL &&
		    extenhsc(bufp + 4, ' ', enhstatcode) > 0)
			*enhstat = enhstatcode;

		/* process the line */
		if (pfunc != NULL)
			(*pfunc)(bufp, firstline, m, mci, e);

		firstline = FALSE;

		/* decode the reply code */
		r = atoi(bufp);

		/* extra semantics: 0xx codes are "informational" */
		if (r < 100)
			continue;

		/* if no continuation lines, return this line */
		if (bufp[3] != '-')
			break;

		/* first line of real reply -- ignore rest */
		bufp = junkbuf;
	}

	/*
	**  Now look at SmtpReplyBuffer -- only care about the first
	**  line of the response from here on out.
	*/

	/* save temporary failure messages for posterity */
	if (SmtpReplyBuffer[0] == '4' &&
	    (bitnset(M_LMTP, m->m_flags) || SmtpError[0] == '\0'))
		snprintf(SmtpError, sizeof SmtpError, "%s", SmtpReplyBuffer);

	/* reply code 421 is "Service Shutting Down" */
	if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD)
	{
		/* send the quit protocol */
		mci->mci_state = MCIS_SSD;
		smtpquit(m, mci, e);
	}

	return r;
}
/*
**  SMTPMESSAGE -- send message to server
**
**	Parameters:
**		f -- format
**		m -- the mailer to control formatting.
**		a, b, c -- parameters
**
**	Returns:
**		none.
**
**	Side Effects:
**		writes message to mci->mci_out.
*/

/*VARARGS1*/
void
# ifdef __STDC__
smtpmessage(char *f, MAILER *m, MCI *mci, ...)
# else /* __STDC__ */
smtpmessage(f, m, mci, va_alist)
	char *f;
	MAILER *m;
	MCI *mci;
	va_dcl
# endif /* __STDC__ */
{
	VA_LOCAL_DECL

	VA_START(mci);
	(void) vsnprintf(SmtpMsgBuffer, sizeof SmtpMsgBuffer, f, ap);
	VA_END;

	if (tTd(18, 1) || Verbose)
		nmessage(">>> %s", SmtpMsgBuffer);
	if (TrafficLogFile != NULL)
		fprintf(TrafficLogFile, "%05d >>> %s\n",
			(int) getpid(), SmtpMsgBuffer);
	if (mci->mci_out != NULL)
	{
		fprintf(mci->mci_out, "%s%s", SmtpMsgBuffer,
			m == NULL ? "\r\n" : m->m_eol);
	}
	else if (tTd(18, 1))
	{
		dprintf("smtpmessage: NULL mci_out\n");
	}
}

#endif /* SMTP */
