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