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