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