1 /* $NetBSD: cleanup_message.c,v 1.4 2022/10/08 16:12:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* cleanup_message 3 6 /* SUMMARY 7 /* process message segment 8 /* SYNOPSIS 9 /* #include "cleanup.h" 10 /* 11 /* void cleanup_message(state, type, buf, len) 12 /* CLEANUP_STATE *state; 13 /* int type; 14 /* const char *buf; 15 /* ssize_t len; 16 /* DESCRIPTION 17 /* This module processes message content records and copies the 18 /* result to the queue file. It validates the input, rewrites 19 /* sender/recipient addresses to canonical form, inserts missing 20 /* message headers, and extracts information from message headers 21 /* to be used later when generating the extracted output segment. 22 /* This routine absorbs but does not emit the content to extracted 23 /* boundary record. 24 /* 25 /* Arguments: 26 /* .IP state 27 /* Queue file and message processing state. This state is updated 28 /* as records are processed and as errors happen. 29 /* .IP type 30 /* Record type. 31 /* .IP buf 32 /* Record content. 33 /* .IP len 34 /* Record content length. 35 /* LICENSE 36 /* .ad 37 /* .fi 38 /* The Secure Mailer license must be distributed with this software. 39 /* AUTHOR(S) 40 /* Wietse Venema 41 /* IBM T.J. Watson Research 42 /* P.O. Box 704 43 /* Yorktown Heights, NY 10598, USA 44 /* 45 /* Wietse Venema 46 /* Google, Inc. 47 /* 111 8th Avenue 48 /* New York, NY 10011, USA 49 /*--*/ 50 51 /* System library. */ 52 53 #include <sys_defs.h> 54 #include <ctype.h> 55 #include <string.h> 56 #include <time.h> 57 #include <unistd.h> 58 59 #ifdef STRCASECMP_IN_STRINGS_H 60 #include <strings.h> 61 #endif 62 63 /* Utility library. */ 64 65 #include <msg.h> 66 #include <vstring.h> 67 #include <vstream.h> 68 #include <argv.h> 69 #include <split_at.h> 70 #include <mymalloc.h> 71 #include <stringops.h> 72 #include <nvtable.h> 73 74 /* Global library. */ 75 76 #include <record.h> 77 #include <rec_type.h> 78 #include <cleanup_user.h> 79 #include <tok822.h> 80 #include <lex_822.h> 81 #include <header_opts.h> 82 #include <quote_822_local.h> 83 #include <mail_params.h> 84 #include <mail_date.h> 85 #include <mail_addr.h> 86 #include <is_header.h> 87 #include <ext_prop.h> 88 #include <mail_proto.h> 89 #include <mime_state.h> 90 #include <lex_822.h> 91 #include <dsn_util.h> 92 #include <conv_time.h> 93 #include <info_log_addr_form.h> 94 #include <hfrom_format.h> 95 96 /* Application-specific. */ 97 98 #include "cleanup.h" 99 100 /* cleanup_fold_header - wrap address list header */ 101 102 static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf) 103 { 104 char *start_line = vstring_str(header_buf); 105 char *end_line; 106 char *next_line; 107 char *line; 108 109 /* 110 * A rewritten address list contains one address per line. The code below 111 * replaces newlines by spaces, to fit as many addresses on a line as 112 * possible (without rearranging the order of addresses). Prepending 113 * white space to the beginning of lines is delegated to the output 114 * routine. 115 */ 116 for (line = start_line; line != 0; line = next_line) { 117 end_line = line + strcspn(line, "\n"); 118 if (line > start_line) { 119 if (end_line - start_line < 70) { /* TAB counts as one */ 120 line[-1] = ' '; 121 } else { 122 start_line = line; 123 } 124 } 125 next_line = *end_line ? end_line + 1 : 0; 126 } 127 cleanup_out_header(state, header_buf); 128 } 129 130 /* cleanup_extract_internal - save unquoted copy of extracted address */ 131 132 static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr) 133 { 134 135 /* 136 * A little routine to stash away a copy of an address that we extracted 137 * from a message header line. 138 */ 139 tok822_internalize(buffer, addr->head, TOK822_STR_DEFL); 140 return (mystrdup(vstring_str(buffer))); 141 } 142 143 /* cleanup_rewrite_sender - sender address rewriting */ 144 145 static void cleanup_rewrite_sender(CLEANUP_STATE *state, 146 const HEADER_OPTS *hdr_opts, 147 VSTRING *header_buf) 148 { 149 TOK822 *tree; 150 TOK822 **addr_list; 151 TOK822 **tpp; 152 int did_rewrite = 0; 153 154 if (msg_verbose) 155 msg_info("rewrite_sender: %s", hdr_opts->name); 156 157 /* 158 * Parse the header line, rewrite each address found, and regenerate the 159 * header line. Finally, pipe the result through the header line folding 160 * routine. 161 */ 162 tree = tok822_parse_limit(vstring_str(header_buf) 163 + strlen(hdr_opts->name) + 1, 164 var_token_limit); 165 addr_list = tok822_grep(tree, TOK822_ADDR); 166 for (tpp = addr_list; *tpp; tpp++) { 167 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp); 168 if (state->flags & CLEANUP_FLAG_MAP_OK) { 169 if (cleanup_send_canon_maps 170 && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM)) 171 did_rewrite |= 172 cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps, 173 cleanup_ext_prop_mask & EXT_PROP_CANONICAL); 174 if (cleanup_comm_canon_maps 175 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM)) 176 did_rewrite |= 177 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, 178 cleanup_ext_prop_mask & EXT_PROP_CANONICAL); 179 if (cleanup_masq_domains 180 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM)) 181 did_rewrite |= 182 cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains); 183 } 184 } 185 if (did_rewrite) { 186 vstring_truncate(header_buf, strlen(hdr_opts->name)); 187 vstring_strcat(header_buf, ": "); 188 tok822_externalize(header_buf, tree, TOK822_STR_HEAD); 189 } 190 myfree((void *) addr_list); 191 tok822_free_tree(tree); 192 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { 193 if (did_rewrite) 194 cleanup_fold_header(state, header_buf); 195 else 196 cleanup_out_header(state, header_buf); 197 } 198 } 199 200 /* cleanup_rewrite_recip - recipient address rewriting */ 201 202 static void cleanup_rewrite_recip(CLEANUP_STATE *state, 203 const HEADER_OPTS *hdr_opts, 204 VSTRING *header_buf) 205 { 206 TOK822 *tree; 207 TOK822 **addr_list; 208 TOK822 **tpp; 209 int did_rewrite = 0; 210 211 if (msg_verbose) 212 msg_info("rewrite_recip: %s", hdr_opts->name); 213 214 /* 215 * Parse the header line, rewrite each address found, and regenerate the 216 * header line. Finally, pipe the result through the header line folding 217 * routine. 218 */ 219 tree = tok822_parse_limit(vstring_str(header_buf) 220 + strlen(hdr_opts->name) + 1, 221 var_token_limit); 222 addr_list = tok822_grep(tree, TOK822_ADDR); 223 for (tpp = addr_list; *tpp; tpp++) { 224 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp); 225 if (state->flags & CLEANUP_FLAG_MAP_OK) { 226 if (cleanup_rcpt_canon_maps 227 && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) 228 did_rewrite |= 229 cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps, 230 cleanup_ext_prop_mask & EXT_PROP_CANONICAL); 231 if (cleanup_comm_canon_maps 232 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) 233 did_rewrite |= 234 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, 235 cleanup_ext_prop_mask & EXT_PROP_CANONICAL); 236 if (cleanup_masq_domains 237 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT)) 238 did_rewrite |= 239 cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains); 240 } 241 } 242 if (did_rewrite) { 243 vstring_truncate(header_buf, strlen(hdr_opts->name)); 244 vstring_strcat(header_buf, ": "); 245 tok822_externalize(header_buf, tree, TOK822_STR_HEAD); 246 } 247 myfree((void *) addr_list); 248 tok822_free_tree(tree); 249 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { 250 if (did_rewrite) 251 cleanup_fold_header(state, header_buf); 252 else 253 cleanup_out_header(state, header_buf); 254 } 255 } 256 257 /* cleanup_act_log - log action with context */ 258 259 static void cleanup_act_log(CLEANUP_STATE *state, 260 const char *action, const char *class, 261 const char *content, const char *text) 262 { 263 const char *attr; 264 265 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0) 266 attr = "unknown"; 267 vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;", 268 state->queue_id, action, class, content, attr); 269 if (state->sender) 270 vstring_sprintf_append(state->temp1, " from=<%s>", 271 info_log_addr_form_sender(state->sender)); 272 if (state->recip) 273 vstring_sprintf_append(state->temp1, " to=<%s>", 274 info_log_addr_form_recipient(state->recip)); 275 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) 276 vstring_sprintf_append(state->temp1, " proto=%s", attr); 277 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) 278 vstring_sprintf_append(state->temp1, " helo=<%s>", attr); 279 if (text && *text) 280 vstring_sprintf_append(state->temp1, ": %s", text); 281 msg_info("%s", vstring_str(state->temp1)); 282 } 283 284 #define CLEANUP_ACT_CTXT_HEADER "header" 285 #define CLEANUP_ACT_CTXT_BODY "body" 286 #define CLEANUP_ACT_CTXT_ANY "content" 287 288 /* cleanup_act - act upon a header/body match */ 289 290 static const char *cleanup_act(CLEANUP_STATE *state, char *context, 291 const char *buf, const char *value, 292 const char *map_class) 293 { 294 const char *optional_text = value + strcspn(value, " \t"); 295 int command_len = optional_text - value; 296 297 #ifdef DELAY_ACTION 298 int defer_delay; 299 300 #endif 301 302 while (*optional_text && ISSPACE(*optional_text)) 303 optional_text++; 304 305 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) 306 #define CLEANUP_ACT_DROP 0 307 308 /* 309 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason 310 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates 311 * queue record processing, and prevents bounces from being sent. 312 */ 313 if (STREQUAL(value, "REJECT", command_len)) { 314 const CLEANUP_STAT_DETAIL *detail; 315 316 if (state->reason) 317 myfree(state->reason); 318 if (*optional_text) { 319 state->reason = dsn_prepend("5.7.1", optional_text); 320 if (*state->reason != '4' && *state->reason != '5') { 321 msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x", 322 optional_text); 323 *state->reason = '4'; 324 } 325 } else { 326 detail = cleanup_stat_detail(CLEANUP_STAT_CONT); 327 state->reason = dsn_prepend(detail->dsn, detail->text); 328 } 329 if (*state->reason == '4') 330 state->errs |= CLEANUP_STAT_DEFER; 331 else 332 state->errs |= CLEANUP_STAT_CONT; 333 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 334 cleanup_act_log(state, "reject", context, buf, state->reason); 335 return (buf); 336 } 337 if (STREQUAL(value, "WARN", command_len)) { 338 cleanup_act_log(state, "warning", context, buf, optional_text); 339 return (buf); 340 } 341 if (STREQUAL(value, "INFO", command_len)) { 342 cleanup_act_log(state, "info", context, buf, optional_text); 343 return (buf); 344 } 345 if (STREQUAL(value, "FILTER", command_len)) { 346 if (*optional_text == 0) { 347 msg_warn("missing FILTER command argument in %s map", map_class); 348 } else if (strchr(optional_text, ':') == 0) { 349 msg_warn("bad FILTER command %s in %s -- " 350 "need transport:destination", 351 optional_text, map_class); 352 } else { 353 if (state->filter) 354 myfree(state->filter); 355 state->filter = mystrdup(optional_text); 356 cleanup_act_log(state, "filter", context, buf, optional_text); 357 } 358 return (buf); 359 } 360 if (STREQUAL(value, "PASS", command_len)) { 361 cleanup_act_log(state, "pass", context, buf, optional_text); 362 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 363 return (buf); 364 } 365 if (STREQUAL(value, "DISCARD", command_len)) { 366 cleanup_act_log(state, "discard", context, buf, optional_text); 367 state->flags |= CLEANUP_FLAG_DISCARD; 368 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 369 return (buf); 370 } 371 if (STREQUAL(value, "HOLD", command_len)) { 372 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { 373 cleanup_act_log(state, "hold", context, buf, optional_text); 374 state->flags |= CLEANUP_FLAG_HOLD; 375 } 376 return (buf); 377 } 378 379 /* 380 * The DELAY feature is disabled because it has too many problems. 1) It 381 * does not work on some remote file systems; 2) mail will be delivered 382 * anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs 383 * down the deferred queue scan with huge amounts of useless disk I/O 384 * operations. 385 */ 386 #ifdef DELAY_ACTION 387 if (STREQUAL(value, "DELAY", command_len)) { 388 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { 389 if (*optional_text == 0) { 390 msg_warn("missing DELAY argument in %s map", map_class); 391 } else if (conv_time(optional_text, &defer_delay, 's') == 0) { 392 msg_warn("ignoring bad DELAY argument %s in %s map", 393 optional_text, map_class); 394 } else { 395 cleanup_act_log(state, "delay", context, buf, optional_text); 396 state->defer_delay = defer_delay; 397 } 398 } 399 return (buf); 400 } 401 #endif 402 if (STREQUAL(value, "PREPEND", command_len)) { 403 if (*optional_text == 0) { 404 msg_warn("PREPEND action without text in %s map", map_class); 405 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) { 406 if (!is_header(optional_text)) { 407 msg_warn("bad PREPEND header text \"%s\" in %s map -- " 408 "need \"headername: headervalue\"", 409 optional_text, map_class); 410 } 411 412 /* 413 * By design, cleanup_out_header() may modify content. Play safe 414 * and prepare for future developments. 415 */ 416 else { 417 VSTRING *temp; 418 419 cleanup_act_log(state, "prepend", context, buf, optional_text); 420 temp = vstring_strcpy(vstring_alloc(strlen(optional_text)), 421 optional_text); 422 cleanup_out_header(state, temp); 423 vstring_free(temp); 424 } 425 } else { 426 cleanup_act_log(state, "prepend", context, buf, optional_text); 427 cleanup_out_string(state, REC_TYPE_NORM, optional_text); 428 } 429 return (buf); 430 } 431 if (STREQUAL(value, "REPLACE", command_len)) { 432 if (*optional_text == 0) { 433 msg_warn("REPLACE action without text in %s map", map_class); 434 return (buf); 435 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0 436 && !is_header(optional_text)) { 437 msg_warn("bad REPLACE header text \"%s\" in %s map -- " 438 "need \"headername: headervalue\"", 439 optional_text, map_class); 440 return (buf); 441 } else { 442 cleanup_act_log(state, "replace", context, buf, optional_text); 443 return (mystrdup(optional_text)); 444 } 445 } 446 if (STREQUAL(value, "REDIRECT", command_len)) { 447 if (strchr(optional_text, '@') == 0) { 448 msg_warn("bad REDIRECT target \"%s\" in %s map -- " 449 "need user@domain", 450 optional_text, map_class); 451 } else { 452 if (state->redirect) 453 myfree(state->redirect); 454 state->redirect = mystrdup(optional_text); 455 cleanup_act_log(state, "redirect", context, buf, optional_text); 456 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 457 } 458 return (buf); 459 } 460 if (STREQUAL(value, "BCC", command_len)) { 461 if (strchr(optional_text, '@') == 0) { 462 msg_warn("bad BCC address \"%s\" in %s map -- " 463 "need user@domain", 464 optional_text, map_class); 465 } else { 466 if (state->hbc_rcpt == 0) 467 state->hbc_rcpt = argv_alloc(1); 468 argv_add(state->hbc_rcpt, optional_text, (char *) 0); 469 cleanup_act_log(state, "bcc", context, buf, optional_text); 470 } 471 return (buf); 472 } 473 if (STREQUAL(value, "STRIP", command_len)) { 474 cleanup_act_log(state, "strip", context, buf, optional_text); 475 return (CLEANUP_ACT_DROP); 476 } 477 /* Allow and ignore optional text after the action. */ 478 479 if (STREQUAL(value, "IGNORE", command_len)) 480 return (CLEANUP_ACT_DROP); 481 482 if (STREQUAL(value, "DUNNO", command_len)) /* preferred */ 483 return (buf); 484 485 if (STREQUAL(value, "OK", command_len)) /* compat */ 486 return (buf); 487 488 msg_warn("unknown command in %s map: %s", map_class, value); 489 return (buf); 490 } 491 492 /* cleanup_header_callback - process one complete header line */ 493 494 static void cleanup_header_callback(void *context, int header_class, 495 const HEADER_OPTS *hdr_opts, 496 VSTRING *header_buf, 497 off_t unused_offset) 498 { 499 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 500 const char *myname = "cleanup_header_callback"; 501 char *hdrval; 502 struct code_map { 503 const char *name; 504 const char *encoding; 505 }; 506 static struct code_map code_map[] = { /* RFC 2045 */ 507 "7bit", MAIL_ATTR_ENC_7BIT, 508 "8bit", MAIL_ATTR_ENC_8BIT, 509 "binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */ 510 "quoted-printable", MAIL_ATTR_ENC_7BIT, 511 "base64", MAIL_ATTR_ENC_7BIT, 512 0, 513 }; 514 struct code_map *cmp; 515 MAPS *checks; 516 const char *map_class; 517 518 if (msg_verbose) 519 msg_info("%s: '%.200s'", myname, vstring_str(header_buf)); 520 521 /* 522 * Crude header filtering. This stops malware that isn't sophisticated 523 * enough to use fancy header encodings. 524 */ 525 #define CHECK(class, maps, var_name) \ 526 (header_class == class && (map_class = var_name, checks = maps) != 0) 527 528 if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME)) 529 header_class = MIME_HDR_MULTIPART; 530 531 /* Update the Received: header count before maybe dropping headers below. */ 532 if (hdr_opts && hdr_opts->type == HDR_RECEIVED) 533 state->hop_count += 1; 534 535 if ((state->flags & CLEANUP_FLAG_FILTER) 536 && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS) 537 || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS) 538 || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) { 539 char *header = vstring_str(header_buf); 540 const char *value; 541 542 if ((value = maps_find(checks, header, 0)) != 0) { 543 const char *result; 544 545 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER, 546 header, value, map_class)) 547 == CLEANUP_ACT_DROP) { 548 return; 549 } else if (result != header) { 550 vstring_strcpy(header_buf, result); 551 hdr_opts = header_opts_find(result); 552 myfree((void *) result); 553 } 554 } else if (checks->error) { 555 msg_warn("%s: %s map lookup problem -- " 556 "message not accepted, try again later", 557 state->queue_id, checks->title); 558 state->errs |= CLEANUP_STAT_WRITE; 559 } 560 } 561 562 /* 563 * If this is an "unknown" header, just copy it to the output without 564 * even bothering to fold long lines. cleanup_out() will split long 565 * headers that do not fit a REC_TYPE_NORM record. 566 */ 567 if (hdr_opts == 0) { 568 cleanup_out_header(state, header_buf); 569 return; 570 } 571 572 /* 573 * Allow 8-bit type info to override 7-bit type info. XXX Should reuse 574 * the effort that went into MIME header parsing. 575 */ 576 hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1; 577 while (ISSPACE(*hdrval)) 578 hdrval++; 579 /* trimblanks(hdrval, 0)[0] = 0; */ 580 if (var_auto_8bit_enc_hdr 581 && hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) { 582 for (cmp = code_map; cmp->name != 0; cmp++) { 583 if (strcasecmp(hdrval, cmp->name) == 0) { 584 if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0) 585 nvtable_update(state->attr, MAIL_ATTR_ENCODING, 586 cmp->encoding); 587 break; 588 } 589 } 590 } 591 592 /* 593 * Copy attachment etc. header blocks without further inspection. 594 */ 595 if (header_class != MIME_HDR_PRIMARY) { 596 cleanup_out_header(state, header_buf); 597 return; 598 } 599 600 /* 601 * Known header. Remember that we have seen at least one. Find out what 602 * we should do with this header: delete, count, rewrite. Note that we 603 * should examine headers even when they will be deleted from the output, 604 * because the addresses in those headers might be needed elsewhere. 605 * 606 * XXX 2821: Return-path breakage. 607 * 608 * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a 609 * message it inserts at the beginning of the mail data a return path 610 * line. The return path line preserves the information in the 611 * <reverse-path> from the MAIL command. Here, final delivery means the 612 * message leaves the SMTP world. Normally, this would mean it has been 613 * delivered to the destination user, but in some cases it may be further 614 * processed and transmitted by another mail system. 615 * 616 * And that is what Postfix implements. Delivery agents prepend 617 * Return-Path:. In order to avoid cluttering up the message with 618 * possibly inconsistent Return-Path: information (the sender can change 619 * as the result of mail forwarding or mailing list delivery), Postfix 620 * removes any existing Return-Path: headers. 621 * 622 * RFC 2821 Section 4.4 specifies: A message-originating SMTP system 623 * SHOULD NOT send a message that already contains a Return-path header. 624 * SMTP servers performing a relay function MUST NOT inspect the message 625 * data, and especially not to the extent needed to determine if 626 * Return-path headers are present. SMTP servers making final delivery 627 * MAY remove Return-path headers before adding their own. 628 */ 629 else { 630 state->headers_seen |= (1 << hdr_opts->type); 631 if (hdr_opts->type == HDR_MESSAGE_ID) 632 msg_info("%s: message-id=%s", state->queue_id, hdrval); 633 if (hdr_opts->type == HDR_RESENT_MESSAGE_ID) 634 msg_info("%s: resent-message-id=%s", state->queue_id, hdrval); 635 if (hdr_opts->type == HDR_RECEIVED) { 636 if (state->hop_count >= var_hopcount_limit) { 637 msg_warn("%s: message rejected: hopcount exceeded", 638 state->queue_id); 639 state->errs |= CLEANUP_STAT_HOPS; 640 } 641 /* Save our Received: header after maybe updating headers above. */ 642 if (state->hop_count == 1) 643 argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END); 644 } 645 if (CLEANUP_OUT_OK(state)) { 646 if (hdr_opts->flags & HDR_OPT_RR) 647 state->resent = "Resent-"; 648 if ((hdr_opts->flags & HDR_OPT_SENDER) 649 && state->hdr_rewrite_context) { 650 cleanup_rewrite_sender(state, hdr_opts, header_buf); 651 } else if ((hdr_opts->flags & HDR_OPT_RECIP) 652 && state->hdr_rewrite_context) { 653 cleanup_rewrite_recip(state, hdr_opts, header_buf); 654 } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { 655 cleanup_out_header(state, header_buf); 656 } 657 } 658 } 659 } 660 661 /* cleanup_header_done_callback - insert missing message headers */ 662 663 static void cleanup_header_done_callback(void *context) 664 { 665 const char *myname = "cleanup_header_done_callback"; 666 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 667 char time_stamp[1024]; /* XXX locale dependent? */ 668 struct tm *tp; 669 TOK822 *token; 670 TOK822 *dummy_token; 671 time_t tv; 672 673 /* 674 * XXX Workaround: when we reach the end of headers, mime_state_update() 675 * may execute up to three call-backs before returning to the caller: 676 * head_out(), head_end(), and body_out() or body_end(). As long as 677 * call-backs don't return a result, each call-back has to check for 678 * itself if the previous call-back experienced a problem. 679 */ 680 if (CLEANUP_OUT_OK(state) == 0) 681 return; 682 683 /* 684 * Future proofing: the Milter client's header suppression algorithm 685 * assumes that the MTA prepends its own Received: header. This 686 * assumption may be violated after some source-code update. The 687 * following check ensures consistency, at least for local submission. 688 */ 689 if (state->hop_count < 1) { 690 msg_warn("%s: message rejected: no Received: header", 691 state->queue_id); 692 state->errs |= CLEANUP_STAT_BAD; 693 return; 694 } 695 696 /* 697 * Add a missing (Resent-)Message-Id: header. The message ID gives the 698 * time in GMT units, plus the local queue ID. 699 * 700 * XXX Message-Id is not a required message header (RFC 822 and RFC 2822). 701 * 702 * XXX It is the queue ID non-inode bits that prevent messages from getting 703 * the same Message-Id within the same second. 704 * 705 * XXX An arbitrary amount of time may pass between the start of the mail 706 * transaction and the creation of a queue file. Since we guarantee queue 707 * ID uniqueness only within a second, we must ensure that the time in 708 * the message ID matches the queue ID creation time, as long as we use 709 * the queue ID in the message ID. 710 * 711 * XXX We log a dummy name=value record so that we (hopefully) don't break 712 * compatibility with existing logfile analyzers, and so that we don't 713 * complicate future code that wants to log more name=value attributes. 714 */ 715 if ((state->hdr_rewrite_context || var_always_add_hdrs) 716 && (state->headers_seen & (1 << (state->resent[0] ? 717 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) { 718 if (var_long_queue_ids) { 719 vstring_sprintf(state->temp1, "%s@%s", 720 state->queue_id, var_myhostname); 721 } else { 722 tv = state->handle->ctime.tv_sec; 723 tp = gmtime(&tv); 724 strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp); 725 vstring_sprintf(state->temp1, "%s.%s@%s", 726 time_stamp, state->queue_id, var_myhostname); 727 } 728 cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s>", 729 state->resent, vstring_str(state->temp1)); 730 msg_info("%s: %smessage-id=<%s>", 731 state->queue_id, *state->resent ? "resent-" : "", 732 vstring_str(state->temp1)); 733 state->headers_seen |= (1 << (state->resent[0] ? 734 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID)); 735 } 736 if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0) 737 msg_info("%s: message-id=<>", state->queue_id); 738 739 /* 740 * Add a missing (Resent-)Date: header. The date is in local time units, 741 * with the GMT offset at the end. 742 */ 743 if ((state->hdr_rewrite_context || var_always_add_hdrs) 744 && (state->headers_seen & (1 << (state->resent[0] ? 745 HDR_RESENT_DATE : HDR_DATE))) == 0) { 746 cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s", 747 state->resent, mail_date(state->arrival_time.tv_sec)); 748 } 749 750 /* 751 * Add a missing (Resent-)From: header. 752 */ 753 if ((state->hdr_rewrite_context || var_always_add_hdrs) 754 && (state->headers_seen & (1 << (state->resent[0] ? 755 HDR_RESENT_FROM : HDR_FROM))) == 0) { 756 quote_822_local(state->temp1, *state->sender ? 757 state->sender : MAIL_ADDR_MAIL_DAEMON); 758 if (*state->sender && state->fullname && *state->fullname) { 759 char *cp; 760 761 /* Enforce some sanity on full name content. */ 762 while ((cp = strchr(state->fullname, '\r')) != 0 763 || (cp = strchr(state->fullname, '\n')) != 0) 764 *cp = ' '; 765 766 /* 767 * "From: phrase <route-addr>". Quote the phrase if it contains 768 * specials or the "%!" legacy address operators. 769 */ 770 if (cleanup_hfrom_format == HFROM_FORMAT_CODE_STD) { 771 vstring_sprintf(state->temp2, "%sFrom: ", state->resent); 772 if (state->fullname[strcspn(state->fullname, 773 "%!" LEX_822_SPECIALS)] == 0) { 774 /* Normalize whitespace. */ 775 token = tok822_scan_limit(state->fullname, &dummy_token, 776 var_token_limit); 777 } else { 778 token = tok822_alloc(TOK822_QSTRING, state->fullname); 779 } 780 if (token) { 781 tok822_externalize(state->temp2, token, TOK822_STR_NONE); 782 tok822_free(token); 783 vstring_strcat(state->temp2, " "); 784 } 785 vstring_sprintf_append(state->temp2, "<%s>", 786 vstring_str(state->temp1)); 787 } 788 789 /* 790 * "From: addr-spec (ctext)". This is the obsolete form. 791 */ 792 else { 793 vstring_sprintf(state->temp2, "%sFrom: %s ", 794 state->resent, vstring_str(state->temp1)); 795 vstring_sprintf(state->temp1, "(%s)", state->fullname); 796 token = tok822_parse(vstring_str(state->temp1)); 797 tok822_externalize(state->temp2, token, TOK822_STR_NONE); 798 tok822_free_tree(token); 799 } 800 } 801 802 /* 803 * "From: addr-spec". This is the form in the absence of full name 804 * information, also used for mail from mailer-daemon. 805 */ 806 else { 807 vstring_sprintf(state->temp2, "%sFrom: %s", 808 state->resent, vstring_str(state->temp1)); 809 } 810 CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2); 811 } 812 813 /* 814 * XXX 2821: Appendix B: The return address in the MAIL command SHOULD, 815 * if possible, be derived from the system's identity for the submitting 816 * (local) user, and the "From:" header field otherwise. If there is a 817 * system identity available, it SHOULD also be copied to the Sender 818 * header field if it is different from the address in the From header 819 * field. (Any Sender field that was already there SHOULD be removed.) 820 * Similar wording appears in RFC 2822 section 3.6.2. 821 * 822 * Postfix presently does not insert a Sender: header if envelope and From: 823 * address differ. Older Postfix versions assumed that the envelope 824 * sender address specifies the system identity and inserted Sender: 825 * whenever envelope and From: differed. This was wrong with relayed 826 * mail, and was often not even desirable with original submissions. 827 * 828 * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can 829 * contain multiple addresses. If this is the case, then a Sender: header 830 * must be provided with a single address. 831 * 832 * Postfix does not count the number of addresses in a From: header 833 * (although doing so is trivial, once the address is parsed). 834 */ 835 836 /* 837 * Add a missing destination header. 838 */ 839 #define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \ 840 | (1 << HDR_CC) | (1 << HDR_RESENT_CC)) 841 842 if ((state->hdr_rewrite_context || var_always_add_hdrs) 843 && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) { 844 if (!is_header(var_rcpt_witheld)) { 845 msg_warn("bad %s header text \"%s\" -- " 846 "need \"headername: headervalue\"", 847 VAR_RCPT_WITHELD, var_rcpt_witheld); 848 } else { 849 cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld); 850 } 851 } 852 853 /* 854 * Place a dummy PTR record right after the last header so that we can 855 * append headers without having to worry about clobbering the 856 * end-of-content marker. 857 */ 858 if (state->milters || cleanup_milters) { 859 if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0) 860 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); 861 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); 862 if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0) 863 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); 864 state->body_offset = state->append_hdr_pt_target; 865 } 866 } 867 868 /* cleanup_body_callback - output one body record */ 869 870 static void cleanup_body_callback(void *context, int type, 871 const char *buf, ssize_t len, 872 off_t offset) 873 { 874 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 875 876 /* 877 * XXX Workaround: when we reach the end of headers, mime_state_update() 878 * may execute up to three call-backs before returning to the caller: 879 * head_out(), head_end(), and body_out() or body_end(). As long as 880 * call-backs don't return a result, each call-back has to check for 881 * itself if the previous call-back experienced a problem. 882 */ 883 if (CLEANUP_OUT_OK(state) == 0) 884 return; 885 886 /* 887 * Crude message body content filter for emergencies. This code has 888 * several problems: it sees one line at a time; it looks at long lines 889 * only in chunks of line_length_limit (2048) characters; it is easily 890 * bypassed with encodings and other tricks. 891 */ 892 if ((state->flags & CLEANUP_FLAG_FILTER) 893 && cleanup_body_checks 894 && (var_body_check_len == 0 || offset < var_body_check_len)) { 895 const char *value; 896 897 if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) { 898 const char *result; 899 900 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY, 901 buf, value, VAR_BODY_CHECKS)) 902 == CLEANUP_ACT_DROP) { 903 return; 904 } else if (result != buf) { 905 cleanup_out(state, type, result, strlen(result)); 906 myfree((void *) result); 907 return; 908 } 909 } else if (cleanup_body_checks->error) { 910 msg_warn("%s: %s map lookup problem -- " 911 "message not accepted, try again later", 912 state->queue_id, cleanup_body_checks->title); 913 state->errs |= CLEANUP_STAT_WRITE; 914 } 915 } 916 cleanup_out(state, type, buf, len); 917 } 918 919 /* cleanup_message_headerbody - process message content, header and body */ 920 921 static void cleanup_message_headerbody(CLEANUP_STATE *state, int type, 922 const char *buf, ssize_t len) 923 { 924 const char *myname = "cleanup_message_headerbody"; 925 const MIME_STATE_DETAIL *detail; 926 const char *cp; 927 char *dst; 928 929 /* 930 * Reject unwanted characters. 931 * 932 * XXX Possible optimization: simplify the loop when the "reject" set 933 * contains only one character. 934 */ 935 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) { 936 for (cp = buf; cp < buf + len; cp++) { 937 if (memchr(vstring_str(cleanup_reject_chars), 938 *(const unsigned char *) cp, 939 VSTRING_LEN(cleanup_reject_chars))) { 940 cleanup_act(state, CLEANUP_ACT_CTXT_ANY, 941 buf, "REJECT disallowed character", 942 "character reject"); 943 return; 944 } 945 } 946 } 947 948 /* 949 * Strip unwanted characters. Don't overwrite the input. 950 * 951 * XXX Possible space+time optimization: use a bitset. 952 * 953 * XXX Possible optimization: simplify the loop when the "strip" set 954 * contains only one character. 955 * 956 * XXX Possible optimization: copy the input only if we really have to. 957 */ 958 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) { 959 VSTRING_RESET(state->stripped_buf); 960 VSTRING_SPACE(state->stripped_buf, len + 1); 961 dst = vstring_str(state->stripped_buf); 962 for (cp = buf; cp < buf + len; cp++) 963 if (!memchr(vstring_str(cleanup_strip_chars), 964 *(const unsigned char *) cp, 965 VSTRING_LEN(cleanup_strip_chars))) 966 *dst++ = *cp; 967 *dst = 0; 968 buf = vstring_str(state->stripped_buf); 969 len = dst - buf; 970 } 971 972 /* 973 * Copy text record to the output. 974 */ 975 if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) { 976 state->mime_errs = mime_state_update(state->mime_state, type, buf, len); 977 } 978 979 /* 980 * If we have reached the end of the message content segment, record the 981 * current file position so we can compute the message size lateron. 982 */ 983 else if (type == REC_TYPE_XTRA) { 984 state->mime_errs = mime_state_update(state->mime_state, type, buf, len); 985 if (state->milters || cleanup_milters) 986 /* Make room for body modification. */ 987 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); 988 /* Ignore header truncation after primary message headers. */ 989 state->mime_errs &= ~MIME_ERR_TRUNC_HEADER; 990 if (state->mime_errs && state->reason == 0) { 991 state->errs |= CLEANUP_STAT_CONT; 992 detail = mime_state_detail(state->mime_errs); 993 state->reason = dsn_prepend(detail->dsn, detail->text); 994 } 995 state->mime_state = mime_state_free(state->mime_state); 996 if ((state->xtra_offset = vstream_ftell(state->dst)) < 0) 997 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); 998 state->cont_length = state->xtra_offset - state->data_offset; 999 state->action = cleanup_extracted; 1000 } 1001 1002 /* 1003 * This should never happen. 1004 */ 1005 else { 1006 msg_warn("%s: message rejected: " 1007 "unexpected record type %d in message content", myname, type); 1008 state->errs |= CLEANUP_STAT_BAD; 1009 } 1010 } 1011 1012 /* cleanup_mime_error_callback - error report call-back routine */ 1013 1014 static void cleanup_mime_error_callback(void *context, int err_code, 1015 const char *text, ssize_t len) 1016 { 1017 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1018 const char *origin; 1019 1020 /* 1021 * Message header too large errors are handled after the end of the 1022 * primary message headers. 1023 */ 1024 if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) { 1025 if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0) 1026 origin = MAIL_ATTR_ORG_NONE; 1027 #define TEXT_LEN (len < 100 ? (int) len : 100) 1028 msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>", 1029 state->queue_id, mime_state_error(err_code), TEXT_LEN, text, 1030 origin, info_log_addr_form_sender(state->sender), 1031 info_log_addr_form_recipient(state->recip ? 1032 state->recip : "unknown")); 1033 } 1034 } 1035 1036 /* cleanup_message - initialize message content segment */ 1037 1038 void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len) 1039 { 1040 const char *myname = "cleanup_message"; 1041 int mime_options; 1042 1043 /* 1044 * Write the start-of-content segment marker. 1045 */ 1046 cleanup_out_string(state, REC_TYPE_MESG, ""); 1047 if ((state->data_offset = vstream_ftell(state->dst)) < 0) 1048 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); 1049 1050 /* 1051 * Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables 1052 * special processing of Content-Type: headers, and thus, causes all text 1053 * after the primary headers to be treated as the message body. 1054 */ 1055 mime_options = 0; 1056 if (var_disable_mime_input) { 1057 mime_options |= MIME_OPT_DISABLE_MIME; 1058 } else { 1059 /* Turn off content checks if bouncing or forwarding mail. */ 1060 if (state->flags & CLEANUP_FLAG_FILTER) { 1061 if (var_strict_8bitmime || var_strict_7bit_hdrs) 1062 mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER; 1063 if (var_strict_8bitmime || var_strict_8bit_body) 1064 mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY; 1065 if (var_strict_encoding) 1066 mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN; 1067 if (var_strict_8bitmime || var_strict_7bit_hdrs 1068 || var_strict_8bit_body || var_strict_encoding 1069 || *var_header_checks || *var_mimehdr_checks 1070 || *var_nesthdr_checks) 1071 mime_options |= MIME_OPT_REPORT_NESTING; 1072 } 1073 } 1074 state->mime_state = mime_state_alloc(mime_options, 1075 cleanup_header_callback, 1076 cleanup_header_done_callback, 1077 cleanup_body_callback, 1078 (MIME_STATE_ANY_END) 0, 1079 cleanup_mime_error_callback, 1080 (void *) state); 1081 1082 /* 1083 * XXX Workaround: truncate a long message header so that we don't exceed 1084 * the default Sendmail libmilter request size limit of 65535. 1085 */ 1086 #define KLUDGE_HEADER_LIMIT 60000 1087 if ((cleanup_milters || state->milters) 1088 && var_header_limit > KLUDGE_HEADER_LIMIT) 1089 var_header_limit = KLUDGE_HEADER_LIMIT; 1090 1091 /* 1092 * Pass control to the header processing routine. 1093 */ 1094 state->action = cleanup_message_headerbody; 1095 cleanup_message_headerbody(state, type, buf, len); 1096 } 1097