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