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