xref: /freebsd/contrib/sendmail/src/err.c (revision 1669d8afc64812c8d2d1d147ae1fd42ff441e1b1)
1 /*
2  * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1988, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  * $FreeBSD$
13  */
14 
15 #include <sendmail.h>
16 
17 SM_RCSID("@(#)$Id: err.c,v 8.196 2006/11/10 23:14:08 ca Exp $")
18 
19 #if LDAPMAP
20 # include <lber.h>
21 # include <ldap.h>			/* for LDAP error codes */
22 #endif /* LDAPMAP */
23 
24 static void	putoutmsg __P((char *, bool, bool));
25 static void	puterrmsg __P((char *));
26 static char	*fmtmsg __P((char *, const char *, const char *, const char *,
27 			     int, const char *, va_list));
28 
29 /*
30 **  FATAL_ERROR -- handle a fatal exception
31 **
32 **	This function is installed as the default exception handler
33 **	in the main sendmail process, and in all child processes
34 **	that we create.  Its job is to handle exceptions that are not
35 **	handled at a lower level.
36 **
37 **	The theory is that unhandled exceptions will be 'fatal' class
38 **	exceptions (with an "F:" prefix), such as the out-of-memory
39 **	exception "F:sm.heap".  As such, they are handled by exiting
40 **	the process in exactly the same way that xalloc() in Sendmail 8.10
41 **	exits the process when it fails due to lack of memory:
42 **	we call syserr with a message beginning with "!".
43 **
44 **	Parameters:
45 **		exc -- exception which is terminating this process
46 **
47 **	Returns:
48 **		none
49 */
50 
51 void
52 fatal_error(exc)
53 	SM_EXC_T *exc;
54 {
55 	static char buf[256];
56 	SM_FILE_T f;
57 
58 	/*
59 	**  This function may be called when the heap is exhausted.
60 	**  The following code writes the message for 'exc' into our
61 	**  static buffer without allocating memory or raising exceptions.
62 	*/
63 
64 	sm_strio_init(&f, buf, sizeof(buf));
65 	sm_exc_write(exc, &f);
66 	(void) sm_io_flush(&f, SM_TIME_DEFAULT);
67 
68 	/*
69 	**  Terminate the process after logging an error and cleaning up.
70 	**  Problems:
71 	**  - syserr decides what class of error this is by looking at errno.
72 	**    That's no good; we should look at the exc structure.
73 	**  - The cleanup code should be moved out of syserr
74 	**    and into individual exception handlers
75 	**    that are part of the module they clean up after.
76 	*/
77 
78 	errno = ENOMEM;
79 	syserr("!%s", buf);
80 }
81 
82 /*
83 **  SYSERR -- Print error message.
84 **
85 **	Prints an error message via sm_io_printf to the diagnostic output.
86 **
87 **	If the first character of the syserr message is `!' it will
88 **	log this as an ALERT message and exit immediately.  This can
89 **	leave queue files in an indeterminate state, so it should not
90 **	be used lightly.
91 **
92 **	If the first character of the syserr message is '!' or '@'
93 **	then syserr knows that the process is about to be terminated,
94 **	so the SMTP reply code defaults to 421.  Otherwise, the
95 **	reply code defaults to 451 or 554, depending on errno.
96 **
97 **	Parameters:
98 **		fmt -- the format string.  An optional '!' or '@',
99 **			followed by an optional three-digit SMTP
100 **			reply code, followed by message text.
101 **		(others) -- parameters
102 **
103 **	Returns:
104 **		none
105 **		Raises E:mta.quickabort if QuickAbort is set.
106 **
107 **	Side Effects:
108 **		increments Errors.
109 **		sets ExitStat.
110 */
111 
112 char		MsgBuf[BUFSIZ*2];	/* text of most recent message */
113 static char	HeldMessageBuf[sizeof(MsgBuf)];	/* for held messages */
114 
115 #if NAMED_BIND && !defined(NO_DATA)
116 # define NO_DATA	NO_ADDRESS
117 #endif /* NAMED_BIND && !defined(NO_DATA) */
118 
119 void
120 /*VARARGS1*/
121 #ifdef __STDC__
122 syserr(const char *fmt, ...)
123 #else /* __STDC__ */
124 syserr(fmt, va_alist)
125 	const char *fmt;
126 	va_dcl
127 #endif /* __STDC__ */
128 {
129 	register char *p;
130 	int save_errno = errno;
131 	bool panic;
132 	bool exiting;
133 	char *user;
134 	char *enhsc;
135 	char *errtxt;
136 	struct passwd *pw;
137 	char ubuf[80];
138 	SM_VA_LOCAL_DECL
139 
140 	switch (*fmt)
141 	{
142 	  case '!':
143 		++fmt;
144 		panic = true;
145 		exiting = true;
146 		break;
147 	  case '@':
148 		++fmt;
149 		panic = false;
150 		exiting = true;
151 		break;
152 	  default:
153 		panic = false;
154 		exiting = false;
155 		break;
156 	}
157 
158 	/* format and output the error message */
159 	if (exiting)
160 	{
161 		/*
162 		**  Since we are terminating the process,
163 		**  we are aborting the entire SMTP session,
164 		**  rather than just the current transaction.
165 		*/
166 
167 		p = "421";
168 		enhsc = "4.0.0";
169 	}
170 	else if (save_errno == 0)
171 	{
172 		p = "554";
173 		enhsc = "5.0.0";
174 	}
175 	else
176 	{
177 		p = "451";
178 		enhsc = "4.0.0";
179 	}
180 	SM_VA_START(ap, fmt);
181 	errtxt = fmtmsg(MsgBuf, (char *) NULL, p, enhsc, save_errno, fmt, ap);
182 	SM_VA_END(ap);
183 	puterrmsg(MsgBuf);
184 
185 	/* save this message for mailq printing */
186 	if (!panic && CurEnv != NULL)
187 	{
188 		char *nmsg = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
189 
190 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
191 			sm_free(CurEnv->e_message);
192 		CurEnv->e_message = nmsg;
193 	}
194 
195 	/* determine exit status if not already set */
196 	if (ExitStat == EX_OK)
197 	{
198 		if (save_errno == 0)
199 			ExitStat = EX_SOFTWARE;
200 		else
201 			ExitStat = EX_OSERR;
202 		if (tTd(54, 1))
203 			sm_dprintf("syserr: ExitStat = %d\n", ExitStat);
204 	}
205 
206 	pw = sm_getpwuid(RealUid);
207 	if (pw != NULL)
208 		user = pw->pw_name;
209 	else
210 	{
211 		user = ubuf;
212 		(void) sm_snprintf(ubuf, sizeof(ubuf), "UID%d", (int) RealUid);
213 	}
214 
215 	if (LogLevel > 0)
216 		sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
217 			  CurEnv == NULL ? NOQID : CurEnv->e_id,
218 			  "SYSERR(%s): %.900s",
219 			  user, errtxt);
220 	switch (save_errno)
221 	{
222 	  case EBADF:
223 	  case ENFILE:
224 	  case EMFILE:
225 	  case ENOTTY:
226 #ifdef EFBIG
227 	  case EFBIG:
228 #endif /* EFBIG */
229 #ifdef ESPIPE
230 	  case ESPIPE:
231 #endif /* ESPIPE */
232 #ifdef EPIPE
233 	  case EPIPE:
234 #endif /* EPIPE */
235 #ifdef ENOBUFS
236 	  case ENOBUFS:
237 #endif /* ENOBUFS */
238 #ifdef ESTALE
239 	  case ESTALE:
240 #endif /* ESTALE */
241 		printopenfds(true);
242 		mci_dump_all(smioout, true);
243 		break;
244 	}
245 	if (panic)
246 	{
247 #if XLA
248 		xla_all_end();
249 #endif /* XLA */
250 		sync_queue_time();
251 		if (tTd(0, 1))
252 			abort();
253 		exit(EX_OSERR);
254 	}
255 	errno = 0;
256 	if (QuickAbort)
257 		sm_exc_raisenew_x(&EtypeQuickAbort, 2);
258 }
259 /*
260 **  USRERR -- Signal user error.
261 **
262 **	This is much like syserr except it is for user errors.
263 **
264 **	Parameters:
265 **		fmt -- the format string.  If it does not begin with
266 **			a three-digit SMTP reply code, 550 is assumed.
267 **		(others) -- sm_io_printf strings
268 **
269 **	Returns:
270 **		none
271 **		Raises E:mta.quickabort if QuickAbort is set.
272 **
273 **	Side Effects:
274 **		increments Errors.
275 */
276 
277 /*VARARGS1*/
278 void
279 #ifdef __STDC__
280 usrerr(const char *fmt, ...)
281 #else /* __STDC__ */
282 usrerr(fmt, va_alist)
283 	const char *fmt;
284 	va_dcl
285 #endif /* __STDC__ */
286 {
287 	char *enhsc;
288 	char *errtxt;
289 	SM_VA_LOCAL_DECL
290 
291 	if (fmt[0] == '5' || fmt[0] == '6')
292 		enhsc = "5.0.0";
293 	else if (fmt[0] == '4' || fmt[0] == '8')
294 		enhsc = "4.0.0";
295 	else if (fmt[0] == '2')
296 		enhsc = "2.0.0";
297 	else
298 		enhsc = NULL;
299 	SM_VA_START(ap, fmt);
300 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
301 	SM_VA_END(ap);
302 
303 	if (SuprErrs)
304 		return;
305 
306 	/* save this message for mailq printing */
307 	switch (MsgBuf[0])
308 	{
309 	  case '4':
310 	  case '8':
311 		if (CurEnv->e_message != NULL)
312 			break;
313 
314 		/* FALLTHROUGH */
315 
316 	  case '5':
317 	  case '6':
318 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
319 			sm_free(CurEnv->e_message);
320 		if (MsgBuf[0] == '6')
321 		{
322 			char buf[MAXLINE];
323 
324 			(void) sm_snprintf(buf, sizeof(buf),
325 					   "Postmaster warning: %.*s",
326 					   (int) sizeof(buf) - 22, errtxt);
327 			CurEnv->e_message =
328 				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
329 		}
330 		else
331 		{
332 			CurEnv->e_message =
333 				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
334 		}
335 		break;
336 	}
337 
338 	puterrmsg(MsgBuf);
339 	if (LogLevel > 3 && LogUsrErrs)
340 		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
341 	if (QuickAbort)
342 		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
343 }
344 /*
345 **  USRERRENH -- Signal user error.
346 **
347 **	Same as usrerr but with enhanced status code.
348 **
349 **	Parameters:
350 **		enhsc -- the enhanced status code.
351 **		fmt -- the format string.  If it does not begin with
352 **			a three-digit SMTP reply code, 550 is assumed.
353 **		(others) -- sm_io_printf strings
354 **
355 **	Returns:
356 **		none
357 **		Raises E:mta.quickabort if QuickAbort is set.
358 **
359 **	Side Effects:
360 **		increments Errors.
361 */
362 
363 /*VARARGS1*/
364 void
365 #ifdef __STDC__
366 usrerrenh(char *enhsc, const char *fmt, ...)
367 #else /* __STDC__ */
368 usrerrenh(enhsc, fmt, va_alist)
369 	char *enhsc;
370 	const char *fmt;
371 	va_dcl
372 #endif /* __STDC__ */
373 {
374 	char *errtxt;
375 	SM_VA_LOCAL_DECL
376 
377 	if (enhsc == NULL || *enhsc == '\0')
378 	{
379 		if (fmt[0] == '5' || fmt[0] == '6')
380 			enhsc = "5.0.0";
381 		else if (fmt[0] == '4' || fmt[0] == '8')
382 			enhsc = "4.0.0";
383 		else if (fmt[0] == '2')
384 			enhsc = "2.0.0";
385 	}
386 	SM_VA_START(ap, fmt);
387 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
388 	SM_VA_END(ap);
389 
390 	if (SuprErrs)
391 		return;
392 
393 	/* save this message for mailq printing */
394 	switch (MsgBuf[0])
395 	{
396 	  case '4':
397 	  case '8':
398 		if (CurEnv->e_message != NULL)
399 			break;
400 
401 		/* FALLTHROUGH */
402 
403 	  case '5':
404 	  case '6':
405 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
406 			sm_free(CurEnv->e_message);
407 		if (MsgBuf[0] == '6')
408 		{
409 			char buf[MAXLINE];
410 
411 			(void) sm_snprintf(buf, sizeof(buf),
412 					   "Postmaster warning: %.*s",
413 					   (int) sizeof(buf) - 22, errtxt);
414 			CurEnv->e_message =
415 				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
416 		}
417 		else
418 		{
419 			CurEnv->e_message =
420 				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
421 		}
422 		break;
423 	}
424 
425 	puterrmsg(MsgBuf);
426 	if (LogLevel > 3 && LogUsrErrs)
427 		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
428 	if (QuickAbort)
429 		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
430 }
431 /*
432 **  MESSAGE -- print message (not necessarily an error)
433 **
434 **	Parameters:
435 **		msg -- the message (sm_io_printf fmt) -- it can begin with
436 **			an SMTP reply code.  If not, 050 is assumed.
437 **		(others) -- sm_io_printf arguments
438 **
439 **	Returns:
440 **		none
441 **
442 **	Side Effects:
443 **		none.
444 */
445 
446 /*VARARGS1*/
447 void
448 #ifdef __STDC__
449 message(const char *msg, ...)
450 #else /* __STDC__ */
451 message(msg, va_alist)
452 	const char *msg;
453 	va_dcl
454 #endif /* __STDC__ */
455 {
456 	char *errtxt;
457 	SM_VA_LOCAL_DECL
458 
459 	errno = 0;
460 	SM_VA_START(ap, msg);
461 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "050", (char *) NULL, 0, msg, ap);
462 	SM_VA_END(ap);
463 	putoutmsg(MsgBuf, false, false);
464 
465 	/* save this message for mailq printing */
466 	switch (MsgBuf[0])
467 	{
468 	  case '4':
469 	  case '8':
470 		if (CurEnv->e_message != NULL)
471 			break;
472 		/* FALLTHROUGH */
473 
474 	  case '5':
475 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
476 			sm_free(CurEnv->e_message);
477 		CurEnv->e_message =
478 			sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
479 		break;
480 	}
481 }
482 /*
483 **  NMESSAGE -- print message (not necessarily an error)
484 **
485 **	Just like "message" except it never puts the to... tag on.
486 **
487 **	Parameters:
488 **		msg -- the message (sm_io_printf fmt) -- if it begins
489 **			with a three digit SMTP reply code, that is used,
490 **			otherwise 050 is assumed.
491 **		(others) -- sm_io_printf arguments
492 **
493 **	Returns:
494 **		none
495 **
496 **	Side Effects:
497 **		none.
498 */
499 
500 /*VARARGS1*/
501 void
502 #ifdef __STDC__
503 nmessage(const char *msg, ...)
504 #else /* __STDC__ */
505 nmessage(msg, va_alist)
506 	const char *msg;
507 	va_dcl
508 #endif /* __STDC__ */
509 {
510 	char *errtxt;
511 	SM_VA_LOCAL_DECL
512 
513 	errno = 0;
514 	SM_VA_START(ap, msg);
515 	errtxt = fmtmsg(MsgBuf, (char *) NULL, "050",
516 			(char *) NULL, 0, msg, ap);
517 	SM_VA_END(ap);
518 	putoutmsg(MsgBuf, false, false);
519 
520 	/* save this message for mailq printing */
521 	switch (MsgBuf[0])
522 	{
523 	  case '4':
524 	  case '8':
525 		if (CurEnv->e_message != NULL)
526 			break;
527 		/* FALLTHROUGH */
528 
529 	  case '5':
530 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
531 			sm_free(CurEnv->e_message);
532 		CurEnv->e_message = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
533 		break;
534 	}
535 }
536 /*
537 **  PUTOUTMSG -- output error message to transcript and channel
538 **
539 **	Parameters:
540 **		msg -- message to output (in SMTP format).
541 **		holdmsg -- if true, don't output a copy of the message to
542 **			our output channel.
543 **		heldmsg -- if true, this is a previously held message;
544 **			don't log it to the transcript file.
545 **
546 **	Returns:
547 **		none.
548 **
549 **	Side Effects:
550 **		Outputs msg to the transcript.
551 **		If appropriate, outputs it to the channel.
552 **		Deletes SMTP reply code number as appropriate.
553 */
554 
555 static void
556 putoutmsg(msg, holdmsg, heldmsg)
557 	char *msg;
558 	bool holdmsg;
559 	bool heldmsg;
560 {
561 	char msgcode = msg[0];
562 	char *errtxt = msg;
563 	char *id;
564 
565 	/* display for debugging */
566 	if (tTd(54, 8))
567 		sm_dprintf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
568 			heldmsg ? " (held)" : "");
569 
570 	/* map warnings to something SMTP can handle */
571 	if (msgcode == '6')
572 		msg[0] = '5';
573 	else if (msgcode == '8')
574 		msg[0] = '4';
575 	id = (CurEnv != NULL) ? CurEnv->e_id : NULL;
576 
577 	/* output to transcript if serious */
578 	if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL &&
579 	    strchr("45", msg[0]) != NULL)
580 		(void) sm_io_fprintf(CurEnv->e_xfp, SM_TIME_DEFAULT, "%s\n",
581 				     msg);
582 
583 	if (LogLevel > 14 && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
584 		sm_syslog(LOG_INFO, id,
585 			  "--- %s%s%s", msg, holdmsg ? " (hold)" : "",
586 			  heldmsg ? " (held)" : "");
587 
588 	if (msgcode == '8')
589 		msg[0] = '0';
590 
591 	/* output to channel if appropriate */
592 	if (!Verbose && msg[0] == '0')
593 		return;
594 	if (holdmsg)
595 	{
596 		/* save for possible future display */
597 		msg[0] = msgcode;
598 		if (HeldMessageBuf[0] == '5' && msgcode == '4')
599 			return;
600 		(void) sm_strlcpy(HeldMessageBuf, msg, sizeof(HeldMessageBuf));
601 		return;
602 	}
603 
604 	(void) sm_io_flush(smioout, SM_TIME_DEFAULT);
605 
606 	if (OutChannel == NULL)
607 		return;
608 
609 	/* find actual text of error (after SMTP status codes) */
610 	if (ISSMTPREPLY(errtxt))
611 	{
612 		int l;
613 
614 		errtxt += 4;
615 		l = isenhsc(errtxt, ' ');
616 		if (l <= 0)
617 			l = isenhsc(errtxt, '\0');
618 		if (l > 0)
619 			errtxt += l + 1;
620 	}
621 
622 	/* if DisConnected, OutChannel now points to the transcript */
623 	if (!DisConnected &&
624 	    (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
625 		(void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\r\n",
626 				     msg);
627 	else
628 		(void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\n",
629 				     errtxt);
630 	if (TrafficLogFile != NULL)
631 		(void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
632 				     "%05d >>> %s\n", (int) CurrentPid,
633 				     (OpMode == MD_SMTP || OpMode == MD_DAEMON)
634 					? msg : errtxt);
635 #if !PIPELINING
636 	/* XXX can't flush here for SMTP pipelining */
637 	if (msg[3] == ' ')
638 		(void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
639 	if (!sm_io_error(OutChannel) || DisConnected)
640 		return;
641 
642 	/*
643 	**  Error on output -- if reporting lost channel, just ignore it.
644 	**  Also, ignore errors from QUIT response (221 message) -- some
645 	**	rude servers don't read result.
646 	*/
647 
648 	if (InChannel == NULL || sm_io_eof(InChannel) ||
649 	    sm_io_error(InChannel) || strncmp(msg, "221", 3) == 0)
650 		return;
651 
652 	/* can't call syserr, 'cause we are using MsgBuf */
653 	HoldErrs = true;
654 	if (LogLevel > 0)
655 		sm_syslog(LOG_CRIT, id,
656 			  "SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
657 			  CURHOSTNAME,
658 			  shortenstring(msg, MAXSHORTSTR), sm_errstring(errno));
659 #endif /* !PIPELINING */
660 }
661 /*
662 **  PUTERRMSG -- like putoutmsg, but does special processing for error messages
663 **
664 **	Parameters:
665 **		msg -- the message to output.
666 **
667 **	Returns:
668 **		none.
669 **
670 **	Side Effects:
671 **		Sets the fatal error bit in the envelope as appropriate.
672 */
673 
674 static void
675 puterrmsg(msg)
676 	char *msg;
677 {
678 	char msgcode = msg[0];
679 
680 	/* output the message as usual */
681 	putoutmsg(msg, HoldErrs, false);
682 
683 	/* be careful about multiple error messages */
684 	if (OnlyOneError)
685 		HoldErrs = true;
686 
687 	/* signal the error */
688 	Errors++;
689 
690 	if (CurEnv == NULL)
691 		return;
692 
693 	if (msgcode == '6')
694 	{
695 		/* notify the postmaster */
696 		CurEnv->e_flags |= EF_PM_NOTIFY;
697 	}
698 	else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
699 	{
700 		/* mark long-term fatal errors */
701 		CurEnv->e_flags |= EF_FATALERRS;
702 	}
703 }
704 /*
705 **  ISENHSC -- check whether a string contains an enhanced status code
706 **
707 **	Parameters:
708 **		s -- string with possible enhanced status code.
709 **		delim -- delim for enhanced status code.
710 **
711 **	Returns:
712 **		0  -- no enhanced status code.
713 **		>4 -- length of enhanced status code.
714 **
715 **	Side Effects:
716 **		none.
717 */
718 int
719 isenhsc(s, delim)
720 	const char *s;
721 	int delim;
722 {
723 	int l, h;
724 
725 	if (s == NULL)
726 		return 0;
727 	if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
728 		return 0;
729 	h = 0;
730 	l = 2;
731 	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
732 		++h;
733 	if (h == 0 || s[l + h] != '.')
734 		return 0;
735 	l += h + 1;
736 	h = 0;
737 	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
738 		++h;
739 	if (h == 0 || s[l + h] != delim)
740 		return 0;
741 	return l + h;
742 }
743 /*
744 **  EXTENHSC -- check and extract an enhanced status code
745 **
746 **	Parameters:
747 **		s -- string with possible enhanced status code.
748 **		delim -- delim for enhanced status code.
749 **		e -- pointer to storage for enhanced status code.
750 **			must be != NULL and have space for at least
751 **			10 characters ([245].[0-9]{1,3}.[0-9]{1,3})
752 **
753 **	Returns:
754 **		0  -- no enhanced status code.
755 **		>4 -- length of enhanced status code.
756 **
757 **	Side Effects:
758 **		fills e with enhanced status code.
759 */
760 
761 int
762 extenhsc(s, delim, e)
763 	const char *s;
764 	int delim;
765 	char *e;
766 {
767 	int l, h;
768 
769 	if (s == NULL)
770 		return 0;
771 	if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
772 		return 0;
773 	h = 0;
774 	l = 2;
775 	e[0] = s[0];
776 	e[1] = '.';
777 	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
778 	{
779 		e[l + h] = s[l + h];
780 		++h;
781 	}
782 	if (h == 0 || s[l + h] != '.')
783 		return 0;
784 	e[l + h] = '.';
785 	l += h + 1;
786 	h = 0;
787 	while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
788 	{
789 		e[l + h] = s[l + h];
790 		++h;
791 	}
792 	if (h == 0 || s[l + h] != delim)
793 		return 0;
794 	e[l + h] = '\0';
795 	return l + h;
796 }
797 /*
798 **  FMTMSG -- format a message into buffer.
799 **
800 **	Parameters:
801 **		eb -- error buffer to get result -- MUST BE MsgBuf.
802 **		to -- the recipient tag for this message.
803 **		num -- default three digit SMTP reply code.
804 **		enhsc -- enhanced status code.
805 **		en -- the error number to display.
806 **		fmt -- format of string.
807 **		ap -- arguments for fmt.
808 **
809 **	Returns:
810 **		pointer to error text beyond status codes.
811 **
812 **	Side Effects:
813 **		none.
814 */
815 
816 static char *
817 fmtmsg(eb, to, num, enhsc, eno, fmt, ap)
818 	register char *eb;
819 	const char *to;
820 	const char *num;
821 	const char *enhsc;
822 	int eno;
823 	const char *fmt;
824 	SM_VA_LOCAL_DECL
825 {
826 	char del;
827 	int l;
828 	int spaceleft = sizeof(MsgBuf);
829 	char *errtxt;
830 
831 	/* output the reply code */
832 	if (ISSMTPCODE(fmt))
833 	{
834 		num = fmt;
835 		fmt += 4;
836 	}
837 	if (num[3] == '-')
838 		del = '-';
839 	else
840 		del = ' ';
841 	if (SoftBounce && num[0] == '5')
842 	{
843 		/* replace 5 by 4 */
844 		(void) sm_snprintf(eb, spaceleft, "4%2.2s%c", num + 1, del);
845 	}
846 	else
847 		(void) sm_snprintf(eb, spaceleft, "%3.3s%c", num, del);
848 	eb += 4;
849 	spaceleft -= 4;
850 
851 	if ((l = isenhsc(fmt, ' ' )) > 0 && l < spaceleft - 4)
852 	{
853 		/* copy enh.status code including trailing blank */
854 		l++;
855 		(void) sm_strlcpy(eb, fmt, l + 1);
856 		eb += l;
857 		spaceleft -= l;
858 		fmt += l;
859 	}
860 	else if ((l = isenhsc(enhsc, '\0')) > 0 && l < spaceleft - 4)
861 	{
862 		/* copy enh.status code */
863 		(void) sm_strlcpy(eb, enhsc, l + 1);
864 		eb[l] = ' ';
865 		eb[++l] = '\0';
866 		eb += l;
867 		spaceleft -= l;
868 	}
869 	if (SoftBounce && eb[-l] == '5')
870 	{
871 		/* replace 5 by 4 */
872 		eb[-l] = '4';
873 	}
874 	errtxt = eb;
875 
876 	/* output the file name and line number */
877 	if (FileName != NULL)
878 	{
879 		(void) sm_snprintf(eb, spaceleft, "%s: line %d: ",
880 				   shortenstring(FileName, 83), LineNumber);
881 		eb += (l = strlen(eb));
882 		spaceleft -= l;
883 	}
884 
885 	/*
886 	**  output the "to" address only if it is defined and one of the
887 	**  following codes is used:
888 	**  050 internal notices, e.g., alias expansion
889 	**  250 Ok
890 	**  252 Cannot VRFY user, but will accept message and attempt delivery
891 	**  450 Requested mail action not taken: mailbox unavailable
892 	**  550 Requested action not taken: mailbox unavailable
893 	**  553 Requested action not taken: mailbox name not allowed
894 	**
895 	**  Notice: this still isn't "the right thing", this code shouldn't
896 	**	(indirectly) depend on CurEnv->e_to.
897 	*/
898 
899 	if (to != NULL && to[0] != '\0' &&
900 	    (strncmp(num, "050", 3) == 0 ||
901 	     strncmp(num, "250", 3) == 0 ||
902 	     strncmp(num, "252", 3) == 0 ||
903 	     strncmp(num, "450", 3) == 0 ||
904 	     strncmp(num, "550", 3) == 0 ||
905 	     strncmp(num, "553", 3) == 0))
906 	{
907 		(void) sm_strlcpyn(eb, spaceleft, 2,
908 				   shortenstring(to, MAXSHORTSTR), "... ");
909 		spaceleft -= strlen(eb);
910 		while (*eb != '\0')
911 			*eb++ &= 0177;
912 	}
913 
914 	/* output the message */
915 	(void) sm_vsnprintf(eb, spaceleft, fmt, ap);
916 	spaceleft -= strlen(eb);
917 	while (*eb != '\0')
918 		*eb++ &= 0177;
919 
920 	/* output the error code, if any */
921 	if (eno != 0)
922 		(void) sm_strlcpyn(eb, spaceleft, 2, ": ", sm_errstring(eno));
923 
924 	return errtxt;
925 }
926 /*
927 **  BUFFER_ERRORS -- arrange to buffer future error messages
928 **
929 **	Parameters:
930 **		none
931 **
932 **	Returns:
933 **		none.
934 */
935 
936 void
937 buffer_errors()
938 {
939 	HeldMessageBuf[0] = '\0';
940 	HoldErrs = true;
941 }
942 /*
943 **  FLUSH_ERRORS -- flush the held error message buffer
944 **
945 **	Parameters:
946 **		print -- if set, print the message, otherwise just
947 **			delete it.
948 **
949 **	Returns:
950 **		none.
951 */
952 
953 void
954 flush_errors(print)
955 	bool print;
956 {
957 	if (print && HeldMessageBuf[0] != '\0')
958 		putoutmsg(HeldMessageBuf, false, true);
959 	HeldMessageBuf[0] = '\0';
960 	HoldErrs = false;
961 }
962 /*
963 **  SM_ERRSTRING -- return string description of error code
964 **
965 **	Parameters:
966 **		errnum -- the error number to translate
967 **
968 **	Returns:
969 **		A string description of errnum.
970 **
971 **	Side Effects:
972 **		none.
973 */
974 
975 const char *
976 sm_errstring(errnum)
977 	int errnum;
978 {
979 	char *dnsmsg;
980 	char *bp;
981 	static char buf[MAXLINE];
982 #if HASSTRERROR
983 	char *err;
984 	char errbuf[30];
985 #endif /* HASSTRERROR */
986 #if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
987 	extern char *sys_errlist[];
988 	extern int sys_nerr;
989 #endif /* !HASSTRERROR && !defined(ERRLIST_PREDEFINED) */
990 
991 	/*
992 	**  Handle special network error codes.
993 	**
994 	**	These are 4.2/4.3bsd specific; they should be in daemon.c.
995 	*/
996 
997 	dnsmsg = NULL;
998 	switch (errnum)
999 	{
1000 	  case ETIMEDOUT:
1001 	  case ECONNRESET:
1002 		bp = buf;
1003 #if HASSTRERROR
1004 		err = strerror(errnum);
1005 		if (err == NULL)
1006 		{
1007 			(void) sm_snprintf(errbuf, sizeof(errbuf),
1008 					   "Error %d", errnum);
1009 			err = errbuf;
1010 		}
1011 		(void) sm_strlcpy(bp, err, SPACELEFT(buf, bp));
1012 #else /* HASSTRERROR */
1013 		if (errnum >= 0 && errnum < sys_nerr)
1014 			(void) sm_strlcpy(bp, sys_errlist[errnum],
1015 					  SPACELEFT(buf, bp));
1016 		else
1017 			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1018 				"Error %d", errnum);
1019 #endif /* HASSTRERROR */
1020 		bp += strlen(bp);
1021 		if (CurHostName != NULL)
1022 		{
1023 			if (errnum == ETIMEDOUT)
1024 			{
1025 				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1026 					" with ");
1027 				bp += strlen(bp);
1028 			}
1029 			else
1030 			{
1031 				bp = buf;
1032 				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1033 					"Connection reset by ");
1034 				bp += strlen(bp);
1035 			}
1036 			(void) sm_strlcpy(bp,
1037 					shortenstring(CurHostName, MAXSHORTSTR),
1038 					SPACELEFT(buf, bp));
1039 			bp += strlen(buf);
1040 		}
1041 		if (SmtpPhase != NULL)
1042 		{
1043 			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1044 				" during %s", SmtpPhase);
1045 		}
1046 		return buf;
1047 
1048 	  case EHOSTDOWN:
1049 		if (CurHostName == NULL)
1050 			break;
1051 		(void) sm_snprintf(buf, sizeof(buf), "Host %s is down",
1052 			shortenstring(CurHostName, MAXSHORTSTR));
1053 		return buf;
1054 
1055 	  case ECONNREFUSED:
1056 		if (CurHostName == NULL)
1057 			break;
1058 		(void) sm_strlcpyn(buf, sizeof(buf), 2, "Connection refused by ",
1059 			shortenstring(CurHostName, MAXSHORTSTR));
1060 		return buf;
1061 
1062 #if NAMED_BIND
1063 	  case HOST_NOT_FOUND + E_DNSBASE:
1064 		dnsmsg = "host not found";
1065 		break;
1066 
1067 	  case TRY_AGAIN + E_DNSBASE:
1068 		dnsmsg = "host name lookup failure";
1069 		break;
1070 
1071 	  case NO_RECOVERY + E_DNSBASE:
1072 		dnsmsg = "non-recoverable error";
1073 		break;
1074 
1075 	  case NO_DATA + E_DNSBASE:
1076 		dnsmsg = "no data known";
1077 		break;
1078 #endif /* NAMED_BIND */
1079 
1080 	  case EPERM:
1081 		/* SunOS gives "Not owner" -- this is the POSIX message */
1082 		return "Operation not permitted";
1083 
1084 	/*
1085 	**  Error messages used internally in sendmail.
1086 	*/
1087 
1088 	  case E_SM_OPENTIMEOUT:
1089 		return "Timeout on file open";
1090 
1091 	  case E_SM_NOSLINK:
1092 		return "Symbolic links not allowed";
1093 
1094 	  case E_SM_NOHLINK:
1095 		return "Hard links not allowed";
1096 
1097 	  case E_SM_REGONLY:
1098 		return "Regular files only";
1099 
1100 	  case E_SM_ISEXEC:
1101 		return "Executable files not allowed";
1102 
1103 	  case E_SM_WWDIR:
1104 		return "World writable directory";
1105 
1106 	  case E_SM_GWDIR:
1107 		return "Group writable directory";
1108 
1109 	  case E_SM_FILECHANGE:
1110 		return "File changed after open";
1111 
1112 	  case E_SM_WWFILE:
1113 		return "World writable file";
1114 
1115 	  case E_SM_GWFILE:
1116 		return "Group writable file";
1117 
1118 	  case E_SM_GRFILE:
1119 		return "Group readable file";
1120 
1121 	  case E_SM_WRFILE:
1122 		return "World readable file";
1123 	}
1124 
1125 	if (dnsmsg != NULL)
1126 	{
1127 		bp = buf;
1128 		bp += sm_strlcpy(bp, "Name server: ", sizeof(buf));
1129 		if (CurHostName != NULL)
1130 		{
1131 			(void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2,
1132 				shortenstring(CurHostName, MAXSHORTSTR), ": ");
1133 			bp += strlen(bp);
1134 		}
1135 		(void) sm_strlcpy(bp, dnsmsg, SPACELEFT(buf, bp));
1136 		return buf;
1137 	}
1138 
1139 #if LDAPMAP
1140 	if (errnum >= E_LDAPBASE)
1141 		return ldap_err2string(errnum - E_LDAPBASE);
1142 #endif /* LDAPMAP */
1143 
1144 #if HASSTRERROR
1145 	err = strerror(errnum);
1146 	if (err == NULL)
1147 	{
1148 		(void) sm_snprintf(buf, sizeof(buf), "Error %d", errnum);
1149 		return buf;
1150 	}
1151 	return err;
1152 #else /* HASSTRERROR */
1153 	if (errnum > 0 && errnum < sys_nerr)
1154 		return sys_errlist[errnum];
1155 
1156 	(void) sm_snprintf(buf, sizeof(buf), "Error %d", errnum);
1157 	return buf;
1158 #endif /* HASSTRERROR */
1159 }
1160