7 /* external mail filter support
9 /* #include <cleanup.h>
11 /* void cleanup_milter_receive(state, count)
12 /* CLEANUP_STATE *state;
15 /* void cleanup_milter_inspect(state, milters)
16 /* CLEANUP_STATE *state;
19 /* cleanup_milter_emul_mail(state, milters, sender)
20 /* CLEANUP_STATE *state;
22 /* const char *sender;
24 /* cleanup_milter_emul_rcpt(state, milters, recipient)
25 /* CLEANUP_STATE *state;
27 /* const char *recipient;
29 /* cleanup_milter_emul_data(state, milters)
30 /* CLEANUP_STATE *state;
33 /* This module implements support for Sendmail-style mail
34 /* filter (milter) applications, including in-place queue file
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
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.
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
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.
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.
63 /* milter(3) generic mail filter interface
65 /* Fatal errors: memory allocation problem.
66 /* Panic: interface violation.
67 /* Warnings: I/O errors (state->errs is updated accordingly).
71 /* The Secure Mailer license must be distributed with this software.
74 /* IBM T.J. Watson Research
76 /* Yorktown Heights, NY 10598, USA
82 #include <sys/socket.h> /* AF_INET */
86 #ifdef STRCASECMP_IN_STRINGS_H
90 /* Utility library. */
95 #include <stringops.h>
100 #include <dsn_mask.h>
101 #include <rec_type.h>
102 #include <cleanup_user.h>
104 #include <rec_attr_map.h>
105 #include <mail_proto.h>
106 #include <mail_params.h>
108 #include <is_header.h>
109 #include <quote_821_local.h>
111 /* Application-specific. */
116 * How Postfix 2.4 edits queue file information:
118 * Mail filter applications (Milters) can send modification requests after
119 * receiving the end of the message body. Postfix implements these
120 * modifications in the cleanup server, so that it can edit the queue file
121 * in place. This avoids the temporary files that would be needed when
122 * modifications were implemented in the SMTP server (Postfix normally does
123 * not store the whole message in main memory). Once a Milter is done
124 * editing, the queue file can be used as input for the next Milter, and so
125 * on. Finally, the cleanup server changes file permissions, calls fsync(),
126 * and waits for successful completion.
128 * To implement in-place queue file edits, we need to introduce surprisingly
129 * little change to the existing Postfix queue file structure. All we need
130 * is a way to mark a record as deleted, and to jump from one place in the
131 * queue file to another. We could implement deleted records with jumps, but
132 * marking is sometimes simpler.
134 * Postfix does not store queue files as plain text files. Instead all
135 * information is stored in records with an explicit type and length, for
136 * sender, recipient, arrival time, and so on. Even the content that makes
137 * up the message header and body is stored as records with explicit types
138 * and lengths. This organization makes it very easy to mark a record as
139 * deleted, and to introduce the pointer records that we will use to jump
140 * from one place in a queue file to another place.
142 * - Deleting a recipient is easiest - simply modify the record type into one
143 * that is skipped by the software that delivers mail. We won't try to reuse
144 * the deleted recipient for other purposes. When deleting a recipient, we
145 * may need to delete multiple recipient records that result from virtual
146 * alias expansion of the original recipient address.
148 * - Replacing a header record involves pointer records. A record is replaced
149 * by overwriting it with a forward pointer to space after the end of the
150 * queue file, putting the new record there, followed by a reverse pointer
151 * to the record that follows the replaced header. To simplify
152 * implementation we follow a short header record with a filler record so
153 * that we can always overwrite a header record with a pointer.
155 * N.B. This is a major difference with Postfix version 2.3, which needed
156 * complex code to save records that follow a short header, before it could
157 * overwrite a short header record. This code contained two of the three
158 * post-release bugs that were found with Postfix header editing.
160 * - Inserting a header record is like replacing one, except that we also
161 * relocate the record that is being overwritten by the forward pointer.
163 * - Deleting a message header is simplest when we replace it by a "skip"
164 * pointer to the information that follows the header. With a multi-line
165 * header we need to update only the first line.
167 * - Appending a recipient or header record involves pointer records as well.
168 * To make this convenient, the queue file already contains dummy pointer
169 * records at the locations where we want to append recipient or header
170 * content. To append, change the dummy pointer into a forward pointer to
171 * space after the end of a message, put the new recipient or header record
172 * there, followed by a reverse pointer to the record that follows the
175 * - To append another header or recipient record, replace the reverse pointer
176 * by a forward pointer to space after the end of a message, put the new
177 * record there, followed by the value of the reverse pointer that we
178 * replace. Thus, there is no one-to-one correspondence between forward and
179 * backward pointers. Instead, there can be multiple forward pointers for
180 * one reverse pointer.
182 * - When a mail filter wants to replace an entire body, we overwrite existing
183 * body records until we run out of space, and then write a pointer to space
184 * after the end of the queue file, followed by more body content. There may
185 * be multiple regions with body content; regions are connected by forward
186 * pointers, and the last region ends with a pointer to the marker that ends
187 * the message content segment. Body regions can be large and therefore they
188 * are reused to avoid wasting space. Sendmail mail filters currently do not
189 * replace individual body records, and that is a good thing.
191 * Making queue file modifications safe:
193 * Postfix queue files are segmented. The first segment is for envelope
194 * records, the second for message header and body content, and the third
195 * segment is for information that was extracted or generated from the
196 * message header or body content. Each segment is terminated by a marker
197 * record. For now we don't want to change their location. That is, we want
198 * to avoid moving the records that mark the start or end of a queue file
201 * To ensure that we can always replace a header or body record by a pointer
202 * record, without having to relocate a marker record, the cleanup server
203 * places a dummy pointer record at the end of the recipients and at the end
204 * of the message header. To support message body modifications, a dummy
205 * pointer record is also placed at the end of the message content.
207 * With all these changes in queue file organization, REC_TYPE_END is no longer
208 * guaranteed to be the last record in a queue file. If an application were
209 * to read beyond the REC_TYPE_END marker, it would go into an infinite
210 * loop, because records after REC_TYPE_END alternate with reverse pointers
211 * to the middle of the queue file. For robustness, the record reading
212 * routine skips forward to the end-of-file position after reading the
213 * REC_TYPE_END marker.
216 /*#define msg_verbose 2*/
218 #define STR(x) vstring_str(x)
219 #define LEN(x) VSTRING_LEN(x)
224 #define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \
225 if ((__state)->reason) \
226 myfree((__state)->reason); \
227 (__state)->reason = mystrdup(__reason); \
228 if ((__state)->smtp_reply) { \
229 myfree((__state)->smtp_reply); \
230 (__state)->smtp_reply = 0; \
234 #define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \
235 if ((__state)->reason) \
236 myfree((__state)->reason); \
237 (__state)->reason = mystrdup(__smtp_reply + 4); \
238 printable((__state)->reason, '_'); \
239 if ((__state)->smtp_reply) \
240 myfree((__state)->smtp_reply); \
241 (__state)->smtp_reply = mystrdup(__smtp_reply); \
244 /* cleanup_milter_set_error - set error flag from errno */
246 static void cleanup_milter_set_error(CLEANUP_STATE
*state
, int err
)
249 msg_warn("%s: queue file size limit exceeded", state
->queue_id
);
250 state
->errs
|= CLEANUP_STAT_SIZE
;
252 msg_warn("%s: write queue file: %m", state
->queue_id
);
253 state
->errs
|= CLEANUP_STAT_WRITE
;
257 /* cleanup_milter_error - return dummy error description */
259 static const char *cleanup_milter_error(CLEANUP_STATE
*state
, int err
)
261 const char *myname
= "cleanup_milter_error";
262 const CLEANUP_STAT_DETAIL
*dp
;
265 * For consistency with error reporting within the milter infrastructure,
266 * content manipulation routines return a null pointer on success, and an
267 * SMTP-like response on error.
269 * However, when cleanup_milter_apply() receives this error response from
270 * the milter infrastructure, it ignores the text since the appropriate
271 * cleanup error flags were already set by cleanup_milter_set_error().
273 * Specify a null error number when the "errno to error flag" mapping was
274 * already done elsewhere, possibly outside this module.
277 cleanup_milter_set_error(state
, err
);
278 else if (CLEANUP_OUT_OK(state
))
279 msg_panic("%s: missing errno to error flag mapping", myname
);
280 if (state
->milter_err_text
== 0)
281 state
->milter_err_text
= vstring_alloc(50);
282 dp
= cleanup_stat_detail(state
->errs
);
283 return (STR(vstring_sprintf(state
->milter_err_text
,
284 "%d %s %s", dp
->smtp
, dp
->dsn
, dp
->text
)));
287 /* cleanup_add_header - append message header */
289 static const char *cleanup_add_header(void *context
, const char *name
,
293 const char *myname
= "cleanup_add_header";
294 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
296 off_t reverse_ptr_offset
;
297 off_t new_hdr_offset
;
300 * To simplify implementation, the cleanup server writes a dummy "header
301 * append" pointer record after the last message header. We cache both
302 * the location and the target of the current "header append" pointer
305 if (state
->append_hdr_pt_offset
< 0)
306 msg_panic("%s: no header append pointer location", myname
);
307 if (state
->append_hdr_pt_target
< 0)
308 msg_panic("%s: no header append pointer target", myname
);
311 * Allocate space after the end of the queue file, and write the header
312 * record(s), followed by a reverse pointer record that points to the
313 * target of the old "header append" pointer record. This reverse pointer
314 * record becomes the new "header append" pointer record.
316 if ((new_hdr_offset
= vstream_fseek(state
->dst
, (off_t
) 0, SEEK_END
)) < 0) {
317 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
318 return (cleanup_milter_error(state
, errno
));
320 buf
= vstring_alloc(100);
321 vstring_sprintf(buf
, "%s:%s%s", name
, space
, value
);
322 cleanup_out_header(state
, buf
); /* Includes padding */
324 if ((reverse_ptr_offset
= vstream_ftell(state
->dst
)) < 0) {
325 msg_warn("%s: vstream_ftell file %s: %m", myname
, cleanup_path
);
326 return (cleanup_milter_error(state
, errno
));
328 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
329 (long) state
->append_hdr_pt_target
);
332 * Pointer flipping: update the old "header append" pointer record value
333 * with the location of the new header record.
335 * XXX To avoid unnecessary seek operations when the new header immediately
336 * follows the old append header pointer, write a null pointer or make
337 * the record reading loop smarter. Making vstream_fseek() smarter does
338 * not help, because it doesn't know if we're going to read or write
339 * after a write+seek sequence.
341 if (vstream_fseek(state
->dst
, state
->append_hdr_pt_offset
, SEEK_SET
) < 0) {
342 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
343 return (cleanup_milter_error(state
, errno
));
345 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
346 (long) new_hdr_offset
);
349 * Update the in-memory "header append" pointer record location with the
350 * location of the reverse pointer record that follows the new header.
351 * The target of the "header append" pointer record does not change; it's
352 * always the record that follows the dummy pointer record that was
353 * written while Postfix received the message.
355 state
->append_hdr_pt_offset
= reverse_ptr_offset
;
358 * In case of error while doing record output.
360 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, 0));
363 /* cleanup_find_header_start - find specific header instance */
365 static off_t
cleanup_find_header_start(CLEANUP_STATE
*state
, ssize_t index
,
366 const char *header_label
,
369 int allow_ptr_backup
,
372 const char *myname
= "cleanup_find_header_start";
373 off_t curr_offset
; /* offset after found record */
374 off_t ptr_offset
; /* pointer to found record */
375 VSTRING
*ptr_buf
= 0;
376 int rec_type
= REC_TYPE_ERROR
;
382 msg_info("%s: index %ld name \"%s\"",
383 myname
, (long) index
, header_label
? header_label
: "(none)");
389 msg_panic("%s: bad header index %ld", myname
, (long) index
);
392 * Skip to the start of the message content, and read records until we
393 * either find the specified header, or until we hit the end of the
396 * The index specifies the header instance: 1 is the first one. The header
397 * label specifies the header name. A null pointer matches any header.
399 * When the specified header is not found, the result value is -1.
401 * When the specified header is found, its first record is stored in the
402 * caller-provided read buffer, and the result value is the queue file
403 * offset of that record. The file read position is left at the start of
404 * the next (non-filler) queue file record, which can be the remainder of
405 * a multi-record header.
407 * When a header is found and allow_ptr_backup is non-zero, then the result
408 * is either the first record of that header, or it is the pointer record
409 * that points to the first record of that header. In the latter case,
410 * the file read position is undefined. Returning the pointer allows us
411 * to do some optimizations when inserting text multiple times at the
414 * XXX We can't use the MIME processor here. It not only buffers up the
415 * input, it also reads the record that follows a complete header before
416 * it invokes the header call-back action. This complicates the way that
417 * we discover header offsets and boundaries. Worse is that the MIME
418 * processor is unaware that multi-record message headers can have PTR
419 * records in the middle.
421 * XXX The draw-back of not using the MIME processor is that we have to
422 * duplicate some of its logic here and in the routine that finds the end
423 * of the header record. To minimize the duplication we define an ugly
424 * macro that is used in all code that scans for header boundaries.
426 * XXX Sendmail compatibility (based on Sendmail 8.13.6 measurements).
428 * - When changing Received: header #1, we change the Received: header that
429 * follows our own one; a request to change Received: header #0 is
430 * silently treated as a request to change Received: header #1.
432 * - When changing Date: header #1, we change the first Date: header; a
433 * request to change Date: header #0 is silently treated as a request to
434 * change Date: header #1.
436 * Thus, header change requests are relative to the content as received,
437 * that is, the content after our own Received: header. They can affect
438 * only the headers that the MTA actually exposes to mail filter
441 * - However, when inserting a header at position 0, the new header appears
442 * before our own Received: header, and when inserting at position 1, the
443 * new header appears after our own Received: header.
445 * Thus, header insert operations are relative to the content as delivered,
446 * that is, the content including our own Received: header.
448 * None of the above is applicable after a Milter inserts a header before
449 * our own Received: header. From then on, our own Received: header
450 * becomes just like other headers.
452 #define CLEANUP_FIND_HEADER_NOTFOUND (-1)
453 #define CLEANUP_FIND_HEADER_IOERROR (-2)
455 #define CLEANUP_FIND_HEADER_RETURN(offs) do { \
457 vstring_free(ptr_buf); \
461 #define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \
462 if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \
463 msg_warn("%s: read file %s: %m", myname, cleanup_path); \
464 cleanup_milter_set_error(state, errno); \
465 do { quit; } while (0); \
467 if (msg_verbose > 1) \
468 msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \
469 LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \
470 if (rec_type == REC_TYPE_DTXT) \
472 if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
473 && rec_type != REC_TYPE_PTR) \
475 /* End of hairy macros. */
477 if (vstream_fseek(state
->dst
, state
->data_offset
, SEEK_SET
) < 0) {
478 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
479 cleanup_milter_set_error(state
, errno
);
480 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR
);
482 for (ptr_offset
= 0, last_type
= 0; /* void */ ; /* void */ ) {
483 if ((curr_offset
= vstream_ftell(state
->dst
)) < 0) {
484 msg_warn("%s: vstream_ftell file %s: %m", myname
, cleanup_path
);
485 cleanup_milter_set_error(state
, errno
);
486 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR
);
488 /* Don't follow the "append header" pointer. */
489 if (curr_offset
== state
->append_hdr_pt_offset
)
491 /* Caution: this macro terminates the loop at end-of-message. */
492 /* Don't do complex processing while breaking out of this loop. */
493 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type
, state
, buf
, curr_offset
,
494 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR
));
495 /* Caution: don't assume ptr->header. This may be header-ptr->body. */
496 if (rec_type
== REC_TYPE_PTR
) {
497 if (rec_goto(state
->dst
, STR(buf
)) < 0) {
498 msg_warn("%s: read file %s: %m", myname
, cleanup_path
);
499 cleanup_milter_set_error(state
, errno
);
500 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR
);
502 /* Save PTR record, in case it points to the start of a header. */
503 if (allow_ptr_backup
) {
504 ptr_offset
= curr_offset
;
506 ptr_buf
= vstring_alloc(100);
507 vstring_strcpy(ptr_buf
, STR(buf
));
509 /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */
512 /* The middle of a multi-record header. */
513 else if (last_type
== REC_TYPE_CONT
|| IS_SPACE_TAB(STR(buf
)[0])) {
514 /* Reset the saved PTR record and update last_type. */
516 /* No more message headers. */
517 else if ((len
= is_header(STR(buf
))) == 0) {
520 /* This the start of a message header. */
521 else if (hdr_count
++ < skip_headers
)
522 /* Reset the saved PTR record and update last_type. */ ;
523 else if ((header_label
== 0
524 || (strncasecmp(header_label
, STR(buf
), len
) == 0
525 && (IS_SPACE_TAB(STR(buf
)[len
])
526 || STR(buf
)[len
] == ':')))
528 /* If we have a saved PTR record, it points to start of header. */
532 last_type
= rec_type
;
536 * In case of failure, return negative start position.
539 curr_offset
= CLEANUP_FIND_HEADER_NOTFOUND
;
543 * Skip over short-header padding, so that the file read pointer is
544 * always positioned at the first non-padding record after the header
545 * record. Insist on padding after short a header record, so that a
546 * short header record can safely be overwritten by a pointer record.
548 if (LEN(buf
) < REC_TYPE_PTR_PAYL_SIZE
) {
549 VSTRING
*rbuf
= (ptr_offset
? buf
:
551 (ptr_buf
= vstring_alloc(100))));
554 if ((rval
= rec_get_raw(state
->dst
, rbuf
, 0, REC_FLAG_NONE
)) < 0) {
555 cleanup_milter_set_error(state
, errno
);
556 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR
);
558 if (rval
!= REC_TYPE_DTXT
)
559 msg_panic("%s: short header without padding", myname
);
563 * Optionally return a pointer to the message header, instead of the
564 * start of the message header itself. In that case the file read
565 * position is undefined (actually it is at the first non-padding
566 * record that follows the message header record).
568 if (ptr_offset
!= 0) {
569 rec_type
= REC_TYPE_PTR
;
570 curr_offset
= ptr_offset
;
571 vstring_strcpy(buf
, STR(ptr_buf
));
573 *prec_type
= rec_type
;
576 msg_info("%s: index %ld name %s type %d offset %ld",
577 myname
, (long) index
, header_label
?
578 header_label
: "(none)", rec_type
, (long) curr_offset
);
580 CLEANUP_FIND_HEADER_RETURN(curr_offset
);
583 /* cleanup_find_header_end - find end of header */
585 static off_t
cleanup_find_header_end(CLEANUP_STATE
*state
,
589 const char *myname
= "cleanup_find_header_end";
594 * This routine is called immediately after cleanup_find_header_start().
595 * rec_buf is the cleanup_find_header_start() result record; last_type is
596 * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file
597 * read position is at the first non-padding record after the result
601 if ((read_offset
= vstream_ftell(state
->dst
)) < 0) {
602 msg_warn("%s: read file %s: %m", myname
, cleanup_path
);
603 cleanup_milter_error(state
, errno
);
606 /* Don't follow the "append header" pointer. */
607 if (read_offset
== state
->append_hdr_pt_offset
)
609 /* Caution: this macro terminates the loop at end-of-message. */
610 /* Don't do complex processing while breaking out of this loop. */
611 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type
, state
, rec_buf
, read_offset
,
612 /* Warning and errno->error mapping are done elsewhere. */
614 if (rec_type
== REC_TYPE_PTR
) {
615 if (rec_goto(state
->dst
, STR(rec_buf
)) < 0) {
616 msg_warn("%s: read file %s: %m", myname
, cleanup_path
);
617 cleanup_milter_error(state
, errno
);
620 /* Don't update last_type; PTR may follow REC_TYPE_CONT. */
623 /* Start of header or message body. */
624 if (last_type
!= REC_TYPE_CONT
&& !IS_SPACE_TAB(STR(rec_buf
)[0]))
626 last_type
= rec_type
;
628 return (read_offset
);
631 /* cleanup_patch_header - patch new header into an existing header */
633 static const char *cleanup_patch_header(CLEANUP_STATE
*state
,
634 const char *new_hdr_name
,
635 const char *hdr_space
,
636 const char *new_hdr_value
,
637 off_t old_rec_offset
,
639 VSTRING
*old_rec_buf
,
642 const char *myname
= "cleanup_patch_header";
643 VSTRING
*buf
= vstring_alloc(100);
644 off_t new_hdr_offset
;
646 #define CLEANUP_PATCH_HEADER_RETURN(ret) do { \
652 msg_info("%s: \"%s\" \"%s\" at %ld",
653 myname
, new_hdr_name
, new_hdr_value
, (long) old_rec_offset
);
656 * Allocate space after the end of the queue file for the new header and
657 * optionally save an existing record to make room for a forward pointer
658 * record. If the saved record was not a PTR record, follow the saved
659 * record by a reverse pointer record that points to the record after the
660 * original location of the saved record.
662 * We update the queue file in a safe manner: save the new header and the
663 * existing records after the end of the queue file, write the reverse
664 * pointer, and only then overwrite the saved records with the forward
665 * pointer to the new header.
667 * old_rec_offset, old_rec_type, and old_rec_buf specify the record that we
668 * are about to overwrite with a pointer record. If the record needs to
669 * be saved (i.e. old_rec_type > 0), the buffer contains the data content
670 * of exactly one PTR or text record.
672 * next_offset specifies the record that follows the to-be-overwritten
673 * record. It is ignored when the to-be-saved record is a pointer record.
677 * Write the new header to a new location after the end of the queue
680 if ((new_hdr_offset
= vstream_fseek(state
->dst
, (off_t
) 0, SEEK_END
)) < 0) {
681 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
682 CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state
, errno
));
684 vstring_sprintf(buf
, "%s:%s%s", new_hdr_name
, hdr_space
, new_hdr_value
);
685 cleanup_out_header(state
, buf
); /* Includes padding */
687 msg_info("%s: %ld: write %.*s", myname
, (long) new_hdr_offset
,
688 LEN(buf
) > 30 ? 30 : (int) LEN(buf
), STR(buf
));
691 * Optionally, save the existing text record or pointer record that will
692 * be overwritten with the forward pointer. Pad a short saved record to
693 * ensure that it, too, can be overwritten by a pointer.
695 if (old_rec_type
> 0) {
696 CLEANUP_OUT_BUF(state
, old_rec_type
, old_rec_buf
);
697 if (LEN(old_rec_buf
) < REC_TYPE_PTR_PAYL_SIZE
)
698 rec_pad(state
->dst
, REC_TYPE_DTXT
,
699 REC_TYPE_PTR_PAYL_SIZE
- LEN(old_rec_buf
));
701 msg_info("%s: write %.*s", myname
, LEN(old_rec_buf
) > 30 ?
702 30 : (int) LEN(old_rec_buf
), STR(old_rec_buf
));
706 * If the saved record wasn't a PTR record, write the reverse pointer
707 * after the saved records. A reverse pointer value of -1 means we were
708 * confused about what we were going to save.
710 if (old_rec_type
!= REC_TYPE_PTR
) {
712 msg_panic("%s: bad reverse pointer %ld",
713 myname
, (long) next_offset
);
714 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
717 msg_info("%s: write PTR %ld", myname
, (long) next_offset
);
721 * Write the forward pointer over the old record. Generally, a pointer
722 * record will be shorter than a header record, so there will be a gap in
723 * the queue file before the next record. In other words, we must always
724 * follow pointer records otherwise we get out of sync with the data.
726 if (vstream_fseek(state
->dst
, old_rec_offset
, SEEK_SET
) < 0) {
727 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
728 CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state
, errno
));
730 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
731 (long) new_hdr_offset
);
733 msg_info("%s: %ld: write PTR %ld", myname
, (long) old_rec_offset
,
734 (long) new_hdr_offset
);
737 * In case of error while doing record output.
739 CLEANUP_PATCH_HEADER_RETURN(CLEANUP_OUT_OK(state
) ? 0 :
740 cleanup_milter_error(state
, 0));
743 * Note: state->append_hdr_pt_target never changes.
747 /* cleanup_ins_header - insert message header */
749 static const char *cleanup_ins_header(void *context
, ssize_t index
,
750 const char *new_hdr_name
,
751 const char *hdr_space
,
752 const char *new_hdr_value
)
754 const char *myname
= "cleanup_ins_header";
755 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
756 VSTRING
*old_rec_buf
= vstring_alloc(100);
757 off_t old_rec_offset
;
762 #define CLEANUP_INS_HEADER_RETURN(ret) do { \
763 vstring_free(old_rec_buf); \
768 msg_info("%s: %ld \"%s\" \"%s\"",
769 myname
, (long) index
, new_hdr_name
, new_hdr_value
);
772 * Look for a header at the specified position.
774 * The lookup result may be a pointer record. This allows us to make some
775 * optimization when multiple insert operations happen in the same place.
777 * Index 1 is the top-most header.
779 #define NO_HEADER_NAME ((char *) 0)
780 #define ALLOW_PTR_BACKUP 1
781 #define SKIP_ONE_HEADER 1
782 #define DONT_SKIP_HEADERS 0
786 old_rec_offset
= cleanup_find_header_start(state
, index
, NO_HEADER_NAME
,
787 old_rec_buf
, &old_rec_type
,
790 if (old_rec_offset
== CLEANUP_FIND_HEADER_IOERROR
)
791 /* Warning and errno->error mapping are done elsewhere. */
792 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state
, 0));
795 * If the header does not exist, simply append the header to the linked
796 * list at the "header append" pointer record.
798 if (old_rec_offset
< 0)
799 CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context
, new_hdr_name
,
800 hdr_space
, new_hdr_value
));
803 * If the header does exist, save both the new and the existing header to
804 * new storage at the end of the queue file, and link the new storage
805 * with a forward and reverse pointer (don't write a reverse pointer if
806 * we are starting with a pointer record).
808 if (old_rec_type
== REC_TYPE_PTR
) {
811 if ((next_offset
= vstream_ftell(state
->dst
)) < 0) {
812 msg_warn("%s: read file %s: %m", myname
, cleanup_path
);
813 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state
, errno
));
816 ret
= cleanup_patch_header(state
, new_hdr_name
, hdr_space
, new_hdr_value
,
817 old_rec_offset
, old_rec_type
,
818 old_rec_buf
, next_offset
);
819 CLEANUP_INS_HEADER_RETURN(ret
);
822 /* cleanup_upd_header - modify or append message header */
824 static const char *cleanup_upd_header(void *context
, ssize_t index
,
825 const char *new_hdr_name
,
826 const char *hdr_space
,
827 const char *new_hdr_value
)
829 const char *myname
= "cleanup_upd_header";
830 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
832 off_t old_rec_offset
;
838 msg_info("%s: %ld \"%s\" \"%s\"",
839 myname
, (long) index
, new_hdr_name
, new_hdr_value
);
844 if (*new_hdr_name
== 0)
845 msg_panic("%s: null header name", myname
);
848 * Find the header that is being modified.
850 * The lookup result will never be a pointer record.
852 * Index 1 is the first matching header instance.
854 * XXX When a header is updated repeatedly we create jumps to jumps. To
855 * eliminate this, rewrite the loop below so that we can start with the
856 * pointer record that points to the header that's being edited.
858 #define DONT_SAVE_RECORD 0
859 #define NO_PTR_BACKUP 0
861 #define CLEANUP_UPD_HEADER_RETURN(ret) do { \
862 vstring_free(rec_buf); \
866 rec_buf
= vstring_alloc(100);
867 old_rec_offset
= cleanup_find_header_start(state
, index
, new_hdr_name
,
871 if (old_rec_offset
== CLEANUP_FIND_HEADER_IOERROR
)
872 /* Warning and errno->error mapping are done elsewhere. */
873 CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state
, 0));
876 * If no old header is found, simply append the new header to the linked
877 * list at the "header append" pointer record.
879 if (old_rec_offset
< 0)
880 CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context
, new_hdr_name
,
881 hdr_space
, new_hdr_value
));
884 * If the old header is found, find the end of the old header, save the
885 * new header to new storage at the end of the queue file, and link the
886 * new storage with a forward and reverse pointer.
888 if ((next_offset
= cleanup_find_header_end(state
, rec_buf
, last_type
)) < 0)
889 /* Warning and errno->error mapping are done elsewhere. */
890 CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state
, 0));
891 ret
= cleanup_patch_header(state
, new_hdr_name
, hdr_space
, new_hdr_value
,
892 old_rec_offset
, DONT_SAVE_RECORD
,
893 (VSTRING
*) 0, next_offset
);
894 CLEANUP_UPD_HEADER_RETURN(ret
);
897 /* cleanup_del_header - delete message header */
899 static const char *cleanup_del_header(void *context
, ssize_t index
,
900 const char *hdr_name
)
902 const char *myname
= "cleanup_del_header";
903 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
910 msg_info("%s: %ld \"%s\"", myname
, (long) index
, hdr_name
);
916 msg_panic("%s: null header name", myname
);
919 * Find the header that is being deleted.
921 * The lookup result will never be a pointer record.
923 * Index 1 is the first matching header instance.
925 #define CLEANUP_DEL_HEADER_RETURN(ret) do { \
926 vstring_free(rec_buf); \
930 rec_buf
= vstring_alloc(100);
931 header_offset
= cleanup_find_header_start(state
, index
, hdr_name
, rec_buf
,
932 &last_type
, NO_PTR_BACKUP
,
934 if (header_offset
== CLEANUP_FIND_HEADER_IOERROR
)
935 /* Warning and errno->error mapping are done elsewhere. */
936 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state
, 0));
939 * Overwrite the beginning of the header record with a pointer to the
940 * information that follows the header. We can't simply overwrite the
941 * header with cleanup_out_header() and a special record type, because
942 * there may be a PTR record in the middle of a multi-line header.
944 if (header_offset
> 0) {
945 if ((next_offset
= cleanup_find_header_end(state
, rec_buf
, last_type
)) < 0)
946 /* Warning and errno->error mapping are done elsewhere. */
947 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state
, 0));
948 /* Mark the header as deleted. */
949 if (vstream_fseek(state
->dst
, header_offset
, SEEK_SET
) < 0) {
950 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
951 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state
, errno
));
953 rec_fprintf(state
->dst
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
956 vstring_free(rec_buf
);
959 * In case of error while doing record output.
961 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, 0));
964 /* cleanup_chg_from - replace sender address, ignore ESMTP arguments */
966 static const char *cleanup_chg_from(void *context
, const char *ext_from
,
967 const char *esmtp_args
)
969 const char *myname
= "cleanup_chg_from";
970 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
971 off_t new_sender_offset
;
975 VSTRING
*int_sender_buf
;
978 msg_info("%s: \"%s\" \"%s\"", myname
, ext_from
, esmtp_args
);
981 msg_warn("%s: %s: ignoring ESMTP arguments \"%.100s\"",
982 state
->queue_id
, myname
, esmtp_args
);
985 * The cleanup server remembers the location of the the original sender
986 * address record (offset in sender_pt_offset) and the file offset of the
987 * record that follows the sender address (offset in sender_pt_target).
988 * Short original sender records are padded, so that they can safely be
989 * overwritten with a pointer record to the new sender address record.
991 if (state
->sender_pt_offset
< 0)
992 msg_panic("%s: no original sender record offset", myname
);
993 if (state
->sender_pt_target
< 0)
994 msg_panic("%s: no post-sender record offset", myname
);
997 * Allocate space after the end of the queue file, and write the new
998 * sender record, followed by a reverse pointer record that points to the
999 * record that follows the original sender address record. No padding is
1000 * needed for a "new" short sender record, since the record is not meant
1001 * to be overwritten. When the "new" sender is replaced, we allocate a
1002 * new record at the end of the queue file.
1004 * We update the queue file in a safe manner: save the new sender after the
1005 * end of the queue file, write the reverse pointer, and only then
1006 * overwrite the old sender record with the forward pointer to the new
1009 if ((new_sender_offset
= vstream_fseek(state
->dst
, (off_t
) 0, SEEK_END
)) < 0) {
1010 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1011 return (cleanup_milter_error(state
, errno
));
1015 * Transform the address from external form to internal form. This also
1016 * removes the enclosing <>, if present.
1018 * XXX vstring_alloc() rejects zero-length requests.
1020 int_sender_buf
= vstring_alloc(strlen(ext_from
) + 1);
1021 tree
= tok822_parse(ext_from
);
1022 for (addr_count
= 0, tp
= tree
; tp
!= 0; tp
= tp
->next
) {
1023 if (tp
->type
== TOK822_ADDR
) {
1024 if (addr_count
== 0) {
1025 tok822_internalize(int_sender_buf
, tp
->head
, TOK822_STR_DEFL
);
1028 msg_warn("%s: Milter request to add multi-sender: \"%s\"",
1029 state
->queue_id
, ext_from
);
1034 tok822_free_tree(tree
);
1035 cleanup_addr_sender(state
, STR(int_sender_buf
));
1036 vstring_free(int_sender_buf
);
1037 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
1038 (long) state
->sender_pt_target
);
1041 * Overwrite the original sender record with the pointer to the new
1042 * sender address record.
1044 if (vstream_fseek(state
->dst
, state
->sender_pt_offset
, SEEK_SET
) < 0) {
1045 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1046 return (cleanup_milter_error(state
, errno
));
1048 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
1049 (long) new_sender_offset
);
1052 * In case of error while doing record output.
1054 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, 0));
1057 /* cleanup_add_rcpt - append recipient address */
1059 static const char *cleanup_add_rcpt(void *context
, const char *ext_rcpt
)
1061 const char *myname
= "cleanup_add_rcpt";
1062 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
1063 off_t new_rcpt_offset
;
1064 off_t reverse_ptr_offset
;
1068 VSTRING
*int_rcpt_buf
;
1071 msg_info("%s: \"%s\"", myname
, ext_rcpt
);
1074 * To simplify implementation, the cleanup server writes a dummy
1075 * "recipient append" pointer record after the last recipient. We cache
1076 * both the location and the target of the current "recipient append"
1079 if (state
->append_rcpt_pt_offset
< 0)
1080 msg_panic("%s: no recipient append pointer location", myname
);
1081 if (state
->append_rcpt_pt_target
< 0)
1082 msg_panic("%s: no recipient append pointer target", myname
);
1085 * Allocate space after the end of the queue file, and write the
1086 * recipient record, followed by a reverse pointer record that points to
1087 * the target of the old "recipient append" pointer record. This reverse
1088 * pointer record becomes the new "recipient append" pointer record.
1090 * We update the queue file in a safe manner: save the new recipient after
1091 * the end of the queue file, write the reverse pointer, and only then
1092 * overwrite the old "recipient append" pointer with the forward pointer
1093 * to the new recipient.
1095 #define NO_DSN_ORCPT ((char *) 0)
1097 if ((new_rcpt_offset
= vstream_fseek(state
->dst
, (off_t
) 0, SEEK_END
)) < 0) {
1098 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1099 return (cleanup_milter_error(state
, errno
));
1103 * Transform recipient from external form to internal form. This also
1104 * removes the enclosing <>, if present.
1106 * XXX vstring_alloc() rejects zero-length requests.
1108 int_rcpt_buf
= vstring_alloc(strlen(ext_rcpt
) + 1);
1109 tree
= tok822_parse(ext_rcpt
);
1110 for (addr_count
= 0, tp
= tree
; tp
!= 0; tp
= tp
->next
) {
1111 if (tp
->type
== TOK822_ADDR
) {
1112 if (addr_count
== 0) {
1113 tok822_internalize(int_rcpt_buf
, tp
->head
, TOK822_STR_DEFL
);
1116 msg_warn("%s: Milter request to add multi-recipient: \"%s\"",
1117 state
->queue_id
, ext_rcpt
);
1122 tok822_free_tree(tree
);
1123 cleanup_addr_bcc(state
, STR(int_rcpt_buf
));
1124 vstring_free(int_rcpt_buf
);
1125 if (addr_count
== 0) {
1126 msg_warn("%s: ignoring attempt from Milter to add null recipient",
1128 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, 0));
1130 if ((reverse_ptr_offset
= vstream_ftell(state
->dst
)) < 0) {
1131 msg_warn("%s: vstream_ftell file %s: %m", myname
, cleanup_path
);
1132 return (cleanup_milter_error(state
, errno
));
1134 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
1135 (long) state
->append_rcpt_pt_target
);
1138 * Pointer flipping: update the old "recipient append" pointer record
1139 * value to the location of the new recipient record.
1141 if (vstream_fseek(state
->dst
, state
->append_rcpt_pt_offset
, SEEK_SET
) < 0) {
1142 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1143 return (cleanup_milter_error(state
, errno
));
1145 cleanup_out_format(state
, REC_TYPE_PTR
, REC_TYPE_PTR_FORMAT
,
1146 (long) new_rcpt_offset
);
1149 * Update the in-memory "recipient append" pointer record location with
1150 * the location of the reverse pointer record that follows the new
1151 * recipient. The target of the "recipient append" pointer record does
1152 * not change; it's always the record that follows the dummy pointer
1153 * record that was written while Postfix received the message.
1155 state
->append_rcpt_pt_offset
= reverse_ptr_offset
;
1158 * In case of error while doing record output.
1160 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, 0));
1163 /* cleanup_add_rcpt_par - append recipient address, ignore ESMTP arguments */
1165 static const char *cleanup_add_rcpt_par(void *context
, const char *ext_rcpt
,
1166 const char *esmtp_args
)
1168 const char *myname
= "cleanup_add_rcpt";
1169 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
1172 msg_warn("%s: %s: ignoring ESMTP arguments \"%.100s\"",
1173 state
->queue_id
, myname
, esmtp_args
);
1174 return (cleanup_add_rcpt(context
, ext_rcpt
));
1177 /* cleanup_del_rcpt - remove recipient and all its expansions */
1179 static const char *cleanup_del_rcpt(void *context
, const char *ext_rcpt
)
1181 const char *myname
= "cleanup_del_rcpt";
1182 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
1187 char *dsn_orcpt
= 0; /* XXX for dup filter cleanup */
1188 int dsn_notify
= 0; /* XXX for dup filter cleanup */
1189 char *orig_rcpt
= 0;
1196 VSTRING
*int_rcpt_buf
;
1200 msg_info("%s: \"%s\"", myname
, ext_rcpt
);
1203 * Virtual aliasing and other address rewriting happens after the mail
1204 * filter sees the envelope address. Therefore we must delete all
1205 * recipient records whose Postfix (not DSN) original recipient address
1206 * matches the specified address.
1208 * As the number of recipients may be very large we can't do an efficient
1209 * two-pass implementation (collect record offsets first, then mark
1210 * records as deleted). Instead we mark records as soon as we find them.
1211 * This is less efficient because we do (seek-write-read) for each marked
1212 * recipient, instead of (seek-write). It's unlikely that VSTREAMs will
1213 * be made smart enough to eliminate unnecessary I/O with small seeks.
1215 * XXX When Postfix original recipients are turned off, we have no option
1216 * but to match against the expanded and rewritten recipient address.
1218 * XXX Remove the (dsn_orcpt, dsn_notify, orcpt, recip) tuple from the
1219 * duplicate recipient filter. This requires that we maintain reference
1222 if (vstream_fseek(state
->dst
, 0L, SEEK_SET
) < 0) {
1223 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1224 return (cleanup_milter_error(state
, errno
));
1226 #define CLEANUP_DEL_RCPT_RETURN(ret) do { \
1227 if (orig_rcpt != 0) \
1228 myfree(orig_rcpt); \
1229 if (dsn_orcpt != 0) \
1230 myfree(dsn_orcpt); \
1231 vstring_free(buf); \
1232 vstring_free(int_rcpt_buf); \
1237 * Transform recipient from external form to internal form. This also
1238 * removes the enclosing <>, if present.
1240 * XXX vstring_alloc() rejects zero-length requests.
1242 int_rcpt_buf
= vstring_alloc(strlen(ext_rcpt
) + 1);
1243 tree
= tok822_parse(ext_rcpt
);
1244 for (addr_count
= 0, tp
= tree
; tp
!= 0; tp
= tp
->next
) {
1245 if (tp
->type
== TOK822_ADDR
) {
1246 if (addr_count
== 0) {
1247 tok822_internalize(int_rcpt_buf
, tp
->head
, TOK822_STR_DEFL
);
1250 msg_warn("%s: Milter request to drop multi-recipient: \"%s\"",
1251 state
->queue_id
, ext_rcpt
);
1256 tok822_free_tree(tree
);
1258 buf
= vstring_alloc(100);
1260 if (CLEANUP_OUT_OK(state
) == 0)
1261 /* Warning and errno->error mapping are done elsewhere. */
1262 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, 0));
1263 if ((curr_offset
= vstream_ftell(state
->dst
)) < 0) {
1264 msg_warn("%s: vstream_ftell file %s: %m", myname
, cleanup_path
);
1265 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1267 if ((rec_type
= rec_get_raw(state
->dst
, buf
, 0, REC_FLAG_NONE
)) <= 0) {
1268 msg_warn("%s: read file %s: %m", myname
, cleanup_path
);
1269 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1271 if (rec_type
== REC_TYPE_END
)
1273 /* Skip over message content. */
1274 if (rec_type
== REC_TYPE_MESG
) {
1275 if (vstream_fseek(state
->dst
, state
->xtra_offset
, SEEK_SET
) < 0) {
1276 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1277 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1282 if (rec_type
== REC_TYPE_PTR
) {
1283 if (rec_goto(state
->dst
, start
) < 0) {
1284 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1285 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1289 /* Map attribute names to pseudo record type. */
1290 if (rec_type
== REC_TYPE_ATTR
) {
1291 if (split_nameval(STR(buf
), &attr_name
, &attr_value
) != 0
1292 || *attr_value
== 0)
1294 if ((junk
= rec_attr_map(attr_name
)) != 0) {
1300 case REC_TYPE_DSN_ORCPT
: /* RCPT TO ORCPT parameter */
1301 if (dsn_orcpt
!= 0) /* can't happen */
1303 dsn_orcpt
= mystrdup(start
);
1305 case REC_TYPE_DSN_NOTIFY
: /* RCPT TO NOTIFY parameter */
1306 if (alldig(start
) && (junk
= atoi(start
)) > 0
1307 && DSN_NOTIFY_OK(junk
))
1312 case REC_TYPE_ORCP
: /* unmodified RCPT TO address */
1313 if (orig_rcpt
!= 0) /* can't happen */
1315 orig_rcpt
= mystrdup(start
);
1317 case REC_TYPE_RCPT
: /* rewritten RCPT TO address */
1318 if (strcmp(orig_rcpt
? orig_rcpt
: start
, STR(int_rcpt_buf
)) == 0) {
1319 if (vstream_fseek(state
->dst
, curr_offset
, SEEK_SET
) < 0) {
1320 msg_warn("%s: seek file %s: %m", myname
, cleanup_path
);
1321 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1323 if (REC_PUT_BUF(state
->dst
, REC_TYPE_DRCP
, buf
) < 0) {
1324 msg_warn("%s: write queue file: %m", state
->queue_id
);
1325 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state
, errno
));
1330 case REC_TYPE_DRCP
: /* canceled recipient */
1331 case REC_TYPE_DONE
: /* can't happen */
1332 if (orig_rcpt
!= 0) {
1336 if (dsn_orcpt
!= 0) {
1346 msg_info("%s: deleted %d records for recipient \"%s\"",
1347 myname
, count
, ext_rcpt
);
1349 CLEANUP_DEL_RCPT_RETURN(0);
1352 /* cleanup_repl_body - replace message body */
1354 static const char *cleanup_repl_body(void *context
, int cmd
, VSTRING
*buf
)
1356 const char *myname
= "cleanup_repl_body";
1357 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
1358 static VSTRING empty
;
1361 * XXX Sendmail compatibility: milters don't see the first body line, so
1362 * don't expect they will send one.
1365 case MILTER_BODY_LINE
:
1366 if (cleanup_body_edit_write(state
, REC_TYPE_NORM
, buf
) < 0)
1367 return (cleanup_milter_error(state
, errno
));
1369 case MILTER_BODY_START
:
1370 VSTRING_RESET(&empty
);
1371 if (cleanup_body_edit_start(state
) < 0
1372 || cleanup_body_edit_write(state
, REC_TYPE_NORM
, &empty
) < 0)
1373 return (cleanup_milter_error(state
, errno
));
1375 case MILTER_BODY_END
:
1376 if (cleanup_body_edit_finish(state
) < 0)
1377 return (cleanup_milter_error(state
, errno
));
1380 msg_panic("%s: bad command: %d", myname
, cmd
);
1382 return (CLEANUP_OUT_OK(state
) ? 0 : cleanup_milter_error(state
, errno
));
1385 /* cleanup_milter_eval - expand macro */
1387 static const char *cleanup_milter_eval(const char *name
, void *ptr
)
1389 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) ptr
;
1392 * Note: if we use XFORWARD attributes here, then consistency requires
1393 * that we forward all Sendmail macros via XFORWARD.
1397 * Canonicalize the name.
1399 if (*name
!= '{') { /* } */
1400 vstring_sprintf(state
->temp1
, "{%s}", name
);
1401 name
= STR(state
->temp1
);
1407 if (strcmp(name
, S8_MAC_DAEMON_NAME
) == 0)
1408 return (var_milt_daemon_name
);
1409 if (strcmp(name
, S8_MAC_V
) == 0)
1410 return (var_milt_v
);
1415 #ifndef CLIENT_ATTR_UNKNOWN
1416 #define CLIENT_ATTR_UNKNOWN "unknown"
1419 if (strcmp(name
, S8_MAC__
) == 0) {
1420 vstring_sprintf(state
->temp1
, "%s [%s]",
1421 state
->reverse_name
, state
->client_addr
);
1422 if (strcasecmp(state
->client_name
, state
->reverse_name
) != 0)
1423 vstring_strcat(state
->temp1
, " (may be forged)");
1424 return (STR(state
->temp1
));
1426 if (strcmp(name
, S8_MAC_J
) == 0)
1427 return (var_myhostname
);
1428 if (strcmp(name
, S8_MAC_CLIENT_ADDR
) == 0)
1429 return (state
->client_addr
);
1430 if (strcmp(name
, S8_MAC_CLIENT_NAME
) == 0)
1431 return (state
->client_name
);
1432 if (strcmp(name
, S8_MAC_CLIENT_PORT
) == 0)
1433 return (state
->client_port
1434 && strcmp(state
->client_port
, CLIENT_ATTR_UNKNOWN
) ?
1435 state
->client_port
: "0");
1436 if (strcmp(name
, S8_MAC_CLIENT_PTR
) == 0)
1437 return (state
->reverse_name
);
1442 if (strcmp(name
, S8_MAC_I
) == 0)
1443 return (state
->queue_id
);
1444 #ifdef USE_SASL_AUTH
1445 if (strcmp(name
, S8_MAC_AUTH_TYPE
) == 0)
1446 return (nvtable_find(state
->attr
, MAIL_ATTR_SASL_METHOD
));
1447 if (strcmp(name
, S8_MAC_AUTH_AUTHEN
) == 0)
1448 return (nvtable_find(state
->attr
, MAIL_ATTR_SASL_USERNAME
));
1449 if (strcmp(name
, S8_MAC_AUTH_AUTHOR
) == 0)
1450 return (nvtable_find(state
->attr
, MAIL_ATTR_SASL_SENDER
));
1452 if (strcmp(name
, S8_MAC_MAIL_ADDR
) == 0)
1453 return (state
->milter_ext_from
? STR(state
->milter_ext_from
) : 0);
1458 if (strcmp(name
, S8_MAC_RCPT_ADDR
) == 0)
1459 return (state
->milter_ext_rcpt
? STR(state
->milter_ext_rcpt
) : 0);
1463 /* cleanup_milter_receive - receive milter instances */
1465 void cleanup_milter_receive(CLEANUP_STATE
*state
, int count
)
1468 milter_free(state
->milters
);
1469 state
->milters
= milter_receive(state
->src
, count
);
1470 if (state
->milters
== 0)
1471 msg_fatal("cleanup_milter_receive: milter receive failed");
1474 milter_macro_callback(state
->milters
, cleanup_milter_eval
, (void *) state
);
1475 milter_edit_callback(state
->milters
,
1476 cleanup_add_header
, cleanup_upd_header
,
1477 cleanup_ins_header
, cleanup_del_header
,
1478 cleanup_chg_from
, cleanup_add_rcpt
,
1479 cleanup_add_rcpt_par
, cleanup_del_rcpt
,
1480 cleanup_repl_body
, (void *) state
);
1483 /* cleanup_milter_apply - apply Milter reponse, non-zero if rejecting */
1485 static const char *cleanup_milter_apply(CLEANUP_STATE
*state
, const char *event
,
1488 const char *myname
= "cleanup_milter_apply";
1492 const char *ret
= 0;
1495 msg_info("%s: %s", myname
, resp
);
1500 if (state
->client_name
== 0)
1501 msg_panic("%s: missing client info initialization", myname
);
1504 * We don't report errors that were already reported by the content
1505 * editing call-back routines. See cleanup_milter_error() above.
1507 if (CLEANUP_OUT_OK(state
) == 0)
1511 /* XXX Should log the reason here. */
1512 if (state
->flags
& CLEANUP_FLAG_HOLD
)
1514 state
->flags
|= CLEANUP_FLAG_HOLD
;
1515 action
= "milter-hold";
1516 text
= "milter triggers HOLD action";
1519 state
->flags
|= CLEANUP_FLAG_DISCARD
;
1520 action
= "milter-discard";
1521 text
= "milter triggers DISCARD action";
1524 /* XXX Can this happen after end-of-message? */
1525 state
->flags
|= CLEANUP_STAT_CONT
;
1526 action
= "milter-reject";
1527 text
= cleanup_strerror(CLEANUP_STAT_CONT
);
1531 * Override permanent reject with temporary reject. This happens when
1532 * the cleanup server has to bounce (hard reject) but is unable to
1533 * store the message (soft reject). After a temporary reject we stop
1534 * inspecting queue file records, so it can't be overruled by
1537 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
1538 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
1539 * queue record processing, and prevents bounces from being sent.
1542 CLEANUP_MILTER_SET_SMTP_REPLY(state
, resp
);
1543 ret
= state
->reason
;
1544 state
->errs
|= CLEANUP_STAT_DEFER
;
1545 action
= "milter-reject";
1549 CLEANUP_MILTER_SET_SMTP_REPLY(state
, resp
);
1550 ret
= state
->reason
;
1551 state
->errs
|= CLEANUP_STAT_CONT
;
1552 action
= "milter-reject";
1556 msg_panic("%s: unexpected mail filter reply: %s", myname
, resp
);
1558 vstring_sprintf(state
->temp1
, "%s: %s: %s from %s[%s]: %s;",
1559 state
->queue_id
, action
, event
, state
->client_name
,
1560 state
->client_addr
, text
);
1562 vstring_sprintf_append(state
->temp1
, " from=<%s>", state
->sender
);
1564 vstring_sprintf_append(state
->temp1
, " to=<%s>", state
->recip
);
1565 if ((attr
= nvtable_find(state
->attr
, MAIL_ATTR_LOG_PROTO_NAME
)) != 0)
1566 vstring_sprintf_append(state
->temp1
, " proto=%s", attr
);
1567 if ((attr
= nvtable_find(state
->attr
, MAIL_ATTR_LOG_HELO_NAME
)) != 0)
1568 vstring_sprintf_append(state
->temp1
, " helo=<%s>", attr
);
1569 msg_info("%s", vstring_str(state
->temp1
));
1574 /* cleanup_milter_client_init - initialize real or ersatz client info */
1576 static void cleanup_milter_client_init(CLEANUP_STATE
*state
)
1578 const char *proto_attr
;
1581 * Either the cleanup client specifies a name, address and protocol, or
1582 * we have a local submission and pretend localhost/127.0.0.1/AF_INET.
1584 #define NO_CLIENT_PORT "0"
1586 state
->client_name
= nvtable_find(state
->attr
, MAIL_ATTR_ACT_CLIENT_NAME
);
1587 state
->reverse_name
=
1588 nvtable_find(state
->attr
, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME
);
1589 state
->client_addr
= nvtable_find(state
->attr
, MAIL_ATTR_ACT_CLIENT_ADDR
);
1590 state
->client_port
= nvtable_find(state
->attr
, MAIL_ATTR_ACT_CLIENT_PORT
);
1591 proto_attr
= nvtable_find(state
->attr
, MAIL_ATTR_ACT_CLIENT_AF
);
1593 if (state
->client_name
== 0 || state
->client_addr
== 0 || proto_attr
== 0
1594 || !alldig(proto_attr
)) {
1595 state
->client_name
= "localhost";
1596 state
->client_addr
= "127.0.0.1";
1597 state
->client_af
= AF_INET
;
1599 state
->client_af
= atoi(proto_attr
);
1600 if (state
->reverse_name
== 0)
1601 state
->reverse_name
= state
->client_name
;
1602 /* Compatibility with pre-2.5 queue files. */
1603 if (state
->client_port
== 0)
1604 state
->client_port
= NO_CLIENT_PORT
;
1607 /* cleanup_milter_inspect - run message through mail filter */
1609 void cleanup_milter_inspect(CLEANUP_STATE
*state
, MILTERS
*milters
)
1611 const char *myname
= "cleanup_milter";
1615 msg_info("enter %s", myname
);
1618 * Initialize, in case we're called via smtpd(8).
1620 if (state
->client_name
== 0)
1621 cleanup_milter_client_init(state
);
1624 * Process mail filter replies. The reply format is verified by the mail
1627 if ((resp
= milter_message(milters
, state
->handle
->stream
,
1628 state
->data_offset
)) != 0)
1629 cleanup_milter_apply(state
, "END-OF-MESSAGE", resp
);
1631 msg_info("leave %s", myname
);
1634 /* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */
1636 void cleanup_milter_emul_mail(CLEANUP_STATE
*state
,
1642 const char *argv
[2];
1645 * Per-connection initialization.
1647 milter_macro_callback(milters
, cleanup_milter_eval
, (void *) state
);
1648 milter_edit_callback(milters
,
1649 cleanup_add_header
, cleanup_upd_header
,
1650 cleanup_ins_header
, cleanup_del_header
,
1651 cleanup_chg_from
, cleanup_add_rcpt
,
1652 cleanup_add_rcpt_par
, cleanup_del_rcpt
,
1653 cleanup_repl_body
, (void *) state
);
1654 if (state
->client_name
== 0)
1655 cleanup_milter_client_init(state
);
1658 * Emulate SMTP events.
1660 if ((resp
= milter_conn_event(milters
, state
->client_name
, state
->client_addr
,
1661 state
->client_port
, state
->client_af
)) != 0) {
1662 cleanup_milter_apply(state
, "CONNECT", resp
);
1665 #define PRETEND_ESMTP 1
1667 if (CLEANUP_MILTER_OK(state
)) {
1668 if ((helo
= nvtable_find(state
->attr
, MAIL_ATTR_ACT_HELO_NAME
)) == 0)
1669 helo
= state
->client_name
;
1670 if ((resp
= milter_helo_event(milters
, helo
, PRETEND_ESMTP
)) != 0) {
1671 cleanup_milter_apply(state
, "EHLO", resp
);
1675 if (CLEANUP_MILTER_OK(state
)) {
1676 if (state
->milter_ext_from
== 0)
1677 state
->milter_ext_from
= vstring_alloc(100);
1678 /* Sendmail 8.13 does not externalize the null address. */
1680 quote_821_local(state
->milter_ext_from
, addr
);
1682 vstring_strcpy(state
->milter_ext_from
, addr
);
1683 argv
[0] = STR(state
->milter_ext_from
);
1685 if ((resp
= milter_mail_event(milters
, argv
)) != 0) {
1686 cleanup_milter_apply(state
, "MAIL", resp
);
1692 /* cleanup_milter_emul_rcpt - emulate rcpt event */
1694 void cleanup_milter_emul_rcpt(CLEANUP_STATE
*state
,
1698 const char *myname
= "cleanup_milter_emul_rcpt";
1700 const char *argv
[2];
1705 if (state
->client_name
== 0)
1706 msg_panic("%s: missing client info initialization", myname
);
1709 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
1710 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
1711 * queue record processing, and prevents bounces from being sent.
1713 if (state
->milter_ext_rcpt
== 0)
1714 state
->milter_ext_rcpt
= vstring_alloc(100);
1715 /* Sendmail 8.13 does not externalize the null address. */
1717 quote_821_local(state
->milter_ext_rcpt
, addr
);
1719 vstring_strcpy(state
->milter_ext_rcpt
, addr
);
1720 argv
[0] = STR(state
->milter_ext_rcpt
);
1722 if ((resp
= milter_rcpt_event(milters
, MILTER_FLAG_NONE
, argv
)) != 0
1723 && cleanup_milter_apply(state
, "RCPT", resp
) != 0) {
1724 msg_warn("%s: milter configuration error: can't reject recipient "
1725 "in non-smtpd(8) submission", state
->queue_id
);
1726 msg_warn("%s: deferring delivery of this message", state
->queue_id
);
1727 CLEANUP_MILTER_SET_REASON(state
, "4.3.5 Server configuration error");
1728 state
->errs
|= CLEANUP_STAT_DEFER
;
1732 /* cleanup_milter_emul_data - emulate data event */
1734 void cleanup_milter_emul_data(CLEANUP_STATE
*state
, MILTERS
*milters
)
1736 const char *myname
= "cleanup_milter_emul_data";
1742 if (state
->client_name
== 0)
1743 msg_panic("%s: missing client info initialization", myname
);
1745 if ((resp
= milter_data_event(milters
)) != 0)
1746 cleanup_milter_apply(state
, "DATA", resp
);
1752 * Queue file editing driver for regression tests. In this case it is OK to
1753 * report fatal errors after I/O errors.
1756 #include <msg_vstream.h>
1757 #include <vstring_vstream.h>
1758 #include <mail_addr.h>
1759 #include <mail_version.h>
1764 VSTRING
*cleanup_trace_path
;
1765 VSTRING
*cleanup_strip_chars
;
1766 int cleanup_comm_canon_flags
;
1767 MAPS
*cleanup_comm_canon_maps
;
1768 int cleanup_ext_prop_mask
;
1769 ARGV
*cleanup_masq_domains
;
1770 int cleanup_masq_flags
;
1771 MAPS
*cleanup_rcpt_bcc_maps
;
1772 int cleanup_rcpt_canon_flags
;
1773 MAPS
*cleanup_rcpt_canon_maps
;
1774 MAPS
*cleanup_send_bcc_maps
;
1775 int cleanup_send_canon_flags
;
1776 MAPS
*cleanup_send_canon_maps
;
1777 int var_dup_filter_limit
= DEF_DUP_FILTER_LIMIT
;
1778 char *var_empty_addr
= DEF_EMPTY_ADDR
;
1779 int var_enable_orcpt
= DEF_ENABLE_ORCPT
;
1780 MAPS
*cleanup_virt_alias_maps
;
1781 char *var_milt_daemon_name
= "host.example.com";
1782 char *var_milt_v
= DEF_MILT_V
;
1783 MILTERS
*cleanup_milters
= (MILTERS
*) ((char *) sizeof(*cleanup_milters
));
1785 /* Dummies to satisfy unused external references. */
1787 int cleanup_masquerade_internal(VSTRING
*addr
, ARGV
*masq_domains
)
1789 msg_panic("cleanup_masquerade_internal dummy");
1792 int cleanup_rewrite_internal(const char *context
, VSTRING
*result
,
1795 vstring_strcpy(result
, addr
);
1799 int cleanup_map11_internal(CLEANUP_STATE
*state
, VSTRING
*addr
,
1800 MAPS
*maps
, int propagate
)
1802 msg_panic("cleanup_map11_internal dummy");
1805 ARGV
*cleanup_map1n_internal(CLEANUP_STATE
*state
, const char *addr
,
1806 MAPS
*maps
, int propagate
)
1808 msg_panic("cleanup_map1n_internal dummy");
1811 void cleanup_envelope(CLEANUP_STATE
*state
, int type
, const char *buf
,
1814 msg_panic("cleanup_envelope dummy");
1817 static void usage(void)
1820 msg_warn(" verbose on|off");
1821 msg_warn(" open pathname");
1823 msg_warn(" add_header index name [value]");
1824 msg_warn(" ins_header index name [value]");
1825 msg_warn(" upd_header index name [value]");
1826 msg_warn(" del_header index name");
1827 msg_warn(" add_rcpt addr");
1828 msg_warn(" del_rcpt addr");
1831 /* flatten_args - unparse partial command line */
1833 static void flatten_args(VSTRING
*buf
, char **argv
)
1838 for (cpp
= argv
; *cpp
; cpp
++) {
1839 vstring_strcat(buf
, *cpp
);
1841 VSTRING_ADDCH(buf
, ' ');
1843 VSTRING_TERMINATE(buf
);
1846 /* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */
1848 static void open_queue_file(CLEANUP_STATE
*state
, const char *path
)
1850 VSTRING
*buf
= vstring_alloc(100);
1858 if (state
->dst
!= 0) {
1859 msg_warn("closing %s", cleanup_path
);
1860 vstream_fclose(state
->dst
);
1862 myfree(cleanup_path
);
1865 if ((state
->dst
= vstream_fopen(path
, O_RDWR
, 0)) == 0) {
1866 msg_warn("open %s: %m", path
);
1868 cleanup_path
= mystrdup(path
);
1870 if ((curr_offset
= vstream_ftell(state
->dst
)) < 0)
1871 msg_fatal("file %s: vstream_ftell: %m", cleanup_path
);
1872 if ((rec_type
= rec_get_raw(state
->dst
, buf
, 0, REC_FLAG_NONE
)) < 0)
1873 msg_fatal("file %s: missing SIZE or PTR record", cleanup_path
);
1874 if (rec_type
== REC_TYPE_SIZE
) {
1875 if (sscanf(STR(buf
), "%ld %ld %ld %ld",
1876 &msg_seg_len
, &data_offset
,
1877 &rcpt_count
, &qmgr_opts
) != 4)
1878 msg_fatal("file %s: bad SIZE record: %s",
1879 cleanup_path
, STR(buf
));
1880 state
->data_offset
= data_offset
;
1881 state
->xtra_offset
= data_offset
+ msg_seg_len
;
1882 } else if (rec_type
== REC_TYPE_FROM
) {
1883 state
->sender_pt_offset
= curr_offset
;
1884 if (LEN(buf
) < REC_TYPE_PTR_PAYL_SIZE
1885 && rec_get_raw(state
->dst
, buf
, 0, REC_FLAG_NONE
) != REC_TYPE_PTR
)
1886 msg_fatal("file %s: missing PTR record after short sender",
1888 if ((state
->sender_pt_target
= vstream_ftell(state
->dst
)) < 0)
1889 msg_fatal("file %s: missing END record", cleanup_path
);
1890 } else if (rec_type
== REC_TYPE_PTR
) {
1891 if (state
->data_offset
< 0)
1892 msg_fatal("file %s: missing SIZE record", cleanup_path
);
1893 if (curr_offset
< state
->data_offset
1894 || curr_offset
> state
->xtra_offset
) {
1895 if (state
->append_rcpt_pt_offset
< 0) {
1896 state
->append_rcpt_pt_offset
= curr_offset
;
1897 if (atol(STR(buf
)) != 0)
1898 msg_fatal("file %s: bad dummy recipient PTR record: %s",
1899 cleanup_path
, STR(buf
));
1900 if ((state
->append_rcpt_pt_target
=
1901 vstream_ftell(state
->dst
)) < 0)
1902 msg_fatal("file %s: vstream_ftell: %m", cleanup_path
);
1905 if (state
->append_hdr_pt_offset
< 0) {
1906 state
->append_hdr_pt_offset
= curr_offset
;
1907 if (atol(STR(buf
)) != 0)
1908 msg_fatal("file %s: bad dummy header PTR record: %s",
1909 cleanup_path
, STR(buf
));
1910 if ((state
->append_hdr_pt_target
=
1911 vstream_ftell(state
->dst
)) < 0)
1912 msg_fatal("file %s: vstream_ftell: %m", cleanup_path
);
1916 if (state
->append_rcpt_pt_offset
> 0
1917 && state
->append_hdr_pt_offset
> 0)
1921 msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld",
1922 (long) state
->append_rcpt_pt_offset
,
1923 (long) state
->append_rcpt_pt_target
);
1924 msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld",
1925 (long) state
->append_hdr_pt_offset
,
1926 (long) state
->append_hdr_pt_target
);
1932 static void close_queue_file(CLEANUP_STATE
*state
)
1934 (void) vstream_fclose(state
->dst
);
1936 myfree(cleanup_path
);
1940 int main(int unused_argc
, char **argv
)
1942 VSTRING
*inbuf
= vstring_alloc(100);
1943 VSTRING
*arg_buf
= vstring_alloc(100);
1945 int istty
= isatty(vstream_fileno(VSTREAM_IN
));
1946 CLEANUP_STATE
*state
= cleanup_state_alloc((VSTREAM
*) 0);
1948 state
->queue_id
= mystrdup("NOQUEUE");
1950 msg_vstream_init(argv
[0], VSTREAM_ERR
);
1951 var_line_limit
= DEF_LINE_LIMIT
;
1952 var_header_limit
= DEF_HEADER_LIMIT
;
1959 vstream_printf("- ");
1960 vstream_fflush(VSTREAM_OUT
);
1962 if (vstring_fgets_nonl(inbuf
, VSTREAM_IN
) == 0)
1965 bufp
= vstring_str(inbuf
);
1967 vstream_printf("> %s\n", bufp
);
1968 vstream_fflush(VSTREAM_OUT
);
1970 if (*bufp
== '#' || *bufp
== 0 || allspace(bufp
))
1972 argv
= argv_split(bufp
, " ");
1973 if (argv
->argc
== 0) {
1974 msg_warn("missing command");
1975 } else if (strcmp(argv
->argv
[0], "?") == 0) {
1977 } else if (strcmp(argv
->argv
[0], "verbose") == 0) {
1978 if (argv
->argc
!= 2) {
1979 msg_warn("bad verbose argument count: %d", argv
->argc
);
1980 } else if (strcmp(argv
->argv
[1], "on") == 0) {
1982 } else if (strcmp(argv
->argv
[1], "off") == 0) {
1985 msg_warn("bad verbose argument");
1987 } else if (strcmp(argv
->argv
[0], "open") == 0) {
1988 if (state
->dst
!= 0) {
1989 msg_info("closing %s", VSTREAM_PATH(state
->dst
));
1990 close_queue_file(state
);
1992 if (argv
->argc
!= 2) {
1993 msg_warn("bad open argument count: %d", argv
->argc
);
1995 open_queue_file(state
, argv
->argv
[1]);
1997 } else if (state
->dst
== 0) {
1998 msg_warn("no open queue file");
1999 } else if (strcmp(argv
->argv
[0], "close") == 0) {
2000 close_queue_file(state
);
2001 } else if (strcmp(argv
->argv
[0], "add_header") == 0) {
2002 if (argv
->argc
< 2) {
2003 msg_warn("bad add_header argument count: %d", argv
->argc
);
2005 flatten_args(arg_buf
, argv
->argv
+ 2);
2006 cleanup_add_header(state
, argv
->argv
[1], " ", STR(arg_buf
));
2008 } else if (strcmp(argv
->argv
[0], "ins_header") == 0) {
2009 if (argv
->argc
< 3) {
2010 msg_warn("bad ins_header argument count: %d", argv
->argc
);
2011 } else if ((index
= atoi(argv
->argv
[1])) < 1) {
2012 msg_warn("bad ins_header index value");
2014 flatten_args(arg_buf
, argv
->argv
+ 3);
2015 cleanup_ins_header(state
, index
, argv
->argv
[2], " ", STR(arg_buf
));
2017 } else if (strcmp(argv
->argv
[0], "upd_header") == 0) {
2018 if (argv
->argc
< 3) {
2019 msg_warn("bad upd_header argument count: %d", argv
->argc
);
2020 } else if ((index
= atoi(argv
->argv
[1])) < 1) {
2021 msg_warn("bad upd_header index value");
2023 flatten_args(arg_buf
, argv
->argv
+ 3);
2024 cleanup_upd_header(state
, index
, argv
->argv
[2], " ", STR(arg_buf
));
2026 } else if (strcmp(argv
->argv
[0], "del_header") == 0) {
2027 if (argv
->argc
!= 3) {
2028 msg_warn("bad del_header argument count: %d", argv
->argc
);
2029 } else if ((index
= atoi(argv
->argv
[1])) < 1) {
2030 msg_warn("bad del_header index value");
2032 cleanup_del_header(state
, index
, argv
->argv
[2]);
2034 } else if (strcmp(argv
->argv
[0], "chg_from") == 0) {
2035 if (argv
->argc
!= 3) {
2036 msg_warn("bad chg_from argument count: %d", argv
->argc
);
2038 cleanup_chg_from(state
, argv
->argv
[1], argv
->argv
[2]);
2040 } else if (strcmp(argv
->argv
[0], "add_rcpt") == 0) {
2041 if (argv
->argc
!= 2) {
2042 msg_warn("bad add_rcpt argument count: %d", argv
->argc
);
2044 cleanup_add_rcpt(state
, argv
->argv
[1]);
2046 } else if (strcmp(argv
->argv
[0], "add_rcpt_par") == 0) {
2047 if (argv
->argc
!= 3) {
2048 msg_warn("bad add_rcpt_par argument count: %d", argv
->argc
);
2050 cleanup_add_rcpt_par(state
, argv
->argv
[1], argv
->argv
[2]);
2052 } else if (strcmp(argv
->argv
[0], "del_rcpt") == 0) {
2053 if (argv
->argc
!= 2) {
2054 msg_warn("bad del_rcpt argument count: %d", argv
->argc
);
2056 cleanup_del_rcpt(state
, argv
->argv
[1]);
2058 } else if (strcmp(argv
->argv
[0], "replbody") == 0) {
2059 if (argv
->argc
!= 2) {
2060 msg_warn("bad replbody argument count: %d", argv
->argc
);
2065 if ((fp
= vstream_fopen(argv
->argv
[1], O_RDONLY
, 0)) == 0) {
2066 msg_warn("open %s file: %m", argv
->argv
[1]);
2068 buf
= vstring_alloc(100);
2069 cleanup_repl_body(state
, MILTER_BODY_START
, buf
);
2070 while (vstring_get_nonl(buf
, fp
) != VSTREAM_EOF
)
2071 cleanup_repl_body(state
, MILTER_BODY_LINE
, buf
);
2072 cleanup_repl_body(state
, MILTER_BODY_END
, buf
);
2078 msg_warn("bad command: %s", argv
->argv
[0]);
2082 vstring_free(inbuf
);
2083 vstring_free(arg_buf
);
2084 cleanup_state_free(state
);