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