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

SM_RCSID("@(#)$Id: err.c,v 1.1.1.2 2002/03/12 18:00:31 zarzycki Exp $")

#if LDAPMAP
# include <lber.h>
# include <ldap.h>			/* for LDAP error codes */
#endif /* LDAPMAP */

static void	putoutmsg __P((char *, bool, bool));
static void	puterrmsg __P((char *));
static char	*fmtmsg __P((char *, const char *, const char *, const char *,
			     int, const char *, va_list));

/*
**  FATAL_ERROR -- handle a fatal exception
**
**	This function is installed as the default exception handler
**	in the main sendmail process, and in all child processes
**	that we create.  Its job is to handle exceptions that are not
**	handled at a lower level.
**
**	The theory is that unhandled exceptions will be 'fatal' class
**	exceptions (with an "F:" prefix), such as the out-of-memory
**	exception "F:sm.heap".  As such, they are handled by exiting
**	the process in exactly the same way that xalloc() in Sendmail 8.10
**	exits the process when it fails due to lack of memory:
**	we call syserr with a message beginning with "!".
**
**	Parameters:
**		exc -- exception which is terminating this process
**
**	Returns:
**		none
*/

void
fatal_error(exc)
	SM_EXC_T *exc;
{
	static char buf[256];
	SM_FILE_T f;

	/*
	**  This function may be called when the heap is exhausted.
	**  The following code writes the message for 'exc' into our
	**  static buffer without allocating memory or raising exceptions.
	*/

	sm_strio_init(&f, buf, sizeof(buf));
	sm_exc_write(exc, &f);
	(void) sm_io_flush(&f, SM_TIME_DEFAULT);

	/*
	**  Terminate the process after logging an error and cleaning up.
	**  Problems:
	**  - syserr decides what class of error this is by looking at errno.
	**    That's no good; we should look at the exc structure.
	**  - The cleanup code should be moved out of syserr
	**    and into individual exception handlers
	**    that are part of the module they clean up after.
	*/

	errno = ENOMEM;
	syserr("!%s", buf);
}

/*
**  SYSERR -- Print error message.
**
**	Prints an error message via sm_io_printf to the diagnostic output.
**
**	If the first character of the syserr message is `!' it will
**	log this as an ALERT message and exit immediately.  This can
**	leave queue files in an indeterminate state, so it should not
**	be used lightly.
**
**	If the first character of the syserr message is '!' or '@'
**	then syserr knows that the process is about to be terminated,
**	so the SMTP reply code defaults to 421.  Otherwise, the
**	reply code defaults to 451 or 554, depending on errno.
**
**	Parameters:
**		fmt -- the format string.  An optional '!' or '@',
**			followed by an optional three-digit SMTP
**			reply code, followed by message text.
**		(others) -- parameters
**
**	Returns:
**		none
**		Raises E:mta.quickabort if QuickAbort is set.
**
**	Side Effects:
**		increments Errors.
**		sets ExitStat.
*/

char		MsgBuf[BUFSIZ*2];	/* text of most recent message */
static char	HeldMessageBuf[sizeof MsgBuf];	/* for held messages */

#if NAMED_BIND && !defined(NO_DATA)
# define NO_DATA	NO_ADDRESS
#endif /* NAMED_BIND && !defined(NO_DATA) */

