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.30 (Berkeley) 04/13/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=", e->e_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 		e->e_sender = "Postmaster";
78 		if (parseaddr(e->e_sender, &e->e_from, 0, '\0', NULL, e) == NULL)
79 		{
80 			syserr("553 Cannot parse Postmaster!");
81 			ExitStat = EX_SOFTWARE;
82 			finis();
83 		}
84 	}
85 	e->e_to = NULL;
86 
87 	/*
88 	**  Basic state machine.
89 	**
90 	**	This machine runs through the following states:
91 	**
92 	**	ESM_QUIET	Errors have already been printed iff the
93 	**			sender is local.
94 	**	ESM_REPORT	Report directly to the sender's terminal.
95 	**	ESM_MAIL	Mail response to the sender.
96 	**	ESM_DEADLETTER	Save response in ~/dead.letter.
97 	**	ESM_POSTMASTER	Mail response to the postmaster.
98 	**	ESM_PANIC	Save response anywhere possible.
99 	*/
100 
101 	/* determine starting state */
102 	switch (e->e_errormode)
103 	{
104 	  case EM_WRITE:
105 		state = ESM_REPORT;
106 		break;
107 
108 	  case EM_BERKNET:
109 		/* mail back, but return o.k. exit status */
110 		ExitStat = EX_OK;
111 
112 		/* fall through.... */
113 
114 	  case EM_MAIL:
115 		state = ESM_MAIL;
116 		break;
117 
118 	  case EM_PRINT:
119 	  case '\0':
120 		state = ESM_QUIET;
121 		break;
122 
123 	  case EM_QUIET:
124 		/* no need to return anything at all */
125 		return;
126 
127 	  default:
128 		syserr("554 savemail: bogus errormode x%x\n", e->e_errormode);
129 		state = ESM_MAIL;
130 		break;
131 	}
132 
133 	while (state != ESM_DONE)
134 	{
135 		if (tTd(6, 5))
136 			printf("  state %d\n", state);
137 
138 		switch (state)
139 		{
140 		  case ESM_QUIET:
141 			if (e->e_from.q_mailer == LocalMailer)
142 				state = ESM_DEADLETTER;
143 			else
144 				state = ESM_MAIL;
145 			break;
146 
147 		  case ESM_REPORT:
148 
149 			/*
150 			**  If the user is still logged in on the same terminal,
151 			**  then write the error messages back to hir (sic).
152 			*/
153 
154 			p = ttypath();
155 			if (p == NULL || freopen(p, "w", stdout) == NULL)
156 			{
157 				state = ESM_MAIL;
158 				break;
159 			}
160 
161 			expand("\201n", buf, &buf[sizeof buf - 1], e);
162 			printf("\r\nMessage from %s...\r\n", buf);
163 			printf("Errors occurred while sending mail.\r\n");
164 			if (e->e_xfp != NULL)
165 			{
166 				(void) fflush(e->e_xfp);
167 				fp = fopen(queuename(e, 'x'), "r");
168 			}
169 			else
170 				fp = NULL;
171 			if (fp == NULL)
172 			{
173 				syserr("Cannot open %s", queuename(e, 'x'));
174 				printf("Transcript of session is unavailable.\r\n");
175 			}
176 			else
177 			{
178 				printf("Transcript follows:\r\n");
179 				while (fgets(buf, sizeof buf, fp) != NULL &&
180 				       !ferror(stdout))
181 					fputs(buf, stdout);
182 				(void) xfclose(fp, "savemail transcript", e->e_id);
183 			}
184 			printf("Original message will be saved in dead.letter.\r\n");
185 			state = ESM_DEADLETTER;
186 			break;
187 
188 		  case ESM_MAIL:
189 			/*
190 			**  If mailing back, do it.
191 			**	Throw away all further output.  Don't alias,
192 			**	since this could cause loops, e.g., if joe
193 			**	mails to joe@x, and for some reason the network
194 			**	for @x is down, then the response gets sent to
195 			**	joe@x, which gives a response, etc.  Also force
196 			**	the mail to be delivered even if a version of
197 			**	it has already been sent to the sender.
198 			*/
199 
200 			if (strcmp(e->e_from.q_paddr, "<>") != 0)
201 				(void) sendtolist(e->e_from.q_paddr,
202 					  (ADDRESS *) NULL,
203 					  &e->e_errorqueue, e);
204 
205 			/* deliver a cc: to the postmaster if desired */
206 			if (PostMasterCopy != NULL)
207 			{
208 				auto ADDRESS *rlist = NULL;
209 
210 				(void) sendtolist(PostMasterCopy,
211 						  (ADDRESS *) NULL,
212 						  &rlist, e);
213 				(void) returntosender(e->e_message,
214 						      rlist, FALSE, e);
215 			}
216 			q = e->e_errorqueue;
217 			if (q == NULL)
218 			{
219 				/* this is an error-error */
220 				state = ESM_POSTMASTER;
221 				break;
222 			}
223 			if (returntosender(e->e_message,
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 			q = parseaddr("postmaster", q, 0, '\0', NULL, e);
240 			if (q == NULL)
241 			{
242 				syserr("553 cannot parse postmaster!");
243 				ExitStat = EX_SOFTWARE;
244 				state = ESM_USRTMP;
245 				break;
246 			}
247 			if (returntosender(e->e_message,
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 				/* no local directory */
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 (msg == NULL)
388 		msg = "Unable to deliver mail";
389 
390 	if (tTd(6, 1))
391 	{
392 		printf("Return To Sender: msg=\"%s\", depth=%d, e=%x, returnq=",
393 		       msg, returndepth, e);
394 		printaddr(returnq, TRUE);
395 	}
396 
397 	if (++returndepth >= MAXRETURNS)
398 	{
399 		if (returndepth != MAXRETURNS)
400 			syserr("554 returntosender: infinite recursion on %s", returnq->q_paddr);
401 		/* don't "unrecurse" and fake a clean exit */
402 		/* returndepth--; */
403 		return (0);
404 	}
405 
406 	SendBody = sendbody;
407 	define('g', e->e_from.q_paddr, e);
408 	ee = newenvelope(&errenvelope, e);
409 	define('a', "\201b", ee);
410 	define('r', "internal", ee);
411 	define('s', "localhost", ee);
412 	define('_', "localhost", ee);
413 	ee->e_puthdr = putheader;
414 	ee->e_putbody = errbody;
415 	ee->e_flags |= EF_RESPONSE;
416 	if (!bitset(EF_OLDSTYLE, e->e_flags))
417 		ee->e_flags &= ~EF_OLDSTYLE;
418 	ee->e_sendqueue = returnq;
419 	ee->e_msgsize = e->e_msgsize + ERRORFUDGE;
420 	openxscript(ee);
421 	for (q = returnq; q != NULL; q = q->q_next)
422 	{
423 		if (bitset(QDONTSEND, q->q_flags))
424 			continue;
425 
426 		ee->e_nrcpts++;
427 
428 		if (!DontPruneRoutes && pruneroute(q->q_paddr))
429 			parseaddr(q->q_paddr, q, 0, '\0', NULL, e);
430 
431 		if (q->q_alias == NULL)
432 			addheader("to", q->q_paddr, ee);
433 	}
434 
435 # ifdef LOG
436 	if (LogLevel > 5)
437 		syslog(LOG_INFO, "%s: %s: return to sender: %s",
438 			e->e_id, ee->e_id, msg);
439 # endif
440 
441 	(void) sprintf(buf, "Returned mail: %s", msg);
442 	addheader("subject", buf, ee);
443 
444 	/* fake up an address header for the from person */
445 	expand("\201n", buf, &buf[sizeof buf - 1], e);
446 	if (parseaddr(buf, &ee->e_from, 1, '\0', NULL, e) == NULL)
447 	{
448 		syserr("553 Can't parse myself!");
449 		ExitStat = EX_SOFTWARE;
450 		returndepth--;
451 		return (-1);
452 	}
453 	ee->e_sender = ee->e_from.q_paddr;
454 
455 	/* push state into submessage */
456 	CurEnv = ee;
457 	define('f', "\201n", ee);
458 	define('x', "Mail Delivery Subsystem", ee);
459 	eatheader(ee, TRUE);
460 
461 	/* actually deliver the error message */
462 	sendall(ee, SM_DEFAULT);
463 
464 	/* restore state */
465 	dropenvelope(ee);
466 	CurEnv = oldcur;
467 	returndepth--;
468 
469 	/* should check for delivery errors here */
470 	return (0);
471 }
472 /*
473 **  ERRBODY -- output the body of an error message.
474 **
475 **	Typically this is a copy of the transcript plus a copy of the
476 **	original offending message.
477 **
478 **	Parameters:
479 **		fp -- the output file.
480 **		m -- the mailer to output to.
481 **		e -- the envelope we are working in.
482 **
483 **	Returns:
484 **		none
485 **
486 **	Side Effects:
487 **		Outputs the body of an error message.
488 */
489 
490 errbody(fp, m, e)
491 	register FILE *fp;
492 	register struct mailer *m;
493 	register ENVELOPE *e;
494 {
495 	register FILE *xfile;
496 	char buf[MAXLINE];
497 	char *p;
498 
499 	if (e->e_parent == NULL)
500 	{
501 		syserr("errbody: null parent");
502 		putline("\n", fp, m);
503 		putline("   ----- Original message lost -----\n", fp, m);
504 		return;
505 	}
506 
507 	/*
508 	**  Output error message header (if specified and available).
509 	*/
510 
511 	if (ErrMsgFile != NULL)
512 	{
513 		if (*ErrMsgFile == '/')
514 		{
515 			xfile = fopen(ErrMsgFile, "r");
516 			if (xfile != NULL)
517 			{
518 				while (fgets(buf, sizeof buf, xfile) != NULL)
519 				{
520 					expand(buf, buf, &buf[sizeof buf - 1], e);
521 					putline(buf, fp, m);
522 				}
523 				(void) fclose(xfile);
524 				fprintf(fp, "\n");
525 			}
526 		}
527 		else
528 		{
529 			expand(ErrMsgFile, buf, &buf[sizeof buf - 1], e);
530 			putline(buf, fp, m);
531 			fprintf(fp, "\n");
532 		}
533 	}
534 
535 	/*
536 	**  Output transcript of errors
537 	*/
538 
539 	(void) fflush(stdout);
540 	p = queuename(e->e_parent, 'x');
541 	if ((xfile = fopen(p, "r")) == NULL)
542 	{
543 		syserr("Cannot open %s", p);
544 		putline("  ----- Transcript of session is unavailable -----\n", fp, m);
545 	}
546 	else
547 	{
548 		putline("   ----- Transcript of session follows -----\n", fp, m);
549 		if (e->e_xfp != NULL)
550 			(void) fflush(e->e_xfp);
551 		while (fgets(buf, sizeof buf, xfile) != NULL)
552 			putline(buf, fp, m);
553 		(void) xfclose(xfile, "errbody xscript", p);
554 	}
555 	errno = 0;
556 
557 	/*
558 	**  Output text of original message
559 	*/
560 
561 	if (NoReturn)
562 		SendBody = FALSE;
563 	if (e->e_parent->e_df != NULL)
564 	{
565 		if (SendBody)
566 		{
567 			putline("\n", fp, m);
568 			putline("   ----- Unsent message follows -----\n", fp, m);
569 			(void) fflush(fp);
570 			putheader(fp, m, e->e_parent);
571 			putline("\n", fp, m);
572 			putbody(fp, m, e->e_parent);
573 		}
574 		else
575 		{
576 			putline("\n", fp, m);
577 			putline("  ----- Message header follows -----\n", fp, m);
578 			(void) fflush(fp);
579 			putheader(fp, m, e->e_parent);
580 		}
581 	}
582 	else
583 	{
584 		putline("\n", fp, m);
585 		putline("  ----- No message was collected -----\n", fp, m);
586 		putline("\n", fp, m);
587 	}
588 
589 	/*
590 	**  Cleanup and exit
591 	*/
592 
593 	if (errno != 0)
594 		syserr("errbody: I/O error");
595 }
596 /*
597 **  PRUNEROUTE -- prune an RFC-822 source route
598 **
599 **	Trims down a source route to the last internet-registered hop.
600 **	This is encouraged by RFC 1123 section 5.3.3.
601 **
602 **	Parameters:
603 **		addr -- the address
604 **
605 **	Returns:
606 **		TRUE -- address was modified
607 **		FALSE -- address could not be pruned
608 **
609 **	Side Effects:
610 **		modifies addr in-place
611 */
612 
613 pruneroute(addr)
614 	char *addr;
615 {
616 #ifdef NAMED_BIND
617 	char *start, *at, *comma;
618 	char c;
619 	int rcode;
620 	char hostbuf[BUFSIZ];
621 	char *mxhosts[MAXMXHOSTS + 1];
622 
623 	/* check to see if this is really a route-addr */
624 	if (*addr != '<' || addr[1] != '@' || addr[strlen(addr) - 1] != '>')
625 		return FALSE;
626 	start = strchr(addr, ':');
627 	at = strrchr(addr, '@');
628 	if (start == NULL || at == NULL || at < start)
629 		return FALSE;
630 
631 	/* slice off the angle brackets */
632 	strcpy(hostbuf, at + 1);
633 	hostbuf[strlen(hostbuf) - 1] = '\0';
634 
635 	while (start)
636 	{
637 		if (getmxrr(hostbuf, mxhosts, "", &rcode) > 0)
638 		{
639 			strcpy(addr + 1, start + 1);
640 			return TRUE;
641 		}
642 		c = *start;
643 		*start = '\0';
644 		comma = strrchr(addr, ',');
645 		if (comma && comma[1] == '@')
646 			strcpy(hostbuf, comma + 2);
647 		else
648 			comma = 0;
649 		*start = c;
650 		start = comma;
651 	}
652 #endif
653 	return FALSE;
654 }
655