1 /* $NetBSD: cleanup_milter.c,v 1.7 2026/05/09 18:49:15 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* cleanup_milter 3 6 /* SUMMARY 7 /* external mail filter support 8 /* SYNOPSIS 9 /* #include <cleanup.h> 10 /* 11 /* void cleanup_milter_header_checks_init(void) 12 /* 13 /* void cleanup_milter_receive(state, count) 14 /* CLEANUP_STATE *state; 15 /* int count; 16 /* 17 /* void cleanup_milter_inspect(state, milters) 18 /* CLEANUP_STATE *state; 19 /* MILTERS *milters; 20 /* 21 /* cleanup_milter_emul_mail(state, milters, sender) 22 /* CLEANUP_STATE *state; 23 /* MILTERS *milters; 24 /* const char *sender; 25 /* 26 /* cleanup_milter_emul_rcpt(state, milters, recipient) 27 /* CLEANUP_STATE *state; 28 /* MILTERS *milters; 29 /* const char *recipient; 30 /* 31 /* cleanup_milter_emul_data(state, milters) 32 /* CLEANUP_STATE *state; 33 /* MILTERS *milters; 34 /* DESCRIPTION 35 /* This module implements support for Sendmail-style mail 36 /* filter (milter) applications, including in-place queue file 37 /* modification. 38 /* 39 /* cleanup_milter_header_checks_init() does pre-jail 40 /* initializations. 41 /* 42 /* cleanup_milter_receive() receives mail filter definitions, 43 /* typically from an smtpd(8) server process, and registers 44 /* local call-back functions for macro expansion and for queue 45 /* file modification. 46 /* 47 /* cleanup_milter_inspect() sends the current message headers 48 /* and body to the mail filters that were received with 49 /* cleanup_milter_receive(), or that are specified with the 50 /* cleanup_milters configuration parameter. 51 /* 52 /* cleanup_milter_emul_mail() emulates connect, helo and mail 53 /* events for mail that does not arrive via the smtpd(8) server. 54 /* The emulation pretends that mail arrives from localhost/127.0.0.1 55 /* via ESMTP. Milters can reject emulated connect, helo, mail 56 /* or data events, but not emulated rcpt events as described 57 /* next. 58 /* 59 /* cleanup_milter_emul_rcpt() emulates an rcpt event for mail 60 /* that does not arrive via the smtpd(8) server. This reports 61 /* a server configuration error condition when the milter 62 /* rejects an emulated rcpt event. 63 /* 64 /* cleanup_milter_emul_data() emulates a data event for mail 65 /* that does not arrive via the smtpd(8) server. It's OK for 66 /* milters to reject emulated data events. 67 /* SEE ALSO 68 /* milter(3) generic mail filter interface 69 /* DIAGNOSTICS 70 /* Fatal errors: memory allocation problem. 71 /* Panic: interface violation. 72 /* Warnings: I/O errors (state->errs is updated accordingly). 73 /* LICENSE 74 /* .ad 75 /* .fi 76 /* The Secure Mailer license must be distributed with this software. 77 /* AUTHOR(S) 78 /* Wietse Venema 79 /* IBM T.J. Watson Research 80 /* P.O. Box 704 81 /* Yorktown Heights, NY 10598, USA 82 /* 83 /* Wietse Venema 84 /* Google, Inc. 85 /* 111 8th Avenue 86 /* New York, NY 10011, USA 87 /*--*/ 88 89 /* System library. */ 90 91 #include <sys_defs.h> 92 #include <sys/socket.h> /* AF_INET */ 93 #include <string.h> 94 #include <errno.h> 95 96 #ifdef STRCASECMP_IN_STRINGS_H 97 #include <strings.h> 98 #endif 99 100 /* Utility library. */ 101 102 #include <msg.h> 103 #include <vstream.h> 104 #include <vstring.h> 105 #include <stringops.h> 106 #include <inet_proto.h> 107 108 /* Global library. */ 109 110 #include <off_cvt.h> 111 #include <dsn_mask.h> 112 #include <rec_type.h> 113 #include <cleanup_user.h> 114 #include <record.h> 115 #include <rec_attr_map.h> 116 #include <mail_proto.h> 117 #include <mail_params.h> 118 #include <lex_822.h> 119 #include <is_header.h> 120 #include <quote_821_local.h> 121 #include <dsn_util.h> 122 #include <xtext.h> 123 #include <info_log_addr_form.h> 124 #include <header_opts.h> 125 126 /* Application-specific. */ 127 128 #include <cleanup.h> 129 130 /* 131 * How Postfix 2.4 edits queue file information: 132 * 133 * Mail filter applications (Milters) can send modification requests after 134 * receiving the end of the message body. Postfix implements these 135 * modifications in the cleanup server, so that it can edit the queue file 136 * in place. This avoids the temporary files that would be needed when 137 * modifications were implemented in the SMTP server (Postfix normally does 138 * not store the whole message in main memory). Once a Milter is done 139 * editing, the queue file can be used as input for the next Milter, and so 140 * on. Finally, the cleanup server changes file permissions, calls fsync(), 141 * and waits for successful completion. 142 * 143 * To implement in-place queue file edits, we need to introduce surprisingly 144 * little change to the existing Postfix queue file structure. All we need 145 * is a way to mark a record as deleted, and to jump from one place in the 146 * queue file to another. We could implement deleted records with jumps, but 147 * marking is sometimes simpler. 148 * 149 * Postfix does not store queue files as plain text files. Instead all 150 * information is stored in records with an explicit type and length, for 151 * sender, recipient, arrival time, and so on. Even the content that makes 152 * up the message header and body is stored as records with explicit types 153 * and lengths. This organization makes it very easy to mark a record as 154 * deleted, and to introduce the pointer records that we will use to jump 155 * from one place in a queue file to another place. 156 * 157 * - Deleting a recipient is easiest - simply modify the record type into one 158 * that is skipped by the software that delivers mail. We won't try to reuse 159 * the deleted recipient for other purposes. When deleting a recipient, we 160 * may need to delete multiple recipient records that result from virtual 161 * alias expansion of the original recipient address. 162 * 163 * - Replacing a header record involves pointer records. A record is replaced 164 * by overwriting it with a forward pointer to space after the end of the 165 * queue file, putting the new record there, followed by a reverse pointer 166 * to the record that follows the replaced header. To simplify 167 * implementation we follow a short header record with a filler record so 168 * that we can always overwrite a header record with a pointer. 169 * 170 * N.B. This is a major difference with Postfix version 2.3, which needed 171 * complex code to save records that follow a short header, before it could 172 * overwrite a short header record. This code contained two of the three 173 * post-release bugs that were found with Postfix header editing. 174 * 175 * - Inserting a header record is like replacing one, except that we also 176 * relocate the record that is being overwritten by the forward pointer. 177 * 178 * - Deleting a message header is simplest when we replace it by a "skip" 179 * pointer to the information that follows the header. With a multi-line 180 * header we need to update only the first line. 181 * 182 * - Appending a recipient or header record involves pointer records as well. 183 * To make this convenient, the queue file already contains dummy pointer 184 * records at the locations where we want to append recipient or header 185 * content. To append, change the dummy pointer into a forward pointer to 186 * space after the end of a message, put the new recipient or header record 187 * there, followed by a reverse pointer to the record that follows the 188 * forward pointer. 189 * 190 * - To append another header or recipient record, replace the reverse pointer 191 * by a forward pointer to space after the end of a message, put the new 192 * record there, followed by the value of the reverse pointer that we 193 * replace. Thus, there is no one-to-one correspondence between forward and 194 * backward pointers. Instead, there can be multiple forward pointers for 195 * one reverse pointer. 196 * 197 * - When a mail filter wants to replace an entire body, we overwrite existing 198 * body records until we run out of space, and then write a pointer to space 199 * after the end of the queue file, followed by more body content. There may 200 * be multiple regions with body content; regions are connected by forward 201 * pointers, and the last region ends with a pointer to the marker that ends 202 * the message content segment. Body regions can be large and therefore they 203 * are reused to avoid wasting space. Sendmail mail filters currently do not 204 * replace individual body records, and that is a good thing. 205 * 206 * Making queue file modifications safe: 207 * 208 * Postfix queue files are segmented. The first segment is for envelope 209 * records, the second for message header and body content, and the third 210 * segment is for information that was extracted or generated from the 211 * message header or body content. Each segment is terminated by a marker 212 * record. For now we don't want to change their location. That is, we want 213 * to avoid moving the records that mark the start or end of a queue file 214 * segment. 215 * 216 * To ensure that we can always replace a header or body record by a pointer 217 * record, without having to relocate a marker record, the cleanup server 218 * places a dummy pointer record at the end of the recipients and at the end 219 * of the message header. To support message body modifications, a dummy 220 * pointer record is also placed at the end of the message content. 221 * 222 * With all these changes in queue file organization, REC_TYPE_END is no longer 223 * guaranteed to be the last record in a queue file. If an application were 224 * to read beyond the REC_TYPE_END marker, it would go into an infinite 225 * loop, because records after REC_TYPE_END alternate with reverse pointers 226 * to the middle of the queue file. For robustness, the record reading 227 * routine skips forward to the end-of-file position after reading the 228 * REC_TYPE_END marker. 229 */ 230 231 /*#define msg_verbose 2*/ 232 233 static HBC_CHECKS *cleanup_milter_hbc_checks; 234 static VSTRING *cleanup_milter_hbc_reply; 235 static void cleanup_milter_set_error(CLEANUP_STATE *, int); 236 static const char *cleanup_add_rcpt_par(void *, const char *, const char *); 237 238 #define STR(x) vstring_str(x) 239 #define LEN(x) VSTRING_LEN(x) 240 241 /* cleanup_milter_hbc_log - log post-milter header/body_checks action */ 242 243 static void cleanup_milter_hbc_log(void *context, const char *action, 244 const char *where, const char *line, 245 const char *optional_text) 246 { 247 const CLEANUP_STATE *state = (CLEANUP_STATE *) context; 248 const char *attr; 249 250 vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.200s from %s[%s];", 251 state->queue_id, where, action, where, line, 252 state->client_name, state->client_addr); 253 if (state->sender) 254 vstring_sprintf_append(state->temp1, " from=<%s>", 255 info_log_addr_form_sender(state->sender)); 256 if (state->recip) 257 vstring_sprintf_append(state->temp1, " to=<%s>", 258 info_log_addr_form_recipient(state->recip)); 259 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) 260 vstring_sprintf_append(state->temp1, " proto=%s", attr); 261 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) 262 vstring_sprintf_append(state->temp1, " helo=<%s>", attr); 263 if (optional_text) 264 vstring_sprintf_append(state->temp1, ": %s", optional_text); 265 msg_info("%s", vstring_str(state->temp1)); 266 } 267 268 /* cleanup_milter_header_prepend - prepend header to milter-generated header */ 269 270 static void cleanup_milter_header_prepend(void *context, int rec_type, 271 const char *buf, ssize_t len, off_t offset) 272 { 273 /* XXX save prepended header to buffer. */ 274 msg_warn("the milter_header/body_checks prepend action is not implemented"); 275 } 276 277 /* cleanup_milter_hbc_extend - additional header/body_checks actions */ 278 279 static char *cleanup_milter_hbc_extend(void *context, const char *command, 280 ssize_t cmd_len, const char *optional_text, 281 const char *where, const char *buf, 282 ssize_t buf_len, off_t offset) 283 { 284 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 285 const char *map_class = VAR_MILT_HEAD_CHECKS; /* XXX */ 286 287 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) 288 289 /* 290 * These are currently our mutually-exclusive ways of not receiving mail: 291 * "reject" and "discard". Only these can be reported to the up-stream 292 * Postfix libmilter code, because sending any reply there causes Postfix 293 * libmilter to skip further "edit" requests. By way of safety net, each 294 * of these must also reset CLEANUP_FLAG_FILTER_ALL. 295 */ 296 #define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \ 297 ((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT)) 298 299 /* 300 * We log all header/body-checks actions here, because we know the 301 * details of the message content that triggered the action. We report 302 * detail-free milter-reply values (reject/discard, stored in the 303 * milter_hbc_reply state member) to the Postfix libmilter code, so that 304 * Postfix libmilter can stop sending requests. 305 * 306 * We also set all applicable cleanup flags here, because there is no 307 * guarantee that Postfix libmilter will propagate our own milter-reply 308 * value to cleanup_milter_inspect() which calls cleanup_milter_apply(). 309 * The latter translates responses from Milter applications into cleanup 310 * flags, and logs the response text. Postfix libmilter can convey only 311 * one milter-reply value per email message, and that reply may even come 312 * from outside Postfix. 313 * 314 * To suppress redundant logging, cleanup_milter_apply() does nothing when 315 * the milter-reply value matches the saved text in the milter_hbc_reply 316 * state member. As we remember only one milter-reply value, we can't 317 * report multiple milter-reply values per email message. We satisfy this 318 * constraint, because we already clear the CLEANUP_FLAG_FILTER_ALL flags 319 * to terminate further header inspection. 320 */ 321 if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0) 322 return ((char *) buf); 323 324 if (STREQUAL(command, "BCC", cmd_len)) { 325 if (strchr(optional_text, '@') == 0) { 326 msg_warn("bad BCC address \"%s\" in %s map -- " 327 "need user@domain", 328 optional_text, VAR_MILT_HEAD_CHECKS); 329 } else { 330 cleanup_milter_hbc_log(context, "bcc", where, buf, optional_text); 331 /* Caller checks state error flags. */ 332 (void) cleanup_add_rcpt_par(state, optional_text, ""); 333 } 334 return ((char *) buf); 335 } 336 if (STREQUAL(command, "REJECT", cmd_len)) { 337 const CLEANUP_STAT_DETAIL *detail; 338 339 if (state->reason) 340 myfree(state->reason); 341 detail = cleanup_stat_detail(CLEANUP_STAT_CONT); 342 if (*optional_text) { 343 state->reason = dsn_prepend(detail->dsn, optional_text); 344 if (*state->reason != '4' && *state->reason != '5') { 345 msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x", 346 optional_text); 347 *state->reason = '4'; 348 } 349 } else { 350 state->reason = dsn_prepend(detail->dsn, detail->text); 351 } 352 if (*state->reason == '4') 353 state->errs |= CLEANUP_STAT_DEFER; 354 else 355 state->errs |= CLEANUP_STAT_CONT; 356 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 357 cleanup_milter_hbc_log(context, "reject", where, buf, state->reason); 358 vstring_sprintf(cleanup_milter_hbc_reply, "%d %s", 359 detail->smtp, state->reason); 360 STR(cleanup_milter_hbc_reply)[0] = *state->reason; 361 return ((char *) buf); 362 } 363 if (STREQUAL(command, "FILTER", cmd_len)) { 364 if (*optional_text == 0) { 365 msg_warn("missing FILTER command argument in %s map", map_class); 366 } else if (strchr(optional_text, ':') == 0) { 367 msg_warn("bad FILTER command %s in %s -- " 368 "need transport:destination", 369 optional_text, map_class); 370 } else { 371 if (state->filter) 372 myfree(state->filter); 373 state->filter = mystrdup(optional_text); 374 cleanup_milter_hbc_log(context, "filter", where, buf, 375 optional_text); 376 } 377 return ((char *) buf); 378 } 379 if (STREQUAL(command, "DISCARD", cmd_len)) { 380 cleanup_milter_hbc_log(context, "discard", where, buf, optional_text); 381 vstring_strcpy(cleanup_milter_hbc_reply, "D"); 382 state->flags |= CLEANUP_FLAG_DISCARD; 383 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 384 return ((char *) buf); 385 } 386 if (STREQUAL(command, "HOLD", cmd_len)) { 387 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { 388 cleanup_milter_hbc_log(context, "hold", where, buf, optional_text); 389 state->flags |= CLEANUP_FLAG_HOLD; 390 } 391 return ((char *) buf); 392 } 393 if (STREQUAL(command, "REDIRECT", cmd_len)) { 394 if (strchr(optional_text, '@') == 0) { 395 msg_warn("bad REDIRECT target \"%s\" in %s map -- " 396 "need user@domain", 397 optional_text, map_class); 398 } else { 399 if (state->redirect) 400 myfree(state->redirect); 401 state->redirect = mystrdup(optional_text); 402 cleanup_milter_hbc_log(context, "redirect", where, buf, 403 optional_text); 404 state->flags &= ~CLEANUP_FLAG_FILTER_ALL; 405 } 406 return ((char *) buf); 407 } 408 return ((char *) HBC_CHECKS_STAT_UNKNOWN); 409 } 410 411 /* cleanup_milter_header_checks - inspect Milter-generated header */ 412 413 static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf) 414 { 415 char *ret; 416 417 /* 418 * Milter application "add/insert/replace header" requests happen at the 419 * end-of-message stage, therefore all the header operations are relative 420 * to the primary message header. 421 */ 422 ret = hbc_header_checks((void *) state, cleanup_milter_hbc_checks, 423 MIME_HDR_PRIMARY, (HEADER_OPTS *) 0, 424 buf, (off_t) 0); 425 if (ret == 0) { 426 return (0); 427 } else if (ret == HBC_CHECKS_STAT_ERROR) { 428 msg_warn("%s: %s map lookup problem -- " 429 "message not accepted, try again later", 430 state->queue_id, VAR_MILT_HEAD_CHECKS); 431 state->errs |= CLEANUP_STAT_WRITE; 432 return (0); 433 } else { 434 if (ret != STR(buf)) { 435 vstring_strcpy(buf, ret); 436 myfree(ret); 437 } 438 return (1); 439 } 440 } 441 442 /* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */ 443 444 static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state) 445 { 446 const char *myname = "cleanup_milter_hbc_add_meta_records"; 447 off_t reverse_ptr_offset; 448 off_t new_meta_offset; 449 450 /* 451 * Note: this code runs while the Milter infrastructure is being torn 452 * down. For this reason we handle all I/O errors here on the spot, 453 * instead of reporting them back through the Milter infrastructure. 454 */ 455 456 /* 457 * Sanity check. 458 */ 459 if (state->append_meta_pt_offset < 0) 460 msg_panic("%s: no meta append pointer location", myname); 461 if (state->append_meta_pt_target < 0) 462 msg_panic("%s: no meta append pointer target", myname); 463 464 /* 465 * Allocate space after the end of the queue file, and write the meta 466 * record(s), followed by a reverse pointer record that points to the 467 * target of the old "meta record append" pointer record. This reverse 468 * pointer record becomes the new "meta record append" pointer record. 469 * Although the new "meta record append" pointer record will never be 470 * used, we update it here to make the code more similar to other code 471 * that inserts/appends content, so that common code can be factored out 472 * later. 473 */ 474 if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { 475 cleanup_milter_set_error(state, errno); 476 return; 477 } 478 if (state->filter != 0) 479 cleanup_out_string(state, REC_TYPE_FILT, state->filter); 480 if (state->redirect != 0) 481 cleanup_out_string(state, REC_TYPE_RDR, state->redirect); 482 if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { 483 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 484 state->errs |= CLEANUP_STAT_WRITE; 485 return; 486 } 487 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 488 (long) state->append_meta_pt_target); 489 490 /* 491 * Pointer flipping: update the old "meta record append" pointer record 492 * value with the location of the new meta record. 493 */ 494 if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) { 495 cleanup_milter_set_error(state, errno); 496 return; 497 } 498 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 499 (long) new_meta_offset); 500 501 /* 502 * Update the in-memory "meta append" pointer record location with the 503 * location of the reverse pointer record that follows the new meta 504 * record. The target of the "meta append" pointer record does not 505 * change; it's always the record that follows the dummy pointer record 506 * that was written while Postfix received the message. 507 */ 508 state->append_meta_pt_offset = reverse_ptr_offset; 509 510 /* 511 * Note: state->append_meta_pt_target never changes. 512 */ 513 } 514 515 /* cleanup_milter_header_checks_init - initialize post-Milter header checks */ 516 517 void cleanup_milter_header_checks_init(void) 518 { 519 static const char myname[] = "cleanup_milter_header_checks_init"; 520 521 #define NO_NESTED_HDR_NAME "" 522 #define NO_NESTED_HDR_VALUE "" 523 #define NO_MIME_HDR_NAME "" 524 #define NO_MIME_HDR_VALUE "" 525 526 static /* XXX not const */ HBC_CALL_BACKS call_backs = { 527 cleanup_milter_hbc_log, 528 cleanup_milter_header_prepend, 529 cleanup_milter_hbc_extend, 530 }; 531 532 if (*var_milt_head_checks == 0) 533 msg_panic("%s: %s is empty", myname, VAR_MILT_HEAD_CHECKS); 534 535 if (cleanup_milter_hbc_checks) 536 msg_panic("%s: cleanup_milter_hbc_checks is not null", myname); 537 cleanup_milter_hbc_checks = 538 hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks, 539 NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE, 540 NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE, 541 &call_backs); 542 543 if (cleanup_milter_hbc_reply) 544 msg_panic("%s: cleanup_milter_hbc_reply is not null", myname); 545 cleanup_milter_hbc_reply = vstring_alloc(100); 546 } 547 548 #ifdef TEST 549 550 /* cleanup_milter_header_checks_deinit - undo cleanup_milter_header_checks_init */ 551 552 static void cleanup_milter_header_checks_deinit(void) 553 { 554 static const char myname[] = "cleanup_milter_header_checks_deinit"; 555 556 if (cleanup_milter_hbc_checks == 0) 557 msg_panic("%s: cleanup_milter_hbc_checks is null", myname); 558 hbc_header_checks_free(cleanup_milter_hbc_checks); 559 cleanup_milter_hbc_checks = 0; 560 561 if (cleanup_milter_hbc_reply == 0) 562 msg_panic("%s: cleanup_milter_hbc_reply is null", myname); 563 vstring_free(cleanup_milter_hbc_reply); 564 cleanup_milter_hbc_reply = 0; 565 } 566 567 #endif 568 569 /* cleanup_milter_header_checks_reinit - re-init post-Milter header checks */ 570 571 static void cleanup_milter_header_checks_reinit(CLEANUP_STATE *state) 572 { 573 if (state->filter) 574 myfree(state->filter); 575 state->filter = 0; 576 if (state->redirect) 577 myfree(state->redirect); 578 state->redirect = 0; 579 VSTRING_RESET(cleanup_milter_hbc_reply); 580 } 581 582 /* cleanup_milter_hbc_finish - finalize post-Milter header checks */ 583 584 static void cleanup_milter_hbc_finish(CLEANUP_STATE *state) 585 { 586 if (CLEANUP_OUT_OK(state) 587 && !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) 588 && (state->filter || state->redirect)) 589 cleanup_milter_hbc_add_meta_records(state); 590 } 591 592 /* 593 * Milter replies. 594 */ 595 #define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \ 596 if ((__state)->reason) \ 597 myfree((__state)->reason); \ 598 (__state)->reason = mystrdup(__reason); \ 599 if ((__state)->smtp_reply) { \ 600 myfree((__state)->smtp_reply); \ 601 (__state)->smtp_reply = 0; \ 602 } \ 603 } while (0) 604 605 #define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \ 606 if ((__state)->reason) \ 607 myfree((__state)->reason); \ 608 (__state)->reason = mystrdup(__smtp_reply + 4); \ 609 printable((__state)->reason, '_'); \ 610 if ((__state)->smtp_reply) \ 611 myfree((__state)->smtp_reply); \ 612 (__state)->smtp_reply = mystrdup(__smtp_reply); \ 613 } while (0) 614 615 /* cleanup_milter_set_error - set error flag from errno */ 616 617 static void cleanup_milter_set_error(CLEANUP_STATE *state, int err) 618 { 619 if (err == EFBIG) { 620 msg_warn("%s: queue file size limit exceeded", state->queue_id); 621 state->errs |= CLEANUP_STAT_SIZE; 622 } else { 623 msg_warn("%s: write queue file: %m", state->queue_id); 624 state->errs |= CLEANUP_STAT_WRITE; 625 } 626 } 627 628 /* cleanup_milter_error - return dummy error description */ 629 630 static const char *cleanup_milter_error(CLEANUP_STATE *state, int err) 631 { 632 const char *myname = "cleanup_milter_error"; 633 const CLEANUP_STAT_DETAIL *dp; 634 635 /* 636 * For consistency with error reporting within the milter infrastructure, 637 * content manipulation routines return a null pointer on success, and an 638 * SMTP-like response on error. 639 * 640 * However, when cleanup_milter_apply() receives this error response from 641 * the milter infrastructure, it ignores the text since the appropriate 642 * cleanup error flags were already set by cleanup_milter_set_error(). 643 * 644 * Specify a null error number when the "errno to error flag" mapping was 645 * already done elsewhere, possibly outside this module. 646 */ 647 if (err) 648 cleanup_milter_set_error(state, err); 649 else if (CLEANUP_OUT_OK(state)) 650 msg_panic("%s: missing errno to error flag mapping", myname); 651 if (state->milter_err_text == 0) 652 state->milter_err_text = vstring_alloc(50); 653 dp = cleanup_stat_detail(state->errs); 654 return (STR(vstring_sprintf(state->milter_err_text, 655 "%d %s %s", dp->smtp, dp->dsn, dp->text))); 656 } 657 658 /* cleanup_add_header - append message header */ 659 660 static const char *cleanup_add_header(void *context, const char *name, 661 const char *space, 662 const char *value) 663 { 664 const char *myname = "cleanup_add_header"; 665 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 666 VSTRING *buf; 667 off_t reverse_ptr_offset; 668 off_t new_hdr_offset; 669 670 /* 671 * To simplify implementation, the cleanup server writes a dummy "header 672 * append" pointer record after the last message header. We cache both 673 * the location and the target of the current "header append" pointer 674 * record. 675 */ 676 if (state->append_hdr_pt_offset < 0) 677 msg_panic("%s: no header append pointer location", myname); 678 if (state->append_hdr_pt_target < 0) 679 msg_panic("%s: no header append pointer target", myname); 680 681 /* 682 * Return early when Milter header checks request that this header record 683 * be dropped, or that the message is discarded. Note: CLEANUP_OUT_OK() 684 * tests CLEANUP_FLAG_DISCARD. We don't want to report the latter as an 685 * error. 686 */ 687 buf = vstring_alloc(100); 688 vstring_sprintf(buf, "%s:%s%s", name, space, value); 689 if (cleanup_milter_hbc_checks) { 690 if (cleanup_milter_header_checks(state, buf) == 0 691 || (state->flags & CLEANUP_FLAG_DISCARD)) { 692 vstring_free(buf); 693 return (0); 694 } 695 if (CLEANUP_OUT_OK(state) == 0) { 696 vstring_free(buf); 697 return (cleanup_milter_error(state, 0)); 698 } 699 } 700 701 /* 702 * Allocate space after the end of the queue file, and write the header 703 * record(s), followed by a reverse pointer record that points to the 704 * target of the old "header append" pointer record. This reverse pointer 705 * record becomes the new "header append" pointer record. 706 */ 707 if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { 708 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 709 vstring_free(buf); 710 return (cleanup_milter_error(state, errno)); 711 } 712 /* XXX emit prepended header, then clear it. */ 713 cleanup_out_header(state, buf); /* Includes padding */ 714 vstring_free(buf); 715 if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { 716 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 717 return (cleanup_milter_error(state, errno)); 718 } 719 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 720 (long) state->append_hdr_pt_target); 721 722 /* 723 * Pointer flipping: update the old "header append" pointer record value 724 * with the location of the new header record. 725 * 726 * XXX To avoid unnecessary seek operations when the new header immediately 727 * follows the old append header pointer, write a null pointer or make 728 * the record reading loop smarter. Making vstream_fseek() smarter does 729 * not help, because it doesn't know if we're going to read or write 730 * after a write+seek sequence. 731 */ 732 if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) { 733 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 734 return (cleanup_milter_error(state, errno)); 735 } 736 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 737 (long) new_hdr_offset); 738 739 /* 740 * Update the in-memory "header append" pointer record location with the 741 * location of the reverse pointer record that follows the new header. 742 * The target of the "header append" pointer record does not change; it's 743 * always the record that follows the dummy pointer record that was 744 * written while Postfix received the message. 745 */ 746 state->append_hdr_pt_offset = reverse_ptr_offset; 747 748 /* 749 * In case of error while doing record output. 750 */ 751 return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) : 752 cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ? 753 STR(cleanup_milter_hbc_reply) : 0); 754 755 /* 756 * Note: state->append_hdr_pt_target never changes. 757 */ 758 } 759 760 /* hidden_header - respect milter header hiding protocol */ 761 762 static int hidden_header(VSTRING *buf, ARGV *auto_hdrs, int *hide_done) 763 { 764 char **cpp; 765 int mask; 766 767 for (cpp = auto_hdrs->argv, mask = 1; *cpp; cpp++, mask <<= 1) 768 if ((*hide_done & mask) == 0 && strncmp(*cpp, STR(buf), LEN(buf)) == 0) 769 return (*hide_done |= mask); 770 return (0); 771 } 772 773 /* cleanup_find_header_start - find specific header instance */ 774 775 static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index, 776 const char *header_label, 777 VSTRING *buf, 778 int *prec_type, 779 int allow_ptr_backup) 780 { 781 const char *myname = "cleanup_find_header_start"; 782 off_t curr_offset; /* offset after found record */ 783 off_t ptr_offset; /* pointer to found record */ 784 VSTRING *ptr_buf = 0; 785 int rec_type = REC_TYPE_ERROR; 786 int last_type; 787 ssize_t len; 788 int hide_done = 0; 789 790 if (msg_verbose) 791 msg_info("%s: index %ld name \"%s\"", 792 myname, (long) index, header_label ? header_label : "(none)"); 793 794 /* 795 * Sanity checks. 796 */ 797 if (index < 1) 798 msg_panic("%s: bad header index %ld", myname, (long) index); 799 800 /* 801 * Skip to the start of the message content, and read records until we 802 * either find the specified header, or until we hit the end of the 803 * headers. 804 * 805 * The index specifies the header instance: 1 is the first one. The header 806 * label specifies the header name. A null pointer matches any header. 807 * 808 * When the specified header is not found, the result value is -1. 809 * 810 * When the specified header is found, its first record is stored in the 811 * caller-provided read buffer, and the result value is the queue file 812 * offset of that record. The file read position is left at the start of 813 * the next (non-filler) queue file record, which can be the remainder of 814 * a multi-record header. 815 * 816 * When a header is found and allow_ptr_backup is non-zero, then the result 817 * is either the first record of that header, or it is the pointer record 818 * that points to the first record of that header. In the latter case, 819 * the file read position is undefined. Returning the pointer allows us 820 * to do some optimizations when inserting text multiple times at the 821 * same place. 822 * 823 * XXX We can't use the MIME processor here. It not only buffers up the 824 * input, it also reads the record that follows a complete header before 825 * it invokes the header call-back action. This complicates the way that 826 * we discover header offsets and boundaries. Worse is that the MIME 827 * processor is unaware that multi-record message headers can have PTR 828 * records in the middle. 829 * 830 * XXX The draw-back of not using the MIME processor is that we have to 831 * duplicate some of its logic here and in the routine that finds the end 832 * of the header record. To minimize the duplication we define an ugly 833 * macro that is used in all code that scans for header boundaries. 834 * 835 * XXX Sendmail compatibility (based on Sendmail 8.13.6 measurements). 836 * 837 * - When changing Received: header #1, we change the Received: header that 838 * follows our own one; a request to change Received: header #0 is 839 * silently treated as a request to change Received: header #1. 840 * 841 * - When changing Date: header #1, we change the first Date: header; a 842 * request to change Date: header #0 is silently treated as a request to 843 * change Date: header #1. 844 * 845 * Thus, header change requests are relative to the content as received, 846 * that is, the content after our own Received: header. They can affect 847 * only the headers that the MTA actually exposes to mail filter 848 * applications. 849 * 850 * - However, when inserting a header at position 0, the new header appears 851 * before our own Received: header, and when inserting at position 1, the 852 * new header appears after our own Received: header. 853 * 854 * Thus, header insert operations are relative to the content as delivered, 855 * that is, the content including our own Received: header. 856 * 857 * None of the above is applicable after a Milter inserts a header before 858 * our own Received: header. From then on, our own Received: header 859 * becomes just like other headers. 860 */ 861 #define CLEANUP_FIND_HEADER_NOTFOUND (-1) 862 #define CLEANUP_FIND_HEADER_IOERROR (-2) 863 864 #define CLEANUP_FIND_HEADER_RETURN(offs) do { \ 865 if (ptr_buf) \ 866 vstring_free(ptr_buf); \ 867 return (offs); \ 868 } while (0) 869 870 #define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \ 871 if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \ 872 msg_warn("%s: read file %s: %m", myname, cleanup_path); \ 873 cleanup_milter_set_error(state, errno); \ 874 do { quit; } while (0); \ 875 } \ 876 if (msg_verbose > 1) \ 877 msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \ 878 LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \ 879 if (rec_type == REC_TYPE_DTXT) \ 880 continue; \ 881 if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \ 882 && rec_type != REC_TYPE_PTR) \ 883 break; 884 /* End of hairy macros. */ 885 886 if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0) { 887 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 888 cleanup_milter_set_error(state, errno); 889 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); 890 } 891 for (ptr_offset = 0, last_type = 0; /* void */ ; /* void */ ) { 892 if ((curr_offset = vstream_ftell(state->dst)) < 0) { 893 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 894 cleanup_milter_set_error(state, errno); 895 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); 896 } 897 /* Don't follow the "append header" pointer. */ 898 if (curr_offset == state->append_hdr_pt_offset) 899 break; 900 /* Caution: this macro terminates the loop at end-of-message. */ 901 /* Don't do complex processing while breaking out of this loop. */ 902 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, 903 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR)); 904 /* Caution: don't assume ptr->header. This may be header-ptr->body. */ 905 if (rec_type == REC_TYPE_PTR) { 906 if (rec_goto(state->dst, STR(buf)) < 0) { 907 msg_warn("%s: read file %s: %m", myname, cleanup_path); 908 cleanup_milter_set_error(state, errno); 909 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); 910 } 911 /* Save PTR record, in case it points to the start of a header. */ 912 if (allow_ptr_backup) { 913 ptr_offset = curr_offset; 914 if (ptr_buf == 0) 915 ptr_buf = vstring_alloc(100); 916 vstring_strcpy(ptr_buf, STR(buf)); 917 } 918 /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */ 919 continue; 920 } 921 /* The middle of a multi-record header. */ 922 else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0])) { 923 /* Reset the saved PTR record and update last_type. */ 924 } 925 /* No more message headers. */ 926 else if ((len = is_header(STR(buf))) == 0) { 927 break; 928 } 929 /* This the start of a message header. */ 930 else if ((header_label == 0 931 || (strncasecmp(header_label, STR(buf), len) == 0 932 && strlen(header_label) == len 933 && !hidden_header(buf, state->auto_hdrs, &hide_done))) 934 && --index == 0) { 935 /* If we have a saved PTR record, it points to start of header. */ 936 break; 937 } 938 ptr_offset = 0; 939 last_type = rec_type; 940 } 941 942 /* 943 * In case of failure, return negative start position. 944 */ 945 if (index > 0) { 946 curr_offset = CLEANUP_FIND_HEADER_NOTFOUND; 947 } else { 948 949 /* 950 * Skip over short-header padding, so that the file read pointer is 951 * always positioned at the first non-padding record after the header 952 * record. Insist on padding after short a header record, so that a 953 * short header record can safely be overwritten by a pointer record. 954 */ 955 if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) { 956 VSTRING *rbuf = (ptr_offset ? buf : 957 (ptr_buf ? ptr_buf : 958 (ptr_buf = vstring_alloc(100)))); 959 int rval; 960 961 if ((rval = rec_get_raw(state->dst, rbuf, 0, REC_FLAG_NONE)) < 0) { 962 cleanup_milter_set_error(state, errno); 963 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); 964 } 965 if (rval != REC_TYPE_DTXT) 966 msg_panic("%s: short header without padding", myname); 967 } 968 969 /* 970 * Optionally return a pointer to the message header, instead of the 971 * start of the message header itself. In that case the file read 972 * position is undefined (actually it is at the first non-padding 973 * record that follows the message header record). 974 */ 975 if (ptr_offset != 0) { 976 rec_type = REC_TYPE_PTR; 977 curr_offset = ptr_offset; 978 vstring_strcpy(buf, STR(ptr_buf)); 979 } 980 *prec_type = rec_type; 981 } 982 if (msg_verbose) 983 msg_info("%s: index %ld name %s type %d offset %ld", 984 myname, (long) index, header_label ? 985 header_label : "(none)", rec_type, (long) curr_offset); 986 987 CLEANUP_FIND_HEADER_RETURN(curr_offset); 988 } 989 990 /* cleanup_find_header_end - find end of header */ 991 992 static off_t cleanup_find_header_end(CLEANUP_STATE *state, 993 VSTRING *rec_buf, 994 int last_type) 995 { 996 const char *myname = "cleanup_find_header_end"; 997 off_t read_offset; 998 int rec_type; 999 1000 /* 1001 * This routine is called immediately after cleanup_find_header_start(). 1002 * rec_buf is the cleanup_find_header_start() result record; last_type is 1003 * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file 1004 * read position is at the first non-padding record after the result 1005 * header record. 1006 */ 1007 for (;;) { 1008 if ((read_offset = vstream_ftell(state->dst)) < 0) { 1009 msg_warn("%s: read file %s: %m", myname, cleanup_path); 1010 cleanup_milter_error(state, errno); 1011 return (-1); 1012 } 1013 /* Don't follow the "append header" pointer. */ 1014 if (read_offset == state->append_hdr_pt_offset) 1015 break; 1016 /* Caution: this macro terminates the loop at end-of-message. */ 1017 /* Don't do complex processing while breaking out of this loop. */ 1018 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset, 1019 /* Warning and errno->error mapping are done elsewhere. */ 1020 return (-1)); 1021 if (rec_type == REC_TYPE_PTR) { 1022 if (rec_goto(state->dst, STR(rec_buf)) < 0) { 1023 msg_warn("%s: read file %s: %m", myname, cleanup_path); 1024 cleanup_milter_error(state, errno); 1025 return (-1); 1026 } 1027 /* Don't update last_type; PTR may follow REC_TYPE_CONT. */ 1028 continue; 1029 } 1030 /* Start of header or message body. */ 1031 if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0])) 1032 break; 1033 last_type = rec_type; 1034 } 1035 return (read_offset); 1036 } 1037 1038 /* cleanup_patch_header - patch new header into an existing header */ 1039 1040 static const char *cleanup_patch_header(CLEANUP_STATE *state, 1041 const char *new_hdr_name, 1042 const char *hdr_space, 1043 const char *new_hdr_value, 1044 off_t old_rec_offset, 1045 int old_rec_type, 1046 VSTRING *old_rec_buf, 1047 off_t next_offset) 1048 { 1049 const char *myname = "cleanup_patch_header"; 1050 VSTRING *buf = vstring_alloc(100); 1051 off_t new_hdr_offset; 1052 1053 #define CLEANUP_PATCH_HEADER_RETURN(ret) do { \ 1054 vstring_free(buf); \ 1055 return (ret); \ 1056 } while (0) 1057 1058 if (msg_verbose) 1059 msg_info("%s: \"%s\" \"%s\" at %ld", 1060 myname, new_hdr_name, new_hdr_value, (long) old_rec_offset); 1061 1062 /* 1063 * Allocate space after the end of the queue file for the new header and 1064 * optionally save an existing record to make room for a forward pointer 1065 * record. If the saved record was not a PTR record, follow the saved 1066 * record by a reverse pointer record that points to the record after the 1067 * original location of the saved record. 1068 * 1069 * We update the queue file in a safe manner: save the new header and the 1070 * existing records after the end of the queue file, write the reverse 1071 * pointer, and only then overwrite the saved records with the forward 1072 * pointer to the new header. 1073 * 1074 * old_rec_offset, old_rec_type, and old_rec_buf specify the record that we 1075 * are about to overwrite with a pointer record. If the record needs to 1076 * be saved (i.e. old_rec_type > 0), the buffer contains the data content 1077 * of exactly one PTR or text record. 1078 * 1079 * next_offset specifies the record that follows the to-be-overwritten 1080 * record. It is ignored when the to-be-saved record is a pointer record. 1081 */ 1082 1083 /* 1084 * Return early when Milter header checks request that this header record 1085 * be dropped. 1086 */ 1087 vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value); 1088 if (cleanup_milter_hbc_checks 1089 && cleanup_milter_header_checks(state, buf) == 0) 1090 CLEANUP_PATCH_HEADER_RETURN(0); 1091 1092 /* 1093 * Write the new header to a new location after the end of the queue 1094 * file. 1095 */ 1096 if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { 1097 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1098 CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno)); 1099 } 1100 /* XXX emit prepended header, then clear it. */ 1101 cleanup_out_header(state, buf); /* Includes padding */ 1102 if (msg_verbose > 1) 1103 msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset, 1104 LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); 1105 1106 /* 1107 * Optionally, save the existing text record or pointer record that will 1108 * be overwritten with the forward pointer. Pad a short saved record to 1109 * ensure that it, too, can be overwritten by a pointer. 1110 */ 1111 if (old_rec_type > 0) { 1112 CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf); 1113 if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE) 1114 rec_pad(state->dst, REC_TYPE_DTXT, 1115 REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf)); 1116 if (msg_verbose > 1) 1117 msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ? 1118 30 : (int) LEN(old_rec_buf), STR(old_rec_buf)); 1119 } 1120 1121 /* 1122 * If the saved record wasn't a PTR record, write the reverse pointer 1123 * after the saved records. A reverse pointer value of -1 means we were 1124 * confused about what we were going to save. 1125 */ 1126 if (old_rec_type != REC_TYPE_PTR) { 1127 if (next_offset < 0) 1128 msg_panic("%s: bad reverse pointer %ld", 1129 myname, (long) next_offset); 1130 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1131 (long) next_offset); 1132 if (msg_verbose > 1) 1133 msg_info("%s: write PTR %ld", myname, (long) next_offset); 1134 } 1135 1136 /* 1137 * Write the forward pointer over the old record. Generally, a pointer 1138 * record will be shorter than a header record, so there will be a gap in 1139 * the queue file before the next record. In other words, we must always 1140 * follow pointer records otherwise we get out of sync with the data. 1141 */ 1142 if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0) { 1143 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1144 CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno)); 1145 } 1146 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1147 (long) new_hdr_offset); 1148 if (msg_verbose > 1) 1149 msg_info("%s: %ld: write PTR %ld", myname, (long) old_rec_offset, 1150 (long) new_hdr_offset); 1151 1152 /* 1153 * In case of error while doing record output. 1154 */ 1155 CLEANUP_PATCH_HEADER_RETURN( 1156 CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) : 1157 cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ? 1158 STR(cleanup_milter_hbc_reply) : 0); 1159 1160 /* 1161 * Note: state->append_hdr_pt_target never changes. 1162 */ 1163 } 1164 1165 /* cleanup_ins_header - insert message header */ 1166 1167 static const char *cleanup_ins_header(void *context, ssize_t index, 1168 const char *new_hdr_name, 1169 const char *hdr_space, 1170 const char *new_hdr_value) 1171 { 1172 const char *myname = "cleanup_ins_header"; 1173 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1174 VSTRING *old_rec_buf = vstring_alloc(100); 1175 off_t old_rec_offset; 1176 int old_rec_type; 1177 off_t next_offset; 1178 const char *ret; 1179 1180 #define CLEANUP_INS_HEADER_RETURN(ret) do { \ 1181 vstring_free(old_rec_buf); \ 1182 return (ret); \ 1183 } while (0) 1184 1185 if (msg_verbose) 1186 msg_info("%s: %ld \"%s\" \"%s\"", 1187 myname, (long) index, new_hdr_name, new_hdr_value); 1188 1189 /* 1190 * Look for a header at the specified position. 1191 * 1192 * The lookup result may be a pointer record. This allows us to make some 1193 * optimization when multiple insert operations happen in the same place. 1194 * 1195 * Index 1 is the top-most header. 1196 */ 1197 #define NO_HEADER_NAME ((char *) 0) 1198 #define ALLOW_PTR_BACKUP 1 1199 1200 if (index < 1) 1201 index = 1; 1202 old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME, 1203 old_rec_buf, &old_rec_type, 1204 ALLOW_PTR_BACKUP); 1205 if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR) 1206 /* Warning and errno->error mapping are done elsewhere. */ 1207 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0)); 1208 1209 /* 1210 * If the header does not exist, simply append the header to the linked 1211 * list at the "header append" pointer record. 1212 */ 1213 if (old_rec_offset < 0) 1214 CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name, 1215 hdr_space, new_hdr_value)); 1216 1217 /* 1218 * If the header does exist, save both the new and the existing header to 1219 * new storage at the end of the queue file, and link the new storage 1220 * with a forward and reverse pointer (don't write a reverse pointer if 1221 * we are starting with a pointer record). 1222 */ 1223 if (old_rec_type == REC_TYPE_PTR) { 1224 next_offset = -1; 1225 } else { 1226 if ((next_offset = vstream_ftell(state->dst)) < 0) { 1227 msg_warn("%s: read file %s: %m", myname, cleanup_path); 1228 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno)); 1229 } 1230 } 1231 ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value, 1232 old_rec_offset, old_rec_type, 1233 old_rec_buf, next_offset); 1234 CLEANUP_INS_HEADER_RETURN(ret); 1235 } 1236 1237 /* cleanup_upd_header - modify or append message header */ 1238 1239 static const char *cleanup_upd_header(void *context, ssize_t index, 1240 const char *new_hdr_name, 1241 const char *hdr_space, 1242 const char *new_hdr_value) 1243 { 1244 const char *myname = "cleanup_upd_header"; 1245 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1246 VSTRING *rec_buf; 1247 off_t old_rec_offset; 1248 off_t next_offset; 1249 int last_type; 1250 const char *ret; 1251 1252 if (msg_verbose) 1253 msg_info("%s: %ld \"%s\" \"%s\"", 1254 myname, (long) index, new_hdr_name, new_hdr_value); 1255 1256 /* 1257 * Sanity check. 1258 */ 1259 if (*new_hdr_name == 0) 1260 msg_panic("%s: null header name", myname); 1261 1262 /* 1263 * Find the header that is being modified. 1264 * 1265 * The lookup result will never be a pointer record. 1266 * 1267 * Index 1 is the first matching header instance. 1268 * 1269 * XXX When a header is updated repeatedly we create jumps to jumps. To 1270 * eliminate this, rewrite the loop below so that we can start with the 1271 * pointer record that points to the header that's being edited. 1272 */ 1273 #define DONT_SAVE_RECORD 0 1274 #define NO_PTR_BACKUP 0 1275 1276 #define CLEANUP_UPD_HEADER_RETURN(ret) do { \ 1277 vstring_free(rec_buf); \ 1278 return (ret); \ 1279 } while (0) 1280 1281 rec_buf = vstring_alloc(100); 1282 old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name, 1283 rec_buf, &last_type, 1284 NO_PTR_BACKUP); 1285 if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR) 1286 /* Warning and errno->error mapping are done elsewhere. */ 1287 CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0)); 1288 1289 /* 1290 * If no old header is found, simply append the new header to the linked 1291 * list at the "header append" pointer record. 1292 */ 1293 if (old_rec_offset < 0) 1294 CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name, 1295 hdr_space, new_hdr_value)); 1296 1297 /* 1298 * If the old header is found, find the end of the old header, save the 1299 * new header to new storage at the end of the queue file, and link the 1300 * new storage with a forward and reverse pointer. 1301 */ 1302 if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0) 1303 /* Warning and errno->error mapping are done elsewhere. */ 1304 CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0)); 1305 ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value, 1306 old_rec_offset, DONT_SAVE_RECORD, 1307 (VSTRING *) 0, next_offset); 1308 CLEANUP_UPD_HEADER_RETURN(ret); 1309 } 1310 1311 /* cleanup_del_header - delete message header */ 1312 1313 static const char *cleanup_del_header(void *context, ssize_t index, 1314 const char *hdr_name) 1315 { 1316 const char *myname = "cleanup_del_header"; 1317 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1318 VSTRING *rec_buf; 1319 off_t header_offset; 1320 off_t next_offset; 1321 int last_type; 1322 1323 if (msg_verbose) 1324 msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name); 1325 1326 /* 1327 * Sanity check. 1328 */ 1329 if (*hdr_name == 0) 1330 msg_panic("%s: null header name", myname); 1331 1332 /* 1333 * Find the header that is being deleted. 1334 * 1335 * The lookup result will never be a pointer record. 1336 * 1337 * Index 1 is the first matching header instance. 1338 */ 1339 #define CLEANUP_DEL_HEADER_RETURN(ret) do { \ 1340 vstring_free(rec_buf); \ 1341 return (ret); \ 1342 } while (0) 1343 1344 rec_buf = vstring_alloc(100); 1345 header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf, 1346 &last_type, NO_PTR_BACKUP); 1347 if (header_offset == CLEANUP_FIND_HEADER_IOERROR) 1348 /* Warning and errno->error mapping are done elsewhere. */ 1349 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0)); 1350 1351 /* 1352 * Overwrite the beginning of the header record with a pointer to the 1353 * information that follows the header. We can't simply overwrite the 1354 * header with cleanup_out_header() and a special record type, because 1355 * there may be a PTR record in the middle of a multi-line header. 1356 */ 1357 if (header_offset > 0) { 1358 if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0) 1359 /* Warning and errno->error mapping are done elsewhere. */ 1360 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0)); 1361 /* Mark the header as deleted. */ 1362 if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0) { 1363 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1364 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno)); 1365 } 1366 rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1367 (long) next_offset); 1368 } 1369 vstring_free(rec_buf); 1370 1371 /* 1372 * In case of error while doing record output. 1373 */ 1374 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); 1375 } 1376 1377 /* cleanup_chg_from - replace sender address, ignore ESMTP arguments */ 1378 1379 static const char *cleanup_chg_from(void *context, const char *ext_from, 1380 const char *esmtp_args) 1381 { 1382 const char *myname = "cleanup_chg_from"; 1383 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1384 off_t new_offset; 1385 off_t new_sender_offset; 1386 off_t after_sender_offs; 1387 int addr_count; 1388 TOK822 *tree; 1389 TOK822 *tp; 1390 VSTRING *int_sender_buf; 1391 int dsn_envid = 0; 1392 int dsn_ret = 0; 1393 1394 if (msg_verbose) 1395 msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args); 1396 1397 /* 1398 * ESMTP support is limited to RET and ENVID, i.e. things that are stored 1399 * together with the sender queue file record. 1400 */ 1401 if (esmtp_args[0]) { 1402 ARGV *esmtp_argv; 1403 int i; 1404 const char *arg; 1405 1406 esmtp_argv = argv_split(esmtp_args, " "); 1407 for (i = 0; i < esmtp_argv->argc; ++i) { 1408 arg = esmtp_argv->argv[i]; 1409 if (strncasecmp(arg, "RET=", 4) == 0) { 1410 if ((dsn_ret = dsn_ret_code(arg + 4)) == 0) { 1411 msg_warn("Ignoring bad ESMTP parameter \"%s\" in " 1412 "SMFI_CHGFROM request", arg); 1413 } else { 1414 state->dsn_ret = dsn_ret; 1415 } 1416 } else if (strncasecmp(arg, "ENVID=", 6) == 0) { 1417 if (state->milter_dsn_buf == 0) 1418 state->milter_dsn_buf = vstring_alloc(20); 1419 dsn_envid = (xtext_unquote(state->milter_dsn_buf, arg + 6) 1420 && allprint(STR(state->milter_dsn_buf))); 1421 if (!dsn_envid) { 1422 msg_warn("Ignoring bad ESMTP parameter \"%s\" in " 1423 "SMFI_CHGFROM request", arg); 1424 } else { 1425 if (state->dsn_envid) 1426 myfree(state->dsn_envid); 1427 state->dsn_envid = mystrdup(STR(state->milter_dsn_buf)); 1428 } 1429 } else { 1430 msg_warn("Ignoring bad ESMTP parameter \"%s\" in " 1431 "SMFI_CHGFROM request", arg); 1432 } 1433 } 1434 argv_free(esmtp_argv); 1435 } 1436 1437 /* 1438 * The cleanup server remembers the file offset of the current sender 1439 * address record (offset in sender_pt_offset) and the file offset of the 1440 * record that follows the sender address (offset in sender_pt_target). 1441 * Short original sender records are padded, so that they can safely be 1442 * overwritten with a pointer record to the new sender address record. 1443 */ 1444 if (state->sender_pt_offset < 0) 1445 msg_panic("%s: no original sender record offset", myname); 1446 if (state->sender_pt_target < 0) 1447 msg_panic("%s: no post-sender record offset", myname); 1448 1449 /* 1450 * Allocate space after the end of the queue file, and write the new {DSN 1451 * envid, DSN ret, sender address, sender BCC} records, followed by a 1452 * reverse pointer record that points to the record that follows the 1453 * original sender record. 1454 * 1455 * We update the queue file in a safe manner: save the new sender after the 1456 * end of the queue file, write the reverse pointer, and only then 1457 * overwrite the old sender record with the forward pointer to the new 1458 * sender. 1459 */ 1460 if ((new_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { 1461 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1462 return (cleanup_milter_error(state, errno)); 1463 } 1464 1465 /* 1466 * Sender DSN attribute records precede the sender record. 1467 */ 1468 if (dsn_envid) 1469 rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%s", 1470 MAIL_ATTR_DSN_ENVID, STR(state->milter_dsn_buf)); 1471 if (dsn_ret) 1472 rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%d", 1473 MAIL_ATTR_DSN_RET, dsn_ret); 1474 if (dsn_envid == 0 && dsn_ret == 0) { 1475 new_sender_offset = new_offset; 1476 } else if ((new_sender_offset = vstream_ftell(state->dst)) < 0) { 1477 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 1478 return (cleanup_milter_error(state, errno)); 1479 } 1480 1481 /* 1482 * Transform the address from external form to internal form. This also 1483 * removes the enclosing <>, if present. 1484 * 1485 * XXX vstring_alloc() rejects zero-length requests. 1486 */ 1487 int_sender_buf = vstring_alloc(strlen(ext_from) + 1); 1488 tree = tok822_parse(ext_from); 1489 for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { 1490 if (tp->type == TOK822_ADDR) { 1491 if (addr_count == 0) { 1492 tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL); 1493 addr_count += 1; 1494 } else { 1495 msg_warn("%s: Milter request to add multi-sender: \"%s\"", 1496 state->queue_id, ext_from); 1497 break; 1498 } 1499 } 1500 } 1501 tok822_free_tree(tree); 1502 after_sender_offs = cleanup_addr_sender(state, STR(int_sender_buf)); 1503 vstring_free(int_sender_buf); 1504 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1505 (long) state->sender_pt_target); 1506 state->sender_pt_target = after_sender_offs; 1507 1508 /* 1509 * Overwrite the current sender record with the pointer to the new {DSN 1510 * envid, DSN ret, sender address, sender BCC} records. 1511 */ 1512 if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0) { 1513 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1514 return (cleanup_milter_error(state, errno)); 1515 } 1516 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1517 (long) new_offset); 1518 1519 /* 1520 * Remember the location of the new current sender record. 1521 */ 1522 state->sender_pt_offset = new_sender_offset; 1523 1524 /* 1525 * In case of error while doing record output. 1526 */ 1527 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); 1528 } 1529 1530 /* cleanup_add_rcpt_par - append recipient address, with ESMTP arguments */ 1531 1532 static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt, 1533 const char *esmtp_args) 1534 { 1535 const char *myname = "cleanup_add_rcpt_par"; 1536 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1537 off_t new_rcpt_offset; 1538 off_t reverse_ptr_offset; 1539 int addr_count; 1540 TOK822 *tree; 1541 TOK822 *tp; 1542 VSTRING *int_rcpt_buf; 1543 VSTRING *orcpt_buf = 0; 1544 ARGV *esmtp_argv; 1545 int dsn_notify = 0; 1546 const char *dsn_orcpt_info = 0; 1547 size_t type_len; 1548 int i; 1549 const char *arg; 1550 const char *arg_val; 1551 1552 if (msg_verbose) 1553 msg_info("%s: \"%s\" \"%s\"", myname, ext_rcpt, esmtp_args); 1554 1555 /* 1556 * To simplify implementation, the cleanup server writes a dummy 1557 * "recipient append" pointer record after the last recipient. We cache 1558 * both the location and the target of the current "recipient append" 1559 * pointer record. 1560 */ 1561 if (state->append_rcpt_pt_offset < 0) 1562 msg_panic("%s: no recipient append pointer location", myname); 1563 if (state->append_rcpt_pt_target < 0) 1564 msg_panic("%s: no recipient append pointer target", myname); 1565 1566 /* 1567 * Allocate space after the end of the queue file, and write the 1568 * recipient record, followed by a reverse pointer record that points to 1569 * the target of the old "recipient append" pointer record. This reverse 1570 * pointer record becomes the new "recipient append" pointer record. 1571 * 1572 * We update the queue file in a safe manner: save the new recipient after 1573 * the end of the queue file, write the reverse pointer, and only then 1574 * overwrite the old "recipient append" pointer with the forward pointer 1575 * to the new recipient. 1576 */ 1577 if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { 1578 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1579 return (cleanup_milter_error(state, errno)); 1580 } 1581 1582 /* 1583 * Parse ESMTP parameters. XXX UTF8SMTP don't assume ORCPT is xtext. 1584 */ 1585 if (esmtp_args[0]) { 1586 esmtp_argv = argv_split(esmtp_args, " "); 1587 for (i = 0; i < esmtp_argv->argc; ++i) { 1588 arg = esmtp_argv->argv[i]; 1589 if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */ 1590 if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) 1591 msg_warn("%s: Bad NOTIFY parameter from Milter or " 1592 "header/body_checks: \"%.100s\"", 1593 state->queue_id, arg); 1594 } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */ 1595 if (orcpt_buf == 0) 1596 orcpt_buf = vstring_alloc(100); 1597 if (dsn_orcpt_info 1598 || (type_len = strcspn(arg_val = arg + 6, ";")) == 0 1599 || (arg_val)[type_len] != ';' 1600 || xtext_unquote_append(vstring_sprintf(orcpt_buf, 1601 "%.*s;", (int) type_len, 1602 arg_val), 1603 arg_val + type_len + 1) == 0) { 1604 msg_warn("%s: Bad ORCPT parameter from Milter or " 1605 "header/body_checks: \"%.100s\"", 1606 state->queue_id, arg); 1607 } else { 1608 dsn_orcpt_info = STR(orcpt_buf); 1609 } 1610 } else { 1611 msg_warn("%s: ignoring ESMTP argument from Milter or " 1612 "header/body_checks: \"%.100s\"", 1613 state->queue_id, arg); 1614 } 1615 } 1616 argv_free(esmtp_argv); 1617 } 1618 1619 /* 1620 * Transform recipient from external form to internal form. This also 1621 * removes the enclosing <>, if present. 1622 * 1623 * XXX vstring_alloc() rejects zero-length requests. 1624 */ 1625 int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1); 1626 tree = tok822_parse(ext_rcpt); 1627 for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { 1628 if (tp->type == TOK822_ADDR) { 1629 if (addr_count == 0) { 1630 tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL); 1631 addr_count += 1; 1632 } else { 1633 msg_warn("%s: Milter or header/body_checks request to " 1634 "add multi-recipient: \"%s\"", 1635 state->queue_id, ext_rcpt); 1636 break; 1637 } 1638 } 1639 } 1640 tok822_free_tree(tree); 1641 if (addr_count != 0) 1642 cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), dsn_orcpt_info, 1643 dsn_notify ? dsn_notify : DEF_DSN_NOTIFY); 1644 else 1645 msg_warn("%s: ignoring attempt from Milter to add null recipient", 1646 state->queue_id); 1647 vstring_free(int_rcpt_buf); 1648 if (orcpt_buf) 1649 vstring_free(orcpt_buf); 1650 1651 /* 1652 * Don't update the queue file when we did not write a recipient record 1653 * (malformed or duplicate BCC recipient). 1654 */ 1655 if (vstream_ftell(state->dst) == new_rcpt_offset) 1656 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); 1657 1658 /* 1659 * Follow the recipient with a "reverse" pointer to the old recipient 1660 * append target. 1661 */ 1662 if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { 1663 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 1664 return (cleanup_milter_error(state, errno)); 1665 } 1666 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1667 (long) state->append_rcpt_pt_target); 1668 1669 /* 1670 * Pointer flipping: update the old "recipient append" pointer record 1671 * value to the location of the new recipient record. 1672 */ 1673 if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0) { 1674 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1675 return (cleanup_milter_error(state, errno)); 1676 } 1677 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 1678 (long) new_rcpt_offset); 1679 1680 /* 1681 * Update the in-memory "recipient append" pointer record location with 1682 * the location of the reverse pointer record that follows the new 1683 * recipient. The target of the "recipient append" pointer record does 1684 * not change; it's always the record that follows the dummy pointer 1685 * record that was written while Postfix received the message. 1686 */ 1687 state->append_rcpt_pt_offset = reverse_ptr_offset; 1688 1689 /* 1690 * In case of error while doing record output. 1691 */ 1692 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); 1693 } 1694 1695 /* cleanup_add_rcpt - append recipient address */ 1696 1697 static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt) 1698 { 1699 return (cleanup_add_rcpt_par(context, ext_rcpt, "")); 1700 } 1701 1702 /* cleanup_del_rcpt - remove recipient and all its expansions */ 1703 1704 static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt) 1705 { 1706 const char *myname = "cleanup_del_rcpt"; 1707 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1708 off_t curr_offset; 1709 VSTRING *buf; 1710 char *attr_name; 1711 char *attr_value; 1712 char *dsn_orcpt = 0; /* XXX for dup filter cleanup */ 1713 int dsn_notify = 0; /* XXX for dup filter cleanup */ 1714 char *orig_rcpt = 0; 1715 char *start; 1716 int rec_type; 1717 int junk; 1718 int count = 0; 1719 TOK822 *tree; 1720 TOK822 *tp; 1721 VSTRING *int_rcpt_buf; 1722 int addr_count; 1723 1724 if (msg_verbose) 1725 msg_info("%s: \"%s\"", myname, ext_rcpt); 1726 1727 /* 1728 * Virtual aliasing and other address rewriting happens after the mail 1729 * filter sees the envelope address. Therefore we must delete all 1730 * recipient records whose Postfix (not DSN) original recipient address 1731 * matches the specified address. 1732 * 1733 * As the number of recipients may be very large we can't do an efficient 1734 * two-pass implementation (collect record offsets first, then mark 1735 * records as deleted). Instead we mark records as soon as we find them. 1736 * This is less efficient because we do (seek-write-read) for each marked 1737 * recipient, instead of (seek-write). It's unlikely that VSTREAMs will 1738 * be made smart enough to eliminate unnecessary I/O with small seeks. 1739 * 1740 * XXX When Postfix original recipients are turned off, we have no option 1741 * but to match against the expanded and rewritten recipient address. 1742 * 1743 * XXX Remove the (dsn_orcpt, dsn_notify, orcpt, recip) tuple from the 1744 * duplicate recipient filter. This requires that we maintain reference 1745 * counts. 1746 */ 1747 if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) { 1748 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1749 return (cleanup_milter_error(state, errno)); 1750 } 1751 #define CLEANUP_DEL_RCPT_RETURN(ret) do { \ 1752 if (orig_rcpt != 0) \ 1753 myfree(orig_rcpt); \ 1754 if (dsn_orcpt != 0) \ 1755 myfree(dsn_orcpt); \ 1756 vstring_free(buf); \ 1757 vstring_free(int_rcpt_buf); \ 1758 return (ret); \ 1759 } while (0) 1760 1761 /* 1762 * Transform recipient from external form to internal form. This also 1763 * removes the enclosing <>, if present. 1764 * 1765 * XXX vstring_alloc() rejects zero-length requests. 1766 */ 1767 int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1); 1768 tree = tok822_parse(ext_rcpt); 1769 for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { 1770 if (tp->type == TOK822_ADDR) { 1771 if (addr_count == 0) { 1772 tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL); 1773 addr_count += 1; 1774 } else { 1775 msg_warn("%s: Milter request to drop multi-recipient: \"%s\"", 1776 state->queue_id, ext_rcpt); 1777 break; 1778 } 1779 } 1780 } 1781 tok822_free_tree(tree); 1782 1783 buf = vstring_alloc(100); 1784 for (;;) { 1785 if (CLEANUP_OUT_OK(state) == 0) 1786 /* Warning and errno->error mapping are done elsewhere. */ 1787 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0)); 1788 if ((curr_offset = vstream_ftell(state->dst)) < 0) { 1789 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); 1790 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1791 } 1792 if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) <= 0) { 1793 msg_warn("%s: read file %s: %m", myname, cleanup_path); 1794 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1795 } 1796 if (rec_type == REC_TYPE_END) 1797 break; 1798 /* Skip over message content. */ 1799 if (rec_type == REC_TYPE_MESG) { 1800 if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) { 1801 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1802 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1803 } 1804 continue; 1805 } 1806 start = STR(buf); 1807 if (rec_type == REC_TYPE_PTR) { 1808 if (rec_goto(state->dst, start) < 0) { 1809 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1810 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1811 } 1812 continue; 1813 } 1814 /* Map attribute names to pseudo record type. */ 1815 if (rec_type == REC_TYPE_ATTR) { 1816 if (split_nameval(STR(buf), &attr_name, &attr_value) != 0 1817 || *attr_value == 0) 1818 continue; 1819 if ((junk = rec_attr_map(attr_name)) != 0) { 1820 start = attr_value; 1821 rec_type = junk; 1822 } 1823 } 1824 switch (rec_type) { 1825 case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */ 1826 if (dsn_orcpt != 0) /* can't happen */ 1827 myfree(dsn_orcpt); 1828 dsn_orcpt = mystrdup(start); 1829 break; 1830 case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */ 1831 if (alldig(start) && (junk = atoi(start)) > 0 1832 && DSN_NOTIFY_OK(junk)) 1833 dsn_notify = junk; 1834 else 1835 dsn_notify = 0; 1836 break; 1837 case REC_TYPE_ORCP: /* unmodified RCPT TO address */ 1838 if (orig_rcpt != 0) /* can't happen */ 1839 myfree(orig_rcpt); 1840 orig_rcpt = mystrdup(start); 1841 break; 1842 case REC_TYPE_RCPT: /* rewritten RCPT TO address */ 1843 if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) { 1844 if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) { 1845 msg_warn("%s: seek file %s: %m", myname, cleanup_path); 1846 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1847 } 1848 if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0) { 1849 msg_warn("%s: write queue file: %m", state->queue_id); 1850 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); 1851 } 1852 count++; 1853 } 1854 if (var_enable_orcpt) 1855 /* Matches been_here() call in cleanup_out_recipient(). */ 1856 been_here_drop(state->dups, "%s\n%d\n%s\n%s", 1857 dsn_orcpt ? dsn_orcpt : "", dsn_notify, 1858 orig_rcpt ? orig_rcpt : "", STR(int_rcpt_buf)); 1859 /* FALLTHROUGH */ 1860 case REC_TYPE_DRCP: /* canceled recipient */ 1861 case REC_TYPE_DONE: /* can't happen */ 1862 if (orig_rcpt != 0) { 1863 myfree(orig_rcpt); 1864 orig_rcpt = 0; 1865 } 1866 if (dsn_orcpt != 0) { 1867 myfree(dsn_orcpt); 1868 dsn_orcpt = 0; 1869 } 1870 dsn_notify = 0; 1871 break; 1872 } 1873 } 1874 /* Matches been_here_fixed() call in cleanup_out_recipient(). */ 1875 if (var_enable_orcpt == 0 && count > 0) 1876 been_here_drop_fixed(state->dups, STR(int_rcpt_buf)); 1877 1878 if (msg_verbose) 1879 msg_info("%s: deleted %d records for recipient \"%s\"", 1880 myname, count, ext_rcpt); 1881 1882 CLEANUP_DEL_RCPT_RETURN(0); 1883 } 1884 1885 /* cleanup_repl_body - replace message body */ 1886 1887 static const char *cleanup_repl_body(void *context, int cmd, int rec_type, 1888 VSTRING *buf) 1889 { 1890 const char *myname = "cleanup_repl_body"; 1891 CLEANUP_STATE *state = (CLEANUP_STATE *) context; 1892 static VSTRING empty; 1893 1894 /* 1895 * XXX Sendmail compatibility: milters don't see the first body line, so 1896 * don't expect they will send one. 1897 */ 1898 switch (cmd) { 1899 case MILTER_BODY_LINE: 1900 if (cleanup_body_edit_write(state, rec_type, buf) < 0) 1901 return (cleanup_milter_error(state, errno)); 1902 break; 1903 case MILTER_BODY_START: 1904 VSTRING_RESET(&empty); 1905 if (cleanup_body_edit_start(state) < 0 1906 || cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0) 1907 return (cleanup_milter_error(state, errno)); 1908 break; 1909 case MILTER_BODY_END: 1910 if (cleanup_body_edit_finish(state) < 0) 1911 return (cleanup_milter_error(state, errno)); 1912 break; 1913 default: 1914 msg_panic("%s: bad command: %d", myname, cmd); 1915 } 1916 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno)); 1917 } 1918 1919 /* cleanup_milter_eval - expand macro */ 1920 1921 static const char *cleanup_milter_eval(const char *name, void *ptr) 1922 { 1923 CLEANUP_STATE *state = (CLEANUP_STATE *) ptr; 1924 1925 /* 1926 * Note: if we use XFORWARD attributes here, then consistency requires 1927 * that we forward all Sendmail macros via XFORWARD. 1928 */ 1929 1930 /* 1931 * System macros. 1932 */ 1933 if (strcmp(name, S8_MAC_DAEMON_NAME) == 0) 1934 return (var_milt_daemon_name); 1935 if (strcmp(name, S8_MAC_V) == 0) 1936 return (var_milt_v); 1937 1938 /* 1939 * Connect macros. 1940 */ 1941 #ifndef CLIENT_ATTR_UNKNOWN 1942 #define CLIENT_ATTR_UNKNOWN "unknown" 1943 #define SERVER_ATTR_UNKNOWN "unknown" 1944 #endif 1945 1946 if (strcmp(name, S8_MAC__) == 0) { 1947 vstring_sprintf(state->temp1, "%s [%s]", 1948 state->reverse_name, state->client_addr); 1949 if (strcasecmp(state->client_name, state->reverse_name) != 0) 1950 vstring_strcat(state->temp1, " (may be forged)"); 1951 return (STR(state->temp1)); 1952 } 1953 if (strcmp(name, S8_MAC_J) == 0) 1954 return (var_myhostname); 1955 if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0) 1956 return (state->client_addr); 1957 if (strcmp(name, S8_MAC_CLIENT_NAME) == 0) 1958 return (state->client_name); 1959 if (strcmp(name, S8_MAC_CLIENT_PORT) == 0) 1960 return (state->client_port 1961 && strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ? 1962 state->client_port : "0"); 1963 if (strcmp(name, S8_MAC_CLIENT_PTR) == 0) 1964 return (state->reverse_name); 1965 /* XXX S8_MAC_CLIENT_RES needs SMTPD_PEER_CODE_XXX from smtpd. */ 1966 if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0) 1967 return (state->server_addr); 1968 if (strcmp(name, S8_MAC_DAEMON_PORT) == 0) 1969 return (state->server_port 1970 && strcmp(state->server_port, SERVER_ATTR_UNKNOWN) ? 1971 state->server_port : "0"); 1972 1973 /* 1974 * MAIL FROM macros. 1975 */ 1976 if (strcmp(name, S8_MAC_I) == 0) 1977 return (state->queue_id); 1978 #ifdef USE_SASL_AUTH 1979 if (strcmp(name, S8_MAC_AUTH_TYPE) == 0) 1980 return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD)); 1981 if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0) 1982 return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME)); 1983 if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0) 1984 return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER)); 1985 #endif 1986 if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) 1987 return (state->milter_ext_from ? STR(state->milter_ext_from) : 0); 1988 1989 /* 1990 * RCPT TO macros. 1991 */ 1992 if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) 1993 return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0); 1994 return (0); 1995 } 1996 1997 /* cleanup_milter_receive - receive milter instances */ 1998 1999 void cleanup_milter_receive(CLEANUP_STATE *state, int count) 2000 { 2001 if (state->milters) 2002 milter_free(state->milters); 2003 state->milters = milter_receive(state->src, count); 2004 if (state->milters == 0) 2005 msg_fatal("cleanup_milter_receive: milter receive failed"); 2006 if (count <= 0) 2007 return; 2008 milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state); 2009 milter_edit_callback(state->milters, 2010 cleanup_add_header, cleanup_upd_header, 2011 cleanup_ins_header, cleanup_del_header, 2012 cleanup_chg_from, cleanup_add_rcpt, 2013 cleanup_add_rcpt_par, cleanup_del_rcpt, 2014 cleanup_repl_body, (void *) state); 2015 } 2016 2017 /* cleanup_milter_apply - apply Milter response, non-zero if rejecting */ 2018 2019 static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event, 2020 const char *resp) 2021 { 2022 const char *myname = "cleanup_milter_apply"; 2023 const char *action; 2024 const char *text; 2025 const char *attr; 2026 const char *ret = 0; 2027 2028 if (msg_verbose) 2029 msg_info("%s: %s", myname, resp); 2030 2031 /* 2032 * Don't process our own milter_header/body checks replies. See comments 2033 * in cleanup_milter_hbc_extend(). 2034 */ 2035 if (cleanup_milter_hbc_reply && 2036 strcmp(resp, STR(cleanup_milter_hbc_reply)) == 0) 2037 return (0); 2038 2039 /* 2040 * Don't process Milter replies that are redundant because header/body 2041 * checks already decided that we will not receive the message; or Milter 2042 * replies that would have conflicting effect with the outcome of 2043 * header/body checks (for example, header_checks "discard" action 2044 * followed by Milter "reject" reply). Logging both actions would look 2045 * silly. 2046 */ 2047 if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) { 2048 if (msg_verbose) 2049 msg_info("%s: ignoring redundant or conflicting milter reply: %s", 2050 state->queue_id, resp); 2051 return (0); 2052 } 2053 2054 /* 2055 * Sanity check. 2056 */ 2057 if (state->client_name == 0) 2058 msg_panic("%s: missing client info initialization", myname); 2059 2060 /* 2061 * We don't report errors that were already reported by the content 2062 * editing call-back routines. See cleanup_milter_error() above. 2063 */ 2064 if (CLEANUP_OUT_OK(state) == 0) 2065 return (0); 2066 switch (resp[0]) { 2067 case 'H': 2068 if (state->flags & CLEANUP_FLAG_HOLD) 2069 return (0); 2070 state->flags |= CLEANUP_FLAG_HOLD; 2071 action = "milter-hold"; 2072 text = resp[1] ? resp + 1 : "milter triggers HOLD action"; 2073 break; 2074 case 'D': 2075 if (state->flags & CLEANUP_FLAG_DISCARD) 2076 return (0); 2077 state->flags |= CLEANUP_FLAG_DISCARD; 2078 action = "milter-discard"; 2079 text = "milter triggers DISCARD action"; 2080 break; 2081 case 'S': 2082 if (state->flags & CLEANUP_STAT_CONT) 2083 return (0); 2084 /* Shutdown' may be the default action for an I/O error. */ 2085 CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); 2086 ret = state->reason; 2087 state->errs |= CLEANUP_STAT_WRITE; 2088 action = "milter-reject"; 2089 text = resp + 4; 2090 break; 2091 2092 /* 2093 * Override permanent reject with temporary reject. This happens when 2094 * the cleanup server has to bounce (hard reject) but is unable to 2095 * store the message (soft reject). After a temporary reject we stop 2096 * inspecting queue file records, so it can't be overruled by 2097 * something else. 2098 * 2099 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason 2100 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates 2101 * queue record processing, and prevents bounces from being sent. 2102 */ 2103 case '4': 2104 CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); 2105 ret = state->reason; 2106 state->errs |= CLEANUP_STAT_DEFER; 2107 action = "milter-reject"; 2108 text = resp + 4; 2109 break; 2110 case '5': 2111 CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); 2112 ret = state->reason; 2113 state->errs |= CLEANUP_STAT_CONT; 2114 action = "milter-reject"; 2115 text = resp + 4; 2116 break; 2117 default: 2118 msg_panic("%s: unexpected mail filter reply: %s", myname, resp); 2119 } 2120 vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;", 2121 state->queue_id, action, event, state->client_name, 2122 state->client_addr, text); 2123 if (state->sender) 2124 vstring_sprintf_append(state->temp1, " from=<%s>", 2125 info_log_addr_form_sender(state->sender)); 2126 if (state->recip) 2127 vstring_sprintf_append(state->temp1, " to=<%s>", 2128 info_log_addr_form_recipient(state->recip)); 2129 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) 2130 vstring_sprintf_append(state->temp1, " proto=%s", attr); 2131 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) 2132 vstring_sprintf_append(state->temp1, " helo=<%s>", attr); 2133 msg_info("%s", vstring_str(state->temp1)); 2134 2135 return (ret); 2136 } 2137 2138 /* cleanup_milter_client_init - initialize real or ersatz client info */ 2139 2140 static void cleanup_milter_client_init(CLEANUP_STATE *state) 2141 { 2142 static const INET_PROTO_INFO *proto_info; 2143 const char *proto_attr; 2144 2145 /* 2146 * Either the cleanup client specifies a name, address and protocol, or 2147 * we have a local submission and pretend localhost/127.0.0.1/AF_INET. 2148 */ 2149 #define NO_CLIENT_PORT "0" 2150 2151 state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME); 2152 state->reverse_name = 2153 nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME); 2154 state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR); 2155 state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT); 2156 proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF); 2157 state->server_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_ADDR); 2158 state->server_port = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_PORT); 2159 2160 if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0 2161 || !alldig(proto_attr)) { 2162 state->client_name = "localhost"; 2163 #ifdef AF_INET6 2164 if (proto_info == 0) 2165 proto_info = inet_proto_info(); 2166 if (proto_info->sa_family_list[0] == PF_INET6) { 2167 state->client_addr = "::1"; 2168 state->client_af = AF_INET6; 2169 } else 2170 #endif 2171 { 2172 state->client_addr = "127.0.0.1"; 2173 state->client_af = AF_INET; 2174 } 2175 state->server_addr = state->client_addr; 2176 } else 2177 state->client_af = atoi(proto_attr); 2178 if (state->reverse_name == 0) 2179 state->reverse_name = state->client_name; 2180 /* Compatibility with pre-2.5 queue files. */ 2181 if (state->client_port == 0) { 2182 state->client_port = NO_CLIENT_PORT; 2183 state->server_port = state->client_port; 2184 } 2185 } 2186 2187 /* cleanup_milter_inspect - run message through mail filter */ 2188 2189 void cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters) 2190 { 2191 const char *myname = "cleanup_milter"; 2192 const char *resp; 2193 2194 if (msg_verbose) 2195 msg_info("enter %s", myname); 2196 2197 /* 2198 * Initialize, in case we're called via smtpd(8). 2199 */ 2200 if (state->client_name == 0) 2201 cleanup_milter_client_init(state); 2202 2203 /* 2204 * Prologue: prepare for Milter header/body checks. 2205 */ 2206 if (*var_milt_head_checks) 2207 cleanup_milter_header_checks_reinit(state); 2208 2209 /* 2210 * Process mail filter replies. The reply format is verified by the mail 2211 * filter library. 2212 */ 2213 if ((resp = milter_message(milters, state->handle->stream, 2214 state->data_offset, state->auto_hdrs)) != 0) 2215 cleanup_milter_apply(state, "END-OF-MESSAGE", resp); 2216 2217 /* 2218 * Epilogue: finalize Milter header/body checks. 2219 */ 2220 if (*var_milt_head_checks) 2221 cleanup_milter_hbc_finish(state); 2222 2223 if (msg_verbose) 2224 msg_info("leave %s", myname); 2225 } 2226 2227 /* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */ 2228 2229 void cleanup_milter_emul_mail(CLEANUP_STATE *state, 2230 MILTERS *milters, 2231 const char *addr) 2232 { 2233 const char *resp; 2234 const char *helo; 2235 const char *argv[2]; 2236 2237 /* 2238 * Per-connection initialization. 2239 */ 2240 milter_macro_callback(milters, cleanup_milter_eval, (void *) state); 2241 milter_edit_callback(milters, 2242 cleanup_add_header, cleanup_upd_header, 2243 cleanup_ins_header, cleanup_del_header, 2244 cleanup_chg_from, cleanup_add_rcpt, 2245 cleanup_add_rcpt_par, cleanup_del_rcpt, 2246 cleanup_repl_body, (void *) state); 2247 if (state->client_name == 0) 2248 cleanup_milter_client_init(state); 2249 2250 /* 2251 * Emulate SMTP events. 2252 */ 2253 if ((resp = milter_conn_event(milters, state->client_name, state->client_addr, 2254 state->client_port, state->client_af)) != 0) { 2255 cleanup_milter_apply(state, "CONNECT", resp); 2256 return; 2257 } 2258 #define PRETEND_ESMTP 1 2259 2260 if (CLEANUP_MILTER_OK(state)) { 2261 if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0) 2262 helo = state->client_name; 2263 if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0) { 2264 cleanup_milter_apply(state, "EHLO", resp); 2265 return; 2266 } 2267 } 2268 if (CLEANUP_MILTER_OK(state)) { 2269 if (state->milter_ext_from == 0) 2270 state->milter_ext_from = vstring_alloc(100); 2271 /* Sendmail 8.13 does not externalize the null address. */ 2272 if (*addr) 2273 quote_821_local(state->milter_ext_from, addr); 2274 else 2275 vstring_strcpy(state->milter_ext_from, addr); 2276 argv[0] = STR(state->milter_ext_from); 2277 argv[1] = 0; 2278 if ((resp = milter_mail_event(milters, argv)) != 0) { 2279 cleanup_milter_apply(state, "MAIL", resp); 2280 return; 2281 } 2282 } 2283 } 2284 2285 /* cleanup_milter_emul_rcpt - emulate rcpt event */ 2286 2287 void cleanup_milter_emul_rcpt(CLEANUP_STATE *state, 2288 MILTERS *milters, 2289 const char *addr) 2290 { 2291 const char *myname = "cleanup_milter_emul_rcpt"; 2292 const char *resp; 2293 const char *argv[2]; 2294 2295 /* 2296 * Sanity check. 2297 */ 2298 if (state->client_name == 0) 2299 msg_panic("%s: missing client info initialization", myname); 2300 2301 /* 2302 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason 2303 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates 2304 * queue record processing, and prevents bounces from being sent. 2305 */ 2306 if (state->milter_ext_rcpt == 0) 2307 state->milter_ext_rcpt = vstring_alloc(100); 2308 /* Sendmail 8.13 does not externalize the null address. */ 2309 if (*addr) 2310 quote_821_local(state->milter_ext_rcpt, addr); 2311 else 2312 vstring_strcpy(state->milter_ext_rcpt, addr); 2313 argv[0] = STR(state->milter_ext_rcpt); 2314 argv[1] = 0; 2315 if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0 2316 && cleanup_milter_apply(state, "RCPT", resp) != 0) { 2317 msg_warn("%s: milter configuration error: can't reject recipient " 2318 "in non-smtpd(8) submission", state->queue_id); 2319 msg_warn("%s: message not accepted, try again later", state->queue_id); 2320 CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error"); 2321 state->errs |= CLEANUP_STAT_DEFER; 2322 } 2323 } 2324 2325 /* cleanup_milter_emul_data - emulate data event */ 2326 2327 void cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters) 2328 { 2329 const char *myname = "cleanup_milter_emul_data"; 2330 const char *resp; 2331 2332 /* 2333 * Sanity check. 2334 */ 2335 if (state->client_name == 0) 2336 msg_panic("%s: missing client info initialization", myname); 2337 2338 if ((resp = milter_data_event(milters)) != 0) 2339 cleanup_milter_apply(state, "DATA", resp); 2340 } 2341 2342 #ifdef TEST 2343 2344 /* 2345 * Queue file editing driver for regression tests. In this case it is OK to 2346 * report fatal errors after I/O errors. 2347 */ 2348 #include <stdio.h> 2349 #include <msg_vstream.h> 2350 #include <vstring_vstream.h> 2351 #include <mail_addr.h> 2352 #include <mail_version.h> 2353 2354 #undef msg_verbose 2355 2356 char *cleanup_path; 2357 VSTRING *cleanup_trace_path; 2358 VSTRING *cleanup_strip_chars; 2359 int cleanup_comm_canon_flags; 2360 MAPS *cleanup_comm_canon_maps; 2361 int cleanup_ext_prop_mask; 2362 ARGV *cleanup_masq_domains; 2363 int cleanup_masq_flags; 2364 MAPS *cleanup_rcpt_bcc_maps; 2365 int cleanup_rcpt_canon_flags; 2366 MAPS *cleanup_rcpt_canon_maps; 2367 MAPS *cleanup_send_bcc_maps; 2368 int cleanup_send_canon_flags; 2369 MAPS *cleanup_send_canon_maps; 2370 int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT; 2371 char *var_empty_addr = DEF_EMPTY_ADDR; 2372 MAPS *cleanup_virt_alias_maps; 2373 char *var_milt_daemon_name = "host.example.com"; 2374 char *var_milt_v = DEF_MILT_V; 2375 MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters)); 2376 char *var_milt_head_checks = ""; 2377 2378 /* Dummies to satisfy unused external references. */ 2379 2380 int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains) 2381 { 2382 msg_panic("cleanup_masquerade_internal dummy"); 2383 } 2384 2385 int cleanup_rewrite_internal(const char *context, VSTRING *result, 2386 const char *addr) 2387 { 2388 vstring_strcpy(result, addr); 2389 return (0); 2390 } 2391 2392 int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr, 2393 MAPS *maps, int propagate) 2394 { 2395 msg_panic("cleanup_map11_internal dummy"); 2396 } 2397 2398 ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, 2399 MAPS *maps, int propagate) 2400 { 2401 msg_panic("cleanup_map1n_internal dummy"); 2402 } 2403 2404 void cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf, 2405 ssize_t len) 2406 { 2407 msg_panic("cleanup_envelope dummy"); 2408 } 2409 2410 static void usage(void) 2411 { 2412 msg_warn("usage:"); 2413 msg_warn(" verbose on|off"); 2414 msg_warn(" open pathname"); 2415 msg_warn(" close"); 2416 msg_warn(" add_header index name [value]"); 2417 msg_warn(" ins_header index name [value]"); 2418 msg_warn(" upd_header index name [value]"); 2419 msg_warn(" del_header index name"); 2420 msg_warn(" chg_from addr parameters"); 2421 msg_warn(" add_rcpt addr"); 2422 msg_warn(" add_rcpt_par addr parameters"); 2423 msg_warn(" del_rcpt addr"); 2424 msg_warn(" replbody pathname"); 2425 msg_warn(" header_checks type:name"); 2426 } 2427 2428 /* flatten_args - unparse partial command line */ 2429 2430 static void flatten_args(VSTRING *buf, char **argv) 2431 { 2432 char **cpp; 2433 2434 VSTRING_RESET(buf); 2435 for (cpp = argv; *cpp; cpp++) { 2436 vstring_strcat(buf, *cpp); 2437 if (cpp[1]) 2438 VSTRING_ADDCH(buf, ' '); 2439 } 2440 VSTRING_TERMINATE(buf); 2441 } 2442 2443 /* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */ 2444 2445 static void open_queue_file(CLEANUP_STATE *state, const char *path) 2446 { 2447 VSTRING *buf = vstring_alloc(100); 2448 off_t curr_offset; 2449 int rec_type; 2450 long msg_seg_len; 2451 long data_offset; 2452 long rcpt_count; 2453 long qmgr_opts; 2454 const HEADER_OPTS *opts; 2455 2456 if (state->dst != 0) { 2457 msg_warn("closing %s", cleanup_path); 2458 vstream_fclose(state->dst); 2459 state->dst = 0; 2460 myfree(cleanup_path); 2461 cleanup_path = 0; 2462 } 2463 if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) { 2464 msg_warn("open %s: %m", path); 2465 } else { 2466 var_drop_hdrs = ""; 2467 cleanup_path = mystrdup(path); 2468 for (;;) { 2469 if ((curr_offset = vstream_ftell(state->dst)) < 0) 2470 msg_fatal("file %s: vstream_ftell: %m", cleanup_path); 2471 if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) 2472 msg_fatal("file %s: missing SIZE or PTR record", cleanup_path); 2473 if (rec_type == REC_TYPE_SIZE) { 2474 if (sscanf(STR(buf), "%ld %ld %ld %ld", 2475 &msg_seg_len, &data_offset, 2476 &rcpt_count, &qmgr_opts) != 4) 2477 msg_fatal("file %s: bad SIZE record: %s", 2478 cleanup_path, STR(buf)); 2479 state->data_offset = data_offset; 2480 state->xtra_offset = data_offset + msg_seg_len; 2481 } else if (rec_type == REC_TYPE_FROM) { 2482 state->sender_pt_offset = curr_offset; 2483 if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE 2484 && rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR) 2485 msg_fatal("file %s: missing PTR record after short sender", 2486 cleanup_path); 2487 if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0) 2488 msg_fatal("file %s: missing END record", cleanup_path); 2489 } else if (rec_type == REC_TYPE_PTR) { 2490 if (state->data_offset < 0) 2491 msg_fatal("file %s: missing SIZE record", cleanup_path); 2492 if (curr_offset < state->data_offset 2493 || curr_offset > state->xtra_offset) { 2494 if (state->append_rcpt_pt_offset < 0) { 2495 state->append_rcpt_pt_offset = curr_offset; 2496 if (atol(STR(buf)) != 0) 2497 msg_fatal("file %s: bad dummy recipient PTR record: %s", 2498 cleanup_path, STR(buf)); 2499 if ((state->append_rcpt_pt_target = 2500 vstream_ftell(state->dst)) < 0) 2501 msg_fatal("file %s: vstream_ftell: %m", cleanup_path); 2502 } else if (curr_offset > state->xtra_offset 2503 && state->append_meta_pt_offset < 0) { 2504 state->append_meta_pt_offset = curr_offset; 2505 if (atol(STR(buf)) != 0) 2506 msg_fatal("file %s: bad dummy meta PTR record: %s", 2507 cleanup_path, STR(buf)); 2508 if ((state->append_meta_pt_target = 2509 vstream_ftell(state->dst)) < 0) 2510 msg_fatal("file %s: vstream_ftell: %m", cleanup_path); 2511 } 2512 } else { 2513 if (state->append_hdr_pt_offset < 0) { 2514 state->append_hdr_pt_offset = curr_offset; 2515 if (atol(STR(buf)) != 0) 2516 msg_fatal("file %s: bad dummy header PTR record: %s", 2517 cleanup_path, STR(buf)); 2518 if ((state->append_hdr_pt_target = 2519 vstream_ftell(state->dst)) < 0) 2520 msg_fatal("file %s: vstream_ftell: %m", cleanup_path); 2521 } 2522 } 2523 } else if (rec_type == REC_TYPE_NORM && state->hop_count == 0 2524 && (opts = header_opts_find(STR(buf))) != 0 2525 && opts->type == HDR_RECEIVED) { 2526 state->hop_count += 1; 2527 /* XXX Only the first line of the first Received: header. */ 2528 argv_add(state->auto_hdrs, STR(buf), ARGV_END); 2529 } 2530 if (state->append_rcpt_pt_offset > 0 2531 && state->append_hdr_pt_offset > 0 2532 && state->hop_count > 0 2533 && (rec_type == REC_TYPE_END 2534 || state->append_meta_pt_offset > 0)) 2535 break; 2536 } 2537 if (msg_verbose) { 2538 msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld", 2539 (long) state->append_rcpt_pt_offset, 2540 (long) state->append_rcpt_pt_target); 2541 msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld", 2542 (long) state->append_hdr_pt_offset, 2543 (long) state->append_hdr_pt_target); 2544 } 2545 } 2546 vstring_free(buf); 2547 } 2548 2549 static void close_queue_file(CLEANUP_STATE *state) 2550 { 2551 (void) vstream_fclose(state->dst); 2552 state->dst = 0; 2553 myfree(cleanup_path); 2554 cleanup_path = 0; 2555 } 2556 2557 int main(int unused_argc, char **argv) 2558 { 2559 VSTRING *inbuf = vstring_alloc(100); 2560 VSTRING *arg_buf = vstring_alloc(100); 2561 char *bufp; 2562 int istty = isatty(vstream_fileno(VSTREAM_IN)); 2563 CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0); 2564 const char *parens = "{}"; 2565 2566 state->queue_id = mystrdup("NOQUEUE"); 2567 state->sender = mystrdup("sender"); 2568 state->recip = mystrdup("recipient"); 2569 state->client_name = "client_name"; 2570 state->client_addr = "client_addr"; 2571 state->flags |= CLEANUP_FLAG_FILTER_ALL; 2572 2573 msg_vstream_init(argv[0], VSTREAM_ERR); 2574 var_line_limit = DEF_LINE_LIMIT; 2575 var_header_limit = DEF_HEADER_LIMIT; 2576 var_enable_orcpt = DEF_ENABLE_ORCPT; 2577 var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM; 2578 2579 for (;;) { 2580 ARGV *argv; 2581 ssize_t index; 2582 const char *resp = 0; 2583 2584 if (istty) { 2585 vstream_printf("- "); 2586 vstream_fflush(VSTREAM_OUT); 2587 } 2588 if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0) 2589 break; 2590 2591 bufp = vstring_str(inbuf); 2592 if (!istty) { 2593 vstream_printf("> %s\n", bufp); 2594 vstream_fflush(VSTREAM_OUT); 2595 } 2596 if (*bufp == '#' || *bufp == 0 || allspace(bufp)) 2597 continue; 2598 argv = argv_splitq(bufp, " ", parens); 2599 if (argv->argc == 0) { 2600 msg_warn("missing command"); 2601 } else if (strcmp(argv->argv[0], "?") == 0) { 2602 usage(); 2603 } else if (strcmp(argv->argv[0], "verbose") == 0) { 2604 if (argv->argc != 2) { 2605 msg_warn("bad verbose argument count: %ld", (long) argv->argc); 2606 } else if (strcmp(argv->argv[1], "on") == 0) { 2607 msg_verbose = 2; 2608 } else if (strcmp(argv->argv[1], "off") == 0) { 2609 msg_verbose = 0; 2610 } else { 2611 msg_warn("bad verbose argument"); 2612 } 2613 } else if (strcmp(argv->argv[0], "line_length_limit") == 0) { 2614 if (argv->argc != 2) { 2615 msg_warn("bad line_length_limit argument count: %ld", 2616 (long) argv->argc); 2617 } else if (alldig(argv->argv[1]) == 0) { 2618 msg_warn("bad line_length_limit argument count: %ld", 2619 (long) argv->argc); 2620 } else if ((var_line_limit = atoi(argv->argv[1])) < DEF_LINE_LIMIT) { 2621 msg_warn("bad line_length_limit argument"); 2622 } 2623 } else if (strcmp(argv->argv[0], "open") == 0) { 2624 if (state->dst != 0) { 2625 msg_info("closing %s", VSTREAM_PATH(state->dst)); 2626 close_queue_file(state); 2627 } 2628 if (argv->argc != 2) { 2629 msg_warn("bad open argument count: %ld", (long) argv->argc); 2630 } else { 2631 open_queue_file(state, argv->argv[1]); 2632 } 2633 } else if (strcmp(argv->argv[0], "enable_original_recipient") == 0) { 2634 if (argv->argc == 1) { 2635 msg_info("enable_original_recipient: %d", var_enable_orcpt); 2636 } else if (argv->argc != 2) { 2637 msg_warn("bad enable_original_recipient argument count: %ld", 2638 (long) argv->argc); 2639 } else if (!alldig(argv->argv[1])) { 2640 msg_warn("non-numeric enable_original_recipient argument: %s", 2641 argv->argv[1]); 2642 } else { 2643 var_enable_orcpt = atoi(argv->argv[1]); 2644 } 2645 } else if (state->dst == 0) { 2646 msg_warn("no open queue file"); 2647 } else if (strcmp(argv->argv[0], "close") == 0) { 2648 if (*var_milt_head_checks) { 2649 cleanup_milter_hbc_finish(state); 2650 myfree(var_milt_head_checks); 2651 var_milt_head_checks = ""; 2652 cleanup_milter_header_checks_deinit(); 2653 } 2654 close_queue_file(state); 2655 } else if (cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply)) { 2656 /* Postfix libmilter would skip further requests. */ 2657 msg_info("ignoring: %s %s %s", argv->argv[0], 2658 argv->argc > 1 ? argv->argv[1] : "", 2659 argv->argc > 2 ? argv->argv[2] : ""); 2660 } else if (strcmp(argv->argv[0], "add_header") == 0) { 2661 if (argv->argc < 2) { 2662 msg_warn("bad add_header argument count: %ld", 2663 (long) argv->argc); 2664 } else { 2665 flatten_args(arg_buf, argv->argv + 2); 2666 resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf)); 2667 } 2668 } else if (strcmp(argv->argv[0], "ins_header") == 0) { 2669 if (argv->argc < 3) { 2670 msg_warn("bad ins_header argument count: %ld", 2671 (long) argv->argc); 2672 } else if ((index = atoi(argv->argv[1])) < 1) { 2673 msg_warn("bad ins_header index value"); 2674 } else { 2675 flatten_args(arg_buf, argv->argv + 3); 2676 resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf)); 2677 } 2678 } else if (strcmp(argv->argv[0], "upd_header") == 0) { 2679 if (argv->argc < 3) { 2680 msg_warn("bad upd_header argument count: %ld", 2681 (long) argv->argc); 2682 } else if ((index = atoi(argv->argv[1])) < 1) { 2683 msg_warn("bad upd_header index value"); 2684 } else { 2685 flatten_args(arg_buf, argv->argv + 3); 2686 resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf)); 2687 } 2688 } else if (strcmp(argv->argv[0], "del_header") == 0) { 2689 if (argv->argc != 3) { 2690 msg_warn("bad del_header argument count: %ld", 2691 (long) argv->argc); 2692 } else if ((index = atoi(argv->argv[1])) < 1) { 2693 msg_warn("bad del_header index value"); 2694 } else { 2695 cleanup_del_header(state, index, argv->argv[2]); 2696 } 2697 } else if (strcmp(argv->argv[0], "chg_from") == 0) { 2698 if (argv->argc != 3) { 2699 msg_warn("bad chg_from argument count: %ld", (long) argv->argc); 2700 } else { 2701 char *arg = argv->argv[2]; 2702 const char *err; 2703 2704 if (*arg == parens[0] 2705 && (err = extpar(&arg, parens, EXTPAR_FLAG_NONE)) != 0) { 2706 msg_warn("%s in \"%s\"", err, arg); 2707 } else { 2708 cleanup_chg_from(state, argv->argv[1], arg); 2709 } 2710 } 2711 } else if (strcmp(argv->argv[0], "add_rcpt") == 0) { 2712 if (argv->argc != 2) { 2713 msg_warn("bad add_rcpt argument count: %ld", (long) argv->argc); 2714 } else { 2715 cleanup_add_rcpt(state, argv->argv[1]); 2716 } 2717 } else if (strcmp(argv->argv[0], "add_rcpt_par") == 0) { 2718 if (argv->argc != 3) { 2719 msg_warn("bad add_rcpt_par argument count: %ld", 2720 (long) argv->argc); 2721 } else { 2722 cleanup_add_rcpt_par(state, argv->argv[1], argv->argv[2]); 2723 } 2724 } else if (strcmp(argv->argv[0], "del_rcpt") == 0) { 2725 if (argv->argc != 2) { 2726 msg_warn("bad del_rcpt argument count: %ld", (long) argv->argc); 2727 } else { 2728 cleanup_del_rcpt(state, argv->argv[1]); 2729 } 2730 } else if (strcmp(argv->argv[0], "replbody") == 0) { 2731 if (argv->argc != 2) { 2732 msg_warn("bad replbody argument count: %ld", (long) argv->argc); 2733 } else { 2734 VSTREAM *fp; 2735 VSTRING *buf; 2736 2737 if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) { 2738 msg_warn("open %s file: %m", argv->argv[1]); 2739 } else { 2740 buf = vstring_alloc(100); 2741 cleanup_repl_body(state, MILTER_BODY_START, 2742 REC_TYPE_NORM, buf); 2743 while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) 2744 cleanup_repl_body(state, MILTER_BODY_LINE, 2745 REC_TYPE_NORM, buf); 2746 cleanup_repl_body(state, MILTER_BODY_END, 2747 REC_TYPE_NORM, buf); 2748 vstring_free(buf); 2749 vstream_fclose(fp); 2750 } 2751 } 2752 } else if (strcmp(argv->argv[0], "header_checks") == 0) { 2753 if (argv->argc != 2) { 2754 msg_warn("bad header_checks argument count: %ld", 2755 (long) argv->argc); 2756 } else if (*var_milt_head_checks) { 2757 msg_warn("can't change header checks"); 2758 } else { 2759 var_milt_head_checks = mystrdup(argv->argv[1]); 2760 cleanup_milter_header_checks_init(); 2761 } 2762 } else if (strcmp(argv->argv[0], "sender_bcc_maps") == 0) { 2763 if (argv->argc != 2) { 2764 msg_warn("bad sender_bcc_maps argument count: %ld", 2765 (long) argv->argc); 2766 } else { 2767 if (cleanup_send_bcc_maps) 2768 maps_free(cleanup_send_bcc_maps); 2769 cleanup_send_bcc_maps = 2770 maps_create("sender_bcc_maps", argv->argv[1], 2771 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX 2772 | DICT_FLAG_UTF8_REQUEST); 2773 state->flags |= CLEANUP_FLAG_BCC_OK; 2774 var_rcpt_delim = ""; 2775 } 2776 } else { 2777 msg_warn("bad command: %s", argv->argv[0]); 2778 } 2779 argv_free(argv); 2780 if (resp) 2781 cleanup_milter_apply(state, "END-OF-MESSAGE", resp); 2782 } 2783 vstring_free(inbuf); 2784 vstring_free(arg_buf); 2785 if (state->append_meta_pt_offset >= 0) { 2786 if (state->flags) 2787 msg_info("flags = %s", cleanup_strflags(state->flags)); 2788 if (state->errs) 2789 msg_info("errs = %s", cleanup_strerror(state->errs)); 2790 } 2791 cleanup_state_free(state); 2792 if (*var_milt_head_checks) 2793 myfree(var_milt_head_checks); 2794 2795 return (0); 2796 } 2797 2798 #endif 2799