xref: /freebsd/contrib/sendmail/src/err.c (revision 7320a0ab)
1 /*
2  * Copyright (c) 1998-2002 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  */
13 
14 #include <sendmail.h>
15 
16 SM_RCSID("@(#)$Id: err.c,v 1.1.1.7 2002/02/17 21:56:39 gshapiro Exp $")
17 
18 /* $FreeBSD$ */
19 
20 #if LDAPMAP
21 # include <lber.h>
22 # include <ldap.h>			/* for LDAP error codes */
23 #endif /* LDAPMAP */
24 
25 static void	putoutmsg __P((char *, bool, bool));
26 static void	puterrmsg __P((char *));
27 static char	*fmtmsg __P((char *, const char *, const char *, const char *,
28 			     int, const char *, va_list));
29 
30 /*
31 **  FATAL_ERROR -- handle a fatal exception
32 **
33 **	This function is installed as the default exception handler
34 **	in the main sendmail process, and in all child processes
35 **	that we create.  Its job is to handle exceptions that are not
36 **	handled at a lower level.
37 **
38 **	The theory is that unhandled exceptions will be 'fatal' class
39 **	exceptions (with an "F:" prefix), such as the out-of-memory
40 **	exception "F:sm.heap".  As such, they are handled by exiting
41 **	the process in exactly the same way that xalloc() in Sendmail 8.10
42 **	exits the process when it fails due to lack of memory:
43 **	we call syserr with a message beginning with "!".
44 **
45 **	Parameters:
46 **		exc -- exception which is terminating this process
47 **
48 **	Returns:
49 **		none
50 */
51 
52 void
53 fatal_error(exc)
54 	SM_EXC_T *exc;
55 {
56 	static char buf[256];
57 	SM_FILE_T f;
58 
59 	/*
60 	**  This function may be called when the heap is exhausted.
61 	**  The following code writes the message for 'exc' into our
62 	**  static buffer without allocating memory or raising exceptions.
63 	*/
64 
65 	sm_strio_init(&f, buf, sizeof(buf));
66 	sm_exc_write(exc, &f);
67 	(void) sm_io_flush(&f, SM_TIME_DEFAULT);
68 
69 	/*
70 	**  Terminate the process after logging an error and cleaning up.
71 	**  Problems:
72 	**  - syserr decides what class of error this is by looking at errno.
73 	**    That's no good; we should look at the exc structure.
74 	**  - The cleanup code should be moved out of syserr
75 	**    and into individual exception handlers
76 	**    that are part of the module they clean up after.
77 	*/
78 
79 	errno = ENOMEM;
80 	syserr("!%s", buf);
81 }
82 
83 /*
84 **  SYSERR -- Print error message.
85 **
86 **	Prints an error message via sm_io_printf to the diagnostic output.
87 **
88 **	If the first character of the syserr message is `!' it will
89 **	log this as an ALERT message and exit immediately.  This can
90 **	leave queue files in an indeterminate state, so it should not
91 **	be used lightly.
92 **
93 **	If the first character of the syserr message is '!' or '@'
94 **	then syserr knows that the process is about to be terminated,
95 **	so the SMTP reply code defaults to 421.  Otherwise, the
96 **	reply code defaults to 451 or 554, depending on errno.
97 **
98 **	Parameters:
99 **		fmt -- the format string.  An optional '!' or '@',
100 **			followed by an optional three-digit SMTP
101 **			reply code, followed by message text.
102 **		(others) -- parameters
103 **
104 **	Returns:
105 **		none
106 **		Raises E:mta.quickabort if QuickAbort is set.
107 **
108 **	Side Effects:
109 **		increments Errors.
110 **		sets ExitStat.
111 */
112 
113 char		MsgBuf[BUFSIZ*2];	/* text of most recent message */
114 static char	HeldMessageBuf[sizeof MsgBuf];	/* for held messages */
115 
116 #if NAMED_BIND && !defined(NO_DATA)
117 # define NO_DATA	NO_ADDRESS
118 #endif /* NAMED_BIND && !defined(NO_DATA) */
119 
120 void
121 /*VARARGS1*/
122 #ifdef __STDC__
123 syserr(const char *fmt, ...)
124 #else /* __STDC__ */
125 syserr(fmt, va_alist)
126 	const char *fmt;
127 	va_dcl
128 #endif /* __STDC__ */
129 {
130 	register char *p;
131 	int save_errno = errno;
132 	bool panic;
133 	bool exiting;
134 	char *user;
135 	char *enhsc;
136 	char *errtxt;
137 	struct passwd *pw;
138 	char ubuf[80];
139 	SM_VA_LOCAL_DECL
140 
141 	switch (*fmt)
142 	{
143 	  case '!':
144 		++fmt;
145 		panic = true;
146 		exiting = true;
147 		break;
148 	  case '@':
149 		++fmt;
150 		panic = false;
151 		exiting = true;
152 		break;
153 	  default:
154 		panic = false;
155 		exiting = false;
156 		break;
157 	}
158 
159 	/* format and output the error message */
160 	if (exiting)
161 	{
162 		/*
163 		**  Since we are terminating the process,
164 		**  we are aborting the entire SMTP session,
165 		**  rather than just the current transaction.
166 		*/
167 
168 		p = "421";
169 		enhsc = "4.0.0";
170 	}
171 	else if (save_errno == 0)
172 	{
173 		p = "554";
174 		enhsc = "5.0.0";
175 	}
176 	else
177 	{
178 		p = "451";
179 		enhsc = "4.0.0";
180 	}
181 	SM_VA_START(ap, fmt);
182 	errtxt = fmtmsg(MsgBuf, (char *) NULL, p, enhsc, save_errno, fmt, ap);
183 	SM_VA_END(ap);
184 	puterrmsg(MsgBuf);
185 
186 	/* save this message for mailq printing */
187 	if (!panic && CurEnv != NULL)
188 	{
189 		char *nmsg = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
190 
191 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
192 			sm_free(CurEnv->e_message);
193 		CurEnv->e_message = nmsg;
194 	}
195 
196 	/* determine exit status if not already set */
197 	if (ExitStat == EX_OK)
198 	{
199 		if (save_errno == 0)
200 			ExitStat = EX_SOFTWARE;
201 		else
202 			ExitStat = EX_OSERR;
203 		if (tTd(54, 1))
204 			sm_dprintf("syserr: ExitStat = %d\n", ExitStat);
205 	}
206 
207 	pw = sm_getpwuid(RealUid);
208 	if (pw != NULL)
209 		user = pw->pw_name;
210 	else
211 	{
212 		user = ubuf;
213 		(void) sm_snprintf(ubuf, sizeof ubuf, "UID%d", (int) RealUid);
214 	}
215 
216 	if (LogLevel > 0)
217 		sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
218 			  CurEnv == NULL ? NOQID : CurEnv->e_id,
219 			  "SYSERR(%s): %.900s",
220 			  user, errtxt);
221 	switch (save_errno)
222 	{
223 	  case EBADF:
224 	  case ENFILE:
225 	  case EMFILE:
226 	  case ENOTTY:
227 #ifdef EFBIG
228 	  case EFBIG:
229 #endif /* EFBIG */
230 #ifdef ESPIPE
231 	  case ESPIPE:
232 #endif /* ESPIPE */
233 #ifdef EPIPE
234 	  case EPIPE:
235 #endif /* EPIPE */
236 #ifdef ENOBUFS
237 	  case ENOBUFS:
238 #endif /* ENOBUFS */
239 #ifdef ESTALE
240 	  case ESTALE:
241 #endif /* ESTALE */
242 		printopenfds(true);
243 		mci_dump_all(true);
244 		break;
245 	}
246 	if (panic)
247 	{
248 #if XLA
249 		xla_all_end();
250 #endif /* XLA */
251 		sync_queue_time();
252 		if (tTd(0, 1))
253 			abort();
254 		exit(EX_OSERR);
255 	}
256 	errno = 0;
257 	if (QuickAbort)
258 		sm_exc_raisenew_x(&EtypeQuickAbort, 2);
259 }
260 /*
261 **  USRERR -- Signal user error.
262 **
263 **	This is much like syserr except it is for user errors.
264 **
265 **	Parameters:
266 **		fmt -- the format string.  If it does not begin with
267 **			a three-digit SMTP reply code, 550 is assumed.
268 **		(others) -- sm_io_printf strings
269 **
270 **	Returns:
271 **		none
272 **		Raises E:mta.quickabort if QuickAbort is set.
273 **
274 **	Side Effects:
275 **		increments Errors.
276 */
277 
278 /*VARARGS1*/
279 void
280 #ifdef __STDC__
281 usrerr(const char *fmt, ...)
282 #else /* __STDC__ */
283 usrerr(fmt, va_alist)
284 	const char *fmt;
285 	va_dcl
286 #endif /* __STDC__ */
287 {
288 	char *enhsc;
289 	char *errtxt;
290 	SM_VA_LOCAL_DECL
291 
292 	if (fmt[0] == '5' || fmt[0] == '6')
293 		enhsc = "5.0.0";
294 	else if (fmt[0] == '4' || fmt[0] == '8')
295 		enhsc = "4.0.0";
296 	else if (fmt[0] == '2')
297 		enhsc = "2.0.0";
298 	else
299 		enhsc = NULL;
300 	SM_VA_START(ap, fmt);
301 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
302 	SM_VA_END(ap);
303 
304 	if (SuprErrs)
305 		return;
306 
307 	/* save this message for mailq printing */
308 	switch (MsgBuf[0])
309 	{
310 	  case '4':
311 	  case '8':
312 		if (CurEnv->e_message != NULL)
313 			break;
314 
315 		/* FALLTHROUGH */
316 
317 	  case '5':
318 	  case '6':
319 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
320 			sm_free(CurEnv->e_message);
321 		if (MsgBuf[0] == '6')
322 		{
323 			char buf[MAXLINE];
324 
325 			(void) sm_snprintf(buf, sizeof buf,
326 					   "Postmaster warning: %.*s",
327 					   (int) sizeof buf - 22, errtxt);
328 			CurEnv->e_message =
329 				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
330 		}
331 		else
332 		{
333 			CurEnv->e_message =
334 				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
335 		}
336 		break;
337 	}
338 
339 	puterrmsg(MsgBuf);
340 	if (LogLevel > 3 && LogUsrErrs)
341 		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
342 	if (QuickAbort)
343 		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
344 }
345 /*
346 **  USRERRENH -- Signal user error.
347 **
348 **	Same as usrerr but with enhanced status code.
349 **
350 **	Parameters:
351 **		enhsc -- the enhanced status code.
352 **		fmt -- the format string.  If it does not begin with
353 **			a three-digit SMTP reply code, 550 is assumed.
354 **		(others) -- sm_io_printf strings
355 **
356 **	Returns:
357 **		none
358 **		Raises E:mta.quickabort if QuickAbort is set.
359 **
360 **	Side Effects:
361 **		increments Errors.
362 */
363 
364 /*VARARGS1*/
365 void
366 #ifdef __STDC__
367 usrerrenh(char *enhsc, const char *fmt, ...)
368 #else /* __STDC__ */
369 usrerrenh(enhsc, fmt, va_alist)
370 	char *enhsc;
371 	const char *fmt;
372 	va_dcl
373 #endif /* __STDC__ */
374 {
375 	char *errtxt;
376 	SM_VA_LOCAL_DECL
377 
378 	if (enhsc == NULL || *enhsc == '\0')
379 	{
380 		if (fmt[0] == '5' || fmt[0] == '6')
381 			enhsc = "5.0.0";
382 		else if (fmt[0] == '4' || fmt[0] == '8')
383 			enhsc = "4.0.0";
384 		else if (fmt[0] == '2')
385 			enhsc = "2.0.0";
386 	}
387 	SM_VA_START(ap, fmt);
388 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
389 	SM_VA_END(ap);
390 
391 	if (SuprErrs)
392 		return;
393 
394 	/* save this message for mailq printing */
395 	switch (MsgBuf[0])
396 	{
397 	  case '4':
398 	  case '8':
399 		if (CurEnv->e_message != NULL)
400 			break;
401 
402 		/* FALLTHROUGH */
403 
404 	  case '5':
405 	  case '6':
406 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
407 			sm_free(CurEnv->e_message);
408 		if (MsgBuf[0] == '6')
409 		{
410 			char buf[MAXLINE];
411 
412 			(void) sm_snprintf(buf, sizeof buf,
413 					   "Postmaster warning: %.*s",
414 					   (int) sizeof buf - 22, errtxt);
415 			CurEnv->e_message =
416 				sm_rpool_strdup_x(CurEnv->e_rpool, buf);
417 		}
418 		else
419 		{
420 			CurEnv->e_message =
421 				sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
422 		}
423 		break;
424 	}
425 
426 	puterrmsg(MsgBuf);
427 	if (LogLevel > 3 && LogUsrErrs)
428 		sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
429 	if (QuickAbort)
430 		sm_exc_raisenew_x(&EtypeQuickAbort, 1);
431 }
432 /*
433 **  MESSAGE -- print message (not necessarily an error)
434 **
435 **	Parameters:
436 **		msg -- the message (sm_io_printf fmt) -- it can begin with
437 **			an SMTP reply code.  If not, 050 is assumed.
438 **		(others) -- sm_io_printf arguments
439 **
440 **	Returns:
441 **		none
442 **
443 **	Side Effects:
444 **		none.
445 */
446 
447 /*VARARGS1*/
448 void
449 #ifdef __STDC__
450 message(const char *msg, ...)
451 #else /* __STDC__ */
452 message(msg, va_alist)
453 	const char *msg;
454 	va_dcl
455 #endif /* __STDC__ */
456 {
457 	char *errtxt;
458 	SM_VA_LOCAL_DECL
459 
460 	errno = 0;
461 	SM_VA_START(ap, msg);
462 	errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "050", (char *) NULL, 0, msg, ap);
463 	SM_VA_END(ap);
464 	putoutmsg(MsgBuf, false, false);
465 
466 	/* save this message for mailq printing */
467 	switch (MsgBuf[0])
468 	{
469 	  case '4':
470 	  case '8':
471 		if (CurEnv->e_message != NULL)
472 			break;
473 		/* FALLTHROUGH */
474 
475 	  case '5':
476 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
477 			sm_free(CurEnv->e_message);
478 		CurEnv->e_message =
479 			sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
480 		break;
481 	}
482 }
483 /*
484 **  NMESSAGE -- print message (not necessarily an error)
485 **
486 **	Just like "message" except it never puts the to... tag on.
487 **
488 **	Parameters:
489 **		msg -- the message (sm_io_printf fmt) -- if it begins
490 **			with a three digit SMTP reply code, that is used,
491 **			otherwise 050 is assumed.
492 **		(others) -- sm_io_printf arguments
493 **
494 **	Returns:
495 **		none
496 **
497 **	Side Effects:
498 **		none.
499 */
500 
501 /*VARARGS1*/
502 void
503 #ifdef __STDC__
504 nmessage(const char *msg, ...)
505 #else /* __STDC__ */
506 nmessage(msg, va_alist)
507 	const char *msg;
508 	va_dcl
509 #endif /* __STDC__ */
510 {
511 	char *errtxt;
512 	SM_VA_LOCAL_DECL
513 
514 	errno = 0;
515 	SM_VA_START(ap, msg);
516 	errtxt = fmtmsg(MsgBuf, (char *) NULL, "050",
517 			(char *) NULL, 0, msg, ap);
518 	SM_VA_END(ap);
519 	putoutmsg(MsgBuf, false, false);
520 
521 	/* save this message for mailq printing */
522 	switch (MsgBuf[0])
523 	{
524 	  case '4':
525 	  case '8':
526 		if (CurEnv->e_message != NULL)
527 			break;
528 		/* FALLTHROUGH */
529 
530 	  case '5':
531 		if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
532 			sm_free(CurEnv->e_message);
533 		CurEnv->e_message =
534 			sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
535 		break;
536 	}
537 }
538 /*
539 **  PUTOUTMSG -- output error message to transcript and channel
540 **
541 **	Parameters:
542 **		msg -- message to output (in SMTP format).
543 **		holdmsg -- if true, don't output a copy of the message to
544 **			our output channel.
545 **		heldmsg -- if true, this is a previously held message;
546 **			don't log it to the transcript file.
547 **
548 **	Returns:
549 **		none.
550 **
551 **	Side Effects:
552 **		Outputs msg to the transcript.
553 **		If appropriate, outputs it to the channel.
554 **		Deletes SMTP reply code number as appropriate.
555 */
556 
557 static void
558 putoutmsg(msg, holdmsg, heldmsg)
559 	char *msg;
560 	bool holdmsg;
561 	bool heldmsg;
562 {
563 	char *errtxt = msg;
564 	char msgcode = msg[0];
565 
566 	/* display for debugging */
567 	if (tTd(54, 8))
568 		sm_dprintf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
569 			heldmsg ? " (held)" : "");
570 
571 	/* map warnings to something SMTP can handle */
572 	if (msgcode == '6')
573 		msg[0] = '5';
574 	else if (msgcode == '8')
575 		msg[0] = '4';
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, CurEnv->e_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, CurEnv->e_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 _FFR_SOFT_BOUNCE
842 	if (SoftBounce && num[0] == '5')
843 	{
844 		/* replace 5 by 4 */
845 		(void) sm_snprintf(eb, spaceleft, "4%2.2s%c", num + 1, del);
846 	}
847 	else
848 #endif /* _FFR_SOFT_BOUNCE */
849 	(void) sm_snprintf(eb, spaceleft, "%3.3s%c", num, del);
850 	eb += 4;
851 	spaceleft -= 4;
852 
853 	if ((l = isenhsc(fmt, ' ' )) > 0 && l < spaceleft - 4)
854 	{
855 		/* copy enh.status code including trailing blank */
856 		l++;
857 		(void) sm_strlcpy(eb, fmt, l + 1);
858 		eb += l;
859 		spaceleft -= l;
860 		fmt += l;
861 	}
862 	else if ((l = isenhsc(enhsc, '\0')) > 0 && l < spaceleft - 4)
863 	{
864 		/* copy enh.status code */
865 		(void) sm_strlcpy(eb, enhsc, l + 1);
866 		eb[l] = ' ';
867 		eb[++l] = '\0';
868 		eb += l;
869 		spaceleft -= l;
870 	}
871 #if _FFR_SOFT_BOUNCE
872 	if (SoftBounce && eb[-l] == '5')
873 	{
874 		/* replace 5 by 4 */
875 		eb[-l] = '4';
876 	}
877 #endif /* _FFR_SOFT_BOUNCE */
878 	errtxt = eb;
879 
880 	/* output the file name and line number */
881 	if (FileName != NULL)
882 	{
883 		(void) sm_snprintf(eb, spaceleft, "%s: line %d: ",
884 				   shortenstring(FileName, 83), LineNumber);
885 		eb += (l = strlen(eb));
886 		spaceleft -= l;
887 	}
888 
889 	/*
890 	**  output the "to" address only if it is defined and one of the
891 	**  following codes is used:
892 	**  050 internal notices, e.g., alias expansion
893 	**  250 Ok
894 	**  252 Cannot VRFY user, but will accept message and attempt delivery
895 	**  450 Requested mail action not taken: mailbox unavailable
896 	**  550 Requested action not taken: mailbox unavailable
897 	**  553 Requested action not taken: mailbox name not allowed
898 	**
899 	**  Notice: this still isn't "the right thing", this code shouldn't
900 	**	(indirectly) depend on CurEnv->e_to.
901 	*/
902 
903 	if (to != NULL && to[0] != '\0' &&
904 	    (strncmp(num, "050", 3) == 0 ||
905 	     strncmp(num, "250", 3) == 0 ||
906 	     strncmp(num, "252", 3) == 0 ||
907 	     strncmp(num, "450", 3) == 0 ||
908 	     strncmp(num, "550", 3) == 0 ||
909 	     strncmp(num, "553", 3) == 0))
910 	{
911 		(void) sm_strlcpyn(eb, spaceleft, 2,
912 				   shortenstring(to, MAXSHORTSTR), "... ");
913 		spaceleft -= strlen(eb);
914 		while (*eb != '\0')
915 			*eb++ &= 0177;
916 	}
917 
918 	/* output the message */
919 	(void) sm_vsnprintf(eb, spaceleft, fmt, ap);
920 	spaceleft -= strlen(eb);
921 	while (*eb != '\0')
922 		*eb++ &= 0177;
923 
924 	/* output the error code, if any */
925 	if (eno != 0)
926 		(void) sm_strlcpyn(eb, spaceleft, 2, ": ", sm_errstring(eno));
927 
928 	return errtxt;
929 }
930 /*
931 **  BUFFER_ERRORS -- arrange to buffer future error messages
932 **
933 **	Parameters:
934 **		none
935 **
936 **	Returns:
937 **		none.
938 */
939 
940 void
941 buffer_errors()
942 {
943 	HeldMessageBuf[0] = '\0';
944 	HoldErrs = true;
945 }
946 /*
947 **  FLUSH_ERRORS -- flush the held error message buffer
948 **
949 **	Parameters:
950 **		print -- if set, print the message, otherwise just
951 **			delete it.
952 **
953 **	Returns:
954 **		none.
955 */
956 
957 void
958 flush_errors(print)
959 	bool print;
960 {
961 	if (print && HeldMessageBuf[0] != '\0')
962 		putoutmsg(HeldMessageBuf, false, true);
963 	HeldMessageBuf[0] = '\0';
964 	HoldErrs = false;
965 }
966 /*
967 **  SM_ERRSTRING -- return string description of error code
968 **
969 **	Parameters:
970 **		errnum -- the error number to translate
971 **
972 **	Returns:
973 **		A string description of errnum.
974 **
975 **	Side Effects:
976 **		none.
977 */
978 
979 const char *
980 sm_errstring(errnum)
981 	int errnum;
982 {
983 	char *dnsmsg;
984 	char *bp;
985 	static char buf[MAXLINE];
986 #if HASSTRERROR
987 	char *err;
988 	char errbuf[30];
989 #endif /* HASSTRERROR */
990 #if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
991 	extern char *sys_errlist[];
992 	extern int sys_nerr;
993 #endif /* !HASSTRERROR && !defined(ERRLIST_PREDEFINED) */
994 
995 	/*
996 	**  Handle special network error codes.
997 	**
998 	**	These are 4.2/4.3bsd specific; they should be in daemon.c.
999 	*/
1000 
1001 	dnsmsg = NULL;
1002 	switch (errnum)
1003 	{
1004 	  case ETIMEDOUT:
1005 	  case ECONNRESET:
1006 		bp = buf;
1007 #if HASSTRERROR
1008 		err = strerror(errnum);
1009 		if (err == NULL)
1010 		{
1011 			(void) sm_snprintf(errbuf, sizeof errbuf,
1012 					   "Error %d", errnum);
1013 			err = errbuf;
1014 		}
1015 		(void) sm_strlcpy(bp, err, SPACELEFT(buf, bp));
1016 #else /* HASSTRERROR */
1017 		if (errnum >= 0 && errnum < sys_nerr)
1018 			(void) sm_strlcpy(bp, sys_errlist[errnum],
1019 					  SPACELEFT(buf, bp));
1020 		else
1021 			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1022 				"Error %d", errnum);
1023 #endif /* HASSTRERROR */
1024 		bp += strlen(bp);
1025 		if (CurHostName != NULL)
1026 		{
1027 			if (errnum == ETIMEDOUT)
1028 			{
1029 				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1030 					" with ");
1031 				bp += strlen(bp);
1032 			}
1033 			else
1034 			{
1035 				bp = buf;
1036 				(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1037 					"Connection reset by ");
1038 				bp += strlen(bp);
1039 			}
1040 			(void) sm_strlcpy(bp,
1041 					shortenstring(CurHostName, MAXSHORTSTR),
1042 					SPACELEFT(buf, bp));
1043 			bp += strlen(buf);
1044 		}
1045 		if (SmtpPhase != NULL)
1046 		{
1047 			(void) sm_snprintf(bp, SPACELEFT(buf, bp),
1048 				" during %s", SmtpPhase);
1049 		}
1050 		return buf;
1051 
1052 	  case EHOSTDOWN:
1053 		if (CurHostName == NULL)
1054 			break;
1055 		(void) sm_snprintf(buf, sizeof buf, "Host %s is down",
1056 			shortenstring(CurHostName, MAXSHORTSTR));
1057 		return buf;
1058 
1059 	  case ECONNREFUSED:
1060 		if (CurHostName == NULL)
1061 			break;
1062 		(void) sm_strlcpyn(buf, sizeof buf, 2, "Connection refused by ",
1063 			shortenstring(CurHostName, MAXSHORTSTR));
1064 		return buf;
1065 
1066 #if NAMED_BIND
1067 	  case HOST_NOT_FOUND + E_DNSBASE:
1068 		dnsmsg = "host not found";
1069 		break;
1070 
1071 	  case TRY_AGAIN + E_DNSBASE:
1072 		dnsmsg = "host name lookup failure";
1073 		break;
1074 
1075 	  case NO_RECOVERY + E_DNSBASE:
1076 		dnsmsg = "non-recoverable error";
1077 		break;
1078 
1079 	  case NO_DATA + E_DNSBASE:
1080 		dnsmsg = "no data known";
1081 		break;
1082 #endif /* NAMED_BIND */
1083 
1084 	  case EPERM:
1085 		/* SunOS gives "Not owner" -- this is the POSIX message */
1086 		return "Operation not permitted";
1087 
1088 	/*
1089 	**  Error messages used internally in sendmail.
1090 	*/
1091 
1092 	  case E_SM_OPENTIMEOUT:
1093 		return "Timeout on file open";
1094 
1095 	  case E_SM_NOSLINK:
1096 		return "Symbolic links not allowed";
1097 
1098 	  case E_SM_NOHLINK:
1099 		return "Hard links not allowed";
1100 
1101 	  case E_SM_REGONLY:
1102 		return "Regular files only";
1103 
1104 	  case E_SM_ISEXEC:
1105 		return "Executable files not allowed";
1106 
1107 	  case E_SM_WWDIR:
1108 		return "World writable directory";
1109 
1110 	  case E_SM_GWDIR:
1111 		return "Group writable directory";
1112 
1113 	  case E_SM_FILECHANGE:
1114 		return "File changed after open";
1115 
1116 	  case E_SM_WWFILE:
1117 		return "World writable file";
1118 
1119 	  case E_SM_GWFILE:
1120 		return "Group writable file";
1121 
1122 	  case E_SM_GRFILE:
1123 		return "Group readable file";
1124 
1125 	  case E_SM_WRFILE:
1126 		return "World readable file";
1127 	}
1128 
1129 	if (dnsmsg != NULL)
1130 	{
1131 		bp = buf;
1132 		bp += sm_strlcpy(bp, "Name server: ", sizeof buf);
1133 		if (CurHostName != NULL)
1134 		{
1135 			(void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2,
1136 				shortenstring(CurHostName, MAXSHORTSTR), ": ");
1137 			bp += strlen(bp);
1138 		}
1139 		(void) sm_strlcpy(bp, dnsmsg, SPACELEFT(buf, bp));
1140 		return buf;
1141 	}
1142 
1143 #if LDAPMAP
1144 	if (errnum >= E_LDAPBASE)
1145 		return ldap_err2string(errnum - E_LDAPBASE);
1146 #endif /* LDAPMAP */
1147 
1148 #if HASSTRERROR
1149 	err = strerror(errnum);
1150 	if (err == NULL)
1151 	{
1152 		(void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
1153 		return buf;
1154 	}
1155 	return err;
1156 #else /* HASSTRERROR */
1157 	if (errnum > 0 && errnum < sys_nerr)
1158 		return sys_errlist[errnum];
1159 
1160 	(void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
1161 	return buf;
1162 #endif /* HASSTRERROR */
1163 }
1164