1 /*	$NetBSD: bounce_notify_util.c,v 1.1.1.1 2009/06/23 10:08:42 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	bounce_notify_util 3
6 /* SUMMARY
7 /*	send non-delivery report to sender, server side
8 /* SYNOPSIS
9 /*	#include "bounce_service.h"
10 /*
11 /*	typedef struct {
12 /* .in +4
13 /*		/* All private members... */
14 /* .in -4
15 /*	} BOUNCE_INFO;
16 /*
17 /*	BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id,
18 /*					encoding, dsn_envid, template)
19 /*	const char *service;
20 /*	const char *queue_name;
21 /*	const char *queue_id;
22 /*	const char *encoding;
23 /*	const char *dsn_envid;
24 /*	const BOUNCE_TEMPLATE *template;
25 /*
26 /*	BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding,
27 /*					dsn_envid, dsn_notify, rcpt_buf,
28 /*					dsn_buf, template)
29 /*	const char *queue_name;
30 /*	const char *queue_id;
31 /*	const char *encoding;
32 /*	int	dsn_notify;
33 /*	const char *dsn_envid;
34 /*	RCPT_BUF *rcpt_buf;
35 /*	DSN_BUF	*dsn_buf;
36 /*	const BOUNCE_TEMPLATE *template;
37 /*
38 /*	void	bounce_mail_free(bounce_info)
39 /*	BOUNCE_INFO *bounce_info;
40 /*
41 /*	int	bounce_header(fp, bounce_info, recipient, postmaster_copy)
42 /*	VSTREAM *fp;
43 /*	BOUNCE_INFO *bounce_info;
44 /*	const char *recipient;
45 /*	int	postmaster_copy;
46 /*
47 /*	int	bounce_boilerplate(fp, bounce_info)
48 /*	VSTREAM *fp;
49 /*	BOUNCE_INFO *bounce_info;
50 /*
51 /*	int     bounce_recipient_log(fp, bounce_info)
52 /*	VSTREAM *fp;
53 /*	BOUNCE_INFO *bounce_info;
54 /*
55 /*	int     bounce_diagnostic_log(fp, bounce_info, notify_filter)
56 /*	VSTREAM *fp;
57 /*	BOUNCE_INFO *bounce_info;
58 /*	int	notify_filter;
59 /*
60 /*	int     bounce_header_dsn(fp, bounce_info)
61 /*	VSTREAM *fp;
62 /*	BOUNCE_INFO *bounce_info;
63 /*
64 /*	int     bounce_recipient_dsn(fp, bounce_info)
65 /*	VSTREAM *fp;
66 /*	BOUNCE_INFO *bounce_info;
67 /*
68 /*	int     bounce_diagnostic_dsn(fp, bounce_info, notify_filter)
69 /*	VSTREAM *fp;
70 /*	BOUNCE_INFO *bounce_info;
71 /*	int	notify_filter;
72 /*
73 /*	int	bounce_original(fp, bounce_info, headers_only)
74 /*	VSTREAM *fp;
75 /*	BOUNCE_INFO *bounce_info;
76 /*	int	headers_only;
77 /*
78 /*	void	bounce_delrcpt(bounce_info)
79 /*	BOUNCE_INFO *bounce_info;
80 /*
81 /*	void	bounce_delrcpt_one(bounce_info)
82 /*	BOUNCE_INFO *bounce_info;
83 /* DESCRIPTION
84 /*	This module implements the grunt work of sending a non-delivery
85 /*	notification. A bounce is sent in a form that satisfies RFC 1894
86 /*	(delivery status notifications).
87 /*
88 /*	bounce_mail_init() bundles up its argument and attempts to
89 /*	open the corresponding logfile and message file. A BOUNCE_INFO
90 /*	structure contains all the necessary information about an
91 /*	undeliverable message.
92 /*
93 /*	bounce_mail_one_init() provides the same function for only
94 /*	one recipient that is not read from bounce logfile.
95 /*
96 /*	bounce_mail_free() releases memory allocated by bounce_mail_init()
97 /*	and closes any files opened by bounce_mail_init().
98 /*
99 /*	bounce_header() produces a standard mail header with the specified
100 /*	recipient and starts a text/plain message segment for the
101 /*	human-readable problem description. postmaster_copy is either
102 /*	POSTMASTER_COPY or NO_POSTMASTER_COPY.
103 /*
104 /*	bounce_boilerplate() produces the standard "sorry" text that
105 /*	creates the illusion that mail systems are civilized.
106 /*
107 /*	bounce_recipient_log() sends a human-readable representation of
108 /*	logfile information for one recipient, with the recipient address
109 /*	and with the text why the recipient was undeliverable.
110 /*
111 /*	bounce_diagnostic_log() sends a human-readable representation of
112 /*	logfile information for all undeliverable recipients. The
113 /*	notify_filter specifies what recipient status records should be
114 /*	reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
115 /*	In the absence of DSN NOTIFY information all records are reported.
116 /*	The result value is -1 in case of error, the number of reported
117 /*	recipients in case of success.
118 /*
119 /*	bounce_header_dsn() starts a message/delivery-status message
120 /*	segment and sends the machine-readable information that identifies
121 /*	the reporting MTA.
122 /*
123 /*	bounce_recipient_dsn() sends a machine-readable representation of
124 /*	logfile information for one recipient, with the recipient address
125 /*	and with the text why the recipient was undeliverable.
126 /*
127 /*	bounce_diagnostic_dsn() sends a machine-readable representation of
128 /*	logfile information for all undeliverable recipients. The
129 /*	notify_filter specifies what recipient status records should be
130 /*	reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY.
131 /*	In the absence of DSN NOTIFY information all records are reported.
132 /*	The result value is -1 in case of error, the number of reported
133 /*	recipients in case of success.
134 /*
135 /*	bounce_original() starts a message/rfc822 or text/rfc822-headers
136 /*	message segment and sends the original message, either full
137 /*	(DSN_RET_FULL) or message headers only (DSN_RET_HDRS).
138 /*
139 /*	bounce_delrcpt() deletes recipients in the logfile from the original
140 /*	queue file.
141 /*
142 /*	bounce_delrcpt_one() deletes one recipient from the original
143 /*	queue file.
144 /* DIAGNOSTICS
145 /*	Fatal error: error opening existing file.
146 /* BUGS
147 /* SEE ALSO
148 /*	bounce(3) basic bounce service client interface
149 /* LICENSE
150 /* .ad
151 /* .fi
152 /*	The Secure Mailer license must be distributed with this software.
153 /* AUTHOR(S)
154 /*	Wietse Venema
155 /*	IBM T.J. Watson Research
156 /*	P.O. Box 704
157 /*	Yorktown Heights, NY 10598, USA
158 /*--*/
159 
160 /* System library. */
161 
162 #include <sys_defs.h>
163 #include <sys/stat.h>
164 #include <stdlib.h>
165 #include <stdio.h>			/* sscanf() */
166 #include <unistd.h>
167 #include <errno.h>
168 #include <string.h>
169 #include <ctype.h>
170 
171 #ifdef STRCASECMP_IN_STRINGS_H
172 #include <strings.h>
173 #endif
174 
175 /* Utility library. */
176 
177 #include <msg.h>
178 #include <mymalloc.h>
179 #include <events.h>
180 #include <vstring.h>
181 #include <vstream.h>
182 #include <line_wrap.h>
183 #include <stringops.h>
184 #include <myflock.h>
185 
186 /* Global library. */
187 
188 #include <mail_queue.h>
189 #include <quote_822_local.h>
190 #include <mail_params.h>
191 #include <is_header.h>
192 #include <record.h>
193 #include <rec_type.h>
194 #include <post_mail.h>
195 #include <mail_addr.h>
196 #include <mail_error.h>
197 #include <bounce_log.h>
198 #include <mail_date.h>
199 #include <mail_proto.h>
200 #include <lex_822.h>
201 #include <deliver_completed.h>
202 #include <dsn_mask.h>
203 
204 /* Application-specific. */
205 
206 #include "bounce_service.h"
207 
208 #define STR vstring_str
209 
210 /* bounce_mail_alloc - initialize */
211 
212 static BOUNCE_INFO *bounce_mail_alloc(const char *service,
213 				              const char *queue_name,
214 				              const char *queue_id,
215 				              const char *encoding,
216 				              const char *dsn_envid,
217 				              RCPT_BUF *rcpt_buf,
218 				              DSN_BUF *dsn_buf,
219 				              BOUNCE_TEMPLATE *template,
220 				              BOUNCE_LOG *log_handle)
221 {
222     BOUNCE_INFO *bounce_info;
223     int     rec_type;
224 
225     /*
226      * Bundle up a bunch of parameters and initialize information that will
227      * be discovered on the fly.
228      */
229     bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
230     bounce_info->service = service;
231     bounce_info->queue_name = queue_name;
232     bounce_info->queue_id = queue_id;
233     if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
234 	bounce_info->mime_encoding = "8bit";
235     } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
236 	bounce_info->mime_encoding = "7bit";
237     } else {
238 	if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
239 	    msg_warn("%s: unknown encoding: %.200s",
240 		     bounce_info->queue_id, encoding);
241 	bounce_info->mime_encoding = 0;
242     }
243     if (dsn_envid && *dsn_envid)
244 	bounce_info->dsn_envid = dsn_envid;
245     else
246 	bounce_info->dsn_envid = 0;
247     bounce_info->template = template;
248     bounce_info->buf = vstring_alloc(100);
249     bounce_info->sender = vstring_alloc(100);
250     bounce_info->arrival_time = 0;
251     bounce_info->orig_offs = 0;
252     bounce_info->message_size = 0;
253     bounce_info->rcpt_buf = rcpt_buf;
254     bounce_info->dsn_buf = dsn_buf;
255     bounce_info->log_handle = log_handle;
256 
257     /*
258      * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
259      * must ensure it is valid.
260      */
261     bounce_info->mail_name = mystrdup(var_mail_name);
262     translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
263 	     "-----------------");
264 
265     /*
266      * Compute a supposedly unique boundary string. This assumes that a queue
267      * ID and a hostname contain acceptable characters for a boundary string,
268      * but the assumption is not verified.
269      */
270     vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
271 		    queue_id, (unsigned long) event_time(), var_myhostname);
272     bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
273 
274     /*
275      * If the original message cannot be found, do not raise a run-time
276      * error. There is nothing we can do about the error, and all we are
277      * doing is to inform the sender of a delivery problem. Bouncing a
278      * message does not have to be a perfect job. But if the system IS
279      * running out of resources, raise a fatal run-time error and force a
280      * backoff.
281      */
282     if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
283 						O_RDWR, 0)) == 0
284 	&& errno != ENOENT)
285 	msg_fatal("open %s %s: %m", service, queue_id);
286 
287     /*
288      * Get time/size/sender information from the original message envelope
289      * records. If the envelope is corrupted just send whatever we can
290      * (remember this is a best effort, it does not have to be perfect).
291      *
292      * Lock the file for shared use, so that queue manager leaves it alone after
293      * restarting.
294      */
295 #define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
296 
297     if (bounce_info->orig_fp != 0) {
298 	if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
299 		    DELIVER_LOCK_MODE) < 0)
300 	    msg_fatal("cannot get shared lock on %s: %m",
301 		      VSTREAM_PATH(bounce_info->orig_fp));
302 	while ((rec_type = rec_get(bounce_info->orig_fp,
303 				   bounce_info->buf, 0)) > 0) {
304 
305 	    /*
306 	     * Postfix version dependent: data offset in SIZE record.
307 	     */
308 	    if (rec_type == REC_TYPE_SIZE) {
309 		if (bounce_info->message_size == 0)
310 		    sscanf(STR(bounce_info->buf), "%ld %ld",
311 			   &bounce_info->message_size,
312 			   &bounce_info->orig_offs);
313 		if (bounce_info->message_size < 0)
314 		    bounce_info->message_size = 0;
315 		if (bounce_info->orig_offs < 0)
316 		    bounce_info->orig_offs = 0;
317 	    }
318 
319 	    /*
320 	     * Information for the Arrival-Date: attribute.
321 	     */
322 	    else if (rec_type == REC_TYPE_TIME) {
323 		if (bounce_info->arrival_time == 0
324 		    && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
325 		    bounce_info->arrival_time = 0;
326 	    }
327 
328 	    /*
329 	     * Information for the X-Postfix-Sender: attribute.
330 	     */
331 	    else if (rec_type == REC_TYPE_FROM) {
332 		quote_822_local_flags(bounce_info->sender,
333 				      VSTRING_LEN(bounce_info->buf) ?
334 				      STR(bounce_info->buf) :
335 				      mail_addr_mail_daemon(), 0);
336 	    }
337 
338 	    /*
339 	     * Backwards compatibility: no data offset in SIZE record.
340 	     */
341 	    else if (rec_type == REC_TYPE_MESG) {
342 		/* XXX Future: sender+recipient after message content. */
343 		if (VSTRING_LEN(bounce_info->sender) == 0)
344 		    msg_warn("%s: no sender before message content record",
345 			     bounce_info->queue_id);
346 		bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
347 		break;
348 	    }
349 	    if (bounce_info->orig_offs > 0
350 		&& bounce_info->arrival_time > 0
351 		&& VSTRING_LEN(bounce_info->sender) > 0)
352 		break;
353 	}
354     }
355     return (bounce_info);
356 }
357 
358 /* bounce_mail_init - initialize */
359 
360 BOUNCE_INFO *bounce_mail_init(const char *service,
361 			              const char *queue_name,
362 			              const char *queue_id,
363 			              const char *encoding,
364 			              const char *dsn_envid,
365 			              BOUNCE_TEMPLATE *template)
366 {
367     BOUNCE_INFO *bounce_info;
368     BOUNCE_LOG *log_handle;
369     RCPT_BUF *rcpt_buf;
370     DSN_BUF *dsn_buf;
371 
372     /*
373      * Initialize the bounce_info structure. If the bounce log cannot be
374      * found, do not raise a fatal run-time error. There is nothing we can do
375      * about the error, and all we are doing is to inform the sender of a
376      * delivery problem, Bouncing a message does not have to be a perfect
377      * job. But if the system IS running out of resources, raise a fatal
378      * run-time error and force a backoff.
379      */
380     if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
381 	if (errno != ENOENT)
382 	    msg_fatal("open %s %s: %m", service, queue_id);
383 	rcpt_buf = 0;
384 	dsn_buf = 0;
385     } else {
386 	rcpt_buf = rcpb_create();
387 	dsn_buf = dsb_create();
388     }
389     bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
390 				    dsn_envid, rcpt_buf, dsn_buf,
391 				    template, log_handle);
392     return (bounce_info);
393 }
394 
395 /* bounce_mail_one_init - initialize */
396 
397 BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
398 				          const char *queue_id,
399 				          const char *encoding,
400 				          const char *dsn_envid,
401 				          RCPT_BUF *rcpt_buf,
402 				          DSN_BUF *dsn_buf,
403 				          BOUNCE_TEMPLATE *template)
404 {
405     BOUNCE_INFO *bounce_info;
406 
407     /*
408      * Initialize the bounce_info structure for just one recipient.
409      */
410     bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
411 				    dsn_envid, rcpt_buf, dsn_buf, template,
412 				    (BOUNCE_LOG *) 0);
413     return (bounce_info);
414 }
415 
416 /* bounce_mail_free - undo bounce_mail_init */
417 
418 void    bounce_mail_free(BOUNCE_INFO *bounce_info)
419 {
420     if (bounce_info->log_handle) {
421 	if (bounce_log_close(bounce_info->log_handle))
422 	    msg_warn("%s: read bounce log %s: %m",
423 		     bounce_info->queue_id, bounce_info->queue_id);
424 	rcpb_free(bounce_info->rcpt_buf);
425 	dsb_free(bounce_info->dsn_buf);
426     }
427     if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
428 	msg_warn("%s: read message file %s %s: %m",
429 		 bounce_info->queue_id, bounce_info->queue_name,
430 		 bounce_info->queue_id);
431     vstring_free(bounce_info->buf);
432     vstring_free(bounce_info->sender);
433     myfree(bounce_info->mail_name);
434     myfree((char *) bounce_info->mime_boundary);
435     myfree((char *) bounce_info);
436 }
437 
438 /* bounce_header - generate bounce message header */
439 
440 int     bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
441 		              const char *dest, int postmaster_copy)
442 {
443     BOUNCE_TEMPLATE *template = bounce_info->template;
444 
445     /*
446      * Print a minimal bounce header. The cleanup service will add other
447      * headers and will make all addresses fully qualified.
448      */
449 #define STREQ(a, b) (strcasecmp((a), (b)) == 0)
450 
451     /*
452      * Generic headers.
453      */
454     bounce_template_headers(post_mail_fprintf, bounce, template,
455 			    STR(quote_822_local(bounce_info->buf, dest)),
456 			    postmaster_copy);
457 
458     /*
459      * Auto-Submitted header, as per RFC 3834.
460      */
461     post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
462 		      "auto-generated" : "auto-replied");
463 
464     /*
465      * MIME header. Use 8bit encoding when either the bounced message or the
466      * template requires it.
467      */
468     post_mail_fprintf(bounce, "MIME-Version: 1.0");
469     post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
470 		      "multipart/report", "delivery-status");
471     post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
472     if (bounce_info->mime_encoding)
473 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
474 		     STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
475 			  bounce_template_encoding(template) :
476 			  bounce_info->mime_encoding);
477     post_mail_fputs(bounce, "");
478     post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
479 
480     /*
481      * MIME header.
482      */
483     post_mail_fputs(bounce, "");
484     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
485     post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
486     post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
487 		      "text/plain", bounce_template_charset(template));
488     post_mail_fputs(bounce, "");
489 
490     return (vstream_ferror(bounce));
491 }
492 
493 /* bounce_boilerplate - generate boiler-plate text */
494 
495 int     bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
496 {
497 
498     /*
499      * Print the boiler-plate text.
500      */
501     bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
502     return (vstream_ferror(bounce));
503 }
504 
505 /* bounce_print - line_wrap callback */
506 
507 static void bounce_print(const char *str, int len, int indent, char *context)
508 {
509     VSTREAM *bounce = (VSTREAM *) context;
510 
511     post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
512 }
513 
514 /* bounce_print_wrap - print and wrap a line */
515 
516 static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
517 			              const char *format,...)
518 {
519     va_list ap;
520 
521 #define LENGTH	79
522 #define INDENT	4
523 
524     va_start(ap, format);
525     vstring_vsprintf(bounce_info->buf, format, ap);
526     va_end(ap);
527     line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
528 	      bounce_print, (char *) bounce);
529 }
530 
531 /* bounce_recipient_log - send one bounce log report entry */
532 
533 int     bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
534 {
535     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
536     DSN    *dsn = &bounce_info->dsn_buf->dsn;
537 
538     /*
539      * Mask control and non-ASCII characters (done in bounce_log_read()),
540      * wrap long lines and prepend one blank, so this data can safely be
541      * piped into other programs. Sort of like TCP Wrapper's safe_finger
542      * program.
543      */
544 #define NON_NULL_EMPTY(s) ((s) && *(s))
545 
546     post_mail_fputs(bounce, "");
547     if (NON_NULL_EMPTY(rcpt->orig_addr)) {
548 	bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
549 			  rcpt->address, rcpt->orig_addr, dsn->reason);
550     } else {
551 	bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
552 			  rcpt->address, dsn->reason);
553     }
554     return (vstream_ferror(bounce));
555 }
556 
557 /* bounce_diagnostic_log - send bounce log report */
558 
559 int     bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
560 			              int notify_filter)
561 {
562     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
563     int     count = 0;
564 
565     /*
566      * Append a human-readable copy of the delivery error log. We're doing a
567      * best effort, so there is no point raising a fatal run-time error in
568      * case of a logfile read error.
569      *
570      * XXX DSN If the logfile with failed recipients is unavailable, pretend
571      * that we found something anyway, so that this notification will not be
572      * canceled.
573      */
574     if (bounce_info->log_handle == 0
575 	|| bounce_log_rewind(bounce_info->log_handle)) {
576 	if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
577 	    post_mail_fputs(bounce, "");
578 	    post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
579 	    count = 1;				/* XXX don't abort */
580 	}
581     } else {
582 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
583 			       bounce_info->dsn_buf) != 0) {
584 	    if (rcpt->dsn_notify == 0		/* compat */
585 		|| (rcpt->dsn_notify & notify_filter)) {
586 		count++;
587 		if (bounce_recipient_log(bounce, bounce_info) != 0)
588 		    break;
589 	    }
590 	}
591     }
592     return (vstream_ferror(bounce) ? -1 : count);
593 }
594 
595 /* bounce_header_dsn - send per-MTA bounce DSN records */
596 
597 int     bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
598 {
599 
600     /*
601      * MIME header.
602      */
603     post_mail_fputs(bounce, "");
604     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
605     post_mail_fprintf(bounce, "Content-Description: %s",
606 		      "Delivery report");
607     post_mail_fprintf(bounce, "Content-Type: %s", "message/delivery-status");
608 
609     /*
610      * According to RFC 1894: The body of a message/delivery-status consists
611      * of one or more "fields" formatted according to the ABNF of RFC 822
612      * header "fields" (see [6]).  The per-message fields appear first,
613      * followed by a blank line.
614      */
615     post_mail_fputs(bounce, "");
616     post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
617 #if 0
618     post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
619 #endif
620     if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
621 	post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
622 			  bounce_info->dsn_envid);
623     }
624     post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
625 		      bounce_info->mail_name, bounce_info->queue_id);
626     if (VSTRING_LEN(bounce_info->sender) > 0)
627 	post_mail_fprintf(bounce, "X-%s-Sender: rfc822; %s",
628 			  bounce_info->mail_name, STR(bounce_info->sender));
629     if (bounce_info->arrival_time > 0)
630 	post_mail_fprintf(bounce, "Arrival-Date: %s",
631 			  mail_date(bounce_info->arrival_time));
632     return (vstream_ferror(bounce));
633 }
634 
635 /* bounce_recipient_dsn - send per-recipient DSN records */
636 
637 int     bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
638 {
639     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
640     DSN    *dsn = &bounce_info->dsn_buf->dsn;
641 
642     post_mail_fputs(bounce, "");
643     post_mail_fprintf(bounce, "Final-Recipient: rfc822; %s", rcpt->address);
644 
645     /*
646      * XXX DSN
647      *
648      * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
649      * recipient, the Original-Recipient field MUST NOT appear."
650      *
651      * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
652      * present in the RCPT command when the message was received, an ORCPT
653      * parameter MAY be added to the RCPT command when the message is
654      * relayed.". Postfix adds an ORCPT parameter under these conditions.
655      *
656      * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
657      * field ontaining this same ORCPT value. When a down-stream MTA can use
658      * that information in their DSNs, it makes no sense that an up-stream
659      * MTA can't use that same information in its own DSNs.
660      *
661      * Postfix always reports an Original-Recipient field, because it is more
662      * more useful and more consistent.
663      */
664     if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
665 	post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
666     } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
667 	post_mail_fprintf(bounce, "Original-Recipient: rfc822; %s",
668 			  rcpt->orig_addr);
669     }
670     post_mail_fprintf(bounce, "Action: %s",
671 		      IS_FAILURE_TEMPLATE(bounce_info->template) ?
672 		      "failed" : dsn->action);
673     post_mail_fprintf(bounce, "Status: %s", dsn->status);
674     if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
675 	bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
676 			  dsn->mtype, dsn->mname);
677     if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
678 	bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
679 			  dsn->dtype, dsn->dtext);
680     else
681 	bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
682 			  bounce_info->mail_name, dsn->reason);
683 #if 0
684     if (dsn->time > 0)
685 	post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
686 			  mail_date(dsn->time));
687 #endif
688     if (IS_DELAY_TEMPLATE(bounce_info->template))
689 	post_mail_fprintf(bounce, "Will-Retry-Until: %s",
690 		 mail_date(bounce_info->arrival_time + var_max_queue_time));
691     return (vstream_ferror(bounce));
692 }
693 
694 /* bounce_diagnostic_dsn - send bounce log report, machine readable form */
695 
696 int     bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
697 			              int notify_filter)
698 {
699     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
700     int     count = 0;
701 
702     /*
703      * Append a machine-readable copy of the delivery error log. We're doing
704      * a best effort, so there is no point raising a fatal run-time error in
705      * case of a logfile read error.
706      *
707      * XXX DSN If the logfile with failed recipients is unavailable, pretend
708      * that we found something anyway, so that this notification will not be
709      * canceled.
710      */
711     if (bounce_info->log_handle == 0
712 	|| bounce_log_rewind(bounce_info->log_handle)) {
713 	if (IS_FAILURE_TEMPLATE(bounce_info->template))
714 	    count = 1;				/* XXX don't abort */
715     } else {
716 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
717 			       bounce_info->dsn_buf) != 0) {
718 	    if (rcpt->dsn_notify == 0		/* compat */
719 		|| (rcpt->dsn_notify & notify_filter)) {
720 		count++;
721 		if (bounce_recipient_dsn(bounce, bounce_info) != 0)
722 		    break;
723 	    }
724 	}
725     }
726     return (vstream_ferror(bounce) ? -1 : count);
727 }
728 
729 /* bounce_original - send a copy of the original to the victim */
730 
731 int     bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
732 			        int headers_only)
733 {
734     int     status = 0;
735     int     rec_type = 0;
736 
737     /*
738      * When truncating a large message, don't damage the MIME structure: send
739      * the message headers only.
740      */
741     if (var_bounce_limit > 0
742 	&& bounce_info->orig_fp
743 	&& (bounce_info->message_size <= 0
744 	    || bounce_info->message_size > var_bounce_limit))
745 	headers_only = DSN_RET_HDRS;
746 
747     /*
748      * MIME headers.
749      */
750 #define IS_UNDELIVERED_TEMPLATE(template) \
751         (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
752 
753     post_mail_fputs(bounce, "");
754     post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
755     post_mail_fprintf(bounce, "Content-Description: %s%s",
756 		      IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
757 		      "Undelivered " : "",
758 		      headers_only == DSN_RET_HDRS ?
759 		      "Message Headers" : "Message");
760     post_mail_fprintf(bounce, "Content-Type: %s",
761 		      headers_only == DSN_RET_HDRS ?
762 		      "text/rfc822-headers" : "message/rfc822");
763     if (bounce_info->mime_encoding)
764 	post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
765 			  bounce_info->mime_encoding);
766     post_mail_fputs(bounce, "");
767 
768     /*
769      * Send place holder if original is unavailable.
770      */
771     if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
772 				    bounce_info->orig_offs, SEEK_SET) < 0) {
773 	post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
774 	return (vstream_ferror(bounce));
775     }
776 
777     /*
778      * XXX The cleanup server removes Return-Path: headers. This should be
779      * done only with mail that enters via a non-SMTP channel, but changing
780      * this now could break other software. Removing Return-Path: could break
781      * digital signatures, though this is unlikely. In any case,
782      * header_checks are more effective when the Return-Path: header is
783      * present, so we prepend one to the bounce message.
784      */
785     post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
786 
787     /*
788      * Copy the original message contents. We're doing raw record output here
789      * so that we don't throw away binary transparency yet.
790      */
791 #define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
792 
793     while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
794 	if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
795 	    break;
796 	if (headers_only == DSN_RET_HDRS
797 	    && !IS_HEADER(vstring_str(bounce_info->buf)))
798 	    break;
799 	status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
800     }
801 
802     /*
803      * Final MIME headers. These require -- at the end of the boundary
804      * string.
805      *
806      * XXX This should be a separate bounce_terminate() entry so we can be
807      * assured that the terminator will always be sent.
808      */
809     post_mail_fputs(bounce, "");
810     post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
811 
812     return (status);
813 }
814 
815 /* bounce_delrcpt - delete recipients from original queue file */
816 
817 void    bounce_delrcpt(BOUNCE_INFO *bounce_info)
818 {
819     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
820 
821     if (bounce_info->orig_fp != 0
822 	&& bounce_info->log_handle != 0
823 	&& bounce_log_rewind(bounce_info->log_handle) == 0)
824 	while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
825 			       bounce_info->dsn_buf) != 0)
826 	    if (rcpt->offset > 0)
827 		deliver_completed(bounce_info->orig_fp, rcpt->offset);
828 }
829 
830 /* bounce_delrcpt_one - delete one recipient from original queue file */
831 
832 void    bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
833 {
834     RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
835 
836     if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
837 	deliver_completed(bounce_info->orig_fp, rcpt->offset);
838 }
839