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