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