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