void
/*VARARGS1*/
#ifdef __STDC__
syserr(const char *fmt, ...)
#else /* __STDC__ */
syserr(fmt, va_alist)
	const char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	register char *p;
	int save_errno = errno;
	bool panic;
	bool exiting;
	char *user;
	char *enhsc;
	char *errtxt;
	struct passwd *pw;
	char ubuf[80];
	SM_VA_LOCAL_DECL

	switch (*fmt)
	{
	  case '!':
		++fmt;
		panic = true;
		exiting = true;
		break;
	  case '@':
		++fmt;
		panic = false;
		exiting = true;
		break;
	  default:
		panic = false;
		exiting = false;
		break;
	}

	/* format and output the error message */
	if (exiting)
	{
		/*
		**  Since we are terminating the process,
		**  we are aborting the entire SMTP session,
		**  rather than just the current transaction.
		*/

		p = "421";
		enhsc = "4.0.0";
	}
	else if (save_errno == 0)
	{
		p = "554";
		enhsc = "5.0.0";
	}
	else
	{
		p = "451";
		enhsc = "4.0.0";
	}
	SM_VA_START(ap, fmt);
	errtxt = fmtmsg(MsgBuf, (char *) NULL, p, enhsc, save_errno, fmt, ap);
	SM_VA_END(ap);
	puterrmsg(MsgBuf);

	/* save this message for mailq printing */
	if (!panic && CurEnv != NULL)
	{
		char *nmsg = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);

		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
			sm_free(CurEnv->e_message);
		CurEnv->e_message = nmsg;
	}

	/* determine exit status if not already set */
	if (ExitStat == EX_OK)
	{
		if (save_errno == 0)
			ExitStat = EX_SOFTWARE;
		else
			ExitStat = EX_OSERR;
		if (tTd(54, 1))
			sm_dprintf("syserr: ExitStat = %d\n", ExitStat);
	}

	pw = sm_getpwuid(RealUid);
	if (pw != NULL)
		user = pw->pw_name;
	else
	{
		user = ubuf;
		(void) sm_snprintf(ubuf, sizeof ubuf, "UID%d", (int) RealUid);
	}

	if (LogLevel > 0)
		sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
			  CurEnv == NULL ? NOQID : CurEnv->e_id,
			  "SYSERR(%s): %.900s",
			  user, errtxt);
	switch (save_errno)
	{
	  case EBADF:
	  case ENFILE:
	  case EMFILE:
	  case ENOTTY:
#ifdef EFBIG
	  case EFBIG:
#endif /* EFBIG */
#ifdef ESPIPE
	  case ESPIPE:
#endif /* ESPIPE */
#ifdef EPIPE
	  case EPIPE:
#endif /* EPIPE */
#ifdef ENOBUFS
	  case ENOBUFS:
#endif /* ENOBUFS */
#ifdef ESTALE
	  case ESTALE:
#endif /* ESTALE */
		printopenfds(true);
		mci_dump_all(true);
		break;
	}
	if (panic)
	{
#if XLA
		xla_all_end();
#endif /* XLA */
		sync_queue_time();
		if (tTd(0, 1))
			abort();
		exit(EX_OSERR);
	}
	errno = 0;
	if (QuickAbort)
		sm_exc_raisenew_x(&EtypeQuickAbort, 2);
}
/*
**  USRERR -- Signal user error.
**
**	This is much like syserr except it is for user errors.
**
**	Parameters:
**		fmt -- the format string.  If it does not begin with
**			a three-digit SMTP reply code, 550 is assumed.
**		(others) -- sm_io_printf strings
**
**	Returns:
**		none
**		Raises E:mta.quickabort if QuickAbort is set.
**
**	Side Effects:
**		increments Errors.
*/

/*VARARGS1*/
void
#ifdef __STDC__
usrerr(const char *fmt, ...)
#else /* __STDC__ */
usrerr(fmt, va_alist)
	const char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	char *enhsc;
	char *errtxt;
	SM_VA_LOCAL_DECL

	if (fmt[0] == '5' || fmt[0] == '6')
		enhsc = "5.0.0";
	else if (fmt[0] == '4' || fmt[0] == '8')
		enhsc = "4.0.0";
	else if (fmt[0] == '2')
		enhsc = "2.0.0";
	else
		enhsc = NULL;
	SM_VA_START(ap, fmt);
	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
	SM_VA_END(ap);

	if (SuprErrs)
		return;

	/* save this message for mailq printing */
	switch (MsgBuf[0])
	{
	  case '4':
	  case '8':
		if (CurEnv->e_message != NULL)
			break;

		/* FALLTHROUGH */

	  case '5':
	  case '6':
		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
			sm_free(CurEnv->e_message);
		if (MsgBuf[0] == '6')
		{
			char buf[MAXLINE];

			(void) sm_snprintf(buf, sizeof buf,
					   "Postmaster warning: %.*s",
					   (int) sizeof buf - 22, errtxt);
			CurEnv->e_message =
				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
		}
		else
		{
			CurEnv->e_message =
				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
		}
		break;
	}

	puterrmsg(MsgBuf);
	if (LogLevel > 3 && LogUsrErrs)
		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
	if (QuickAbort)
		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
}
/*
**  USRERRENH -- Signal user error.
**
**	Same as usrerr but with enhanced status code.
**
**	Parameters:
**		enhsc -- the enhanced status code.
**		fmt -- the format string.  If it does not begin with
**			a three-digit SMTP reply code, 550 is assumed.
**		(others) -- sm_io_printf strings
**
**	Returns:
**		none
**		Raises E:mta.quickabort if QuickAbort is set.
**
**	Side Effects:
**		increments Errors.
*/

