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