xref: /csrg-svn/usr.sbin/sendmail/src/err.c (revision 68692)
1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)err.c	8.31 (Berkeley) 03/31/95";
11 #endif /* not lint */
12 
13 # include "sendmail.h"
14 # include <errno.h>
15 # include <netdb.h>
16 # include <pwd.h>
17 
18 /*
19 **  SYSERR -- Print error message.
20 **
21 **	Prints an error message via printf to the diagnostic
22 **	output.  If LOG is defined, it logs it also.
23 **
24 **	If the first character of the syserr message is `!' it will
25 **	log this as an ALERT message and exit immediately.  This can
26 **	leave queue files in an indeterminate state, so it should not
27 **	be used lightly.
28 **
29 **	Parameters:
30 **		fmt -- the format string.  If it does not begin with
31 **			a three-digit SMTP reply code, either 554 or
32 **			451 is assumed depending on whether errno
33 **			is set.
34 **		(others) -- parameters
35 **
36 **	Returns:
37 **		none
38 **		Through TopFrame if QuickAbort is set.
39 **
40 **	Side Effects:
41 **		increments Errors.
42 **		sets ExitStat.
43 */
44 
45 char	MsgBuf[BUFSIZ*2];		/* text of most recent message */
46 char	HeldMessageBuf[sizeof MsgBuf];	/* for held messages */
47 
48 static void	fmtmsg();
49 
50 #if NAMED_BIND && !defined(NO_DATA)
51 # define NO_DATA	NO_ADDRESS
52 #endif
53 
54 void
55 /*VARARGS1*/
56 #ifdef __STDC__
57 syserr(const char *fmt, ...)
58 #else
59 syserr(fmt, va_alist)
60 	const char *fmt;
61 	va_dcl
62 #endif
63 {
64 	register char *p;
65 	int olderrno = errno;
66 	bool panic;
67 #ifdef LOG
68 	char *uname;
69 	struct passwd *pw;
70 	char ubuf[80];
71 #endif
72 	VA_LOCAL_DECL
73 
74 	panic = *fmt == '!';
75 	if (panic)
76 		fmt++;
77 
78 	/* format and output the error message */
79 	if (olderrno == 0)
80 		p = "554";
81 	else
82 		p = "451";
83 	VA_START(fmt);
84 	fmtmsg(MsgBuf, (char *) NULL, p, olderrno, fmt, ap);
85 	VA_END;
86 	puterrmsg(MsgBuf);
87 
88 	/* save this message for mailq printing */
89 	if (!panic)
90 	{
91 		if (CurEnv->e_message != NULL)
92 			free(CurEnv->e_message);
93 		CurEnv->e_message = newstr(MsgBuf + 4);
94 	}
95 
96 	/* determine exit status if not already set */
97 	if (ExitStat == EX_OK)
98 	{
99 		if (olderrno == 0)
100 			ExitStat = EX_SOFTWARE;
101 		else
102 			ExitStat = EX_OSERR;
103 		if (tTd(54, 1))
104 			printf("syserr: ExitStat = %d\n", ExitStat);
105 	}
106 
107 # ifdef LOG
108 	pw = getpwuid(getuid());
109 	if (pw != NULL)
110 		uname = pw->pw_name;
111 	else
112 	{
113 		uname = ubuf;
114 		sprintf(ubuf, "UID%d", getuid());
115 	}
116 
117 	if (LogLevel > 0)
118 		syslog(panic ? LOG_ALERT : LOG_CRIT, "%s: SYSERR(%s): %s",
119 			CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id,
120 			uname, &MsgBuf[4]);
121 # endif /* LOG */
122 	if (olderrno == EMFILE)
123 	{
124 		printopenfds(TRUE);
125 		mci_dump_all(TRUE);
126 	}
127 	if (panic)
128 	{
129 #ifdef XLA
130 		xla_all_end();
131 #endif
132 		exit(EX_OSERR);
133 	}
134 	errno = 0;
135 	if (QuickAbort)
136 		longjmp(TopFrame, 2);
137 }
138 /*
139 **  USRERR -- Signal user error.
140 **
141 **	This is much like syserr except it is for user errors.
142 **
143 **	Parameters:
144 **		fmt -- the format string.  If it does not begin with
145 **			a three-digit SMTP reply code, 501 is assumed.
146 **		(others) -- printf strings
147 **
148 **	Returns:
149 **		none
150 **		Through TopFrame if QuickAbort is set.
151 **
152 **	Side Effects:
153 **		increments Errors.
154 */
155 
156 /*VARARGS1*/
157 void
158 #ifdef __STDC__
159 usrerr(const char *fmt, ...)
160 #else
161 usrerr(fmt, va_alist)
162 	const char *fmt;
163 	va_dcl
164 #endif
165 {
166 	VA_LOCAL_DECL
167 
168 	if (SuprErrs)
169 		return;
170 
171 	VA_START(fmt);
172 	fmtmsg(MsgBuf, CurEnv->e_to, "501", 0, fmt, ap);
173 	VA_END;
174 	puterrmsg(MsgBuf);
175 
176 	/* save this message for mailq printing */
177 	if (MsgBuf[0] == '5' || (CurEnv->e_message == NULL && MsgBuf[0] == '4'))
178 	{
179 		if (CurEnv->e_message != NULL)
180 			free(CurEnv->e_message);
181 		CurEnv->e_message = newstr(MsgBuf + 4);
182 	}
183 
184 # ifdef LOG
185 	if (LogLevel > 3 && LogUsrErrs)
186 		syslog(LOG_NOTICE, "%s: %s",
187 			CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id,
188 			&MsgBuf[4]);
189 # endif /* LOG */
190 
191 	if (QuickAbort)
192 		longjmp(TopFrame, 1);
193 }
194 /*
195 **  MESSAGE -- print message (not necessarily an error)
196 **
197 **	Parameters:
198 **		msg -- the message (printf fmt) -- it can begin with
199 **			an SMTP reply code.  If not, 050 is assumed.
200 **		(others) -- printf arguments
201 **
202 **	Returns:
203 **		none
204 **
205 **	Side Effects:
206 **		none.
207 */
208 
209 /*VARARGS2*/
210 void
211 #ifdef __STDC__
212 message(const char *msg, ...)
213 #else
214 message(msg, va_alist)
215 	const char *msg;
216 	va_dcl
217 #endif
218 {
219 	VA_LOCAL_DECL
220 
221 	errno = 0;
222 	VA_START(msg);
223 	fmtmsg(MsgBuf, CurEnv->e_to, "050", 0, msg, ap);
224 	VA_END;
225 	putoutmsg(MsgBuf, FALSE, FALSE);
226 
227 	/* save this message for mailq printing */
228 	if (MsgBuf[0] == '5' || (CurEnv->e_message == NULL && MsgBuf[0] == '4'))
229 	{
230 		if (CurEnv->e_message != NULL)
231 			free(CurEnv->e_message);
232 		CurEnv->e_message = newstr(MsgBuf + 4);
233 	}
234 }
235 /*
236 **  NMESSAGE -- print message (not necessarily an error)
237 **
238 **	Just like "message" except it never puts the to... tag on.
239 **
240 **	Parameters:
241 **		msg -- the message (printf fmt) -- if it begins
242 **			with a three digit SMTP reply code, that is used,
243 **			otherwise 050 is assumed.
244 **		(others) -- printf arguments
245 **
246 **	Returns:
247 **		none
248 **
249 **	Side Effects:
250 **		none.
251 */
252 
253 /*VARARGS2*/
254 void
255 #ifdef __STDC__
256 nmessage(const char *msg, ...)
257 #else
258 nmessage(msg, va_alist)
259 	const char *msg;
260 	va_dcl
261 #endif
262 {
263 	VA_LOCAL_DECL
264 
265 	errno = 0;
266 	VA_START(msg);
267 	fmtmsg(MsgBuf, (char *) NULL, "050", 0, msg, ap);
268 	VA_END;
269 	putoutmsg(MsgBuf, FALSE, FALSE);
270 }
271 /*
272 **  PUTOUTMSG -- output error message to transcript and channel
273 **
274 **	Parameters:
275 **		msg -- message to output (in SMTP format).
276 **		holdmsg -- if TRUE, don't output a copy of the message to
277 **			our output channel.
278 **		heldmsg -- if TRUE, this is a previously held message;
279 **			don't log it to the transcript file.
280 **
281 **	Returns:
282 **		none.
283 **
284 **	Side Effects:
285 **		Outputs msg to the transcript.
286 **		If appropriate, outputs it to the channel.
287 **		Deletes SMTP reply code number as appropriate.
288 */
289 
290 putoutmsg(msg, holdmsg, heldmsg)
291 	char *msg;
292 	bool holdmsg;
293 	bool heldmsg;
294 {
295 	/* display for debugging */
296 	if (tTd(54, 8))
297 		printf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
298 			heldmsg ? " (held)" : "");
299 
300 	/* output to transcript if serious */
301 	if (!heldmsg && CurEnv->e_xfp != NULL && strchr("456", msg[0]) != NULL)
302 		fprintf(CurEnv->e_xfp, "%s\n", msg);
303 
304 	/* output to channel if appropriate */
305 	if (!Verbose && msg[0] == '0')
306 		return;
307 	if (holdmsg)
308 	{
309 		/* save for possible future display */
310 		strcpy(HeldMessageBuf, msg);
311 		return;
312 	}
313 
314 	/* map warnings to something SMTP can handle */
315 	if (msg[0] == '6')
316 		msg[0] = '5';
317 
318 	(void) fflush(stdout);
319 
320 	/* if DisConnected, OutChannel now points to the transcript */
321 	if (!DisConnected &&
322 	    (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
323 		fprintf(OutChannel, "%s\r\n", msg);
324 	else
325 		fprintf(OutChannel, "%s\n", &msg[4]);
326 	if (TrafficLogFile != NULL)
327 		fprintf(TrafficLogFile, "%05d >>> %s\n", getpid(),
328 			(OpMode == MD_SMTP || OpMode == MD_DAEMON) ? msg : &msg[4]);
329 	if (msg[3] == ' ')
330 		(void) fflush(OutChannel);
331 	if (!ferror(OutChannel) || DisConnected)
332 		return;
333 
334 	/*
335 	**  Error on output -- if reporting lost channel, just ignore it.
336 	**  Also, ignore errors from QUIT response (221 message) -- some
337 	**	rude servers don't read result.
338 	*/
339 
340 	if (feof(InChannel) || ferror(InChannel) || strncmp(msg, "221", 3) == 0)
341 		return;
342 
343 	/* can't call syserr, 'cause we are using MsgBuf */
344 	HoldErrs = TRUE;
345 #ifdef LOG
346 	if (LogLevel > 0)
347 		syslog(LOG_CRIT,
348 			"%s: SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
349 			CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id,
350 			CurHostName == NULL ? "NO-HOST" : CurHostName,
351 			msg, errstring(errno));
352 #endif
353 }
354 /*
355 **  PUTERRMSG -- like putoutmsg, but does special processing for error messages
356 **
357 **	Parameters:
358 **		msg -- the message to output.
359 **
360 **	Returns:
361 **		none.
362 **
363 **	Side Effects:
364 **		Sets the fatal error bit in the envelope as appropriate.
365 */
366 
367 puterrmsg(msg)
368 	char *msg;
369 {
370 	char msgcode = msg[0];
371 
372 	/* output the message as usual */
373 	putoutmsg(msg, HoldErrs, FALSE);
374 
375 	/* signal the error */
376 	Errors++;
377 	if (msgcode == '6')
378 	{
379 		/* notify the postmaster */
380 		CurEnv->e_flags |= EF_PM_NOTIFY;
381 	}
382 	else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
383 	{
384 		/* mark long-term fatal errors */
385 		CurEnv->e_flags |= EF_FATALERRS;
386 	}
387 }
388 /*
389 **  FMTMSG -- format a message into buffer.
390 **
391 **	Parameters:
392 **		eb -- error buffer to get result.
393 **		to -- the recipient tag for this message.
394 **		num -- arpanet error number.
395 **		en -- the error number to display.
396 **		fmt -- format of string.
397 **		a, b, c, d, e -- arguments.
398 **
399 **	Returns:
400 **		none.
401 **
402 **	Side Effects:
403 **		none.
404 */
405 
406 static void
407 fmtmsg(eb, to, num, eno, fmt, ap)
408 	register char *eb;
409 	char *to;
410 	char *num;
411 	int eno;
412 	char *fmt;
413 	va_list ap;
414 {
415 	char del;
416 	char *meb;
417 
418 	/* output the reply code */
419 	if (isdigit(fmt[0]) && isdigit(fmt[1]) && isdigit(fmt[2]))
420 	{
421 		num = fmt;
422 		fmt += 4;
423 	}
424 	if (num[3] == '-')
425 		del = '-';
426 	else
427 		del = ' ';
428 	(void) sprintf(eb, "%3.3s%c", num, del);
429 	eb += 4;
430 
431 	/* output the file name and line number */
432 	if (FileName != NULL)
433 	{
434 		(void) sprintf(eb, "%s: line %d: ", FileName, LineNumber);
435 		eb += strlen(eb);
436 	}
437 
438 	/* output the "to" person */
439 	if (to != NULL && to[0] != '\0')
440 	{
441 		(void) sprintf(eb, "%s... ", shortenstring(to, 203));
442 		while (*eb != '\0')
443 			*eb++ &= 0177;
444 	}
445 
446 	meb = eb;
447 
448 	/* output the message */
449 	(void) vsprintf(eb, fmt, ap);
450 	while (*eb != '\0')
451 		*eb++ &= 0177;
452 
453 	/* output the error code, if any */
454 	if (eno != 0)
455 	{
456 		(void) sprintf(eb, ": %s", errstring(eno));
457 		eb += strlen(eb);
458 	}
459 }
460 /*
461 **  BUFFER_ERRORS -- arrange to buffer future error messages
462 **
463 **	Parameters:
464 **		none
465 **
466 **	Returns:
467 **		none.
468 */
469 
470 void
471 buffer_errors()
472 {
473 	HeldMessageBuf[0] = '\0';
474 	HoldErrs = TRUE;
475 }
476 /*
477 **  FLUSH_ERRORS -- flush the held error message buffer
478 **
479 **	Parameters:
480 **		print -- if set, print the message, otherwise just
481 **			delete it.
482 **
483 **	Returns:
484 **		none.
485 */
486 
487 void
488 flush_errors(print)
489 	bool print;
490 {
491 	if (print && HeldMessageBuf[0] != '\0')
492 		putoutmsg(HeldMessageBuf, FALSE, TRUE);
493 	HeldMessageBuf[0] = '\0';
494 	HoldErrs = FALSE;
495 }
496 /*
497 **  ERRSTRING -- return string description of error code
498 **
499 **	Parameters:
500 **		errnum -- the error number to translate
501 **
502 **	Returns:
503 **		A string description of errnum.
504 **
505 **	Side Effects:
506 **		none.
507 */
508 
509 const char *
510 errstring(errnum)
511 	int errnum;
512 {
513 	char *dnsmsg;
514 	static char buf[MAXLINE];
515 # ifndef ERRLIST_PREDEFINED
516 	extern char *sys_errlist[];
517 	extern int sys_nerr;
518 # endif
519 # ifdef SMTP
520 	extern char *SmtpPhase;
521 # endif /* SMTP */
522 
523 	/*
524 	**  Handle special network error codes.
525 	**
526 	**	These are 4.2/4.3bsd specific; they should be in daemon.c.
527 	*/
528 
529 	dnsmsg = NULL;
530 	switch (errnum)
531 	{
532 # if defined(DAEMON) && defined(ETIMEDOUT)
533 	  case ETIMEDOUT:
534 	  case ECONNRESET:
535 		(void) strcpy(buf, sys_errlist[errnum]);
536 		if (SmtpPhase != NULL)
537 		{
538 			(void) strcat(buf, " during ");
539 			(void) strcat(buf, SmtpPhase);
540 		}
541 		if (CurHostName != NULL)
542 		{
543 			(void) strcat(buf, " with ");
544 			(void) strcat(buf, CurHostName);
545 		}
546 		return (buf);
547 
548 	  case EHOSTDOWN:
549 		if (CurHostName == NULL)
550 			break;
551 		(void) sprintf(buf, "Host %s is down", CurHostName);
552 		return (buf);
553 
554 	  case ECONNREFUSED:
555 		if (CurHostName == NULL)
556 			break;
557 		(void) sprintf(buf, "Connection refused by %s", CurHostName);
558 		return (buf);
559 # endif
560 
561 	  case EOPENTIMEOUT:
562 		return "Timeout on file open";
563 
564 # if NAMED_BIND
565 	  case HOST_NOT_FOUND + E_DNSBASE:
566 		dnsmsg = "host not found";
567 		break;
568 
569 	  case TRY_AGAIN + E_DNSBASE:
570 		dnsmsg = "host name lookup failure";
571 		break;
572 
573 	  case NO_RECOVERY + E_DNSBASE:
574 		dnsmsg = "non-recoverable error";
575 		break;
576 
577 	  case NO_DATA + E_DNSBASE:
578 		dnsmsg = "no data known";
579 		break;
580 # endif
581 
582 	  case EPERM:
583 		/* SunOS gives "Not owner" -- this is the POSIX message */
584 		return "Operation not permitted";
585 	}
586 
587 	if (dnsmsg != NULL)
588 	{
589 		(void) strcpy(buf, "Name server: ");
590 		if (CurHostName != NULL)
591 		{
592 			(void) strcat(buf, CurHostName);
593 			(void) strcat(buf, ": ");
594 		}
595 		(void) strcat(buf, dnsmsg);
596 		return buf;
597 	}
598 
599 	if (errnum > 0 && errnum < sys_nerr)
600 		return (sys_errlist[errnum]);
601 
602 	(void) sprintf(buf, "Error %d", errnum);
603 	return (buf);
604 }
605