/*VARARGS1*/
void
#ifdef __STDC__
usrerrenh(char *enhsc, const char *fmt, ...)
#else /* __STDC__ */
usrerrenh(enhsc, fmt, va_alist)
	char *enhsc;
	const char *fmt;
	va_dcl
#endif /* __STDC__ */
{
	char *errtxt;
	SM_VA_LOCAL_DECL

	if (enhsc == NULL || *enhsc == '\0')
	{
		if (fmt[0] == '5' || fmt[0] == '6')
			enhsc = "5.0.0";
		else if (fmt[0] == '4' || fmt[0] == '8')
			enhsc = "4.0.0";
		else if (fmt[0] == '2')
			enhsc = "2.0.0";
	}
	SM_VA_START(ap, fmt);
	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
	SM_VA_END(ap);

	if (SuprErrs)
		return;

	/* save this message for mailq printing */
	switch (MsgBuf[0])
	{
	  case '4':
	  case '8':
		if (CurEnv->e_message != NULL)
			break;

		/* FALLTHROUGH */

	  case '5':
	  case '6':
		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
			sm_free(CurEnv->e_message);
		if (MsgBuf[0] == '6')
		{
			char buf[MAXLINE];

			(void) sm_snprintf(buf, sizeof buf,
					   "Postmaster warning: %.*s",
					   (int) sizeof buf - 22, errtxt);
			CurEnv->e_message =
				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
		}
		else
		{
			CurEnv->e_message =
				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
		}
		break;
	}

	puterrmsg(MsgBuf);
	if (LogLevel > 3 && LogUsrErrs)
		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
	if (QuickAbort)
		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
}
/*
**  MESSAGE -- print message (not necessarily an error)
**
**	Parameters:
**		msg -- the message (sm_io_printf fmt) -- it can begin with
**			an SMTP reply code.  If not, 050 is assumed.
**		(others) -- sm_io_printf arguments
**
**	Returns:
**		none
**
**	Side Effects:
**		none.
*/

/*VARARGS1*/
void
#ifdef __STDC__
message(const char *msg, ...)
#else /* __STDC__ */
message(msg, va_alist)
	const char *msg;
	va_dcl
#endif /* __STDC__ */
{
	char *errtxt;
	SM_VA_LOCAL_DECL

	errno = 0;
	SM_VA_START(ap, msg);
	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "050", (char *) NULL, 0, msg, ap);
	SM_VA_END(ap);
	putoutmsg(MsgBuf, false, false);

	/* save this message for mailq printing */
	switch (MsgBuf[0])
	{
	  case '4':
	  case '8':
		if (CurEnv->e_message != NULL)
			break;
		/* FALLTHROUGH */

	  case '5':
		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
			sm_free(CurEnv->e_message);
		CurEnv->e_message =
			sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
		break;
	}
}
/*
**  NMESSAGE -- print message (not necessarily an error)
**
**	Just like "message" except it never puts the to... tag on.
**
**	Parameters:
**		msg -- the message (sm_io_printf fmt) -- if it begins
**			with a three digit SMTP reply code, that is used,
**			otherwise 050 is assumed.
**		(others) -- sm_io_printf arguments
**
**	Returns:
**		none
**
**	Side Effects:
**		none.
*/

/*VARARGS1*/
void
#ifdef __STDC__
nmessage(const char *msg, ...)
#else /* __STDC__ */
nmessage(msg, va_alist)
	const char *msg;
	va_dcl
