No empty .Rs/.Re
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / cleanup / cleanup_milter.c
blobdf75114b48d8e106f1b7bb5977176b84e6730f49
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* cleanup_milter 3
6 /* SUMMARY
7 /* external mail filter support
8 /* SYNOPSIS
9 /* #include <cleanup.h>
11 /* void cleanup_milter_receive(state, count)
12 /* CLEANUP_STATE *state;
13 /* int count;
15 /* void cleanup_milter_inspect(state, milters)
16 /* CLEANUP_STATE *state;
17 /* MILTERS *milters;
19 /* cleanup_milter_emul_mail(state, milters, sender)
20 /* CLEANUP_STATE *state;
21 /* MILTERS *milters;
22 /* const char *sender;
24 /* cleanup_milter_emul_rcpt(state, milters, recipient)
25 /* CLEANUP_STATE *state;
26 /* MILTERS *milters;
27 /* const char *recipient;
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.
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.
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
52 /* next.
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.
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 /*--*/
79 /* System library. */
81 #include <sys_defs.h>
82 #include <sys/socket.h> /* AF_INET */
83 #include <string.h>
84 #include <errno.h>
86 #ifdef STRCASECMP_IN_STRINGS_H
87 #include <strings.h>
88 #endif
90 /* Utility library. */
92 #include <msg.h>
93 #include <vstream.h>
94 #include <vstring.h>
95 #include <stringops.h>
97 /* Global library. */
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>
111 /* Application-specific. */
113 #include <cleanup.h>
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
173 * forward pointer.
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
199 * segment.
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)
222 * Milter replies.
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; \
232 } while (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); \
242 } while (0)
244 /* cleanup_milter_set_error - set error flag from errno */
246 static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
248 if (err == EFBIG) {
249 msg_warn("%s: queue file size limit exceeded", state->queue_id);
250 state->errs |= CLEANUP_STAT_SIZE;
251 } else {
252 msg_warn("%s: write queue file: %m", state->queue_id);
253 state->errs |= CLEANUP_STAT_WRITE;
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.
276 if (err)
277 cleanup_milter_set_error(state, err);
278 else if (CLEANUP_OUT_OK(state))
279 msg_panic("%s: missing errno to error flag mapping", myname);
280 if (state->milter_err_text == 0)
281 state->milter_err_text = vstring_alloc(50);
282 dp = cleanup_stat_detail(state->errs);
283 return (STR(vstring_sprintf(state->milter_err_text,
284 "%d %s %s", dp->smtp, dp->dsn, dp->text)));
287 /* cleanup_add_header - append message header */
289 static const char *cleanup_add_header(void *context, const char *name,
290 const char *space,
291 const char *value)
293 const char *myname = "cleanup_add_header";
294 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
295 VSTRING *buf;
296 off_t reverse_ptr_offset;
297 off_t new_hdr_offset;
300 * To simplify implementation, the cleanup server writes a dummy "header
301 * append" pointer record after the last message header. We cache both
302 * the location and the target of the current "header append" pointer
303 * record.
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 */
323 vstring_free(buf);
324 if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
325 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
326 return (cleanup_milter_error(state, errno));
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,
367 VSTRING *buf,
368 int *prec_type,
369 int allow_ptr_backup,
370 int skip_headers)
372 const char *myname = "cleanup_find_header_start";
373 off_t curr_offset; /* offset after found record */
374 off_t ptr_offset; /* pointer to found record */
375 VSTRING *ptr_buf = 0;
376 int rec_type = REC_TYPE_ERROR;
377 int last_type;
378 ssize_t len;
379 int hdr_count = 0;
381 if (msg_verbose)
382 msg_info("%s: index %ld name \"%s\"",
383 myname, (long) index, header_label ? header_label : "(none)");
386 * Sanity checks.
388 if (index < 1)
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
394 * headers.
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
412 * same place.
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
439 * applications.
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 { \
456 if (ptr_buf) \
457 vstring_free(ptr_buf); \
458 return (offs); \
459 } while (0)
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) \
471 continue; \
472 if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
473 && rec_type != REC_TYPE_PTR) \
474 break;
475 /* End of hairy macros. */
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)
490 break;
491 /* Caution: this macro terminates the loop at end-of-message. */
492 /* Don't do complex processing while breaking out of this loop. */
493 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset,
494 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR));
495 /* Caution: don't assume ptr->header. This may be header-ptr->body. */
496 if (rec_type == REC_TYPE_PTR) {
497 if (rec_goto(state->dst, STR(buf)) < 0) {
498 msg_warn("%s: read file %s: %m", myname, cleanup_path);
499 cleanup_milter_set_error(state, errno);
500 CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
502 /* Save PTR record, in case it points to the start of a header. */
503 if (allow_ptr_backup) {
504 ptr_offset = curr_offset;
505 if (ptr_buf == 0)
506 ptr_buf = vstring_alloc(100);
507 vstring_strcpy(ptr_buf, STR(buf));
509 /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */
510 continue;
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) {
518 break;
520 /* This the start of a message header. */
521 else if (hdr_count++ < skip_headers)
522 /* Reset the saved PTR record and update last_type. */ ;
523 else if ((header_label == 0
524 || (strncasecmp(header_label, STR(buf), len) == 0
525 && (IS_SPACE_TAB(STR(buf)[len])
526 || STR(buf)[len] == ':')))
527 && --index == 0) {
528 /* If we have a saved PTR record, it points to start of header. */
529 break;
531 ptr_offset = 0;
532 last_type = rec_type;
536 * In case of failure, return negative start position.
538 if (index > 0) {
539 curr_offset = CLEANUP_FIND_HEADER_NOTFOUND;
540 } else {
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 :
550 (ptr_buf ? ptr_buf :
551 (ptr_buf = vstring_alloc(100))));
552 int rval;
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;
575 if (msg_verbose)
576 msg_info("%s: index %ld name %s type %d offset %ld",
577 myname, (long) index, header_label ?
578 header_label : "(none)", rec_type, (long) curr_offset);
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,
586 VSTRING *rec_buf,
587 int last_type)
589 const char *myname = "cleanup_find_header_end";
590 off_t read_offset;
591 int rec_type;
594 * This routine is called immediately after cleanup_find_header_start().
595 * rec_buf is the cleanup_find_header_start() result record; last_type is
596 * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file
597 * read position is at the first non-padding record after the result
598 * header record.
600 for (;;) {
601 if ((read_offset = vstream_ftell(state->dst)) < 0) {
602 msg_warn("%s: read file %s: %m", myname, cleanup_path);
603 cleanup_milter_error(state, errno);
604 return (-1);
606 /* Don't follow the "append header" pointer. */
607 if (read_offset == state->append_hdr_pt_offset)
608 break;
609 /* Caution: this macro terminates the loop at end-of-message. */
610 /* Don't do complex processing while breaking out of this loop. */
611 GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset,
612 /* Warning and errno->error mapping are done elsewhere. */
613 return (-1));
614 if (rec_type == REC_TYPE_PTR) {
615 if (rec_goto(state->dst, STR(rec_buf)) < 0) {
616 msg_warn("%s: read file %s: %m", myname, cleanup_path);
617 cleanup_milter_error(state, errno);
618 return (-1);
620 /* Don't update last_type; PTR may follow REC_TYPE_CONT. */
621 continue;
623 /* Start of header or message body. */
624 if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0]))
625 break;
626 last_type = rec_type;
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,
638 int old_rec_type,
639 VSTRING *old_rec_buf,
640 off_t next_offset)
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 { \
647 vstring_free(buf); \
648 return (ret); \
649 } while (0)
651 if (msg_verbose)
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
678 * file.
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 */
686 if (msg_verbose > 1)
687 msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset,
688 LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf));
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));
700 if (msg_verbose > 1)
701 msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ?
702 30 : (int) LEN(old_rec_buf), STR(old_rec_buf));
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) {
711 if (next_offset < 0)
712 msg_panic("%s: bad reverse pointer %ld",
713 myname, (long) next_offset);
714 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
715 (long) next_offset);
716 if (msg_verbose > 1)
717 msg_info("%s: write PTR %ld", myname, (long) next_offset);
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);
732 if (msg_verbose > 1)
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;
758 int old_rec_type;
759 off_t next_offset;
760 const char *ret;
762 #define CLEANUP_INS_HEADER_RETURN(ret) do { \
763 vstring_free(old_rec_buf); \
764 return (ret); \
765 } while (0)
767 if (msg_verbose)
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
784 if (index < 1)
785 index = 1;
786 old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
787 old_rec_buf, &old_rec_type,
788 ALLOW_PTR_BACKUP,
789 DONT_SKIP_HEADERS);
790 if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
791 /* Warning and errno->error mapping are done elsewhere. */
792 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0));
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) {
809 next_offset = -1;
810 } else {
811 if ((next_offset = vstream_ftell(state->dst)) < 0) {
812 msg_warn("%s: read file %s: %m", myname, cleanup_path);
813 CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno));
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;
831 VSTRING *rec_buf;
832 off_t old_rec_offset;
833 off_t next_offset;
834 int last_type;
835 const char *ret;
837 if (msg_verbose)
838 msg_info("%s: %ld \"%s\" \"%s\"",
839 myname, (long) index, new_hdr_name, new_hdr_value);
842 * Sanity check.
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); \
863 return (ret); \
864 } while (0)
866 rec_buf = vstring_alloc(100);
867 old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
868 rec_buf, &last_type,
869 NO_PTR_BACKUP,
870 SKIP_ONE_HEADER);
871 if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
872 /* Warning and errno->error mapping are done elsewhere. */
873 CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
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;
904 VSTRING *rec_buf;
905 off_t header_offset;
906 off_t next_offset;
907 int last_type;
909 if (msg_verbose)
910 msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name);
913 * Sanity check.
915 if (*hdr_name == 0)
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); \
927 return (ret); \
928 } while (0)
930 rec_buf = vstring_alloc(100);
931 header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
932 &last_type, NO_PTR_BACKUP,
933 SKIP_ONE_HEADER);
934 if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
935 /* Warning and errno->error mapping are done elsewhere. */
936 CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
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,
954 (long) next_offset);
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;
972 int addr_count;
973 TOK822 *tree;
974 TOK822 *tp;
975 VSTRING *int_sender_buf;
977 if (msg_verbose)
978 msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args);
980 if (esmtp_args[0])
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
1007 * sender.
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);
1026 addr_count += 1;
1027 } else {
1028 msg_warn("%s: Milter request to add multi-sender: \"%s\"",
1029 state->queue_id, ext_from);
1030 break;
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;
1065 int addr_count;
1066 TOK822 *tree;
1067 TOK822 *tp;
1068 VSTRING *int_rcpt_buf;
1070 if (msg_verbose)
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"
1077 * pointer record.
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);
1114 addr_count += 1;
1115 } else {
1116 msg_warn("%s: Milter request to add multi-recipient: \"%s\"",
1117 state->queue_id, ext_rcpt);
1118 break;
1122 tok822_free_tree(tree);
1123 cleanup_addr_bcc(state, STR(int_rcpt_buf));
1124 vstring_free(int_rcpt_buf);
1125 if (addr_count == 0) {
1126 msg_warn("%s: ignoring attempt from Milter to add null recipient",
1127 state->queue_id);
1128 return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
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;
1171 if (esmtp_args[0])
1172 msg_warn("%s: %s: ignoring ESMTP arguments \"%.100s\"",
1173 state->queue_id, myname, esmtp_args);
1174 return (cleanup_add_rcpt(context, ext_rcpt));
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;
1183 off_t curr_offset;
1184 VSTRING *buf;
1185 char *attr_name;
1186 char *attr_value;
1187 char *dsn_orcpt = 0; /* XXX for dup filter cleanup */
1188 int dsn_notify = 0; /* XXX for dup filter cleanup */
1189 char *orig_rcpt = 0;
1190 char *start;
1191 int rec_type;
1192 int junk;
1193 int count = 0;
1194 TOK822 *tree;
1195 TOK822 *tp;
1196 VSTRING *int_rcpt_buf;
1197 int addr_count;
1199 if (msg_verbose)
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
1220 * counts.
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); \
1233 return (ret); \
1234 } while (0)
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);
1248 addr_count += 1;
1249 } else {
1250 msg_warn("%s: Milter request to drop multi-recipient: \"%s\"",
1251 state->queue_id, ext_rcpt);
1252 break;
1256 tok822_free_tree(tree);
1258 buf = vstring_alloc(100);
1259 for (;;) {
1260 if (CLEANUP_OUT_OK(state) == 0)
1261 /* Warning and errno->error mapping are done elsewhere. */
1262 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0));
1263 if ((curr_offset = vstream_ftell(state->dst)) < 0) {
1264 msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
1265 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
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)
1272 break;
1273 /* Skip over message content. */
1274 if (rec_type == REC_TYPE_MESG) {
1275 if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) {
1276 msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1277 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1279 continue;
1281 start = STR(buf);
1282 if (rec_type == REC_TYPE_PTR) {
1283 if (rec_goto(state->dst, start) < 0) {
1284 msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1285 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1287 continue;
1289 /* Map attribute names to pseudo record type. */
1290 if (rec_type == REC_TYPE_ATTR) {
1291 if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
1292 || *attr_value == 0)
1293 continue;
1294 if ((junk = rec_attr_map(attr_name)) != 0) {
1295 start = attr_value;
1296 rec_type = junk;
1299 switch (rec_type) {
1300 case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */
1301 if (dsn_orcpt != 0) /* can't happen */
1302 myfree(dsn_orcpt);
1303 dsn_orcpt = mystrdup(start);
1304 break;
1305 case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */
1306 if (alldig(start) && (junk = atoi(start)) > 0
1307 && DSN_NOTIFY_OK(junk))
1308 dsn_notify = junk;
1309 else
1310 dsn_notify = 0;
1311 break;
1312 case REC_TYPE_ORCP: /* unmodified RCPT TO address */
1313 if (orig_rcpt != 0) /* can't happen */
1314 myfree(orig_rcpt);
1315 orig_rcpt = mystrdup(start);
1316 break;
1317 case REC_TYPE_RCPT: /* rewritten RCPT TO address */
1318 if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) {
1319 if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) {
1320 msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1321 CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
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));
1327 count++;
1329 /* FALLTHROUGH */
1330 case REC_TYPE_DRCP: /* canceled recipient */
1331 case REC_TYPE_DONE: /* can't happen */
1332 if (orig_rcpt != 0) {
1333 myfree(orig_rcpt);
1334 orig_rcpt = 0;
1336 if (dsn_orcpt != 0) {
1337 myfree(dsn_orcpt);
1338 dsn_orcpt = 0;
1340 dsn_notify = 0;
1341 break;
1345 if (msg_verbose)
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.
1364 switch (cmd) {
1365 case MILTER_BODY_LINE:
1366 if (cleanup_body_edit_write(state, REC_TYPE_NORM, buf) < 0)
1367 return (cleanup_milter_error(state, errno));
1368 break;
1369 case MILTER_BODY_START:
1370 VSTRING_RESET(&empty);
1371 if (cleanup_body_edit_start(state) < 0
1372 || cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0)
1373 return (cleanup_milter_error(state, errno));
1374 break;
1375 case MILTER_BODY_END:
1376 if (cleanup_body_edit_finish(state) < 0)
1377 return (cleanup_milter_error(state, errno));
1378 break;
1379 default:
1380 msg_panic("%s: bad command: %d", myname, cmd);
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);
1405 * System macros.
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);
1413 * Connect macros.
1415 #ifndef CLIENT_ATTR_UNKNOWN
1416 #define CLIENT_ATTR_UNKNOWN "unknown"
1417 #endif
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);
1440 * MAIL FROM macros.
1442 if (strcmp(name, S8_MAC_I) == 0)
1443 return (state->queue_id);
1444 #ifdef USE_SASL_AUTH
1445 if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
1446 return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD));
1447 if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
1448 return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME));
1449 if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
1450 return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER));
1451 #endif
1452 if (strcmp(name, S8_MAC_MAIL_ADDR) == 0)
1453 return (state->milter_ext_from ? STR(state->milter_ext_from) : 0);
1456 * RCPT TO macros.
1458 if (strcmp(name, S8_MAC_RCPT_ADDR) == 0)
1459 return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0);
1460 return (0);
1463 /* cleanup_milter_receive - receive milter instances */
1465 void cleanup_milter_receive(CLEANUP_STATE *state, int count)
1467 if (state->milters)
1468 milter_free(state->milters);
1469 state->milters = milter_receive(state->src, count);
1470 if (state->milters == 0)
1471 msg_fatal("cleanup_milter_receive: milter receive failed");
1472 if (count <= 0)
1473 return;
1474 milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state);
1475 milter_edit_callback(state->milters,
1476 cleanup_add_header, cleanup_upd_header,
1477 cleanup_ins_header, cleanup_del_header,
1478 cleanup_chg_from, cleanup_add_rcpt,
1479 cleanup_add_rcpt_par, cleanup_del_rcpt,
1480 cleanup_repl_body, (void *) state);
1483 /* cleanup_milter_apply - apply Milter reponse, non-zero if rejecting */
1485 static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event,
1486 const char *resp)
1488 const char *myname = "cleanup_milter_apply";
1489 const char *action;
1490 const char *text;
1491 const char *attr;
1492 const char *ret = 0;
1494 if (msg_verbose)
1495 msg_info("%s: %s", myname, resp);
1498 * Sanity check.
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)
1508 return (0);
1509 switch (resp[0]) {
1510 case 'H':
1511 /* XXX Should log the reason here. */
1512 if (state->flags & CLEANUP_FLAG_HOLD)
1513 return (0);
1514 state->flags |= CLEANUP_FLAG_HOLD;
1515 action = "milter-hold";
1516 text = "milter triggers HOLD action";
1517 break;
1518 case 'D':
1519 state->flags |= CLEANUP_FLAG_DISCARD;
1520 action = "milter-discard";
1521 text = "milter triggers DISCARD action";
1522 break;
1523 case 'S':
1524 /* XXX Can this happen after end-of-message? */
1525 state->flags |= CLEANUP_STAT_CONT;
1526 action = "milter-reject";
1527 text = cleanup_strerror(CLEANUP_STAT_CONT);
1528 break;
1531 * Override permanent reject with temporary reject. This happens when
1532 * the cleanup server has to bounce (hard reject) but is unable to
1533 * store the message (soft reject). After a temporary reject we stop
1534 * inspecting queue file records, so it can't be overruled by
1535 * something else.
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.
1541 case '4':
1542 CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
1543 ret = state->reason;
1544 state->errs |= CLEANUP_STAT_DEFER;
1545 action = "milter-reject";
1546 text = resp + 4;
1547 break;
1548 case '5':
1549 CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
1550 ret = state->reason;
1551 state->errs |= CLEANUP_STAT_CONT;
1552 action = "milter-reject";
1553 text = resp + 4;
1554 break;
1555 default:
1556 msg_panic("%s: unexpected mail filter reply: %s", myname, resp);
1558 vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;",
1559 state->queue_id, action, event, state->client_name,
1560 state->client_addr, text);
1561 if (state->sender)
1562 vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
1563 if (state->recip)
1564 vstring_sprintf_append(state->temp1, " to=<%s>", state->recip);
1565 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
1566 vstring_sprintf_append(state->temp1, " proto=%s", attr);
1567 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
1568 vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
1569 msg_info("%s", vstring_str(state->temp1));
1571 return (ret);
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;
1598 } else
1599 state->client_af = atoi(proto_attr);
1600 if (state->reverse_name == 0)
1601 state->reverse_name = state->client_name;
1602 /* Compatibility with pre-2.5 queue files. */
1603 if (state->client_port == 0)
1604 state->client_port = NO_CLIENT_PORT;
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";
1612 const char *resp;
1614 if (msg_verbose)
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
1625 * filter library.
1627 if ((resp = milter_message(milters, state->handle->stream,
1628 state->data_offset)) != 0)
1629 cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
1630 if (msg_verbose)
1631 msg_info("leave %s", myname);
1634 /* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */
1636 void cleanup_milter_emul_mail(CLEANUP_STATE *state,
1637 MILTERS *milters,
1638 const char *addr)
1640 const char *resp;
1641 const char *helo;
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);
1663 return;
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);
1672 return;
1675 if (CLEANUP_MILTER_OK(state)) {
1676 if (state->milter_ext_from == 0)
1677 state->milter_ext_from = vstring_alloc(100);
1678 /* Sendmail 8.13 does not externalize the null address. */
1679 if (*addr)
1680 quote_821_local(state->milter_ext_from, addr);
1681 else
1682 vstring_strcpy(state->milter_ext_from, addr);
1683 argv[0] = STR(state->milter_ext_from);
1684 argv[1] = 0;
1685 if ((resp = milter_mail_event(milters, argv)) != 0) {
1686 cleanup_milter_apply(state, "MAIL", resp);
1687 return;
1692 /* cleanup_milter_emul_rcpt - emulate rcpt event */
1694 void cleanup_milter_emul_rcpt(CLEANUP_STATE *state,
1695 MILTERS *milters,
1696 const char *addr)
1698 const char *myname = "cleanup_milter_emul_rcpt";
1699 const char *resp;
1700 const char *argv[2];
1703 * Sanity check.
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. */
1716 if (*addr)
1717 quote_821_local(state->milter_ext_rcpt, addr);
1718 else
1719 vstring_strcpy(state->milter_ext_rcpt, addr);
1720 argv[0] = STR(state->milter_ext_rcpt);
1721 argv[1] = 0;
1722 if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0
1723 && cleanup_milter_apply(state, "RCPT", resp) != 0) {
1724 msg_warn("%s: milter configuration error: can't reject recipient "
1725 "in non-smtpd(8) submission", state->queue_id);
1726 msg_warn("%s: deferring delivery of this message", state->queue_id);
1727 CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error");
1728 state->errs |= CLEANUP_STAT_DEFER;
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";
1737 const char *resp;
1740 * Sanity check.
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);
1749 #ifdef TEST
1752 * Queue file editing driver for regression tests. In this case it is OK to
1753 * report fatal errors after I/O errors.
1755 #include <stdio.h>
1756 #include <msg_vstream.h>
1757 #include <vstring_vstream.h>
1758 #include <mail_addr.h>
1759 #include <mail_version.h>
1761 #undef msg_verbose
1763 char *cleanup_path;
1764 VSTRING *cleanup_trace_path;
1765 VSTRING *cleanup_strip_chars;
1766 int cleanup_comm_canon_flags;
1767 MAPS *cleanup_comm_canon_maps;
1768 int cleanup_ext_prop_mask;
1769 ARGV *cleanup_masq_domains;
1770 int cleanup_masq_flags;
1771 MAPS *cleanup_rcpt_bcc_maps;
1772 int cleanup_rcpt_canon_flags;
1773 MAPS *cleanup_rcpt_canon_maps;
1774 MAPS *cleanup_send_bcc_maps;
1775 int cleanup_send_canon_flags;
1776 MAPS *cleanup_send_canon_maps;
1777 int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
1778 char *var_empty_addr = DEF_EMPTY_ADDR;
1779 int var_enable_orcpt = DEF_ENABLE_ORCPT;
1780 MAPS *cleanup_virt_alias_maps;
1781 char *var_milt_daemon_name = "host.example.com";
1782 char *var_milt_v = DEF_MILT_V;
1783 MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters));
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,
1793 const char *addr)
1795 vstring_strcpy(result, addr);
1796 return (0);
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,
1812 ssize_t len)
1814 msg_panic("cleanup_envelope dummy");
1817 static void usage(void)
1819 msg_warn("usage:");
1820 msg_warn(" verbose on|off");
1821 msg_warn(" open pathname");
1822 msg_warn(" close");
1823 msg_warn(" add_header index name [value]");
1824 msg_warn(" ins_header index name [value]");
1825 msg_warn(" upd_header index name [value]");
1826 msg_warn(" del_header index name");
1827 msg_warn(" add_rcpt addr");
1828 msg_warn(" del_rcpt addr");
1831 /* flatten_args - unparse partial command line */
1833 static void flatten_args(VSTRING *buf, char **argv)
1835 char **cpp;
1837 VSTRING_RESET(buf);
1838 for (cpp = argv; *cpp; cpp++) {
1839 vstring_strcat(buf, *cpp);
1840 if (cpp[1])
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);
1851 off_t curr_offset;
1852 int rec_type;
1853 long msg_seg_len;
1854 long data_offset;
1855 long rcpt_count;
1856 long qmgr_opts;
1858 if (state->dst != 0) {
1859 msg_warn("closing %s", cleanup_path);
1860 vstream_fclose(state->dst);
1861 state->dst = 0;
1862 myfree(cleanup_path);
1863 cleanup_path = 0;
1865 if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) {
1866 msg_warn("open %s: %m", path);
1867 } else {
1868 cleanup_path = mystrdup(path);
1869 for (;;) {
1870 if ((curr_offset = vstream_ftell(state->dst)) < 0)
1871 msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
1872 if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0)
1873 msg_fatal("file %s: missing SIZE or PTR record", cleanup_path);
1874 if (rec_type == REC_TYPE_SIZE) {
1875 if (sscanf(STR(buf), "%ld %ld %ld %ld",
1876 &msg_seg_len, &data_offset,
1877 &rcpt_count, &qmgr_opts) != 4)
1878 msg_fatal("file %s: bad SIZE record: %s",
1879 cleanup_path, STR(buf));
1880 state->data_offset = data_offset;
1881 state->xtra_offset = data_offset + msg_seg_len;
1882 } else if (rec_type == REC_TYPE_FROM) {
1883 state->sender_pt_offset = curr_offset;
1884 if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE
1885 && rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR)
1886 msg_fatal("file %s: missing PTR record after short sender",
1887 cleanup_path);
1888 if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0)
1889 msg_fatal("file %s: missing END record", cleanup_path);
1890 } else if (rec_type == REC_TYPE_PTR) {
1891 if (state->data_offset < 0)
1892 msg_fatal("file %s: missing SIZE record", cleanup_path);
1893 if (curr_offset < state->data_offset
1894 || curr_offset > state->xtra_offset) {
1895 if (state->append_rcpt_pt_offset < 0) {
1896 state->append_rcpt_pt_offset = curr_offset;
1897 if (atol(STR(buf)) != 0)
1898 msg_fatal("file %s: bad dummy recipient PTR record: %s",
1899 cleanup_path, STR(buf));
1900 if ((state->append_rcpt_pt_target =
1901 vstream_ftell(state->dst)) < 0)
1902 msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
1904 } else {
1905 if (state->append_hdr_pt_offset < 0) {
1906 state->append_hdr_pt_offset = curr_offset;
1907 if (atol(STR(buf)) != 0)
1908 msg_fatal("file %s: bad dummy header PTR record: %s",
1909 cleanup_path, STR(buf));
1910 if ((state->append_hdr_pt_target =
1911 vstream_ftell(state->dst)) < 0)
1912 msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
1916 if (state->append_rcpt_pt_offset > 0
1917 && state->append_hdr_pt_offset > 0)
1918 break;
1920 if (msg_verbose) {
1921 msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld",
1922 (long) state->append_rcpt_pt_offset,
1923 (long) state->append_rcpt_pt_target);
1924 msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld",
1925 (long) state->append_hdr_pt_offset,
1926 (long) state->append_hdr_pt_target);
1929 vstring_free(buf);
1932 static void close_queue_file(CLEANUP_STATE *state)
1934 (void) vstream_fclose(state->dst);
1935 state->dst = 0;
1936 myfree(cleanup_path);
1937 cleanup_path = 0;
1940 int main(int unused_argc, char **argv)
1942 VSTRING *inbuf = vstring_alloc(100);
1943 VSTRING *arg_buf = vstring_alloc(100);
1944 char *bufp;
1945 int istty = isatty(vstream_fileno(VSTREAM_IN));
1946 CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0);
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;
1954 for (;;) {
1955 ARGV *argv;
1956 ssize_t index;
1958 if (istty) {
1959 vstream_printf("- ");
1960 vstream_fflush(VSTREAM_OUT);
1962 if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
1963 break;
1965 bufp = vstring_str(inbuf);
1966 if (!istty) {
1967 vstream_printf("> %s\n", bufp);
1968 vstream_fflush(VSTREAM_OUT);
1970 if (*bufp == '#' || *bufp == 0 || allspace(bufp))
1971 continue;
1972 argv = argv_split(bufp, " ");
1973 if (argv->argc == 0) {
1974 msg_warn("missing command");
1975 } else if (strcmp(argv->argv[0], "?") == 0) {
1976 usage();
1977 } else if (strcmp(argv->argv[0], "verbose") == 0) {
1978 if (argv->argc != 2) {
1979 msg_warn("bad verbose argument count: %d", argv->argc);
1980 } else if (strcmp(argv->argv[1], "on") == 0) {
1981 msg_verbose = 2;
1982 } else if (strcmp(argv->argv[1], "off") == 0) {
1983 msg_verbose = 0;
1984 } else {
1985 msg_warn("bad verbose argument");
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);
1994 } else {
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);
2004 } else {
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");
2013 } else {
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");
2022 } else {
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");
2031 } else {
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);
2037 } else {
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);
2043 } else {
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);
2049 } else {
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);
2055 } else {
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);
2061 } else {
2062 VSTREAM *fp;
2063 VSTRING *buf;
2065 if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) {
2066 msg_warn("open %s file: %m", argv->argv[1]);
2067 } else {
2068 buf = vstring_alloc(100);
2069 cleanup_repl_body(state, MILTER_BODY_START, buf);
2070 while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
2071 cleanup_repl_body(state, MILTER_BODY_LINE, buf);
2072 cleanup_repl_body(state, MILTER_BODY_END, buf);
2073 vstring_free(buf);
2074 vstream_fclose(fp);
2077 } else {
2078 msg_warn("bad command: %s", argv->argv[0]);
2080 argv_free(argv);
2082 vstring_free(inbuf);
2083 vstring_free(arg_buf);
2084 cleanup_state_free(state);
2086 return (0);
2089 #endif