1 /* $NetBSD: bounce_notify_util.c,v 1.6 2026/05/09 18:49:14 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 /* sendopts, dsn_envid, template) 19 /* const char *service; 20 /* const char *queue_name; 21 /* const char *queue_id; 22 /* const char *encoding; 23 /* int sendopts; 24 /* const char *dsn_envid; 25 /* const BOUNCE_TEMPLATE *template; 26 /* 27 /* BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding, 28 /* sendopts, 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 sendopts; 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 /* Wietse Venema 167 /* porcupine.org 168 /*--*/ 169 170 /* System library. */ 171 172 #include <sys_defs.h> 173 #include <sys/stat.h> 174 #include <stdlib.h> 175 #include <stdio.h> /* sscanf() */ 176 #include <unistd.h> 177 #include <errno.h> 178 #include <string.h> 179 #include <ctype.h> 180 181 #ifdef STRCASECMP_IN_STRINGS_H 182 #include <strings.h> 183 #endif 184 185 /* Utility library. */ 186 187 #include <msg.h> 188 #include <mymalloc.h> 189 #include <events.h> 190 #include <vstring.h> 191 #include <vstream.h> 192 #include <line_wrap.h> 193 #include <stringops.h> 194 #include <myflock.h> 195 196 /* Global library. */ 197 198 #include <mail_queue.h> 199 #include <quote_822_local.h> 200 #include <mail_params.h> 201 #include <is_header.h> 202 #include <record.h> 203 #include <rec_type.h> 204 #include <post_mail.h> 205 #include <mail_addr.h> 206 #include <mail_error.h> 207 #include <bounce_log.h> 208 #include <mail_date.h> 209 #include <mail_proto.h> 210 #include <lex_822.h> 211 #include <deliver_completed.h> 212 #include <dsn_mask.h> 213 #include <smtputf8.h> 214 #include <header_opts.h> 215 216 /* Application-specific. */ 217 218 #include "bounce_service.h" 219 220 #define STR vstring_str 221 #define LEN VSTRING_LEN 222 223 /* bounce_mail_alloc - initialize */ 224 225 static BOUNCE_INFO *bounce_mail_alloc(const char *service, 226 const char *queue_name, 227 const char *queue_id, 228 const char *encoding, 229 int sendopts, 230 const char *dsn_envid, 231 RCPT_BUF *rcpt_buf, 232 DSN_BUF *dsn_buf, 233 BOUNCE_TEMPLATE *template, 234 BOUNCE_LOG *log_handle) 235 { 236 BOUNCE_INFO *bounce_info; 237 int rec_type; 238 int skip_message_segment = 0; 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->sendopts = sendopts; 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 while ((rec_type = 328 rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) { 329 330 /* 331 * Postfix version dependent: data offset in SIZE record. 332 */ 333 if (rec_type == REC_TYPE_SIZE) { 334 if (bounce_info->message_size == 0) 335 sscanf(STR(bounce_info->buf), "%ld %ld", 336 &bounce_info->message_size, 337 &bounce_info->orig_offs); 338 if (bounce_info->message_size < 0) 339 bounce_info->message_size = 0; 340 if (bounce_info->orig_offs < 0) 341 bounce_info->orig_offs = 0; 342 } 343 344 /* 345 * Information for the Arrival-Date: attribute. 346 */ 347 else if (rec_type == REC_TYPE_TIME) { 348 if (bounce_info->arrival_time == 0 349 && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0) 350 bounce_info->arrival_time = 0; 351 } 352 353 /* 354 * Information for the X-Postfix-Sender: attribute. 355 */ 356 else if (rec_type == REC_TYPE_FROM) { 357 quote_822_local_flags(bounce_info->sender, 358 VSTRING_LEN(bounce_info->buf) ? 359 STR(bounce_info->buf) : 360 mail_addr_mail_daemon(), 361 QUOTE_FLAG_8BITCLEAN); 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 skip_message_segment = 1; 374 } 375 376 /* 377 * Extract Message-ID from extracted segment, for use in threaded 378 * bounces. 379 */ 380 else if (rec_type == REC_TYPE_ATTR && var_threaded_bounce) { 381 char *cp = STR(bounce_info->buf); 382 ssize_t len = sizeof(MAIL_ATTR_MESSAGE_ID); 383 char *err; 384 385 if (strncmp(cp, MAIL_ATTR_MESSAGE_ID "=", len) == 0) { 386 cp += len; 387 if ((err = extpar(&cp, "<>", EXTPAR_FLAG_NONE)) != 0) { 388 msg_warn("%s: malformed Message-ID attribute: %s", 389 bounce_info->queue_id, err); 390 myfree(err); 391 } else { 392 vstring_sprintf(bounce_info->orig_msgid, "<%s>", cp); 393 } 394 } 395 } 396 397 /* 398 * Are we done yet? 399 */ 400 if (bounce_info->orig_offs > 0 401 && bounce_info->arrival_time > 0 402 && VSTRING_LEN(bounce_info->sender) > 0 403 && (var_threaded_bounce == 0 404 || VSTRING_LEN(bounce_info->orig_msgid) > 0)) { 405 break; 406 } 407 408 /* 409 * Skip over (the remainder of) the message segment. If that 410 * fails, degrade. 411 */ 412 if (skip_message_segment) { 413 if (vstream_fseek(bounce_info->orig_fp, 414 bounce_info->orig_offs + 415 bounce_info->message_size, 416 SEEK_SET) < 0) 417 /* void */ ; 418 skip_message_segment = 0; 419 } 420 } 421 } 422 return (bounce_info); 423 } 424 425 /* bounce_mail_init - initialize */ 426 427 BOUNCE_INFO *bounce_mail_init(const char *service, 428 const char *queue_name, 429 const char *queue_id, 430 const char *encoding, 431 int sendopts, 432 const char *dsn_envid, 433 BOUNCE_TEMPLATE *template) 434 { 435 BOUNCE_INFO *bounce_info; 436 BOUNCE_LOG *log_handle; 437 RCPT_BUF *rcpt_buf; 438 DSN_BUF *dsn_buf; 439 440 /* 441 * Initialize the bounce_info structure. If the bounce log cannot be 442 * found, do not raise a fatal run-time error. There is nothing we can do 443 * about the error, and all we are doing is to inform the sender of a 444 * delivery problem, Bouncing a message does not have to be a perfect 445 * job. But if the system IS running out of resources, raise a fatal 446 * run-time error and force a backoff. 447 */ 448 if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) { 449 if (errno != ENOENT) 450 msg_fatal("open %s %s: %m", service, queue_id); 451 rcpt_buf = 0; 452 dsn_buf = 0; 453 } else { 454 rcpt_buf = rcpb_create(); 455 dsn_buf = dsb_create(); 456 } 457 bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding, 458 sendopts, dsn_envid, rcpt_buf, dsn_buf, 459 template, log_handle); 460 return (bounce_info); 461 } 462 463 /* bounce_mail_one_init - initialize */ 464 465 BOUNCE_INFO *bounce_mail_one_init(const char *queue_name, 466 const char *queue_id, 467 const char *encoding, 468 int sendopts, 469 const char *dsn_envid, 470 RCPT_BUF *rcpt_buf, 471 DSN_BUF *dsn_buf, 472 BOUNCE_TEMPLATE *template) 473 { 474 BOUNCE_INFO *bounce_info; 475 476 /* 477 * Initialize the bounce_info structure for just one recipient. 478 */ 479 bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding, 480 sendopts, dsn_envid, rcpt_buf, dsn_buf, 481 template, (BOUNCE_LOG *) 0); 482 return (bounce_info); 483 } 484 485 /* bounce_mail_free - undo bounce_mail_init */ 486 487 void bounce_mail_free(BOUNCE_INFO *bounce_info) 488 { 489 if (bounce_info->log_handle) { 490 if (bounce_log_close(bounce_info->log_handle)) 491 msg_warn("%s: read bounce log %s: %m", 492 bounce_info->queue_id, bounce_info->queue_id); 493 vstring_free(bounce_info->orig_msgid); 494 rcpb_free(bounce_info->rcpt_buf); 495 dsb_free(bounce_info->dsn_buf); 496 } 497 if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp)) 498 msg_warn("%s: read message file %s %s: %m", 499 bounce_info->queue_id, bounce_info->queue_name, 500 bounce_info->queue_id); 501 vstring_free(bounce_info->buf); 502 vstring_free(bounce_info->sender); 503 myfree(bounce_info->mail_name); 504 myfree((void *) bounce_info->mime_boundary); 505 myfree((void *) bounce_info); 506 } 507 508 /* bounce_header - generate bounce message header */ 509 510 int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info, 511 const char *dest, int postmaster_copy) 512 { 513 BOUNCE_TEMPLATE *template = bounce_info->template; 514 515 /* 516 * Print a minimal bounce header. The cleanup service will add other 517 * headers and will make all addresses fully qualified. 518 */ 519 #define STREQ(a, b) (strcasecmp((a), (b)) == 0) 520 #define STRNE(a, b) (strcasecmp((a), (b)) != 0) 521 522 /* 523 * Generic headers. 524 */ 525 bounce_template_headers(post_mail_fprintf, bounce, template, 526 STR(quote_822_local(bounce_info->buf, dest)), 527 postmaster_copy); 528 529 /* 530 * References and Reply-To header that references the original message-id 531 * for better threading in MUAs. 532 */ 533 if (VSTRING_LEN(bounce_info->orig_msgid) > 0) { 534 post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid)); 535 post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid)); 536 } 537 538 /* 539 * Trade confidentiality against availability. See also up-stream code in 540 * edit_notification_properties(). 541 */ 542 if (var_tls_required_enable 543 && (bounce_info->sendopts & SOPT_REQUIRETLS_HEADER) != 0) 544 post_mail_fprintf(bounce, "TLS-Required: no"); 545 546 /* 547 * Auto-Submitted header, as per RFC 3834. 548 */ 549 post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ? 550 "auto-generated" : "auto-replied"); 551 552 /* 553 * MIME header. Use 8bit encoding when either the bounced message or the 554 * template requires it. 555 */ 556 post_mail_fprintf(bounce, "MIME-Version: 1.0"); 557 post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;", 558 "multipart/report", "delivery-status"); 559 post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary); 560 if (bounce_info->mime_encoding) 561 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", 562 STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ? 563 bounce_template_encoding(template) : 564 bounce_info->mime_encoding); 565 post_mail_fputs(bounce, ""); 566 post_mail_fputs(bounce, "This is a MIME-encapsulated message."); 567 568 /* 569 * MIME header. 570 */ 571 #define NOT_US_ASCII(tp) \ 572 STRNE(bounce_template_charset(template), "us-ascii") 573 574 #define NOT_7BIT_MIME(bp) \ 575 (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT)) 576 577 post_mail_fputs(bounce, ""); 578 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); 579 post_mail_fprintf(bounce, "Content-Description: %s", "Notification"); 580 /* Fix 20140718: UTF-8 address or $myhostname expansion. */ 581 post_mail_fprintf(bounce, "Content-Type: %s; charset=%s", 582 "text/plain", NOT_US_ASCII(template) ? 583 bounce_template_charset(template) : 584 NOT_7BIT_MIME(bounce_info) ? 585 "utf-8" : "us-ascii"); 586 /* Fix 20140709: addresses may be 8bit. */ 587 if (NOT_7BIT_MIME(bounce_info)) 588 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", 589 bounce_info->mime_encoding); 590 post_mail_fputs(bounce, ""); 591 592 return (vstream_ferror(bounce)); 593 } 594 595 /* bounce_boilerplate - generate boiler-plate text */ 596 597 int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info) 598 { 599 600 /* 601 * Print the boiler-plate text. 602 */ 603 bounce_template_expand(post_mail_fputs, bounce, bounce_info->template); 604 return (vstream_ferror(bounce)); 605 } 606 607 /* bounce_print - line_wrap callback */ 608 609 static void bounce_print(const char *str, int len, int indent, void *context) 610 { 611 VSTREAM *bounce = (VSTREAM *) context; 612 613 post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str); 614 } 615 616 /* bounce_print_wrap - print and wrap a line */ 617 618 static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info, 619 const char *format,...) 620 { 621 va_list ap; 622 623 #define LENGTH 79 624 #define INDENT 4 625 626 va_start(ap, format); 627 vstring_vsprintf(bounce_info->buf, format, ap); 628 va_end(ap); 629 line_wrap(STR(bounce_info->buf), LENGTH, INDENT, 630 bounce_print, (void *) bounce); 631 } 632 633 /* bounce_recipient_log - send one bounce log report entry */ 634 635 int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info) 636 { 637 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 638 DSN *dsn = &bounce_info->dsn_buf->dsn; 639 640 /* 641 * Mask control and non-ASCII characters (done in bounce_log_read()), 642 * wrap long lines and prepend one blank, so this data can safely be 643 * piped into other programs. Sort of like TCP Wrapper's safe_finger 644 * program. 645 */ 646 #define NON_NULL_EMPTY(s) ((s) && *(s)) 647 648 post_mail_fputs(bounce, ""); 649 if (NON_NULL_EMPTY(rcpt->orig_addr)) { 650 bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s", 651 rcpt->address, rcpt->orig_addr, dsn->reason); 652 } else { 653 bounce_print_wrap(bounce, bounce_info, "<%s>: %s", 654 rcpt->address, dsn->reason); 655 } 656 return (vstream_ferror(bounce)); 657 } 658 659 /* bounce_diagnostic_log - send bounce log report */ 660 661 int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info, 662 int notify_filter) 663 { 664 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 665 int count = 0; 666 667 /* 668 * Append a human-readable copy of the delivery error log. We're doing a 669 * best effort, so there is no point raising a fatal run-time error in 670 * case of a logfile read error. 671 * 672 * XXX DSN If the logfile with failed recipients is unavailable, pretend 673 * that we found something anyway, so that this notification will not be 674 * canceled. 675 */ 676 if (bounce_info->log_handle == 0 677 || bounce_log_rewind(bounce_info->log_handle)) { 678 if (IS_FAILURE_TEMPLATE(bounce_info->template)) { 679 post_mail_fputs(bounce, ""); 680 post_mail_fputs(bounce, "\t--- Delivery report unavailable ---"); 681 count = 1; /* XXX don't abort */ 682 } 683 } else { 684 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, 685 bounce_info->dsn_buf) != 0) { 686 if (rcpt->dsn_notify == 0 /* compat */ 687 || (rcpt->dsn_notify & notify_filter)) { 688 count++; 689 if (bounce_recipient_log(bounce, bounce_info) != 0) 690 break; 691 } 692 } 693 } 694 return (vstream_ferror(bounce) ? -1 : count); 695 } 696 697 /* bounce_header_dsn - send per-MTA bounce DSN records */ 698 699 int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) 700 { 701 702 /* 703 * MIME header. 704 */ 705 post_mail_fputs(bounce, ""); 706 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); 707 post_mail_fprintf(bounce, "Content-Description: %s", 708 "Delivery report"); 709 /* Generate *global* only if the original requested SMTPUTF8 support. */ 710 post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status", 711 (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED) ? 712 "global-" : ""); 713 /* Fix 20140709: addresses may be 8bit. */ 714 if (NOT_7BIT_MIME(bounce_info) 715 /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */ 716 && (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED)) 717 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", 718 bounce_info->mime_encoding); 719 720 /* 721 * According to RFC 1894: The body of a message/delivery-status consists 722 * of one or more "fields" formatted according to the ABNF of RFC 822 723 * header "fields" (see [6]). The per-message fields appear first, 724 * followed by a blank line. 725 */ 726 post_mail_fputs(bounce, ""); 727 post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname); 728 #if 0 729 post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever"); 730 #endif 731 if (NON_NULL_EMPTY(bounce_info->dsn_envid)) { 732 post_mail_fprintf(bounce, "Original-Envelope-Id: %s", 733 bounce_info->dsn_envid); 734 } 735 post_mail_fprintf(bounce, "X-%s-Queue-ID: %s", 736 bounce_info->mail_name, bounce_info->queue_id); 737 738 #define IS_UTF8_ADDRESS(str) \ 739 ((str)[0] != 0 && !allascii(str) && valid_utf8_stringz(str)) 740 741 /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */ 742 if (VSTRING_LEN(bounce_info->sender) > 0) 743 post_mail_fprintf(bounce, "X-%s-Sender: %s; %s", 744 bounce_info->mail_name, 745 (bounce_info->sendopts & SMTPUTF8_FLAG_ALL) 746 && IS_UTF8_ADDRESS(STR(bounce_info->sender)) ? 747 "utf-8" : "rfc822", STR(bounce_info->sender)); 748 if (bounce_info->arrival_time > 0) 749 post_mail_fprintf(bounce, "Arrival-Date: %s", 750 mail_date(bounce_info->arrival_time)); 751 return (vstream_ferror(bounce)); 752 } 753 754 /* bounce_recipient_dsn - send per-recipient DSN records */ 755 756 int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) 757 { 758 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 759 DSN *dsn = &bounce_info->dsn_buf->dsn; 760 761 post_mail_fputs(bounce, ""); 762 /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */ 763 post_mail_fprintf(bounce, "Final-Recipient: %s; %s", 764 (bounce_info->sendopts & SMTPUTF8_FLAG_ALL) 765 && IS_UTF8_ADDRESS(rcpt->address) ? 766 "utf-8" : "rfc822", rcpt->address); 767 768 /* 769 * XXX DSN 770 * 771 * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this 772 * recipient, the Original-Recipient field MUST NOT appear." 773 * 774 * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was 775 * present in the RCPT command when the message was received, an ORCPT 776 * parameter MAY be added to the RCPT command when the message is 777 * relayed.". Postfix adds an ORCPT parameter under these conditions. 778 * 779 * Therefore, all down-stream MTAs will send DSNs with Original-Recipient 780 * field containing this same ORCPT value. When a down-stream MTA can use 781 * that information in their DSNs, it makes no sense that an up-stream 782 * MTA can't use that same information in its own DSNs. 783 * 784 * Postfix always reports an Original-Recipient field, because it is more 785 * more useful and more consistent. 786 */ 787 if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) { 788 post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt); 789 } else if (NON_NULL_EMPTY(rcpt->orig_addr)) { 790 /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */ 791 post_mail_fprintf(bounce, "Original-Recipient: %s; %s", 792 (bounce_info->sendopts & SMTPUTF8_FLAG_ALL) 793 && IS_UTF8_ADDRESS(rcpt->orig_addr) ? 794 "utf-8" : "rfc822", rcpt->orig_addr); 795 } 796 post_mail_fprintf(bounce, "Action: %s", 797 IS_FAILURE_TEMPLATE(bounce_info->template) ? 798 "failed" : dsn->action); 799 post_mail_fprintf(bounce, "Status: %s", dsn->status); 800 if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname)) 801 bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s", 802 dsn->mtype, dsn->mname); 803 if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext)) 804 bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s", 805 dsn->dtype, dsn->dtext); 806 else 807 bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s", 808 bounce_info->mail_name, dsn->reason); 809 #if 0 810 if (dsn->time > 0) 811 post_mail_fprintf(bounce, "Last-Attempt-Date: %s", 812 mail_date(dsn->time)); 813 #endif 814 if (IS_DELAY_TEMPLATE(bounce_info->template)) 815 post_mail_fprintf(bounce, "Will-Retry-Until: %s", 816 mail_date(bounce_info->arrival_time + var_max_queue_time)); 817 return (vstream_ferror(bounce)); 818 } 819 820 /* bounce_diagnostic_dsn - send bounce log report, machine readable form */ 821 822 int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info, 823 int notify_filter) 824 { 825 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 826 int count = 0; 827 828 /* 829 * Append a machine-readable copy of the delivery error log. We're doing 830 * a best effort, so there is no point raising a fatal run-time error in 831 * case of a logfile read error. 832 * 833 * XXX DSN If the logfile with failed recipients is unavailable, pretend 834 * that we found something anyway, so that this notification will not be 835 * canceled. 836 */ 837 if (bounce_info->log_handle == 0 838 || bounce_log_rewind(bounce_info->log_handle)) { 839 if (IS_FAILURE_TEMPLATE(bounce_info->template)) 840 count = 1; /* XXX don't abort */ 841 } else { 842 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, 843 bounce_info->dsn_buf) != 0) { 844 if (rcpt->dsn_notify == 0 /* compat */ 845 || (rcpt->dsn_notify & notify_filter)) { 846 count++; 847 if (bounce_recipient_dsn(bounce, bounce_info) != 0) 848 break; 849 } 850 } 851 } 852 return (vstream_ferror(bounce) ? -1 : count); 853 } 854 855 /* bounce_original - send a copy of the original to the victim */ 856 857 int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info, 858 int headers_only) 859 { 860 int status = 0; 861 int rec_type = 0; 862 863 /* 864 * When truncating a large message, don't damage the MIME structure: send 865 * the message headers only. 866 */ 867 if (var_bounce_limit > 0 868 && bounce_info->orig_fp 869 && (bounce_info->message_size <= 0 870 || bounce_info->message_size > var_bounce_limit)) 871 headers_only = DSN_RET_HDRS; 872 873 /* 874 * MIME headers. 875 */ 876 #define IS_UNDELIVERED_TEMPLATE(template) \ 877 (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template)) 878 879 post_mail_fputs(bounce, ""); 880 post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); 881 post_mail_fprintf(bounce, "Content-Description: %s%s", 882 IS_UNDELIVERED_TEMPLATE(bounce_info->template) ? 883 "Undelivered " : "", 884 headers_only == DSN_RET_HDRS ? 885 "Message Headers" : "Message"); 886 /* Generate *global* only if the original requested SMTPUTF8 support. */ 887 if (bounce_info->sendopts & SMTPUTF8_FLAG_REQUESTED) 888 post_mail_fprintf(bounce, "Content-Type: message/%s", 889 headers_only == DSN_RET_HDRS ? 890 "global-headers" : "global"); 891 else 892 post_mail_fprintf(bounce, "Content-Type: %s", 893 headers_only == DSN_RET_HDRS ? 894 "text/rfc822-headers" : "message/rfc822"); 895 if (NOT_7BIT_MIME(bounce_info)) 896 post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", 897 bounce_info->mime_encoding); 898 post_mail_fputs(bounce, ""); 899 900 /* 901 * Send place holder if original is unavailable. 902 */ 903 if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp, 904 bounce_info->orig_offs, SEEK_SET) < 0) { 905 post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---"); 906 return (vstream_ferror(bounce)); 907 } 908 909 /* 910 * XXX The cleanup server removes Return-Path: headers. This should be 911 * done only with mail that enters via a non-SMTP channel, but changing 912 * this now could break other software. Removing Return-Path: could break 913 * digital signatures, though this is unlikely. In any case, 914 * header_checks are more effective when the Return-Path: header is 915 * present, so we prepend one to the bounce message. 916 */ 917 post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender)); 918 919 /* 920 * Copy the original message contents. We're doing raw record output here 921 * so that we don't throw away binary transparency yet. 922 */ 923 #define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s)) 924 925 while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) { 926 if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) 927 break; 928 if (headers_only == DSN_RET_HDRS 929 && !IS_HEADER(vstring_str(bounce_info->buf))) 930 break; 931 status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type); 932 } 933 934 /* 935 * Final MIME headers. These require -- at the end of the boundary 936 * string. 937 * 938 * XXX This should be a separate bounce_terminate() entry so we can be 939 * assured that the terminator will always be sent. 940 */ 941 post_mail_fputs(bounce, ""); 942 post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary); 943 944 return (status); 945 } 946 947 /* bounce_delrcpt - delete recipients from original queue file */ 948 949 void bounce_delrcpt(BOUNCE_INFO *bounce_info) 950 { 951 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 952 953 if (bounce_info->orig_fp != 0 954 && bounce_info->log_handle != 0 955 && bounce_log_rewind(bounce_info->log_handle) == 0) 956 while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, 957 bounce_info->dsn_buf) != 0) 958 if (rcpt->offset > 0) 959 deliver_completed(bounce_info->orig_fp, rcpt->offset); 960 } 961 962 /* bounce_delrcpt_one - delete one recipient from original queue file */ 963 964 void bounce_delrcpt_one(BOUNCE_INFO *bounce_info) 965 { 966 RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; 967 968 if (bounce_info->orig_fp != 0 && rcpt->offset > 0) 969 deliver_completed(bounce_info->orig_fp, rcpt->offset); 970 } 971