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