1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988 Regents of the University of California.
4  * All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)savemail.c	6.22 (Berkeley) 03/16/93";
11 #endif /* not lint */
12 
13 # include <pwd.h>
14 # include "sendmail.h"
15 
16 /*
17 **  SAVEMAIL -- Save mail on error
18 **
19 **	If mailing back errors, mail it back to the originator
20 **	together with an error message; otherwise, just put it in
21 **	dead.letter in the user's home directory (if he exists on
22 **	this machine).
23 **
24 **	Parameters:
25 **		e -- the envelope containing the message in error.
26 **
27 **	Returns:
28 **		none
29 **
30 **	Side Effects:
31 **		Saves the letter, by writing or mailing it back to the
32 **		sender, or by putting it in dead.letter in her home
33 **		directory.
34 */
35 
36 /* defines for state machine */
37 # define ESM_REPORT	0	/* report to sender's terminal */
38 # define ESM_MAIL	1	/* mail back to sender */
39 # define ESM_QUIET	2	/* messages have already been returned */
40 # define ESM_DEADLETTER	3	/* save in ~/dead.letter */
41 # define ESM_POSTMASTER	4	/* return to postmaster */
42 # define ESM_USRTMP	5	/* save in /usr/tmp/dead.letter */
43 # define ESM_PANIC	6	/* leave the locked queue/transcript files */
44 # define ESM_DONE	7	/* the message is successfully delivered */
45 
46 
47 savemail(e)
48 	register ENVELOPE *e;
49 {
50 	register struct passwd *pw;
51 	register FILE *fp;
52 	int state;
53 	auto ADDRESS *q;
54 	char buf[MAXLINE+1];
55 	extern struct passwd *getpwnam();
56 	register char *p;
57 	extern char *ttypath();
58 	typedef int (*fnptr)();
59 
60 	if (tTd(6, 1))
61 	{
62 		printf("\nsavemail, ErrorMode = %c\n  e_from=", ErrorMode);
63 		printaddr(&e->e_from, FALSE);
64 	}
65 
66 	if (bitset(EF_RESPONSE, e->e_flags))
67 		return;
68 	e->e_flags &= ~EF_FATALERRS;
69 
70 	/*
71 	**  In the unhappy event we don't know who to return the mail
72 	**  to, make someone up.
73 	*/
74 
75 	if (e->e_from.q_paddr == NULL)
76 	{
77 		if (parseaddr("root", &e->e_from, 0, '\0', NULL, e) == NULL)
78 		{
79 			syserr("553 Cannot parse root!");
80 			ExitStat = EX_SOFTWARE;
81 			finis();
82 		}
83 	}
84 	e->e_to = NULL;
85 
86 	/*
87 	**  Basic state machine.
88 	**
89 	**	This machine runs through the following states:
90 	**
91 	**	ESM_QUIET	Errors have already been printed iff the
92 	**			sender is local.
93 	**	ESM_REPORT	Report directly to the sender's terminal.
94 	**	ESM_MAIL	Mail response to the sender.
95 	**	ESM_DEADLETTER	Save response in ~/dead.letter.
96 	**	ESM_POSTMASTER	Mail response to the postmaster.
97 	**	ESM_PANIC	Save response anywhere possible.
98 	*/
99 
100 	/* determine starting state */
101 	switch (ErrorMode)
102 	{
103 	  case EM_WRITE:
104 		state = ESM_REPORT;
105 		break;
106 
107 	  case EM_BERKNET:
108 		/* mail back, but return o.k. exit status */
109 		ExitStat = EX_OK;
110 
111 		/* fall through.... */
112 
113 	  case EM_MAIL:
114 		state = ESM_MAIL;
115 		break;
116 
117 	  case EM_PRINT:
118 	  case '\0':
119 		state = ESM_QUIET;
120 		break;
121 
122 	  case EM_QUIET:
123 		/* no need to return anything at all */
124 		return;
125 
126 	  default:
127 		syserr("554 savemail: ErrorMode x%x\n");
128 		state = ESM_MAIL;
129 		break;
130 	}
131 
132 	while (state != ESM_DONE)
133 	{
134 		if (tTd(6, 5))
135 			printf("  state %d\n", state);
136 
137 		switch (state)
138 		{
139 		  case ESM_QUIET:
140 			if (e->e_from.q_mailer == LocalMailer)
141 				state = ESM_DEADLETTER;
142 			else
143 				state = ESM_MAIL;
144 			break;
145 
146 		  case ESM_REPORT:
147 
148 			/*
149 			**  If the user is still logged in on the same terminal,
150 			**  then write the error messages back to hir (sic).
151 			*/
152 
153 			p = ttypath();
154 			if (p == NULL || freopen(p, "w", stdout) == NULL)
155 			{
156 				state = ESM_MAIL;
157 				break;
158 			}
159 
160 			expand("\201n", buf, &buf[sizeof buf - 1], e);
161 			printf("\r\nMessage from %s...\r\n", buf);
162 			printf("Errors occurred while sending mail.\r\n");
163 			if (e->e_xfp != NULL)
164 			{
165 				(void) fflush(e->e_xfp);
166 				fp = fopen(queuename(e, 'x'), "r");
167 			}
168 			else
169 				fp = NULL;
170 			if (fp == NULL)
171 			{
172 				syserr("Cannot open %s", queuename(e, 'x'));
173 				printf("Transcript of session is unavailable.\r\n");
174 			}
175 			else
176 			{
177 				printf("Transcript follows:\r\n");
178 				while (fgets(buf, sizeof buf, fp) != NULL &&
179 				       !ferror(stdout))
180 					fputs(buf, stdout);
181 				(void) xfclose(fp, "savemail transcript", e->e_id);
182 			}
183 			printf("Original message will be saved in dead.letter.\r\n");
184 			state = ESM_DEADLETTER;
185 			break;
186 
187 		  case ESM_MAIL:
188 			/*
189 			**  If mailing back, do it.
190 			**	Throw away all further output.  Don't alias,
191 			**	since this could cause loops, e.g., if joe
192 			**	mails to joe@x, and for some reason the network
193 			**	for @x is down, then the response gets sent to
194 			**	joe@x, which gives a response, etc.  Also force
195 			**	the mail to be delivered even if a version of
196 			**	it has already been sent to the sender.
197 			*/
198 
199 			if (strcmp(e->e_from.q_paddr, "<>") != 0)
200 				(void) sendtolist(e->e_from.q_paddr,
201 					  (ADDRESS *) NULL,
202 					  &e->e_errorqueue, e);
203 
204 			/* deliver a cc: to the postmaster if desired */
205 			if (PostMasterCopy != NULL)
206 			{
207 				auto ADDRESS *rlist = NULL;
208 
209 				(void) sendtolist(PostMasterCopy,
210 						  (ADDRESS *) NULL,
211 						  &rlist, e);
212 				(void) returntosender(e->e_message,
213 						      rlist, FALSE, e);
214 			}
215 			q = e->e_errorqueue;
216 			if (q == NULL)
217 			{
218 				/* this is an error-error */
219 				state = ESM_POSTMASTER;
220 				break;
221 			}
222 			if (returntosender(e->e_message != NULL ? e->e_message :
223 					   "Unable to deliver mail",
224 					   q, (e->e_class >= 0), e) == 0)
225 			{
226 				state = ESM_DONE;
227 				break;
228 			}
229 
230 			/* didn't work -- return to postmaster */
231 			state = ESM_POSTMASTER;
232 			break;
233 
234 		  case ESM_POSTMASTER:
235 			/*
236 			**  Similar to previous case, but to system postmaster.
237 			*/
238 
239 			if (parseaddr("postmaster", q, 0, '\0', NULL, e) == NULL)
240 			{
241 				syserr("553 cannot parse postmaster!");
242 				ExitStat = EX_SOFTWARE;
243 				state = ESM_USRTMP;
244 				break;
245 			}
246 			if (returntosender(e->e_message != NULL ? e->e_message :
247 					   "Unable to deliver mail",
248 					   q, (e->e_class >= 0), e) == 0)
249 			{
250 				state = ESM_DONE;
251 				break;
252 			}
253 
254 			/* didn't work -- last resort */
255 			state = ESM_USRTMP;
256 			break;
257 
258 		  case ESM_DEADLETTER:
259 			/*
260 			**  Save the message in dead.letter.
261 			**	If we weren't mailing back, and the user is
262 			**	local, we should save the message in
263 			**	~/dead.letter so that the poor person doesn't
264 			**	have to type it over again -- and we all know
265 			**	what poor typists UNIX users are.
266 			*/
267 
268 			p = NULL;
269 			if (e->e_from.q_mailer == LocalMailer)
270 			{
271 				if (e->e_from.q_home != NULL)
272 					p = e->e_from.q_home;
273 				else if ((pw = getpwnam(e->e_from.q_user)) != NULL)
274 					p = pw->pw_dir;
275 			}
276 			if (p == NULL)
277 			{
278 				syserr("554 Can't return mail to %s", e->e_from.q_paddr);
279 				state = ESM_MAIL;
280 				break;
281 			}
282 			if (e->e_dfp != NULL)
283 			{
284 				auto ADDRESS *q;
285 				bool oldverb = Verbose;
286 
287 				/* we have a home directory; open dead.letter */
288 				define('z', p, e);
289 				expand("\201z/dead.letter", buf, &buf[sizeof buf - 1], e);
290 				Verbose = TRUE;
291 				message("Saving message in %s", buf);
292 				Verbose = oldverb;
293 				e->e_to = buf;
294 				q = NULL;
295 				(void) sendtolist(buf, &e->e_from, &q, e);
296 				if (deliver(e, q) == 0)
297 					state = ESM_DONE;
298 				else
299 					state = ESM_MAIL;
300 			}
301 			else
302 			{
303 				/* no data file -- try mailing back */
304 				state = ESM_MAIL;
305 			}
306 			break;
307 
308 		  case ESM_USRTMP:
309 			/*
310 			**  Log the mail in /usr/tmp/dead.letter.
311 			*/
312 
313 			if (e->e_class < 0)
314 			{
315 				state = ESM_DONE;
316 				break;
317 			}
318 
319 			fp = dfopen("/usr/tmp/dead.letter", "a");
320 			if (fp == NULL)
321 			{
322 				state = ESM_PANIC;
323 				break;
324 			}
325 
326 			putfromline(fp, FileMailer, e);
327 			(*e->e_puthdr)(fp, FileMailer, e);
328 			putline("\n", fp, FileMailer);
329 			(*e->e_putbody)(fp, FileMailer, e);
330 			putline("\n", fp, FileMailer);
331 			(void) fflush(fp);
332 			state = ferror(fp) ? ESM_PANIC : ESM_DONE;
333 			(void) xfclose(fp, "savemail", "/usr/tmp/dead.letter");
334 			break;
335 
336 		  default:
337 			syserr("554 savemail: unknown state %d", state);
338 
339 			/* fall through ... */
340 
341 		  case ESM_PANIC:
342 			/* leave the locked queue & transcript files around */
343 			syserr("554 savemail: cannot save rejected email anywhere");
344 			exit(EX_SOFTWARE);
345 		}
346 	}
347 }
348 /*
349 **  RETURNTOSENDER -- return a message to the sender with an error.
350 **
351 **	Parameters:
352 **		msg -- the explanatory message.
353 **		returnq -- the queue of people to send the message to.
354 **		sendbody -- if TRUE, also send back the body of the
355 **			message; otherwise just send the header.
356 **		e -- the current envelope.
357 **
358 **	Returns:
359 **		zero -- if everything went ok.
360 **		else -- some error.
361 **
362 **	Side Effects:
363 **		Returns the current message to the sender via
364 **		mail.
365 */
366 
367 static bool	SendBody;
368 
369 #define MAXRETURNS	6	/* max depth of returning messages */
370 #define ERRORFUDGE	100	/* nominal size of error message text */
371 
372 returntosender(msg, returnq, sendbody, e)
373 	char *msg;
374 	ADDRESS *returnq;
375 	bool sendbody;
376 	register ENVELOPE *e;
377 {
378 	char buf[MAXNAME];
379 	extern putheader(), errbody();
380 	register ENVELOPE *ee;
381 	ENVELOPE *oldcur = CurEnv;
382 	extern ENVELOPE *newenvelope();
383 	ENVELOPE errenvelope;
384 	static int returndepth;
385 	register ADDRESS *q;
386 
387 	if (tTd(6, 1))
388 	{
389 		printf("Return To Sender: msg=\"%s\", depth=%d, e=%x, returnq=",
390 		       msg, returndepth, e);
391 		printaddr(returnq, TRUE);
392 	}
393 
394 	if (++returndepth >= MAXRETURNS)
395 	{
396 		if (returndepth != MAXRETURNS)
397 			syserr("554 returntosender: infinite recursion on %s", returnq->q_paddr);
398 		/* don't "unrecurse" and fake a clean exit */
399 		/* returndepth--; */
400 		return (0);
401 	}
402 
403 	SendBody = sendbody;
404 	define('g', e->e_from.q_paddr, e);
405 	ee = newenvelope(&errenvelope, e);
406 	define('a', "\201b", ee);
407 	ee->e_puthdr = putheader;
408 	ee->e_putbody = errbody;
409 	ee->e_flags |= EF_RESPONSE;
410 	if (!bitset(EF_OLDSTYLE, e->e_flags))
411 		ee->e_flags &= ~EF_OLDSTYLE;
412 	ee->e_sendqueue = returnq;
413 	ee->e_msgsize = e->e_msgsize + ERRORFUDGE;
414 	openxscript(ee);
415 	for (q = returnq; q != NULL; q = q->q_next)
416 	{
417 		if (bitset(QDONTSEND, q->q_flags))
418 			continue;
419 
420 		ee->e_nrcpts++;
421 
422 		if (!DontPruneRoutes && pruneroute(q->q_paddr))
423 			parseaddr(q->q_paddr, q, 0, '\0', NULL, e);
424 
425 		if (q->q_alias == NULL)
426 			addheader("to", q->q_paddr, ee);
427 	}
428 
429 # ifdef LOG
430 	if (LogLevel > 5)
431 		syslog(LOG_INFO, "%s: %s: return to sender: %s",
432 			e->e_id, ee->e_id, msg);
433 # endif
434 
435 	(void) sprintf(buf, "Returned mail: %s", msg);
436 	addheader("subject", buf, ee);
437 
438 	/* fake up an address header for the from person */
439 	expand("\201n", buf, &buf[sizeof buf - 1], e);
440 	if (parseaddr(buf, &ee->e_from, 1, '\0', NULL, e) == NULL)
441 	{
442 		syserr("553 Can't parse myself!");
443 		ExitStat = EX_SOFTWARE;
444 		returndepth--;
445 		return (-1);
446 	}
447 
448 	/* push state into submessage */
449 	CurEnv = ee;
450 	define('f', "\201n", ee);
451 	define('x', "Mail Delivery Subsystem", ee);
452 	eatheader(ee, FALSE);
453 
454 	/* actually deliver the error message */
455 	sendall(ee, SM_DEFAULT);
456 
457 	/* restore state */
458 	dropenvelope(ee);
459 	CurEnv = oldcur;
460 	returndepth--;
461 
462 	/* should check for delivery errors here */
463 	return (0);
464 }
465 /*
466 **  ERRBODY -- output the body of an error message.
467 **
468 **	Typically this is a copy of the transcript plus a copy of the
469 **	original offending message.
470 **
471 **	Parameters:
472 **		fp -- the output file.
473 **		m -- the mailer to output to.
474 **		e -- the envelope we are working in.
475 **
476 **	Returns:
477 **		none
478 **
479 **	Side Effects:
480 **		Outputs the body of an error message.
481 */
482 
483 errbody(fp, m, e)
484 	register FILE *fp;
485 	register struct mailer *m;
486 	register ENVELOPE *e;
487 {
488 	register FILE *xfile;
489 	char buf[MAXLINE];
490 	char *p;
491 
492 	if (e->e_parent == NULL)
493 	{
494 		syserr("errbody: null parent");
495 		putline("\n", fp, m);
496 		putline("   ----- Original message lost -----\n", fp, m);
497 		return;
498 	}
499 
500 	/*
501 	**  Output error message header (if specified and available).
502 	*/
503 
504 	if (ErrMsgFile != NULL)
505 	{
506 		if (*ErrMsgFile == '/')
507 		{
508 			xfile = fopen(ErrMsgFile, "r");
509 			if (xfile != NULL)
510 			{
511 				while (fgets(buf, sizeof buf, xfile) != NULL)
512 				{
513 					expand(buf, buf, &buf[sizeof buf - 1], e);
514 					putline(buf, fp, m);
515 				}
516 				(void) fclose(xfile);
517 				fprintf(fp, "\n");
518 			}
519 		}
520 		else
521 		{
522 			expand(ErrMsgFile, buf, &buf[sizeof buf - 1], e);
523 			putline(buf, fp, m);
524 			fprintf(fp, "\n");
525 		}
526 	}
527 
528 	/*
529 	**  Output transcript of errors
530 	*/
531 
532 	(void) fflush(stdout);
533 	p = queuename(e->e_parent, 'x');
534 	if ((xfile = fopen(p, "r")) == NULL)
535 	{
536 		syserr("Cannot open %s", p);
537 		putline("  ----- Transcript of session is unavailable -----\n", fp, m);
538 	}
539 	else
540 	{
541 		putline("   ----- Transcript of session follows -----\n", fp, m);
542 		if (e->e_xfp != NULL)
543 			(void) fflush(e->e_xfp);
544 		while (fgets(buf, sizeof buf, xfile) != NULL)
545 			putline(buf, fp, m);
546 		(void) xfclose(xfile, "errbody xscript", p);
547 	}
548 	errno = 0;
549 
550 	/*
551 	**  Output text of original message
552 	*/
553 
554 	if (NoReturn)
555 		SendBody = FALSE;
556 	if (e->e_parent->e_df != NULL)
557 	{
558 		if (SendBody)
559 		{
560 			putline("\n", fp, m);
561 			putline("   ----- Unsent message follows -----\n", fp, m);
562 			(void) fflush(fp);
563 			putheader(fp, m, e->e_parent);
564 			putline("\n", fp, m);
565 			putbody(fp, m, e->e_parent);
566 		}
567 		else
568 		{
569 			putline("\n", fp, m);
570 			putline("  ----- Message header follows -----\n", fp, m);
571 			(void) fflush(fp);
572 			putheader(fp, m, e->e_parent);
573 		}
574 	}
575 	else
576 	{
577 		putline("\n", fp, m);
578 		putline("  ----- No message was collected -----\n", fp, m);
579 		putline("\n", fp, m);
580 	}
581 
582 	/*
583 	**  Cleanup and exit
584 	*/
585 
586 	if (errno != 0)
587 		syserr("errbody: I/O error");
588 }
589 /*
590 **  PRUNEROUTE -- prune an RFC-822 source route
591 **
592 **	Trims down a source route to the last internet-registered hop.
593 **	This is encouraged by RFC 1123 section 5.3.3.
594 **
595 **	Parameters:
596 **		addr -- the address
597 **
598 **	Returns:
599 **		TRUE -- address was modified
600 **		FALSE -- address could not be pruned
601 **
602 **	Side Effects:
603 **		modifies addr in-place
604 */
605 
606 pruneroute(addr)
607 	char *addr;
608 {
609 #ifdef NAMED_BIND
610 	char *start, *at, *comma;
611 	char c;
612 	int rcode;
613 	char hostbuf[BUFSIZ];
614 	char *mxhosts[MAXMXHOSTS + 1];
615 
616 	/* check to see if this is really a route-addr */
617 	if (*addr != '<' || addr[1] != '@' || addr[strlen(addr) - 1] != '>')
618 		return FALSE;
619 	start = strchr(addr, ':');
620 	at = strrchr(addr, '@');
621 	if (start == NULL || at == NULL || at < start)
622 		return FALSE;
623 
624 	/* slice off the angle brackets */
625 	strcpy(hostbuf, at + 1);
626 	hostbuf[strlen(hostbuf) - 1] = '\0';
627 
628 	while (start)
629 	{
630 		if (getmxrr(hostbuf, mxhosts, "", &rcode) > 0)
631 		{
632 			strcpy(addr + 1, start + 1);
633 			return TRUE;
634 		}
635 		c = *start;
636 		*start = '\0';
637 		comma = strrchr(addr, ',');
638 		if (comma && comma[1] == '@')
639 			strcpy(hostbuf, comma + 2);
640 		else
641 			comma = 0;
642 		*start = c;
643 		start = comma;
644 	}
645 #endif
646 	return FALSE;
647 }
648