1 /* $NetBSD: bounce_notify_util.c,v 1.4 2022/10/08 16:12:45 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 #include <header_opts.h>
212
213 /* Application-specific. */
214
215 #include "bounce_service.h"
216
217 #define STR vstring_str
218 #define LEN VSTRING_LEN
219
220 /* bounce_mail_alloc - initialize */
221
bounce_mail_alloc(const char * service,const char * queue_name,const char * queue_id,const char * encoding,int smtputf8,const char * dsn_envid,RCPT_BUF * rcpt_buf,DSN_BUF * dsn_buf,BOUNCE_TEMPLATE * template,BOUNCE_LOG * log_handle)222 static BOUNCE_INFO *bounce_mail_alloc(const char *service,
223 const char *queue_name,
224 const char *queue_id,
225 const char *encoding,
226 int smtputf8,
227 const char *dsn_envid,
228 RCPT_BUF *rcpt_buf,
229 DSN_BUF *dsn_buf,
230 BOUNCE_TEMPLATE *template,
231 BOUNCE_LOG *log_handle)
232 {
233 BOUNCE_INFO *bounce_info;
234 int rec_type;
235 int prev_type;
236 int all_headers_seen = 0;
237 int skip_message_segment = 0;
238 int in_envelope = 1;
239
240 /*
241 * Bundle up a bunch of parameters and initialize information that will
242 * be discovered on the fly.
243 *
244 * XXX Instead of overriding the returned-message MIME encoding, separate
245 * the returned-message MIME encoding from the (boiler plate, delivery
246 * status) MIME encoding.
247 */
248 bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info));
249 bounce_info->service = service;
250 bounce_info->queue_name = queue_name;
251 bounce_info->queue_id = queue_id;
252 bounce_info->smtputf8 = smtputf8;
253 /* Fix 20140708: override MIME encoding: addresses may be 8bit. */
254 /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */
255 if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) {
256 bounce_info->mime_encoding = "8bit";
257 } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) {
258 bounce_info->mime_encoding = "8bit";
259 } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) {
260 bounce_info->mime_encoding = "7bit";
261 } else {
262 if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0)
263 msg_warn("%s: unknown encoding: %.200s",
264 bounce_info->queue_id, encoding);
265 bounce_info->mime_encoding = 0;
266 }
267 if (dsn_envid && *dsn_envid)
268 bounce_info->dsn_envid = dsn_envid;
269 else
270 bounce_info->dsn_envid = 0;
271 bounce_info->template = template;
272 bounce_info->buf = vstring_alloc(100);
273 bounce_info->sender = vstring_alloc(100);
274 bounce_info->arrival_time = 0;
275 bounce_info->orig_offs = 0;
276 bounce_info->message_size = 0;
277 bounce_info->orig_msgid = vstring_alloc(100);
278 bounce_info->rcpt_buf = rcpt_buf;
279 bounce_info->dsn_buf = dsn_buf;
280 bounce_info->log_handle = log_handle;
281
282 /*
283 * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and
284 * must ensure it is valid.
285 */
286 bounce_info->mail_name = mystrdup(var_mail_name);
287 translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]",
288 "-----------------");
289
290 /*
291 * Compute a supposedly unique boundary string. This assumes that a queue
292 * ID and a hostname contain acceptable characters for a boundary string,
293 * but the assumption is not verified.
294 */
295 vstring_sprintf(bounce_info->buf, "%s.%lu/%s",
296 queue_id, (unsigned long) event_time(), var_myhostname);
297 bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf));
298
299 /*
300 * If the original message cannot be found, do not raise a run-time
301 * error. There is nothing we can do about the error, and all we are
302 * doing is to inform the sender of a delivery problem. Bouncing a
303 * message does not have to be a perfect job. But if the system IS
304 * running out of resources, raise a fatal run-time error and force a
305 * backoff.
306 */
307 if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id,
308 O_RDWR, 0)) == 0
309 && errno != ENOENT)
310 msg_fatal("open %s %s: %m", service, queue_id);
311
312 /*
313 * Get time/size/sender information from the original message envelope
314 * records. If the envelope is corrupted just send whatever we can
315 * (remember this is a best effort, it does not have to be perfect).
316 *
317 * Lock the file for shared use, so that queue manager leaves it alone after
318 * restarting.
319 */
320 #define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT)
321
322 if (bounce_info->orig_fp != 0) {
323 if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK,
324 DELIVER_LOCK_MODE) < 0)
325 msg_fatal("cannot get shared lock on %s: %m",
326 VSTREAM_PATH(bounce_info->orig_fp));
327 for (prev_type = 0;
328 (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0;
329 prev_type = rec_type) {
330
331 /*
332 * Postfix version dependent: data offset in SIZE record.
333 */
334 if (rec_type == REC_TYPE_SIZE) {
335 if (bounce_info->message_size == 0)
336 sscanf(STR(bounce_info->buf), "%ld %ld",
337 &bounce_info->message_size,
338 &bounce_info->orig_offs);
339 if (bounce_info->message_size < 0)
340 bounce_info->message_size = 0;
341 if (bounce_info->orig_offs < 0)
342 bounce_info->orig_offs = 0;
343 }
344
345 /*
346 * Information for the Arrival-Date: attribute.
347 */
348 else if (rec_type == REC_TYPE_TIME) {
349 if (bounce_info->arrival_time == 0
350 && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0)
351 bounce_info->arrival_time = 0;
352 }
353
354 /*
355 * Information for the X-Postfix-Sender: attribute.
356 */
357 else if (rec_type == REC_TYPE_FROM) {
358 quote_822_local_flags(bounce_info->sender,
359 VSTRING_LEN(bounce_info->buf) ?
360 STR(bounce_info->buf) :
361 mail_addr_mail_daemon(), 0);
362 }
363
364 /*
365 * Backwards compatibility: no data offset in SIZE record.
366 */
367 else if (rec_type == REC_TYPE_MESG) {
368 /* XXX Future: sender+recipient after message content. */
369 if (VSTRING_LEN(bounce_info->sender) == 0)
370 msg_warn("%s: no sender before message content record",
371 bounce_info->queue_id);
372 bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp);
373 if (var_threaded_bounce == 0)
374 skip_message_segment = 1;
375 else
376 in_envelope = 0;
377 }
378
379 /*
380 * Extract Message-ID for threaded bounces.
381 */
382 else if (in_envelope == 0
383 && (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)) {
384 const HEADER_OPTS *hdr;
385 char *cp;
386
387 /*
388 * Skip records that we cannot use. Degrade if we could not
389 * skip over the message content.
390 */
391 if (var_threaded_bounce == 0 || all_headers_seen
392 || prev_type == REC_TYPE_CONT) {
393 /* void */ ;
394 }
395
396 /*
397 * Extract message-id header value.
398 */
399 else if (is_header(STR(bounce_info->buf))) {
400 if ((hdr = header_opts_find(
401 vstring_str(bounce_info->buf))) != 0
402 && hdr->type == HDR_MESSAGE_ID) {
403 vstring_truncate(bounce_info->buf,
404 trimblanks(STR(bounce_info->buf),
405 LEN(bounce_info->buf))
406 - STR(bounce_info->buf));
407 cp = STR(bounce_info->buf) + strlen(hdr->name) + 1;
408 while (ISSPACE(*cp))
409 cp++;
410 if (*cp == '<' && vstring_end(bounce_info->buf)[-1] == '>')
411 vstring_strcpy(bounce_info->orig_msgid, cp);
412 else
413 msg_warn("%s: ignoring malformed Message-ID",
414 bounce_info->queue_id);
415 }
416 }
417
418 /*
419 * Skip remainder of multiline header.
420 */
421 else if (ISSPACE(*STR(bounce_info->buf))) {
422 /* void */ ;
423 }
424
425 /*
426 * Start of body.
427 */
428 else {
429 all_headers_seen = 1;
430 skip_message_segment = 1;
431 }
432 }
433
434 /*
435 * In case we ever want to process records from the extracted
436 * segment, and in case there was no "start of body" event.
437 */
438 else if (rec_type == REC_TYPE_XTRA) {
439 if (VSTRING_LEN(bounce_info->orig_msgid) == 0)
440 if (var_threaded_bounce)
441 all_headers_seen = 1;
442 in_envelope = 1;
443 }
444
445 /*
446 * Are we done yet?
447 */
448 if (bounce_info->orig_offs > 0
449 && bounce_info->arrival_time > 0
450 && VSTRING_LEN(bounce_info->sender) > 0
451 && (var_threaded_bounce == 0 || all_headers_seen
452 || VSTRING_LEN(bounce_info->orig_msgid) > 0)) {
453 break;
454 }
455
456 /*
457 * Skip over (the remainder of) the message segment. If that
458 * fails, degrade.
459 */
460 if (skip_message_segment) {
461 if (vstream_fseek(bounce_info->orig_fp,
462 bounce_info->orig_offs +
463 bounce_info->message_size,
464 SEEK_SET) < 0)
465 /* void */ ;
466 skip_message_segment = 0;
467 }
468 }
469 }
470 return (bounce_info);
471 }
472
473 /* bounce_mail_init - initialize */
474
bounce_mail_init(const char * service,const char * queue_name,const char * queue_id,const char * encoding,int smtputf8,const char * dsn_envid,BOUNCE_TEMPLATE * template)475 BOUNCE_INFO *bounce_mail_init(const char *service,
476 const char *queue_name,
477 const char *queue_id,
478 const char *encoding,
479 int smtputf8,
480 const char *dsn_envid,
481 BOUNCE_TEMPLATE *template)
482 {
483 BOUNCE_INFO *bounce_info;
484 BOUNCE_LOG *log_handle;
485 RCPT_BUF *rcpt_buf;
486 DSN_BUF *dsn_buf;
487
488 /*
489 * Initialize the bounce_info structure. If the bounce log cannot be
490 * found, do not raise a fatal run-time error. There is nothing we can do
491 * about the error, and all we are doing is to inform the sender of a
492 * delivery problem, Bouncing a message does not have to be a perfect
493 * job. But if the system IS running out of resources, raise a fatal
494 * run-time error and force a backoff.
495 */
496 if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) {
497 if (errno != ENOENT)
498 msg_fatal("open %s %s: %m", service, queue_id);
499 rcpt_buf = 0;
500 dsn_buf = 0;
501 } else {
502 rcpt_buf = rcpb_create();
503 dsn_buf = dsb_create();
504 }
505 bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding,
506 smtputf8, dsn_envid, rcpt_buf, dsn_buf,
507 template, log_handle);
508 return (bounce_info);
509 }
510
511 /* bounce_mail_one_init - initialize */
512
bounce_mail_one_init(const char * queue_name,const char * queue_id,const char * encoding,int smtputf8,const char * dsn_envid,RCPT_BUF * rcpt_buf,DSN_BUF * dsn_buf,BOUNCE_TEMPLATE * template)513 BOUNCE_INFO *bounce_mail_one_init(const char *queue_name,
514 const char *queue_id,
515 const char *encoding,
516 int smtputf8,
517 const char *dsn_envid,
518 RCPT_BUF *rcpt_buf,
519 DSN_BUF *dsn_buf,
520 BOUNCE_TEMPLATE *template)
521 {
522 BOUNCE_INFO *bounce_info;
523
524 /*
525 * Initialize the bounce_info structure for just one recipient.
526 */
527 bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding,
528 smtputf8, dsn_envid, rcpt_buf, dsn_buf,
529 template, (BOUNCE_LOG *) 0);
530 return (bounce_info);
531 }
532
533 /* bounce_mail_free - undo bounce_mail_init */
534
bounce_mail_free(BOUNCE_INFO * bounce_info)535 void bounce_mail_free(BOUNCE_INFO *bounce_info)
536 {
537 if (bounce_info->log_handle) {
538 if (bounce_log_close(bounce_info->log_handle))
539 msg_warn("%s: read bounce log %s: %m",
540 bounce_info->queue_id, bounce_info->queue_id);
541 vstring_free(bounce_info->orig_msgid);
542 rcpb_free(bounce_info->rcpt_buf);
543 dsb_free(bounce_info->dsn_buf);
544 }
545 if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp))
546 msg_warn("%s: read message file %s %s: %m",
547 bounce_info->queue_id, bounce_info->queue_name,
548 bounce_info->queue_id);
549 vstring_free(bounce_info->buf);
550 vstring_free(bounce_info->sender);
551 myfree(bounce_info->mail_name);
552 myfree((void *) bounce_info->mime_boundary);
553 myfree((void *) bounce_info);
554 }
555
556 /* bounce_header - generate bounce message header */
557
bounce_header(VSTREAM * bounce,BOUNCE_INFO * bounce_info,const char * dest,int postmaster_copy)558 int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
559 const char *dest, int postmaster_copy)
560 {
561 BOUNCE_TEMPLATE *template = bounce_info->template;
562
563 /*
564 * Print a minimal bounce header. The cleanup service will add other
565 * headers and will make all addresses fully qualified.
566 */
567 #define STREQ(a, b) (strcasecmp((a), (b)) == 0)
568 #define STRNE(a, b) (strcasecmp((a), (b)) != 0)
569
570 /*
571 * Generic headers.
572 */
573 bounce_template_headers(post_mail_fprintf, bounce, template,
574 STR(quote_822_local(bounce_info->buf, dest)),
575 postmaster_copy);
576
577 /*
578 * References and Reply-To header that references the original message-id
579 * for better threading in MUAs.
580 */
581 if (VSTRING_LEN(bounce_info->orig_msgid) > 0) {
582 post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid));
583 post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid));
584 }
585
586 /*
587 * Auto-Submitted header, as per RFC 3834.
588 */
589 post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ?
590 "auto-generated" : "auto-replied");
591
592 /*
593 * MIME header. Use 8bit encoding when either the bounced message or the
594 * template requires it.
595 */
596 post_mail_fprintf(bounce, "MIME-Version: 1.0");
597 post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;",
598 "multipart/report", "delivery-status");
599 post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary);
600 if (bounce_info->mime_encoding)
601 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
602 STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ?
603 bounce_template_encoding(template) :
604 bounce_info->mime_encoding);
605 post_mail_fputs(bounce, "");
606 post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
607
608 /*
609 * MIME header.
610 */
611 #define NOT_US_ASCII(tp) \
612 STRNE(bounce_template_charset(template), "us-ascii")
613
614 #define NOT_7BIT_MIME(bp) \
615 (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT))
616
617 post_mail_fputs(bounce, "");
618 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
619 post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
620 /* Fix 20140718: UTF-8 address or $myhostname expansion. */
621 post_mail_fprintf(bounce, "Content-Type: %s; charset=%s",
622 "text/plain", NOT_US_ASCII(template) ?
623 bounce_template_charset(template) :
624 NOT_7BIT_MIME(bounce_info) ?
625 "utf-8" : "us-ascii");
626 /* Fix 20140709: addresses may be 8bit. */
627 if (NOT_7BIT_MIME(bounce_info))
628 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
629 bounce_info->mime_encoding);
630 post_mail_fputs(bounce, "");
631
632 return (vstream_ferror(bounce));
633 }
634
635 /* bounce_boilerplate - generate boiler-plate text */
636
bounce_boilerplate(VSTREAM * bounce,BOUNCE_INFO * bounce_info)637 int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
638 {
639
640 /*
641 * Print the boiler-plate text.
642 */
643 bounce_template_expand(post_mail_fputs, bounce, bounce_info->template);
644 return (vstream_ferror(bounce));
645 }
646
647 /* bounce_print - line_wrap callback */
648
bounce_print(const char * str,int len,int indent,void * context)649 static void bounce_print(const char *str, int len, int indent, void *context)
650 {
651 VSTREAM *bounce = (VSTREAM *) context;
652
653 post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str);
654 }
655
656 /* bounce_print_wrap - print and wrap a line */
657
bounce_print_wrap(VSTREAM * bounce,BOUNCE_INFO * bounce_info,const char * format,...)658 static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
659 const char *format,...)
660 {
661 va_list ap;
662
663 #define LENGTH 79
664 #define INDENT 4
665
666 va_start(ap, format);
667 vstring_vsprintf(bounce_info->buf, format, ap);
668 va_end(ap);
669 line_wrap(STR(bounce_info->buf), LENGTH, INDENT,
670 bounce_print, (void *) bounce);
671 }
672
673 /* bounce_recipient_log - send one bounce log report entry */
674
bounce_recipient_log(VSTREAM * bounce,BOUNCE_INFO * bounce_info)675 int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
676 {
677 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
678 DSN *dsn = &bounce_info->dsn_buf->dsn;
679
680 /*
681 * Mask control and non-ASCII characters (done in bounce_log_read()),
682 * wrap long lines and prepend one blank, so this data can safely be
683 * piped into other programs. Sort of like TCP Wrapper's safe_finger
684 * program.
685 */
686 #define NON_NULL_EMPTY(s) ((s) && *(s))
687
688 post_mail_fputs(bounce, "");
689 if (NON_NULL_EMPTY(rcpt->orig_addr)) {
690 bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s",
691 rcpt->address, rcpt->orig_addr, dsn->reason);
692 } else {
693 bounce_print_wrap(bounce, bounce_info, "<%s>: %s",
694 rcpt->address, dsn->reason);
695 }
696 return (vstream_ferror(bounce));
697 }
698
699 /* bounce_diagnostic_log - send bounce log report */
700
bounce_diagnostic_log(VSTREAM * bounce,BOUNCE_INFO * bounce_info,int notify_filter)701 int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
702 int notify_filter)
703 {
704 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
705 int count = 0;
706
707 /*
708 * Append a human-readable copy of the delivery error log. We're doing a
709 * best effort, so there is no point raising a fatal run-time error in
710 * case of a logfile read error.
711 *
712 * XXX DSN If the logfile with failed recipients is unavailable, pretend
713 * that we found something anyway, so that this notification will not be
714 * canceled.
715 */
716 if (bounce_info->log_handle == 0
717 || bounce_log_rewind(bounce_info->log_handle)) {
718 if (IS_FAILURE_TEMPLATE(bounce_info->template)) {
719 post_mail_fputs(bounce, "");
720 post_mail_fputs(bounce, "\t--- Delivery report unavailable ---");
721 count = 1; /* XXX don't abort */
722 }
723 } else {
724 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
725 bounce_info->dsn_buf) != 0) {
726 if (rcpt->dsn_notify == 0 /* compat */
727 || (rcpt->dsn_notify & notify_filter)) {
728 count++;
729 if (bounce_recipient_log(bounce, bounce_info) != 0)
730 break;
731 }
732 }
733 }
734 return (vstream_ferror(bounce) ? -1 : count);
735 }
736
737 /* bounce_header_dsn - send per-MTA bounce DSN records */
738
bounce_header_dsn(VSTREAM * bounce,BOUNCE_INFO * bounce_info)739 int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
740 {
741
742 /*
743 * MIME header.
744 */
745 post_mail_fputs(bounce, "");
746 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
747 post_mail_fprintf(bounce, "Content-Description: %s",
748 "Delivery report");
749 /* Generate *global* only if the original requested SMTPUTF8 support. */
750 post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status",
751 (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ?
752 "global-" : "");
753 /* Fix 20140709: addresses may be 8bit. */
754 if (NOT_7BIT_MIME(bounce_info)
755 /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */
756 && (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED))
757 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
758 bounce_info->mime_encoding);
759
760 /*
761 * According to RFC 1894: The body of a message/delivery-status consists
762 * of one or more "fields" formatted according to the ABNF of RFC 822
763 * header "fields" (see [6]). The per-message fields appear first,
764 * followed by a blank line.
765 */
766 post_mail_fputs(bounce, "");
767 post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname);
768 #if 0
769 post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever");
770 #endif
771 if (NON_NULL_EMPTY(bounce_info->dsn_envid)) {
772 post_mail_fprintf(bounce, "Original-Envelope-Id: %s",
773 bounce_info->dsn_envid);
774 }
775 post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
776 bounce_info->mail_name, bounce_info->queue_id);
777
778 #define IS_UTF8_ADDRESS(str, len) \
779 ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len)))
780
781 /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
782 if (VSTRING_LEN(bounce_info->sender) > 0)
783 post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
784 bounce_info->mail_name, bounce_info->smtputf8
785 && IS_UTF8_ADDRESS(STR(bounce_info->sender),
786 VSTRING_LEN(bounce_info->sender)) ?
787 "utf-8" : "rfc822", STR(bounce_info->sender));
788 if (bounce_info->arrival_time > 0)
789 post_mail_fprintf(bounce, "Arrival-Date: %s",
790 mail_date(bounce_info->arrival_time));
791 return (vstream_ferror(bounce));
792 }
793
794 /* bounce_recipient_dsn - send per-recipient DSN records */
795
bounce_recipient_dsn(VSTREAM * bounce,BOUNCE_INFO * bounce_info)796 int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
797 {
798 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
799 DSN *dsn = &bounce_info->dsn_buf->dsn;
800
801 post_mail_fputs(bounce, "");
802 /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
803 post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
804 bounce_info->smtputf8
805 && IS_UTF8_ADDRESS(rcpt->address,
806 strlen(rcpt->address)) ?
807 "utf-8" : "rfc822", rcpt->address);
808
809 /*
810 * XXX DSN
811 *
812 * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this
813 * recipient, the Original-Recipient field MUST NOT appear."
814 *
815 * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was
816 * present in the RCPT command when the message was received, an ORCPT
817 * parameter MAY be added to the RCPT command when the message is
818 * relayed.". Postfix adds an ORCPT parameter under these conditions.
819 *
820 * Therefore, all down-stream MTAs will send DSNs with Original-Recipient
821 * field containing this same ORCPT value. When a down-stream MTA can use
822 * that information in their DSNs, it makes no sense that an up-stream
823 * MTA can't use that same information in its own DSNs.
824 *
825 * Postfix always reports an Original-Recipient field, because it is more
826 * more useful and more consistent.
827 */
828 if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) {
829 post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt);
830 } else if (NON_NULL_EMPTY(rcpt->orig_addr)) {
831 /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
832 post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
833 bounce_info->smtputf8
834 && IS_UTF8_ADDRESS(rcpt->orig_addr,
835 strlen(rcpt->orig_addr)) ?
836 "utf-8" : "rfc822", rcpt->orig_addr);
837 }
838 post_mail_fprintf(bounce, "Action: %s",
839 IS_FAILURE_TEMPLATE(bounce_info->template) ?
840 "failed" : dsn->action);
841 post_mail_fprintf(bounce, "Status: %s", dsn->status);
842 if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname))
843 bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s",
844 dsn->mtype, dsn->mname);
845 if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext))
846 bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s",
847 dsn->dtype, dsn->dtext);
848 else
849 bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s",
850 bounce_info->mail_name, dsn->reason);
851 #if 0
852 if (dsn->time > 0)
853 post_mail_fprintf(bounce, "Last-Attempt-Date: %s",
854 mail_date(dsn->time));
855 #endif
856 if (IS_DELAY_TEMPLATE(bounce_info->template))
857 post_mail_fprintf(bounce, "Will-Retry-Until: %s",
858 mail_date(bounce_info->arrival_time + var_max_queue_time));
859 return (vstream_ferror(bounce));
860 }
861
862 /* bounce_diagnostic_dsn - send bounce log report, machine readable form */
863
bounce_diagnostic_dsn(VSTREAM * bounce,BOUNCE_INFO * bounce_info,int notify_filter)864 int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
865 int notify_filter)
866 {
867 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
868 int count = 0;
869
870 /*
871 * Append a machine-readable copy of the delivery error log. We're doing
872 * a best effort, so there is no point raising a fatal run-time error in
873 * case of a logfile read error.
874 *
875 * XXX DSN If the logfile with failed recipients is unavailable, pretend
876 * that we found something anyway, so that this notification will not be
877 * canceled.
878 */
879 if (bounce_info->log_handle == 0
880 || bounce_log_rewind(bounce_info->log_handle)) {
881 if (IS_FAILURE_TEMPLATE(bounce_info->template))
882 count = 1; /* XXX don't abort */
883 } else {
884 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
885 bounce_info->dsn_buf) != 0) {
886 if (rcpt->dsn_notify == 0 /* compat */
887 || (rcpt->dsn_notify & notify_filter)) {
888 count++;
889 if (bounce_recipient_dsn(bounce, bounce_info) != 0)
890 break;
891 }
892 }
893 }
894 return (vstream_ferror(bounce) ? -1 : count);
895 }
896
897 /* bounce_original - send a copy of the original to the victim */
898
bounce_original(VSTREAM * bounce,BOUNCE_INFO * bounce_info,int headers_only)899 int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
900 int headers_only)
901 {
902 int status = 0;
903 int rec_type = 0;
904
905 /*
906 * When truncating a large message, don't damage the MIME structure: send
907 * the message headers only.
908 */
909 if (var_bounce_limit > 0
910 && bounce_info->orig_fp
911 && (bounce_info->message_size <= 0
912 || bounce_info->message_size > var_bounce_limit))
913 headers_only = DSN_RET_HDRS;
914
915 /*
916 * MIME headers.
917 */
918 #define IS_UNDELIVERED_TEMPLATE(template) \
919 (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template))
920
921 post_mail_fputs(bounce, "");
922 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary);
923 post_mail_fprintf(bounce, "Content-Description: %s%s",
924 IS_UNDELIVERED_TEMPLATE(bounce_info->template) ?
925 "Undelivered " : "",
926 headers_only == DSN_RET_HDRS ?
927 "Message Headers" : "Message");
928 /* Generate *global* only if the original requested SMTPUTF8 support. */
929 if (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED)
930 post_mail_fprintf(bounce, "Content-Type: message/%s",
931 headers_only == DSN_RET_HDRS ?
932 "global-headers" : "global");
933 else
934 post_mail_fprintf(bounce, "Content-Type: %s",
935 headers_only == DSN_RET_HDRS ?
936 "text/rfc822-headers" : "message/rfc822");
937 if (NOT_7BIT_MIME(bounce_info))
938 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s",
939 bounce_info->mime_encoding);
940 post_mail_fputs(bounce, "");
941
942 /*
943 * Send place holder if original is unavailable.
944 */
945 if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp,
946 bounce_info->orig_offs, SEEK_SET) < 0) {
947 post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---");
948 return (vstream_ferror(bounce));
949 }
950
951 /*
952 * XXX The cleanup server removes Return-Path: headers. This should be
953 * done only with mail that enters via a non-SMTP channel, but changing
954 * this now could break other software. Removing Return-Path: could break
955 * digital signatures, though this is unlikely. In any case,
956 * header_checks are more effective when the Return-Path: header is
957 * present, so we prepend one to the bounce message.
958 */
959 post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender));
960
961 /*
962 * Copy the original message contents. We're doing raw record output here
963 * so that we don't throw away binary transparency yet.
964 */
965 #define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s))
966
967 while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) {
968 if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
969 break;
970 if (headers_only == DSN_RET_HDRS
971 && !IS_HEADER(vstring_str(bounce_info->buf)))
972 break;
973 status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type);
974 }
975
976 /*
977 * Final MIME headers. These require -- at the end of the boundary
978 * string.
979 *
980 * XXX This should be a separate bounce_terminate() entry so we can be
981 * assured that the terminator will always be sent.
982 */
983 post_mail_fputs(bounce, "");
984 post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary);
985
986 return (status);
987 }
988
989 /* bounce_delrcpt - delete recipients from original queue file */
990
bounce_delrcpt(BOUNCE_INFO * bounce_info)991 void bounce_delrcpt(BOUNCE_INFO *bounce_info)
992 {
993 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
994
995 if (bounce_info->orig_fp != 0
996 && bounce_info->log_handle != 0
997 && bounce_log_rewind(bounce_info->log_handle) == 0)
998 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf,
999 bounce_info->dsn_buf) != 0)
1000 if (rcpt->offset > 0)
1001 deliver_completed(bounce_info->orig_fp, rcpt->offset);
1002 }
1003
1004 /* bounce_delrcpt_one - delete one recipient from original queue file */
1005
bounce_delrcpt_one(BOUNCE_INFO * bounce_info)1006 void bounce_delrcpt_one(BOUNCE_INFO *bounce_info)
1007 {
1008 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt;
1009
1010 if (bounce_info->orig_fp != 0 && rcpt->offset > 0)
1011 deliver_completed(bounce_info->orig_fp, rcpt->offset);
1012 }
1013