#endif /* __STDC__ */
{
	char *errtxt;
	SM_VA_LOCAL_DECL

	errno = 0;
	SM_VA_START(ap, msg);
	errtxt = fmtmsg(MsgBuf, (char *) NULL, "050",
			(char *) NULL, 0, msg, ap);
	SM_VA_END(ap);
	putoutmsg(MsgBuf, false, false);

	/* save this message for mailq printing */
	switch (MsgBuf[0])
	{
	  case '4':
	  case '8':
		if (CurEnv->e_message != NULL)
			break;
		/* FALLTHROUGH */

	  case '5':
		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
			sm_free(CurEnv->e_message);
		CurEnv->e_message =
			sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
		break;
	}
}
/*
**  PUTOUTMSG -- output error message to transcript and channel
**
**	Parameters:
**		msg -- message to output (in SMTP format).
**		holdmsg -- if true, don't output a copy of the message to
**			our output channel.
**		heldmsg -- if true, this is a previously held message;
**			don't log it to the transcript file.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Outputs msg to the transcript.
**		If appropriate, outputs it to the channel.
**		Deletes SMTP reply code number as appropriate.
*/

static void
putoutmsg(msg, holdmsg, heldmsg)
	char *msg;
	bool holdmsg;
	bool heldmsg;
{
	char *errtxt = msg;
	char msgcode = msg[0];

	/* display for debugging */
	if (tTd(54, 8))
		sm_dprintf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
			heldmsg ? " (held)" : "");

	/* map warnings to something SMTP can handle */
	if (msgcode == '6')
		msg[0] = '5';
	else if (msgcode == '8')
		msg[0] = '4';

	/* output to transcript if serious */
	if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL &&
	    strchr("45", msg[0]) != NULL)
		(void) sm_io_fprintf(CurEnv->e_xfp, SM_TIME_DEFAULT, "%s\n",
				     msg);

	if (LogLevel > 14 && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
		sm_syslog(LOG_INFO, CurEnv->e_id,
			  "--- %s%s%s", msg, holdmsg ? " (hold)" : "",
			  heldmsg ? " (held)" : "");

	if (msgcode == '8')
		msg[0] = '0';

	/* output to channel if appropriate */
	if (!Verbose && msg[0] == '0')
		return;
	if (holdmsg)
	{
		/* save for possible future display */
		msg[0] = msgcode;
		if (HeldMessageBuf[0] == '5' && msgcode == '4')
			return;
		(void) sm_strlcpy(HeldMessageBuf, msg, sizeof HeldMessageBuf);
		return;
	}

	(void) sm_io_flush(smioout, SM_TIME_DEFAULT);

	if (OutChannel == NULL)
		return;

	/* find actual text of error (after SMTP status codes) */
	if (ISSMTPREPLY(errtxt))
	{
		int l;

		errtxt += 4;
		l = isenhsc(errtxt, ' ');
		if (l <= 0)
			l = isenhsc(errtxt, '\0');
		if (l > 0)
			errtxt += l + 1;
	}

	/* if DisConnected, OutChannel now points to the transcript */
	if (!DisConnected &&
	    (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
		(void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\r\n",
				     msg);
	else
		(void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\n",
				     errtxt);
	if (TrafficLogFile != NULL)
		(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
				     "%05d >>> %s\n", (int) CurrentPid,
				     (OpMode == MD_SMTP || OpMode == MD_DAEMON)
					? msg : errtxt);
#if !PIPELINING
	/* XXX can't flush here for SMTP pipelining */
	if (msg[3] == ' ')
		(void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
	if (!sm_io_error(OutChannel) || DisConnected)
		return;

	/*
	**  Error on output -- if reporting lost channel, just ignore it.
	**  Also, ignore errors from QUIT response (221 message) -- some
	**	rude servers don't read result.
	*/

	if (InChannel == NULL || sm_io_eof(InChannel) ||
	    sm_io_error(InChannel) || strncmp(msg, "221", 3) == 0)
		return;

	/* can't call syserr, 'cause we are using MsgBuf */
	HoldErrs = true;
	if (LogLevel > 0)
		sm_syslog(LOG_CRIT, CurEnv->e_id,
			  "SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
			  CURHOSTNAME,
			  shortenstring(msg, MAXSHORTSTR), sm_errstring(errno));
#endif /* !PIPELINING */
}
/*
**  PUTERRMSG -- like putoutmsg, but does special processing for error messages
**
**	Parameters:
**		msg -- the message to output.
**
**	Returns:
**		none.
**
**	Side Effects:
**		Sets the fatal error bit in the envelope as appropriate.
*/

static void
puterrmsg(msg)
	char *msg;
{
	char msgcode = msg[0];

	/* output the message as usual */
	putoutmsg(msg, HoldErrs, false);

	/* be careful about multiple error messages */
	if (OnlyOneError)
		HoldErrs = true;

	/* signal the error */
	Errors++;

	if (CurEnv == NULL)
		return;

	if (msgcode == '6')
	{
		/* notify the postmaster */
		CurEnv->e_flags |= EF_PM_NOTIFY;
	}
	else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
	{
		/* mark long-term fatal errors */
		CurEnv->e_flags |= EF_FATALERRS;
	}
}
/*
**  ISENHSC -- check whether a string contains an enhanced status code
**
**	Parameters:
**		s -- string with possible enhanced status code.
**		delim -- delim for enhanced status code.
**
**	Returns:
**		0  -- no enhanced status code.
**		>4 -- length of enhanced status code.
**
**	Side Effects:
**		none.
*/
int
isenhsc(s, delim)
	const char *s;
	int delim;
{
	int l, h;

	if (s == NULL)
		return 0;
	if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
		return 0;
	h = 0;
	l = 2;
	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
		++h;
	if (h == 0 || s[l + h] != '.')
		return 0;
	l += h + 1;
	h = 0;
	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
		++h;
	if (h == 0 || s[l + h] != delim)
		return 0;
	return l + h;
}
/*
**  EXTENHSC -- check and extract an enhanced status code
**
**	Parameters:
**		s -- string with possible enhanced status code.
**		delim -- delim for enhanced status code.
**		e -- pointer to storage for enhanced status code.
**			must be != NULL and have space for at least
**			10 characters ([245].[0-9]{1,3}.[0-9]{1,3})
**
**	Returns:
**		0  -- no enhanced status code.
**		>4 -- length of enhanced status code.
**
**	Side Effects:
**		fills e with enhanced status code.
*/

int
extenhsc(s, delim, e)
	const char *s;
	int delim;
	char *e;
{
	int l, h;

	if (s == NULL)
		return 0;
	if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
		return 0;
	h = 0;
	l = 2;
	e[0] = s[0];
	e[1] = '.';
	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
	{
		e[l + h] = s[l + h];
		++h;
	}
	if (h == 0 || s[l + h] != '.')
		return 0;
	e[l + h] = '.';
	l += h + 1;
	h = 0;
	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
	{
		e[l + h] = s[l + h];
		++h;
	}
	if (h == 0 || s[l + h] != delim)
		return 0;
	e[l + h] = '\0';
	return l + h;
}
/*
**  FMTMSG -- format a message into buffer.
**
**	Parameters:
**		eb -- error buffer to get result -- MUST BE MsgBuf.
**		to -- the recipient tag for this message.
**		num -- default three digit SMTP reply code.
**		enhsc -- enhanced status code.
**		en -- the error number to display.
**		fmt -- format of string.
**		ap -- arguments for fmt.
**
**	Returns:
**		pointer to error text beyond status codes.
**
**	Side Effects:
**		none.
*/

static char *
fmtmsg(eb, to, num, enhsc, eno, fmt, ap)
	register char *eb;
	const char *to;
	const char *num;
	const char *enhsc;
	int eno;
	const char *fmt;
	SM_VA_LOCAL_DECL
{
	char del;
	int l;
	int spaceleft = sizeof MsgBuf;
	char *errtxt;

	/* output the reply code */
	if (ISSMTPCODE(fmt))
	{
		num = fmt;
		fmt += 4;
	}
	if (num[3] == '-')
		del = '-';
	else
		del = ' ';
#if _FFR_SOFT_BOUNCE
	if (SoftBounce && num[0] == '5')
	{
		/* replace 5 by 4 */
		(void) sm_snprintf(eb, spaceleft, "4%2.2s%c", num + 1, del);
	}
	else
#endif /* _FFR_SOFT_BOUNCE */
	(void) sm_snprintf(eb, spaceleft, "%3.3s%c", num, del);
	eb += 4;
	spaceleft -= 4;

	if ((l = isenhsc(fmt, ' ' )) > 0 && l < spaceleft - 4)
	{
		/* copy enh.status code including trailing blank */
		l++;
		(void) sm_strlcpy(eb, fmt, l + 1);
		eb += l;
		spaceleft -= l;
		fmt += l;
	}
	else if ((l = isenhsc(enhsc, '\0')) > 0 && l < spaceleft - 4)
	{
		/* copy enh.status code */
		(void) sm_strlcpy(eb, enhsc, l + 1);
		eb[l] = ' ';
		eb[++l] = '\0';
		eb += l;
		spaceleft -= l;
	}
#if _FFR_SOFT_BOUNCE
	if (SoftBounce && eb[-l] == '5')
	{
		/* replace 5 by 4 */
		eb[-l] = '4';
	}
#endif /* _FFR_SOFT_BOUNCE */
	errtxt = eb;

	/* output the file name and line number */
	if (FileName != NULL)
	{
		(void) sm_snprintf(eb, spaceleft, "%s: line %d: ",
				   shortenstring(FileName, 83), LineNumber);
		eb += (l = strlen(eb));
		spaceleft -= l;
	}

	/*
	**  output the "to" address only if it is defined and one of the
	**  following codes is used:
	**  050 internal notices, e.g., alias expansion
	**  250 Ok
	**  252 Cannot VRFY user, but will accept message and attempt delivery
	**  450 Requested mail action not taken: mailbox unavailable
	**  550 Requested action not taken: mailbox unavailable
	**  553 Requested action not taken: mailbox name not allowed
	**
	**  Notice: this still isn't "the right thing", this code shouldn't
	**	(indirectly) depend on CurEnv->e_to.
	*/

	if (to != NULL && to[0] != '\0' &&
	    (strncmp(num, "050", 3) == 0 ||
	     strncmp(num, "250", 3) == 0 ||
	     strncmp(num, "252", 3) == 0 ||
	     strncmp(num, "450", 3) == 0 ||
	     strncmp(num, "550", 3) == 0 ||
	     strncmp(num, "553", 3) == 0))
	{
		(void) sm_strlcpyn(eb, spaceleft, 2,
				   shortenstring(to, MAXSHORTSTR), "... ");
		spaceleft -= strlen(eb);
		while (*eb != '\0')
			*eb++ &= 0177;
	}

	/* output the message */
	(void) sm_vsnprintf(eb, spaceleft, fmt, ap);
	spaceleft -= strlen(eb);
	while (*eb != '\0')
		*eb++ &= 0177;

	/* output the error code, if any */
	if (eno != 0)
		(void) sm_strlcpyn(eb, spaceleft, 2, ": ", sm_errstring(eno));

	return errtxt;
}
/*
**  BUFFER_ERRORS -- arrange to buffer future error messages
**
**	Parameters:
**		none
**
**	Returns:
**		none.
*/

void
buffer_errors()
{
	HeldMessageBuf[0] = '\0';
	HoldErrs = true;
}
/*
**  FLUSH_ERRORS -- flush the held error message buffer
**
**	Parameters:
**		print -- if set, print the message, otherwise just
**			delete it.
**
**	Returns:
**		none.
*/

void
flush_errors(print)
	bool print;
{
	if (print && HeldMessageBuf[0] != '\0')
		putoutmsg(HeldMessageBuf, false, true);
	HeldMessageBuf[0] = '\0';
	HoldErrs = false;
}
/*
**  SM_ERRSTRING -- return string description of error code
**
**	Parameters:
**		errnum -- the error number to translate
**
**	Returns:
**		A string description of errnum.
**
**	Side Effects:
**		none.
*/

const char *
sm_errstring(errnum)
	int errnum;
{
	char *dnsmsg;
	char *bp;
	static char buf[MAXLINE];
#if HASSTRERROR
	char *err;
	char errbuf[30];
#endif /* HASSTRERROR */
#if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
	extern char *sys_errlist[];
	extern int sys_nerr;
#endif /* !HASSTRERROR && !defined(ERRLIST_PREDEFINED) */

	/*
	**  Handle special network error codes.
	**
	**	These are 4.2/4.3bsd specific; they should be in daemon.c.
	*/

	dnsmsg = NULL;
	switch (errnum)
	{
	  case ETIMEDOUT:
	  case ECONNRESET:
		bp = buf;
#if HASSTRERROR
		err = strerror(errnum);
		if (err == NULL)
		{
			(void) sm_snprintf(errbuf, sizeof errbuf,
					   "Error %d", errnum);
			err = errbuf;
		}
		(void) sm_strlcpy(bp, err, SPACELEFT(buf, bp));
#else /* HASSTRERROR */
		if (errnum >= 0 && errnum < sys_nerr)
			(void) sm_strlcpy(bp, sys_errlist[errnum],
					  SPACELEFT(buf, bp));
		else
			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
				"Error %d", errnum);
#endif /* HASSTRERROR */
		bp += strlen(bp);
		if (CurHostName != NULL)
		{
			if (errnum == ETIMEDOUT)
			{
				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
					" with ");
				bp += strlen(bp);
			}
			else
			{
				bp = buf;
				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
					"Connection reset by ");
				bp += strlen(bp);
			}
			(void) sm_strlcpy(bp,
					shortenstring(CurHostName, MAXSHORTSTR),
					SPACELEFT(buf, bp));
			bp += strlen(buf);
		}
		if (SmtpPhase != NULL)
		{
			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
				" during %s", SmtpPhase);
		}
		return buf;

	  case EHOSTDOWN:
		if (CurHostName == NULL)
			break;
		(void) sm_snprintf(buf, sizeof buf, "Host %s is down",
			shortenstring(CurHostName, MAXSHORTSTR));
		return buf;

	  case ECONNREFUSED:
		if (CurHostName == NULL)
			break;
		(void) sm_strlcpyn(buf, sizeof buf, 2, "Connection refused by ",
			shortenstring(CurHostName, MAXSHORTSTR));
		return buf;

#if NAMED_BIND
	  case HOST_NOT_FOUND + E_DNSBASE:
		dnsmsg = "host not found";
		break;

	  case TRY_AGAIN + E_DNSBASE:
		dnsmsg = "host name lookup failure";
		break;

	  case NO_RECOVERY + E_DNSBASE:
		dnsmsg = "non-recoverable error";
		break;

	  case NO_DATA + E_DNSBASE:
		dnsmsg = "no data known";
		break;
#endif /* NAMED_BIND */

	  case EPERM:
		/* SunOS gives "Not owner" -- this is the POSIX message */
		return "Operation not permitted";

	/*
	**  Error messages used internally in sendmail.
	*/

	  case E_SM_OPENTIMEOUT:
		return "Timeout on file open";

	  case E_SM_NOSLINK:
		return "Symbolic links not allowed";

	  case E_SM_NOHLINK:
		return "Hard links not allowed";

	  case E_SM_REGONLY:
		return "Regular files only";

	  case E_SM_ISEXEC:
		return "Executable files not allowed";

	  case E_SM_WWDIR:
		return "World writable directory";

	  case E_SM_GWDIR:
		return "Group writable directory";

	  case E_SM_FILECHANGE:
		return "File changed after open";

	  case E_SM_WWFILE:
		return "World writable file";

	  case E_SM_GWFILE:
		return "Group writable file";

	  case E_SM_GRFILE:
		return "Group readable file";

	  case E_SM_WRFILE:
		return "World readable file";
	}

	if (dnsmsg != NULL)
	{
		bp = buf;
		bp += sm_strlcpy(bp, "Name server: ", sizeof buf);
		if (CurHostName != NULL)
		{
			(void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2,
				shortenstring(CurHostName, MAXSHORTSTR), ": ");
			bp += strlen(bp);
		}
		(void) sm_strlcpy(bp, dnsmsg, SPACELEFT(buf, bp));
		return buf;
	}

#if LDAPMAP
	if (errnum >= E_LDAPBASE)
		return ldap_err2string(errnum - E_LDAPBASE);
#endif /* LDAPMAP */

#if HASSTRERROR
	err = strerror(errnum);
	if (err == NULL)
	{
		(void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
		return buf;
	}
	return err;
#else /* HASSTRERROR */
	if (errnum > 0 && errnum < sys_nerr)
		return sys_errlist[errnum];

	(void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
	return buf;
#endif /* HASSTRERROR */
}
