xref: /freebsd/contrib/sendmail/src/err.c (revision 6e8394b8baa7d5d9153ab90de6824bcd19b3b4e1)
1 /*
2  * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
4  * Copyright (c) 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * By using this file, you agree to the terms and conditions set
8  * forth in the LICENSE file which can be found at the top level of
9  * the sendmail distribution.
10  *
11  */
12 
13 #ifndef lint
14 static char sccsid[] = "@(#)err.c	8.74 (Berkeley) 6/4/1998";
15 #endif /* not lint */
16 
17 # include "sendmail.h"
18 # include <errno.h>
19 
20 /*
21 **  SYSERR -- Print error message.
22 **
23 **	Prints an error message via printf to the diagnostic output.
24 **
25 **	If the first character of the syserr message is `!' it will
26 **	log this as an ALERT message and exit immediately.  This can
27 **	leave queue files in an indeterminate state, so it should not
28 **	be used lightly.
29 **
30 **	Parameters:
31 **		fmt -- the format string.  If it does not begin with
32 **			a three-digit SMTP reply code, either 554 or
33 **			451 is assumed depending on whether errno
34 **			is set.
35 **		(others) -- parameters
36 **
37 **	Returns:
38 **		none
39 **		Through TopFrame if QuickAbort is set.
40 **
41 **	Side Effects:
42 **		increments Errors.
43 **		sets ExitStat.
44 */
45 
46 char	MsgBuf[BUFSIZ*2];		/* text of most recent message */
47 char	HeldMessageBuf[sizeof MsgBuf];	/* for held messages */
48 
49 extern void	putoutmsg __P((char *, bool, bool));
50 extern void	puterrmsg __P((char *));
51 static void	fmtmsg __P((char *, const char *, const char *, int, const char *, va_list));
52 
53 #if NAMED_BIND && !defined(NO_DATA)
54 # define NO_DATA	NO_ADDRESS
55 #endif
56 
57 void
58 /*VARARGS1*/
59 #ifdef __STDC__
60 syserr(const char *fmt, ...)
61 #else
62 syserr(fmt, va_alist)
63 	const char *fmt;
64 	va_dcl
65 #endif
66 {
67 	register char *p;
68 	int olderrno = errno;
69 	bool panic;
70 	char *uname;
71 	struct passwd *pw;
72 	char ubuf[80];
73 	VA_LOCAL_DECL
74 
75 	panic = *fmt == '!';
76 	if (panic)
77 	{
78 		fmt++;
79 		HoldErrs = FALSE;
80 	}
81 
82 	/* format and output the error message */
83 	if (olderrno == 0)
84 		p = "554";
85 	else
86 		p = "451";
87 	VA_START(fmt);
88 	fmtmsg(MsgBuf, (char *) NULL, p, olderrno, fmt, ap);
89 	VA_END;
90 	puterrmsg(MsgBuf);
91 
92 	/* save this message for mailq printing */
93 	if (!panic && CurEnv != NULL)
94 	{
95 		if (CurEnv->e_message != NULL)
96 			free(CurEnv->e_message);
97 		CurEnv->e_message = newstr(MsgBuf + 4);
98 	}
99 
100 	/* determine exit status if not already set */
101 	if (ExitStat == EX_OK)
102 	{
103 		if (olderrno == 0)
104 			ExitStat = EX_SOFTWARE;
105 		else
106 			ExitStat = EX_OSERR;
107 		if (tTd(54, 1))
108 			printf("syserr: ExitStat = %d\n", ExitStat);
109 	}
110 
111 	pw = sm_getpwuid(getuid());
112 	if (pw != NULL)
113 		uname = pw->pw_name;
114 	else
115 	{
116 		uname = ubuf;
117 		snprintf(ubuf, sizeof ubuf, "UID%d", getuid());
118 	}
119 
120 	if (LogLevel > 0)
121 		sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
122 			  CurEnv == NULL ? NOQID : CurEnv->e_id,
123 			  "SYSERR(%s): %.900s",
124 			  uname, &MsgBuf[4]);
125 	switch (olderrno)
126 	{
127 	  case EBADF:
128 	  case ENFILE:
129 	  case EMFILE:
130 	  case ENOTTY:
131 #ifdef EFBIG
132 	  case EFBIG:
133 #endif
134 #ifdef ESPIPE
135 	  case ESPIPE:
136 #endif
137 #ifdef EPIPE
138 	  case EPIPE:
139 #endif
140 #ifdef ENOBUFS
141 	  case ENOBUFS:
142 #endif
143 #ifdef ESTALE
144 	  case ESTALE:
145 #endif
146 		printopenfds(TRUE);
147 		mci_dump_all(TRUE);
148 		break;
149 	}
150 	if (panic)
151 	{
152 #ifdef XLA
153 		xla_all_end();
154 #endif
155 		if (tTd(0, 1))
156 			abort();
157 		exit(EX_OSERR);
158 	}
159 	errno = 0;
160 	if (QuickAbort)
161 		longjmp(TopFrame, 2);
162 }
163 /*
164 **  USRERR -- Signal user error.
165 **
166 **	This is much like syserr except it is for user errors.
167 **
168 **	Parameters:
169 **		fmt -- the format string.  If it does not begin with
170 **			a three-digit SMTP reply code, 501 is assumed.
171 **		(others) -- printf strings
172 **
173 **	Returns:
174 **		none
175 **		Through TopFrame if QuickAbort is set.
176 **
177 **	Side Effects:
178 **		increments Errors.
179 */
180 
181 /*VARARGS1*/
182 void
183 #ifdef __STDC__
184 usrerr(const char *fmt, ...)
185 #else
186 usrerr(fmt, va_alist)
187 	const char *fmt;
188 	va_dcl
189 #endif
190 {
191 	VA_LOCAL_DECL
192 
193 	if (SuprErrs)
194 		return;
195 
196 	VA_START(fmt);
197 	fmtmsg(MsgBuf, CurEnv->e_to, "501", 0, fmt, ap);
198 	VA_END;
199 
200 	/* save this message for mailq printing */
201 	switch (MsgBuf[0])
202 	{
203 	  case '4':
204 	  case '8':
205 		if (CurEnv->e_message != NULL)
206 			break;
207 
208 		/* fall through.... */
209 
210 	  case '5':
211 	  case '6':
212 		if (CurEnv->e_message != NULL)
213 			free(CurEnv->e_message);
214 		if (MsgBuf[0] == '6')
215 		{
216 			char buf[MAXLINE];
217 
218 			snprintf(buf, sizeof buf, "Postmaster warning: %.*s",
219 				(int)sizeof buf - 22, MsgBuf + 4);
220 			CurEnv->e_message = newstr(buf);
221 		}
222 		else
223 		{
224 			CurEnv->e_message = newstr(MsgBuf + 4);
225 		}
226 		break;
227 	}
228 
229 	puterrmsg(MsgBuf);
230 
231 	if (LogLevel > 3 && LogUsrErrs)
232 		sm_syslog(LOG_NOTICE, CurEnv->e_id,
233 			"%.900s",
234 			&MsgBuf[4]);
235 
236 	if (QuickAbort)
237 		longjmp(TopFrame, 1);
238 }
239 /*
240 **  MESSAGE -- print message (not necessarily an error)
241 **
242 **	Parameters:
243 **		msg -- the message (printf fmt) -- it can begin with
244 **			an SMTP reply code.  If not, 050 is assumed.
245 **		(others) -- printf arguments
246 **
247 **	Returns:
248 **		none
249 **
250 **	Side Effects:
251 **		none.
252 */
253 
254 /*VARARGS1*/
255 void
256 #ifdef __STDC__
257 message(const char *msg, ...)
258 #else
259 message(msg, va_alist)
260 	const char *msg;
261 	va_dcl
262 #endif
263 {
264 	VA_LOCAL_DECL
265 
266 	errno = 0;
267 	VA_START(msg);
268 	fmtmsg(MsgBuf, CurEnv->e_to, "050", 0, msg, ap);
269 	VA_END;
270 	putoutmsg(MsgBuf, FALSE, FALSE);
271 
272 	/* save this message for mailq printing */
273 	switch (MsgBuf[0])
274 	{
275 	  case '4':
276 	  case '8':
277 		if (CurEnv->e_message != NULL)
278 			break;
279 		/* fall through.... */
280 
281 	  case '5':
282 		if (CurEnv->e_message != NULL)
283 			free(CurEnv->e_message);
284 		CurEnv->e_message = newstr(MsgBuf + 4);
285 		break;
286 	}
287 }
288 /*
289 **  NMESSAGE -- print message (not necessarily an error)
290 **
291 **	Just like "message" except it never puts the to... tag on.
292 **
293 **	Parameters:
294 **		msg -- the message (printf fmt) -- if it begins
295 **			with a three digit SMTP reply code, that is used,
296 **			otherwise 050 is assumed.
297 **		(others) -- printf arguments
298 **
299 **	Returns:
300 **		none
301 **
302 **	Side Effects:
303 **		none.
304 */
305 
306 /*VARARGS1*/
307 void
308 #ifdef __STDC__
309 nmessage(const char *msg, ...)
310 #else
311 nmessage(msg, va_alist)
312 	const char *msg;
313 	va_dcl
314 #endif
315 {
316 	VA_LOCAL_DECL
317 
318 	errno = 0;
319 	VA_START(msg);
320 	fmtmsg(MsgBuf, (char *) NULL, "050", 0, msg, ap);
321 	VA_END;
322 	putoutmsg(MsgBuf, FALSE, FALSE);
323 
324 	/* save this message for mailq printing */
325 	switch (MsgBuf[0])
326 	{
327 	  case '4':
328 	  case '8':
329 		if (CurEnv->e_message != NULL)
330 			break;
331 		/* fall through.... */
332 
333 	  case '5':
334 		if (CurEnv->e_message != NULL)
335 			free(CurEnv->e_message);
336 		CurEnv->e_message = newstr(MsgBuf + 4);
337 		break;
338 	}
339 }
340 /*
341 **  PUTOUTMSG -- output error message to transcript and channel
342 **
343 **	Parameters:
344 **		msg -- message to output (in SMTP format).
345 **		holdmsg -- if TRUE, don't output a copy of the message to
346 **			our output channel.
347 **		heldmsg -- if TRUE, this is a previously held message;
348 **			don't log it to the transcript file.
349 **
350 **	Returns:
351 **		none.
352 **
353 **	Side Effects:
354 **		Outputs msg to the transcript.
355 **		If appropriate, outputs it to the channel.
356 **		Deletes SMTP reply code number as appropriate.
357 */
358 
359 void
360 putoutmsg(msg, holdmsg, heldmsg)
361 	char *msg;
362 	bool holdmsg;
363 	bool heldmsg;
364 {
365 	char msgcode = msg[0];
366 
367 	/* display for debugging */
368 	if (tTd(54, 8))
369 		printf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
370 			heldmsg ? " (held)" : "");
371 
372 	/* map warnings to something SMTP can handle */
373 	if (msgcode == '6')
374 		msg[0] = '5';
375 	else if (msgcode == '8')
376 		msg[0] = '4';
377 
378 	/* output to transcript if serious */
379 	if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL &&
380 	    strchr("45", msg[0]) != NULL)
381 		fprintf(CurEnv->e_xfp, "%s\n", msg);
382 
383 	if (LogLevel >= 15 && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
384 		sm_syslog(LOG_INFO, CurEnv->e_id,
385 			"--> %s%s",
386 			msg, holdmsg ? " (held)" : "");
387 
388 	if (msgcode == '8')
389 		msg[0] = '0';
390 
391 	/* output to channel if appropriate */
392 	if (!Verbose && msg[0] == '0')
393 		return;
394 	if (holdmsg)
395 	{
396 		/* save for possible future display */
397 		msg[0] = msgcode;
398 		snprintf(HeldMessageBuf, sizeof HeldMessageBuf, "%s", msg);
399 		return;
400 	}
401 
402 	(void) fflush(stdout);
403 
404 	if (OutChannel == NULL)
405 		return;
406 
407 	/* if DisConnected, OutChannel now points to the transcript */
408 	if (!DisConnected &&
409 	    (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
410 		fprintf(OutChannel, "%s\r\n", msg);
411 	else
412 		fprintf(OutChannel, "%s\n", &msg[4]);
413 	if (TrafficLogFile != NULL)
414 		fprintf(TrafficLogFile, "%05d >>> %s\n", (int) getpid(),
415 			(OpMode == MD_SMTP || OpMode == MD_DAEMON) ? msg : &msg[4]);
416 	if (msg[3] == ' ')
417 		(void) fflush(OutChannel);
418 	if (!ferror(OutChannel) || DisConnected)
419 		return;
420 
421 	/*
422 	**  Error on output -- if reporting lost channel, just ignore it.
423 	**  Also, ignore errors from QUIT response (221 message) -- some
424 	**	rude servers don't read result.
425 	*/
426 
427 	if (InChannel == NULL || feof(InChannel) || ferror(InChannel) ||
428 	    strncmp(msg, "221", 3) == 0)
429 		return;
430 
431 	/* can't call syserr, 'cause we are using MsgBuf */
432 	HoldErrs = TRUE;
433 	if (LogLevel > 0)
434 		sm_syslog(LOG_CRIT, CurEnv->e_id,
435 			"SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
436 			CurHostName == NULL ? "NO-HOST" : CurHostName,
437 			shortenstring(msg, MAXSHORTSTR), errstring(errno));
438 }
439 /*
440 **  PUTERRMSG -- like putoutmsg, but does special processing for error messages
441 **
442 **	Parameters:
443 **		msg -- the message to output.
444 **
445 **	Returns:
446 **		none.
447 **
448 **	Side Effects:
449 **		Sets the fatal error bit in the envelope as appropriate.
450 */
451 
452 void
453 puterrmsg(msg)
454 	char *msg;
455 {
456 	char msgcode = msg[0];
457 
458 	/* output the message as usual */
459 	putoutmsg(msg, HoldErrs, FALSE);
460 
461 	/* be careful about multiple error messages */
462 	if (OnlyOneError)
463 		HoldErrs = TRUE;
464 
465 	/* signal the error */
466 	Errors++;
467 
468 	if (CurEnv == NULL)
469 		return;
470 
471 	if (msgcode == '6')
472 	{
473 		/* notify the postmaster */
474 		CurEnv->e_flags |= EF_PM_NOTIFY;
475 	}
476 	else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
477 	{
478 		/* mark long-term fatal errors */
479 		CurEnv->e_flags |= EF_FATALERRS;
480 	}
481 }
482 /*
483 **  FMTMSG -- format a message into buffer.
484 **
485 **	Parameters:
486 **		eb -- error buffer to get result.
487 **		to -- the recipient tag for this message.
488 **		num -- arpanet error number.
489 **		en -- the error number to display.
490 **		fmt -- format of string.
491 **		a, b, c, d, e -- arguments.
492 **
493 **	Returns:
494 **		none.
495 **
496 **	Side Effects:
497 **		none.
498 */
499 
500 static void
501 fmtmsg(eb, to, num, eno, fmt, ap)
502 	register char *eb;
503 	const char *to;
504 	const char *num;
505 	int eno;
506 	const char *fmt;
507 	va_list ap;
508 {
509 	char del;
510 	int l;
511 	int spaceleft = sizeof MsgBuf;
512 
513 	/* output the reply code */
514 	if (isascii(fmt[0]) && isdigit(fmt[0]) &&
515 	    isascii(fmt[1]) && isdigit(fmt[1]) &&
516 	    isascii(fmt[2]) && isdigit(fmt[2]))
517 	{
518 		num = fmt;
519 		fmt += 4;
520 	}
521 	if (num[3] == '-')
522 		del = '-';
523 	else
524 		del = ' ';
525 	(void) snprintf(eb, spaceleft, "%3.3s%c", num, del);
526 	eb += 4;
527 	spaceleft -= 4;
528 
529 	/* output the file name and line number */
530 	if (FileName != NULL)
531 	{
532 		(void) snprintf(eb, spaceleft, "%s: line %d: ",
533 			shortenstring(FileName, 83), LineNumber);
534 		eb += (l = strlen(eb));
535 		spaceleft -= l;
536 	}
537 
538 	/* output the "to" person */
539 	if (to != NULL && to[0] != '\0' &&
540 	    strncmp(num, "551", 3) != 0 &&
541 	    strncmp(num, "251", 3) != 0)
542 	{
543 		(void) snprintf(eb, spaceleft, "%s... ",
544 			shortenstring(to, MAXSHORTSTR));
545 		spaceleft -= strlen(eb);
546 		while (*eb != '\0')
547 			*eb++ &= 0177;
548 	}
549 
550 	/* output the message */
551 	(void) vsnprintf(eb, spaceleft, fmt, ap);
552 	spaceleft -= strlen(eb);
553 	while (*eb != '\0')
554 		*eb++ &= 0177;
555 
556 	/* output the error code, if any */
557 	if (eno != 0)
558 		(void) snprintf(eb, spaceleft, ": %s", errstring(eno));
559 }
560 /*
561 **  BUFFER_ERRORS -- arrange to buffer future error messages
562 **
563 **	Parameters:
564 **		none
565 **
566 **	Returns:
567 **		none.
568 */
569 
570 void
571 buffer_errors()
572 {
573 	HeldMessageBuf[0] = '\0';
574 	HoldErrs = TRUE;
575 }
576 /*
577 **  FLUSH_ERRORS -- flush the held error message buffer
578 **
579 **	Parameters:
580 **		print -- if set, print the message, otherwise just
581 **			delete it.
582 **
583 **	Returns:
584 **		none.
585 */
586 
587 void
588 flush_errors(print)
589 	bool print;
590 {
591 	if (print && HeldMessageBuf[0] != '\0')
592 		putoutmsg(HeldMessageBuf, FALSE, TRUE);
593 	HeldMessageBuf[0] = '\0';
594 	HoldErrs = FALSE;
595 }
596 /*
597 **  ERRSTRING -- return string description of error code
598 **
599 **	Parameters:
600 **		errnum -- the error number to translate
601 **
602 **	Returns:
603 **		A string description of errnum.
604 **
605 **	Side Effects:
606 **		none.
607 */
608 
609 const char *
610 errstring(errnum)
611 	int errnum;
612 {
613 	char *dnsmsg;
614 	char *bp;
615 	static char buf[MAXLINE];
616 # if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
617 	extern char *sys_errlist[];
618 	extern int sys_nerr;
619 # endif
620 # if SMTP
621 	extern char *SmtpPhase;
622 # endif /* SMTP */
623 
624 	/*
625 	**  Handle special network error codes.
626 	**
627 	**	These are 4.2/4.3bsd specific; they should be in daemon.c.
628 	*/
629 
630 	dnsmsg = NULL;
631 	switch (errnum)
632 	{
633 # if defined(DAEMON) && defined(ETIMEDOUT)
634 	  case ETIMEDOUT:
635 	  case ECONNRESET:
636 		bp = buf;
637 #if HASSTRERROR
638 		snprintf(bp, SPACELEFT(buf, bp), "%s", strerror(errnum));
639 #else
640 		if (errnum >= 0 && errnum < sys_nerr)
641 			snprintf(bp, SPACELEFT(buf, bp), "%s", sys_errlist[errnum]);
642 		else
643 			snprintf(bp, SPACELEFT(buf, bp), "Error %d", errnum);
644 #endif
645 		bp += strlen(bp);
646 		if (CurHostName != NULL)
647 		{
648 			if (errnum == ETIMEDOUT)
649 			{
650 				snprintf(bp, SPACELEFT(buf, bp), " with ");
651 				bp += strlen(bp);
652 			}
653 			else
654 			{
655 				bp = buf;
656 				snprintf(bp, SPACELEFT(buf, bp),
657 					"Connection reset by ");
658 				bp += strlen(bp);
659 			}
660 			snprintf(bp, SPACELEFT(buf, bp), "%s",
661 				shortenstring(CurHostName, MAXSHORTSTR));
662 			bp += strlen(buf);
663 		}
664 		if (SmtpPhase != NULL)
665 		{
666 			snprintf(bp, SPACELEFT(buf, bp), " during %s",
667 				SmtpPhase);
668 		}
669 		return (buf);
670 
671 	  case EHOSTDOWN:
672 		if (CurHostName == NULL)
673 			break;
674 		(void) snprintf(buf, sizeof buf, "Host %s is down",
675 			shortenstring(CurHostName, MAXSHORTSTR));
676 		return (buf);
677 
678 	  case ECONNREFUSED:
679 		if (CurHostName == NULL)
680 			break;
681 		(void) snprintf(buf, sizeof buf, "Connection refused by %s",
682 			shortenstring(CurHostName, MAXSHORTSTR));
683 		return (buf);
684 # endif
685 
686 # if NAMED_BIND
687 	  case HOST_NOT_FOUND + E_DNSBASE:
688 		dnsmsg = "host not found";
689 		break;
690 
691 	  case TRY_AGAIN + E_DNSBASE:
692 		dnsmsg = "host name lookup failure";
693 		break;
694 
695 	  case NO_RECOVERY + E_DNSBASE:
696 		dnsmsg = "non-recoverable error";
697 		break;
698 
699 	  case NO_DATA + E_DNSBASE:
700 		dnsmsg = "no data known";
701 		break;
702 # endif
703 
704 	  case EPERM:
705 		/* SunOS gives "Not owner" -- this is the POSIX message */
706 		return "Operation not permitted";
707 
708 	/*
709 	**  Error messages used internally in sendmail.
710 	*/
711 
712 	  case E_SM_OPENTIMEOUT:
713 		return "Timeout on file open";
714 
715 	  case E_SM_NOSLINK:
716 		return "Symbolic links not allowed";
717 
718 	  case E_SM_NOHLINK:
719 		return "Hard links not allowed";
720 
721 	  case E_SM_REGONLY:
722 		return "Regular files only";
723 
724 	  case E_SM_ISEXEC:
725 		return "Executable files not allowed";
726 
727 	  case E_SM_WWDIR:
728 		return "World writable directory";
729 
730 	  case E_SM_GWDIR:
731 		return "Group writable directory";
732 
733 	  case E_SM_FILECHANGE:
734 		return "File changed after open";
735 
736 	  case E_SM_WWFILE:
737 		return "World writable file";
738 
739 	  case E_SM_GWFILE:
740 		return "Group writable file";
741 	}
742 
743 	if (dnsmsg != NULL)
744 	{
745 		bp = buf;
746 		strcpy(bp, "Name server: ");
747 		bp += strlen(bp);
748 		if (CurHostName != NULL)
749 		{
750 			snprintf(bp, SPACELEFT(buf, bp), "%s: ",
751 				shortenstring(CurHostName, MAXSHORTSTR));
752 			bp += strlen(bp);
753 		}
754 		snprintf(bp, SPACELEFT(buf, bp), "%s", dnsmsg);
755 		return buf;
756 	}
757 
758 #if HASSTRERROR
759 	return strerror(errnum);
760 #else
761 	if (errnum > 0 && errnum < sys_nerr)
762 		return (sys_errlist[errnum]);
763 
764 	(void) snprintf(buf, sizeof buf, "Error %d", errnum);
765 	return (buf);
766 #endif
767 }
768