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