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