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