diff -cr /var/tmp/postfix-2.6.2/html/cleanup.8.html ./html/cleanup.8.html
*** /var/tmp/postfix-2.6.2/html/cleanup.8.html	Tue Apr 28 13:49:23 2009
--- ./html/cleanup.8.html	Sun Jun  7 16:43:45 2009
***************
*** 224,229 ****
--- 224,236 ----
                The  macros  that  are sent to Milter (mail filter)
                applications after the end of the message header.
  
+        Available in Postfix version 2.7 and later:
+ 
+        <b><a href="postconf.5.html#milter_header_checks">milter_header_checks</a> (empty)</b>
+               Optional lookup tables for  content  inspection  of
+               message  headers that are produced by Milter appli-
+               cations.
+ 
  <b>MIME PROCESSING CONTROLS</b>
         Available in Postfix version 2.0 and later:
  
diff -cr /var/tmp/postfix-2.6.2/html/postconf.5.html ./html/postconf.5.html
*** /var/tmp/postfix-2.6.2/html/postconf.5.html	Wed May  6 14:53:52 2009
--- ./html/postconf.5.html	Sun Jun  7 16:43:45 2009
***************
*** 5647,5652 ****
--- 5647,5685 ----
  
  </DD>
  
+ <DT><b><a name="milter_header_checks">milter_header_checks</a>
+ (default: empty)</b></DT><DD>
+ 
+ <p> Optional lookup tables for content inspection of message headers
+ that are produced by Milter applications.  See the <a href="header_checks.5.html">header_checks(5)</a>
+ manual page available actions. Currently, PREPEND is not implemented.
+ </p>
+ 
+ <p> The following example sends all mail that is marked as SPAM to
+ a spam handling machine. Note that matches are case-insensitive
+ by default. </p>
+ 
+ <blockquote>
+ <pre>
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+     <a href="postconf.5.html#milter_header_checks">milter_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/<a href="postconf.5.html#milter_header_checks">milter_header_checks</a>
+ </pre>
+ <pre>
+ /etc/postfix/<a href="postconf.5.html#milter_header_checks">milter_header_checks</a>:
+     /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+ </pre>
+ </blockquote>
+ 
+ <p> The <a href="postconf.5.html#milter_header_checks">milter_header_checks</a> mechanism could also be used for
+ whitelisting. For example it could be used to skip heavy content
+ scanning for DKIM-signed mail from known friendly domains. </p>
+ 
+ <p> This feature is available in Postfix 2.7, and as an optional
+ patch for Postfix 2.6. </p>
+ 
+ 
+ </DD>
+ 
  <DT><b><a name="milter_helo_macros">milter_helo_macros</a>
  (default: see "postconf -d" output)</b></DT><DD>
  
diff -cr /var/tmp/postfix-2.6.2/man/man5/postconf.5 ./man/man5/postconf.5
*** /var/tmp/postfix-2.6.2/man/man5/postconf.5	Wed May  6 14:53:53 2009
--- ./man/man5/postconf.5	Sun Jun  7 16:43:45 2009
***************
*** 3138,3143 ****
--- 3138,3177 ----
  of available macro names and their meanings.
  .PP
  This feature is available in Postfix 2.5 and later.
+ .SH milter_header_checks (default: empty)
+ Optional lookup tables for content inspection of message headers
+ that are produced by Milter applications.  See the \fBheader_checks\fR(5)
+ manual page available actions. Currently, PREPEND is not implemented.
+ .PP
+ The following example sends all mail that is marked as SPAM to
+ a spam handling machine. Note that matches are case-insensitive
+ by default.
+ .sp
+ .in +4
+ .nf
+ .na
+ .ft C
+ /etc/postfix/main.cf:
+     milter_header_checks = pcre:/etc/postfix/milter_header_checks
+ .fi
+ .ad
+ .ft R
+ .nf
+ .na
+ .ft C
+ /etc/postfix/milter_header_checks:
+     /^X-SPAM-FLAG:\es+YES/ FILTER mysmtp:sanitizer.example.com:25
+ .fi
+ .ad
+ .ft R
+ .in -4
+ .PP
+ The milter_header_checks mechanism could also be used for
+ whitelisting. For example it could be used to skip heavy content
+ scanning for DKIM-signed mail from known friendly domains.
+ .PP
+ This feature is available in Postfix 2.7, and as an optional
+ patch for Postfix 2.6.
  .SH milter_helo_macros (default: see "postconf -d" output)
  The macros that are sent to Milter (mail filter) applications
  after the SMTP HELO or EHLO command. See
diff -cr /var/tmp/postfix-2.6.2/man/man8/cleanup.8 ./man/man8/cleanup.8
*** /var/tmp/postfix-2.6.2/man/man8/cleanup.8	Tue Apr 28 13:49:23 2009
--- ./man/man8/cleanup.8	Sun Jun  7 16:43:45 2009
***************
*** 190,195 ****
--- 190,200 ----
  .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR"
  The macros that are sent to Milter (mail filter) applications
  after the end of the message header.
+ .PP
+ Available in Postfix version 2.7 and later:
+ .IP "\fBmilter_header_checks (empty)\fR"
+ Optional lookup tables for content inspection of message headers
+ that are produced by Milter applications.
  .SH "MIME PROCESSING CONTROLS"
  .na
  .nf
diff -cr /var/tmp/postfix-2.6.2/mantools/postlink ./mantools/postlink
*** /var/tmp/postfix-2.6.2/mantools/postlink	Sun Apr 26 20:35:34 2009
--- ./mantools/postlink	Thu Jun  4 20:56:43 2009
***************
*** 868,873 ****
--- 868,874 ----
      s;\bmilter_unknown_command_macros\b;<a href="postconf.5.html#milter_unknown_command_macros">$&</a>;g;
      s;\bmilter_end_of_data_macros\b;<a href="postconf.5.html#milter_end_of_data_macros">$&</a>;g;
      s;\bmilter_end_of_header_macros\b;<a href="postconf.5.html#milter_end_of_header_macros">$&</a>;g;
+     s;\bmilter_header_checks\b;<a href="postconf.5.html#milter_header_checks">$&</a>;g;
  
      # Multi-instance support
      s;\bmulti_instance_directo[-</bB>]*\n*[ <bB>]*ries\b;<a href="postconf.5.html#multi_instance_directories">$&</a>;g;
diff -cr /var/tmp/postfix-2.6.2/proto/postconf.proto ./proto/postconf.proto
*** /var/tmp/postfix-2.6.2/proto/postconf.proto	Wed May  6 14:53:29 2009
--- ./proto/postconf.proto	Sun Jun  7 16:39:55 2009
***************
*** 12269,12271 ****
--- 12269,12300 ----
  when clients match the local_header_rewrite_clients parameter
  setting.  Earlier Postfix versions always add these headers; this
  may break DKIM signatures that cover non-existent headers. </p>
+ 
+ %PARAM milter_header_checks
+ 
+ <p> Optional lookup tables for content inspection of message headers
+ that are produced by Milter applications.  See the header_checks(5)
+ manual page available actions. Currently, PREPEND is not implemented.
+ </p>
+ 
+ <p> The following example sends all mail that is marked as SPAM to
+ a spam handling machine. Note that matches are case-insensitive
+ by default. </p>
+ 
+ <blockquote>
+ <pre>
+ /etc/postfix/main.cf:
+     milter_header_checks = pcre:/etc/postfix/milter_header_checks
+ </pre>
+ <pre>
+ /etc/postfix/milter_header_checks:
+     /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+ </pre>
+ </blockquote> 
+ 
+ <p> The milter_header_checks mechanism could also be used for
+ whitelisting. For example it could be used to skip heavy content
+ scanning for DKIM-signed mail from known friendly domains. </p>
+ 
+ <p> This feature is available in Postfix 2.7, and as an optional
+ patch for Postfix 2.6. </p>
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/Makefile.in ./src/cleanup/Makefile.in
*** /var/tmp/postfix-2.6.2/src/cleanup/Makefile.in	Mon Apr 27 20:53:31 2009
--- ./src/cleanup/Makefile.in	Sun Jun  7 16:37:46 2009
***************
*** 349,354 ****
--- 349,355 ----
  cleanup.o: ../../include/been_here.h
  cleanup.o: ../../include/cleanup_user.h
  cleanup.o: ../../include/dict.h
+ cleanup.o: ../../include/header_body_checks.h
  cleanup.o: ../../include/header_opts.h
  cleanup.o: ../../include/htable.h
  cleanup.o: ../../include/iostuff.h
***************
*** 385,390 ****
--- 386,392 ----
  cleanup_addr.o: ../../include/dict.h
  cleanup_addr.o: ../../include/dsn_mask.h
  cleanup_addr.o: ../../include/ext_prop.h
+ cleanup_addr.o: ../../include/header_body_checks.h
  cleanup_addr.o: ../../include/header_opts.h
  cleanup_addr.o: ../../include/htable.h
  cleanup_addr.o: ../../include/iostuff.h
***************
*** 422,427 ****
--- 424,430 ----
  cleanup_api.o: ../../include/dict.h
  cleanup_api.o: ../../include/dsn.h
  cleanup_api.o: ../../include/dsn_buf.h
+ cleanup_api.o: ../../include/header_body_checks.h
  cleanup_api.o: ../../include/header_opts.h
  cleanup_api.o: ../../include/htable.h
  cleanup_api.o: ../../include/iostuff.h
***************
*** 456,461 ****
--- 459,465 ----
  cleanup_body_edit.o: ../../include/been_here.h
  cleanup_body_edit.o: ../../include/cleanup_user.h
  cleanup_body_edit.o: ../../include/dict.h
+ cleanup_body_edit.o: ../../include/header_body_checks.h
  cleanup_body_edit.o: ../../include/header_opts.h
  cleanup_body_edit.o: ../../include/htable.h
  cleanup_body_edit.o: ../../include/mail_conf.h
***************
*** 490,495 ****
--- 494,500 ----
  cleanup_bounce.o: ../../include/dsn_buf.h
  cleanup_bounce.o: ../../include/dsn_mask.h
  cleanup_bounce.o: ../../include/dsn_util.h
+ cleanup_bounce.o: ../../include/header_body_checks.h
  cleanup_bounce.o: ../../include/header_opts.h
  cleanup_bounce.o: ../../include/htable.h
  cleanup_bounce.o: ../../include/iostuff.h
***************
*** 527,532 ****
--- 532,538 ----
  cleanup_envelope.o: ../../include/cleanup_user.h
  cleanup_envelope.o: ../../include/dict.h
  cleanup_envelope.o: ../../include/dsn_mask.h
+ cleanup_envelope.o: ../../include/header_body_checks.h
  cleanup_envelope.o: ../../include/header_opts.h
  cleanup_envelope.o: ../../include/htable.h
  cleanup_envelope.o: ../../include/iostuff.h
***************
*** 545,550 ****
--- 551,557 ----
  cleanup_envelope.o: ../../include/qmgr_user.h
  cleanup_envelope.o: ../../include/rec_attr_map.h
  cleanup_envelope.o: ../../include/rec_type.h
+ cleanup_envelope.o: ../../include/recipient_list.h
  cleanup_envelope.o: ../../include/record.h
  cleanup_envelope.o: ../../include/resolve_clnt.h
  cleanup_envelope.o: ../../include/string_list.h
***************
*** 563,568 ****
--- 570,576 ----
  cleanup_extracted.o: ../../include/cleanup_user.h
  cleanup_extracted.o: ../../include/dict.h
  cleanup_extracted.o: ../../include/dsn_mask.h
+ cleanup_extracted.o: ../../include/header_body_checks.h
  cleanup_extracted.o: ../../include/header_opts.h
  cleanup_extracted.o: ../../include/htable.h
  cleanup_extracted.o: ../../include/iostuff.h
***************
*** 597,602 ****
--- 605,611 ----
  cleanup_final.o: ../../include/been_here.h
  cleanup_final.o: ../../include/cleanup_user.h
  cleanup_final.o: ../../include/dict.h
+ cleanup_final.o: ../../include/header_body_checks.h
  cleanup_final.o: ../../include/header_opts.h
  cleanup_final.o: ../../include/htable.h
  cleanup_final.o: ../../include/mail_conf.h
***************
*** 626,631 ****
--- 635,641 ----
  cleanup_init.o: ../../include/dict.h
  cleanup_init.o: ../../include/ext_prop.h
  cleanup_init.o: ../../include/flush_clnt.h
+ cleanup_init.o: ../../include/header_body_checks.h
  cleanup_init.o: ../../include/header_opts.h
  cleanup_init.o: ../../include/htable.h
  cleanup_init.o: ../../include/iostuff.h
***************
*** 658,663 ****
--- 668,674 ----
  cleanup_map11.o: ../../include/been_here.h
  cleanup_map11.o: ../../include/cleanup_user.h
  cleanup_map11.o: ../../include/dict.h
+ cleanup_map11.o: ../../include/header_body_checks.h
  cleanup_map11.o: ../../include/header_opts.h
  cleanup_map11.o: ../../include/htable.h
  cleanup_map11.o: ../../include/mail_addr_map.h
***************
*** 687,692 ****
--- 698,704 ----
  cleanup_map1n.o: ../../include/been_here.h
  cleanup_map1n.o: ../../include/cleanup_user.h
  cleanup_map1n.o: ../../include/dict.h
+ cleanup_map1n.o: ../../include/header_body_checks.h
  cleanup_map1n.o: ../../include/header_opts.h
  cleanup_map1n.o: ../../include/htable.h
  cleanup_map1n.o: ../../include/mail_addr_map.h
***************
*** 717,722 ****
--- 729,735 ----
  cleanup_masquerade.o: ../../include/been_here.h
  cleanup_masquerade.o: ../../include/cleanup_user.h
  cleanup_masquerade.o: ../../include/dict.h
+ cleanup_masquerade.o: ../../include/header_body_checks.h
  cleanup_masquerade.o: ../../include/header_opts.h
  cleanup_masquerade.o: ../../include/htable.h
  cleanup_masquerade.o: ../../include/mail_conf.h
***************
*** 750,755 ****
--- 763,769 ----
  cleanup_message.o: ../../include/dict.h
  cleanup_message.o: ../../include/dsn_util.h
  cleanup_message.o: ../../include/ext_prop.h
+ cleanup_message.o: ../../include/header_body_checks.h
  cleanup_message.o: ../../include/header_opts.h
  cleanup_message.o: ../../include/htable.h
  cleanup_message.o: ../../include/iostuff.h
***************
*** 790,795 ****
--- 804,811 ----
  cleanup_milter.o: ../../include/cleanup_user.h
  cleanup_milter.o: ../../include/dict.h
  cleanup_milter.o: ../../include/dsn_mask.h
+ cleanup_milter.o: ../../include/dsn_util.h
+ cleanup_milter.o: ../../include/header_body_checks.h
  cleanup_milter.o: ../../include/header_opts.h
  cleanup_milter.o: ../../include/htable.h
  cleanup_milter.o: ../../include/iostuff.h
***************
*** 828,833 ****
--- 844,850 ----
  cleanup_out.o: ../../include/been_here.h
  cleanup_out.o: ../../include/cleanup_user.h
  cleanup_out.o: ../../include/dict.h
+ cleanup_out.o: ../../include/header_body_checks.h
  cleanup_out.o: ../../include/header_opts.h
  cleanup_out.o: ../../include/htable.h
  cleanup_out.o: ../../include/lex_822.h
***************
*** 865,870 ****
--- 882,888 ----
  cleanup_out_recipient.o: ../../include/dsn_buf.h
  cleanup_out_recipient.o: ../../include/dsn_mask.h
  cleanup_out_recipient.o: ../../include/ext_prop.h
+ cleanup_out_recipient.o: ../../include/header_body_checks.h
  cleanup_out_recipient.o: ../../include/header_opts.h
  cleanup_out_recipient.o: ../../include/htable.h
  cleanup_out_recipient.o: ../../include/iostuff.h
***************
*** 899,904 ****
--- 917,923 ----
  cleanup_region.o: ../../include/been_here.h
  cleanup_region.o: ../../include/cleanup_user.h
  cleanup_region.o: ../../include/dict.h
+ cleanup_region.o: ../../include/header_body_checks.h
  cleanup_region.o: ../../include/header_opts.h
  cleanup_region.o: ../../include/htable.h
  cleanup_region.o: ../../include/mail_conf.h
***************
*** 925,930 ****
--- 944,950 ----
  cleanup_rewrite.o: ../../include/been_here.h
  cleanup_rewrite.o: ../../include/cleanup_user.h
  cleanup_rewrite.o: ../../include/dict.h
+ cleanup_rewrite.o: ../../include/header_body_checks.h
  cleanup_rewrite.o: ../../include/header_opts.h
  cleanup_rewrite.o: ../../include/htable.h
  cleanup_rewrite.o: ../../include/iostuff.h
***************
*** 956,961 ****
--- 976,982 ----
  cleanup_state.o: ../../include/been_here.h
  cleanup_state.o: ../../include/cleanup_user.h
  cleanup_state.o: ../../include/dict.h
+ cleanup_state.o: ../../include/header_body_checks.h
  cleanup_state.o: ../../include/header_opts.h
  cleanup_state.o: ../../include/htable.h
  cleanup_state.o: ../../include/iostuff.h
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup.c ./src/cleanup/cleanup.c
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup.c	Tue Apr 28 13:49:23 2009
--- ./src/cleanup/cleanup.c	Sun Jun  7 16:37:46 2009
***************
*** 170,175 ****
--- 170,180 ----
  /* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR"
  /*	The macros that are sent to Milter (mail filter) applications
  /*	after the end of the message header.
+ /* .PP
+ /*	Available in Postfix version 2.7 and later:
+ /* .IP "\fBmilter_header_checks (empty)\fR"
+ /*	Optional lookup tables for content inspection of message headers
+ /*	that are produced by Milter applications.
  /* MIME PROCESSING CONTROLS
  /* .ad
  /* .fi
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup.h ./src/cleanup/cleanup.h
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup.h	Mon Apr 27 14:36:57 2009
--- ./src/cleanup/cleanup.h	Sun Jun  7 16:37:46 2009
***************
*** 32,37 ****
--- 32,38 ----
  #include <mime_state.h>
  #include <string_list.h>
  #include <cleanup_user.h>
+ #include <header_body_checks.h>
  
   /*
    * Milter library.
***************
*** 78,83 ****
--- 79,86 ----
      off_t   append_rcpt_pt_target;	/* target of above record */
      off_t   append_hdr_pt_offset;	/* append header here */
      off_t   append_hdr_pt_target;	/* target of above record */
+     off_t   append_meta_pt_offset;	/* append meta record here */
+     off_t   append_meta_pt_target;	/* target of above record */
      ssize_t rcpt_count;			/* recipient count */
      char   *reason;			/* failure reason */
      char   *smtp_reply;			/* failure reason, SMTP-style */
***************
*** 108,113 ****
--- 111,118 ----
      VSTRING *milter_ext_from;		/* externalized sender */
      VSTRING *milter_ext_rcpt;		/* externalized recipient */
      VSTRING *milter_err_text;		/* milter call-back reply */
+     HBC_CHECKS *milter_hbc_checks;	/* Milter header checks */
+     VSTRING *milter_hbc_reply;		/* Milter header checks reply */
  
      /*
       * Support for Milter body replacement requests.
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup_extracted.c ./src/cleanup/cleanup_extracted.c
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup_extracted.c	Sun May 10 14:50:52 2009
--- ./src/cleanup/cleanup_extracted.c	Sun Jun  7 16:37:46 2009
***************
*** 188,193 ****
--- 188,201 ----
  	    cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
  			       MAIL_ATTR_ENCODING, encoding);
  	state->flags |= CLEANUP_FLAG_INRCPT;
+ 	/* Make room to append more meta records. */
+ 	if (state->milters || cleanup_milters) {
+ 	    if ((state->append_meta_pt_offset = vstream_ftell(state->dst)) < 0)
+ 		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ 	    cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ 	    if ((state->append_meta_pt_target = vstream_ftell(state->dst)) < 0)
+ 		msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ 	}
      }
  
      /*
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup_init.c ./src/cleanup/cleanup_init.c
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup_init.c	Mon Mar 30 16:52:34 2009
--- ./src/cleanup/cleanup_init.c	Sun Jun  7 16:37:46 2009
***************
*** 161,166 ****
--- 161,167 ----
  char   *var_milt_eod_macros;		/* end-of-data macros */
  char   *var_milt_unk_macros;		/* unknown command macros */
  char   *var_cleanup_milters;		/* non-SMTP mail */
+ char   *var_milt_head_checks;		/* post-Milter header checks */
  int     var_auto_8bit_enc_hdr;		/* auto-detect 8bit encoding header */
  int     var_always_add_hdrs;		/* always add missing headers */
  
***************
*** 227,232 ****
--- 228,234 ----
      VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0,
      VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0,
      VAR_CLEANUP_MILTERS, DEF_CLEANUP_MILTERS, &var_cleanup_milters, 0, 0,
+     VAR_MILT_HEAD_CHECKS, DEF_MILT_HEAD_CHECKS, &var_milt_head_checks, 0, 0,
      0,
  };
  
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup_milter.c ./src/cleanup/cleanup_milter.c
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup_milter.c	Tue Apr 28 15:43:45 2009
--- ./src/cleanup/cleanup_milter.c	Sun Jun  7 16:37:46 2009
***************
*** 105,110 ****
--- 105,111 ----
  #include <lex_822.h>
  #include <is_header.h>
  #include <quote_821_local.h>
+ #include <dsn_util.h>
  
  /* Application-specific. */
  
***************
*** 216,221 ****
--- 217,523 ----
  #define STR(x)		vstring_str(x)
  #define LEN(x)		VSTRING_LEN(x)
  
+ /* cleanup_milter_hbc_log - log post-milter header/body_checks action */
+ 
+ static void cleanup_milter_hbc_log(void *context, const char *action,
+ 				        const char *where, const char *line,
+ 				           const char *optional_text)
+ {
+     const CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+     const char *attr;
+ 
+     vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.60s from %s[%s];",
+ 		    state->queue_id, where, action, where, line,
+ 		    state->client_name, state->client_addr);
+     if (state->sender)
+ 	vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
+     if (state->recip)
+ 	vstring_sprintf_append(state->temp1, " to=<%s>", state->recip);
+     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
+ 	vstring_sprintf_append(state->temp1, " proto=%s", attr);
+     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
+ 	vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
+     if (optional_text)
+ 	vstring_sprintf_append(state->temp1, ": %s", optional_text);
+     msg_info("%s", vstring_str(state->temp1));
+ }
+ 
+ /* cleanup_milter_header_prepend - prepend header to milter-generated header */
+ 
+ static void cleanup_milter_header_prepend(void *context, int rec_type,
+ 			         const char *buf, ssize_t len, off_t offset)
+ {
+     msg_warn("the milter_header/body_checks prepend action is not implemented");
+ }
+ 
+ /* cleanup_milter_hbc_extend - additional header/body_checks actions */
+ 
+ static char *cleanup_milter_hbc_extend(void *context, const char *command,
+ 			             int cmd_len, const char *optional_text,
+ 				         const char *where, const char *buf,
+ 				               ssize_t buf_len, off_t offset)
+ {
+     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+     const char *map_class = VAR_MILT_HEAD_CHECKS;	/* XXX */
+ 
+ #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+ 
+     /*
+      * These are currently our mutually-exclusive ways of not receiving mail:
+      * "reject" and "discard". Only these can be reported to the up-stream
+      * Postfix libmilter code, because sending any reply there causes Postfix
+      * libmilter to skip further "edit" requests. By way of safety net, each
+      * of these must also reset CLEANUP_FLAG_FILTER_ALL.
+      */
+ #define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \
+     ((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT))
+ 
+     /*
+      * We log all header/body-checks actions here, because we know the
+      * details of the message content that triggered the action. We report
+      * detail-free milter-reply values (reject/discard, stored in the
+      * milter_hbc_reply state member) to the Postfix libmilter code, so that
+      * Postfix libmilter can stop sending requests.
+      * 
+      * We also set all applicable cleanup flags here, because there is no
+      * guarantee that Postfix libmilter will propagate our own milter-reply
+      * value to cleanup_milter_inspect() which calls cleanup_milter_apply().
+      * The latter translates responses from Milter applications into cleanup
+      * flags, and logs the response text. Postfix libmilter can convey only
+      * one milter-reply value per email message, and that reply may even come
+      * from outside Postfix.
+      * 
+      * To suppress redundant logging, cleanup_milter_apply() does nothing when
+      * the milter-reply value matches the saved text in the milter_hbc_reply
+      * state member. As we remember only one milter-reply value, we can't
+      * report multiple milter-reply values per email message. We satisfy this
+      * constraint, because we already clear the CLEANUP_FLAG_FILTER_ALL flags
+      * to terminate further header inspection.
+      */
+     if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0)
+ 	return ((char *) buf);
+ 
+     if (STREQUAL(command, "REJECT", cmd_len)) {
+ 	const CLEANUP_STAT_DETAIL *detail;
+ 
+ 	if (state->reason)
+ 	    myfree(state->reason);
+ 	detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
+ 	if (*optional_text) {
+ 	    state->reason = dsn_prepend(detail->dsn, optional_text);
+ 	    if (*state->reason != '4' && *state->reason != '5') {
+ 		msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
+ 			 optional_text);
+ 		*state->reason = '4';
+ 	    }
+ 	} else {
+ 	    state->reason = dsn_prepend(detail->dsn, detail->text);
+ 	}
+ 	if (*state->reason == '4')
+ 	    state->errs |= CLEANUP_STAT_DEFER;
+ 	else
+ 	    state->errs |= CLEANUP_STAT_CONT;
+ 	state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ 	cleanup_milter_hbc_log(context, "reject", where, buf, state->reason);
+ 	vstring_sprintf(state->milter_hbc_reply, "%d %s",
+ 			detail->smtp, state->reason);
+ 	STR(state->milter_hbc_reply)[0] = *state->reason;
+ 	return ((char *) buf);
+     }
+     if (STREQUAL(command, "FILTER", cmd_len)) {
+ 	if (*optional_text == 0) {
+ 	    msg_warn("missing FILTER command argument in %s map", map_class);
+ 	} else if (strchr(optional_text, ':') == 0) {
+ 	    msg_warn("bad FILTER command %s in %s -- "
+ 		     "need transport:destination",
+ 		     optional_text, map_class);
+ 	} else {
+ 	    if (state->filter)
+ 		myfree(state->filter);
+ 	    state->filter = mystrdup(optional_text);
+ 	    cleanup_milter_hbc_log(context, "filter", where, buf,
+ 				   optional_text);
+ 	}
+ 	return ((char *) buf);
+     }
+     if (STREQUAL(command, "DISCARD", cmd_len)) {
+ 	cleanup_milter_hbc_log(context, "discard", where, buf, optional_text);
+ 	vstring_strcpy(state->milter_hbc_reply, "D");
+ 	state->flags |= CLEANUP_FLAG_DISCARD;
+ 	state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ 	return ((char *) buf);
+     }
+     if (STREQUAL(command, "HOLD", cmd_len)) {
+ 	if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
+ 	    cleanup_milter_hbc_log(context, "hold", where, buf, optional_text);
+ 	    state->flags |= CLEANUP_FLAG_HOLD;
+ 	}
+ 	return ((char *) buf);
+     }
+     if (STREQUAL(command, "REDIRECT", cmd_len)) {
+ 	if (strchr(optional_text, '@') == 0) {
+ 	    msg_warn("bad REDIRECT target \"%s\" in %s map -- "
+ 		     "need user@domain",
+ 		     optional_text, map_class);
+ 	} else {
+ 	    if (state->redirect)
+ 		myfree(state->redirect);
+ 	    state->redirect = mystrdup(optional_text);
+ 	    cleanup_milter_hbc_log(context, "redirect", where, buf,
+ 				   optional_text);
+ 	    state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ 	}
+ 	return ((char *) buf);
+     }
+     msg_warn("unknown command in %s map: %s", map_class, command);
+     return ((char *) buf);
+ }
+ 
+ /* cleanup_milter_header_checks - inspect Milter-generated header */
+ 
+ static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf)
+ {
+     char   *ret;
+ 
+     /*
+      * Milter application "add/insert/replace header" requests happen at the
+      * end-of-message stage, therefore all the header operations are relative
+      * to the primary message header.
+      */
+     ret = hbc_header_checks((void *) state, state->milter_hbc_checks,
+ 			    MIME_HDR_PRIMARY, (HEADER_OPTS *) 0,
+ 			    buf, (off_t) 0);
+     if (ret == 0) {
+ 	return (0);
+     } else {
+ 	if (ret != STR(buf)) {
+ 	    vstring_strcpy(buf, ret);
+ 	    myfree(ret);
+ 	}
+ 	return (1);
+     }
+ }
+ 
+ /* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */
+ 
+ static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state)
+ {
+     const char *myname = "cleanup_milter_hbc_add_meta_records";
+     off_t   reverse_ptr_offset;
+     off_t   new_meta_offset;
+ 
+     /*
+      * Note: this code runs while the Milter infrastructure is being torn
+      * down. For this reason we handle all I/O errors here on the spot,
+      * instead of reporting them back through the Milter infrastructure.
+      */
+ 
+     /*
+      * Sanity check.
+      */
+     if (state->append_meta_pt_offset < 0)
+ 	msg_panic("%s: no meta append pointer location", myname);
+     if (state->append_meta_pt_target < 0)
+ 	msg_panic("%s: no meta append pointer target", myname);
+ 
+     /*
+      * Allocate space after the end of the queue file, and write the meta
+      * record(s), followed by a reverse pointer record that points to the
+      * target of the old "meta record append" pointer record. This reverse
+      * pointer record becomes the new "meta record append" pointer record.
+      * Although the new "meta record append" pointer record will never be
+      * used, we update it here to make the code more similar to other code
+      * that inserts/appends content, so that common code can be factored out
+      * later.
+      */
+     if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ 	msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ 	state->errs |= CLEANUP_STAT_WRITE;
+ 	return;
+     }
+     if (state->filter != 0)
+ 	cleanup_out_string(state, REC_TYPE_FILT, state->filter);
+     if (state->redirect != 0)
+ 	cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
+     if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
+ 	msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ 	state->errs |= CLEANUP_STAT_WRITE;
+ 	return;
+     }
+     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ 		       (long) state->append_meta_pt_target);
+ 
+     /*
+      * Pointer flipping: update the old "meta record append" pointer record
+      * value with the location of the new meta record.
+      */
+     if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) {
+ 	msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ 	state->errs |= CLEANUP_STAT_WRITE;
+ 	return;
+     }
+     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ 		       (long) new_meta_offset);
+ 
+     /*
+      * Update the in-memory "meta append" pointer record location with the
+      * location of the reverse pointer record that follows the new meta
+      * record. The target of the "meta append" pointer record does not
+      * change; it's always the record that follows the dummy pointer record
+      * that was written while Postfix received the message.
+      */
+     state->append_meta_pt_offset = reverse_ptr_offset;
+ 
+     /*
+      * Note: state->append_meta_pt_target never changes.
+      */
+ }
+ 
+ /* cleanup_milter_header_checks_init - initialize post-Milter header checks */
+ 
+ static void cleanup_milter_header_checks_init(CLEANUP_STATE *state)
+ {
+ #define NO_NESTED_HDR_NAME	""
+ #define NO_NESTED_HDR_VALUE	""
+ #define NO_MIME_HDR_NAME	""
+ #define NO_MIME_HDR_VALUE	""
+ 
+     static /* XXX not const */ HBC_CALL_BACKS call_backs = {
+ 	cleanup_milter_hbc_log,
+ 	cleanup_milter_header_prepend,
+ 	cleanup_milter_hbc_extend,
+     };
+ 
+     state->milter_hbc_checks =
+ 	hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks,
+ 				 NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE,
+ 				 NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE,
+ 				 &call_backs);
+     state->milter_hbc_reply = vstring_alloc(100);
+     if (state->filter)
+ 	myfree(state->filter);
+     state->filter = 0;
+     if (state->redirect)
+ 	myfree(state->redirect);
+     state->redirect = 0;
+ }
+ 
+ /* cleanup_milter_hbc_finish - finalize post-Milter header checks */
+ 
+ static void cleanup_milter_hbc_finish(CLEANUP_STATE *state)
+ {
+     if (state->milter_hbc_checks)
+ 	hbc_header_checks_free(state->milter_hbc_checks);
+     state->milter_hbc_checks = 0;
+     if (state->milter_hbc_reply)
+ 	vstring_free(state->milter_hbc_reply);
+     state->milter_hbc_reply = 0;
+     if (CLEANUP_OUT_OK(state)
+ 	&& !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)
+ 	&& (state->filter || state->redirect))
+ 	cleanup_milter_hbc_add_meta_records(state);
+ }
+ 
   /*
    * Milter replies.
    */
***************
*** 306,311 ****
--- 608,625 ----
  	msg_panic("%s: no header append pointer target", myname);
  
      /*
+      * Return early when Milter header checks request that this header record
+      * be dropped.
+      */
+     buf = vstring_alloc(100);
+     vstring_sprintf(buf, "%s:%s%s", name, space, value);
+     if (state->milter_hbc_checks
+ 	&& cleanup_milter_header_checks(state, buf) == 0) {
+ 	vstring_free(buf);
+ 	return (0);
+     }
+ 
+     /*
       * Allocate space after the end of the queue file, and write the header
       * record(s), followed by a reverse pointer record that points to the
       * target of the old "header append" pointer record. This reverse pointer
***************
*** 313,322 ****
       */
      if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
  	msg_warn("%s: seek file %s: %m", myname, cleanup_path);
  	return (cleanup_milter_error(state, errno));
      }
-     buf = vstring_alloc(100);
-     vstring_sprintf(buf, "%s:%s%s", name, space, value);
      cleanup_out_header(state, buf);		/* Includes padding */
      vstring_free(buf);
      if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
--- 627,635 ----
       */
      if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
  	msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ 	vstring_free(buf);
  	return (cleanup_milter_error(state, errno));
      }
      cleanup_out_header(state, buf);		/* Includes padding */
      vstring_free(buf);
      if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
***************
*** 355,361 ****
      /*
       * In case of error while doing record output.
       */
!     return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
  }
  
  /* cleanup_find_header_start - find specific header instance */
--- 668,680 ----
      /*
       * In case of error while doing record output.
       */
!     return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
! 	    state->milter_hbc_reply && LEN(state->milter_hbc_reply) ?
! 	    STR(state->milter_hbc_reply) : 0);
! 
!     /*
!      * Note: state->append_hdr_pt_target never changes.
!      */
  }
  
  /* cleanup_find_header_start - find specific header instance */
***************
*** 672,677 ****
--- 991,1005 ----
       */
  
      /*
+      * Return early when Milter header checks request that this header record
+      * be dropped.
+      */
+     vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value);
+     if (state->milter_hbc_checks
+ 	&& cleanup_milter_header_checks(state, buf) == 0)
+ 	CLEANUP_PATCH_HEADER_RETURN(0);
+ 
+     /*
       * Write the new header to a new location after the end of the queue
       * file.
       */
***************
*** 679,685 ****
  	msg_warn("%s: seek file %s: %m", myname, cleanup_path);
  	CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
      }
-     vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value);
      cleanup_out_header(state, buf);		/* Includes padding */
      if (msg_verbose > 1)
  	msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset,
--- 1007,1012 ----
***************
*** 734,741 ****
      /*
       * In case of error while doing record output.
       */
!     CLEANUP_PATCH_HEADER_RETURN(CLEANUP_OUT_OK(state) ? 0 :
! 				cleanup_milter_error(state, 0));
  
      /*
       * Note: state->append_hdr_pt_target never changes.
--- 1061,1070 ----
      /*
       * In case of error while doing record output.
       */
!     CLEANUP_PATCH_HEADER_RETURN(
! 	       CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
! 		   state->milter_hbc_reply && LEN(state->milter_hbc_reply) ?
! 				STR(state->milter_hbc_reply) : 0);
  
      /*
       * Note: state->append_hdr_pt_target never changes.
***************
*** 1493,1498 ****
--- 1822,1850 ----
  	msg_info("%s: %s", myname, resp);
  
      /*
+      * Don't process our own milter_header/body checks replies. See comments
+      * in cleanup_milter_hbc_extend().
+      */
+     if (state->milter_hbc_reply &&
+ 	strcmp(resp, STR(state->milter_hbc_reply)) == 0)
+ 	return (0);
+ 
+     /*
+      * Don't process Milter replies that are redundant because header/body
+      * checks already decided that we will not receive the message; or Milter
+      * replies that would have conflicting effect with the outcome of
+      * header/body checks (for example, header_checks "discard" action
+      * followed by Milter "reject" reply). Logging both actions would look
+      * silly.
+      */
+     if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) {
+ 	if (msg_verbose)
+ 	    msg_info("%s: ignoring redundant or conflicting milter reply: %s",
+ 		     state->queue_id, resp);
+ 	return (0);
+     }
+ 
+     /*
       * Sanity check.
       */
      if (state->client_name == 0)
***************
*** 1619,1630 ****
--- 1971,1995 ----
  	cleanup_milter_client_init(state);
  
      /*
+      * Prologue: prepare for Milter header/body checks.
+      */
+     if (*var_milt_head_checks)
+ 	cleanup_milter_header_checks_init(state);
+ 
+     /*
       * Process mail filter replies. The reply format is verified by the mail
       * filter library.
       */
      if ((resp = milter_message(milters, state->handle->stream,
  			       state->data_offset)) != 0)
  	cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
+ 
+     /*
+      * Epilogue: finalize Milter header/body checks.
+      */
+     if (*var_milt_head_checks)
+ 	cleanup_milter_hbc_finish(state);
+ 
      if (msg_verbose)
  	msg_info("leave %s", myname);
  }
***************
*** 1779,1784 ****
--- 2144,2150 ----
  char   *var_milt_daemon_name = "host.example.com";
  char   *var_milt_v = DEF_MILT_V;
  MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters));
+ char   *var_milt_head_checks = "";
  
  /* Dummies to satisfy unused external references. */
  
***************
*** 1822,1829 ****
--- 2188,2199 ----
      msg_warn("    ins_header index name [value]");
      msg_warn("    upd_header index name [value]");
      msg_warn("    del_header index name");
+     msg_warn("    chg_from addr parameters");
      msg_warn("    add_rcpt addr");
+     msg_warn("    add_rcpt_par addr parameters");
      msg_warn("    del_rcpt addr");
+     msg_warn("    replbody pathname");
+     msg_warn("    header_checks type:name");
  }
  
  /* flatten_args - unparse partial command line */
***************
*** 1898,1903 ****
--- 2268,2282 ----
  			if ((state->append_rcpt_pt_target =
  			     vstream_ftell(state->dst)) < 0)
  			    msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
+ 		    } else if (curr_offset > state->xtra_offset
+ 			       && state->append_meta_pt_offset < 0) {
+ 			state->append_meta_pt_offset = curr_offset;
+ 			if (atol(STR(buf)) != 0)
+ 			    msg_fatal("file %s: bad dummy meta PTR record: %s",
+ 				      cleanup_path, STR(buf));
+ 			if ((state->append_meta_pt_target =
+ 			     vstream_ftell(state->dst)) < 0)
+ 			    msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
  		    }
  		} else {
  		    if (state->append_hdr_pt_offset < 0) {
***************
*** 1912,1918 ****
  		}
  	    }
  	    if (state->append_rcpt_pt_offset > 0
! 		&& state->append_hdr_pt_offset > 0)
  		break;
  	}
  	if (msg_verbose) {
--- 2291,2299 ----
  		}
  	    }
  	    if (state->append_rcpt_pt_offset > 0
! 		&& state->append_hdr_pt_offset > 0
! 		&& (rec_type == REC_TYPE_END
! 		    || state->append_meta_pt_offset > 0))
  		break;
  	}
  	if (msg_verbose) {
***************
*** 1944,1949 ****
--- 2325,2335 ----
      CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0);
  
      state->queue_id = mystrdup("NOQUEUE");
+     state->sender = mystrdup("sender");
+     state->recip = mystrdup("recipient");
+     state->client_name = "client_name";
+     state->client_addr = "client_addr";
+     state->flags |= CLEANUP_FLAG_FILTER_ALL;
  
      msg_vstream_init(argv[0], VSTREAM_ERR);
      var_line_limit = DEF_LINE_LIMIT;
***************
*** 1952,1957 ****
--- 2338,2344 ----
      for (;;) {
  	ARGV   *argv;
  	ssize_t index;
+ 	const char *resp = 0;
  
  	if (istty) {
  	    vstream_printf("- ");
***************
*** 1995,2007 ****
  	} else if (state->dst == 0) {
  	    msg_warn("no open queue file");
  	} else if (strcmp(argv->argv[0], "close") == 0) {
  	    close_queue_file(state);
  	} else if (strcmp(argv->argv[0], "add_header") == 0) {
  	    if (argv->argc < 2) {
  		msg_warn("bad add_header argument count: %d", argv->argc);
  	    } else {
  		flatten_args(arg_buf, argv->argv + 2);
! 		cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "ins_header") == 0) {
  	    if (argv->argc < 3) {
--- 2382,2404 ----
  	} else if (state->dst == 0) {
  	    msg_warn("no open queue file");
  	} else if (strcmp(argv->argv[0], "close") == 0) {
+ 	    if (*var_milt_head_checks) {
+ 		cleanup_milter_hbc_finish(state);
+ 		var_milt_head_checks = "";
+ 	    }
  	    close_queue_file(state);
+ 	} else if (state->milter_hbc_reply && LEN(state->milter_hbc_reply)) {
+ 	    /* Postfix libmilter would skip further requests. */
+ 	    msg_info("ignoring: %s %s %s", argv->argv[0],
+ 		     argv->argc > 1 ? argv->argv[1] : "",
+ 		     argv->argc > 2 ? argv->argv[2] : "");
+ 	    continue;
  	} else if (strcmp(argv->argv[0], "add_header") == 0) {
  	    if (argv->argc < 2) {
  		msg_warn("bad add_header argument count: %d", argv->argc);
  	    } else {
  		flatten_args(arg_buf, argv->argv + 2);
! 		resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "ins_header") == 0) {
  	    if (argv->argc < 3) {
***************
*** 2010,2016 ****
  		msg_warn("bad ins_header index value");
  	    } else {
  		flatten_args(arg_buf, argv->argv + 3);
! 		cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "upd_header") == 0) {
  	    if (argv->argc < 3) {
--- 2407,2413 ----
  		msg_warn("bad ins_header index value");
  	    } else {
  		flatten_args(arg_buf, argv->argv + 3);
! 		resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "upd_header") == 0) {
  	    if (argv->argc < 3) {
***************
*** 2019,2025 ****
  		msg_warn("bad upd_header index value");
  	    } else {
  		flatten_args(arg_buf, argv->argv + 3);
! 		cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "del_header") == 0) {
  	    if (argv->argc != 3) {
--- 2416,2422 ----
  		msg_warn("bad upd_header index value");
  	    } else {
  		flatten_args(arg_buf, argv->argv + 3);
! 		resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf));
  	    }
  	} else if (strcmp(argv->argv[0], "del_header") == 0) {
  	    if (argv->argc != 3) {
***************
*** 2072,2084 ****
--- 2469,2498 ----
  		    vstream_fclose(fp);
  		}
  	    }
+ 	} else if (strcmp(argv->argv[0], "header_checks") == 0) {
+ 	    if (argv->argc != 2) {
+ 		msg_warn("bad header_checks argument count: %d", argv->argc);
+ 	    } else if (*var_milt_head_checks) {
+ 		msg_warn("can't change header checks");
+ 	    } else {
+ 		var_milt_head_checks = mystrdup(argv->argv[1]);
+ 		cleanup_milter_header_checks_init(state);
+ 	    }
  	} else {
  	    msg_warn("bad command: %s", argv->argv[0]);
  	}
  	argv_free(argv);
+ 	if (resp)
+ 	    cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
      }
      vstring_free(inbuf);
      vstring_free(arg_buf);
+     if (state->append_meta_pt_offset >= 0) {
+ 	if (state->flags)
+ 	    msg_info("flags = %s", cleanup_strflags(state->flags));
+ 	if (state->errs)
+ 	    msg_info("errs = %s", cleanup_strerror(state->errs));
+     }
      cleanup_state_free(state);
  
      return (0);
diff -cr /var/tmp/postfix-2.6.2/src/cleanup/cleanup_state.c ./src/cleanup/cleanup_state.c
*** /var/tmp/postfix-2.6.2/src/cleanup/cleanup_state.c	Mon Apr 27 14:37:16 2009
--- ./src/cleanup/cleanup_state.c	Sun Jun  7 16:37:46 2009
***************
*** 97,102 ****
--- 97,106 ----
      state->append_rcpt_pt_target = -1;
      state->append_hdr_pt_offset = -1;
      state->append_hdr_pt_target = -1;
+     state->append_meta_pt_offset = -1;
+     state->append_meta_pt_target = -1;
+     state->milter_hbc_checks = 0;
+     state->milter_hbc_reply = 0;
      state->rcpt_count = 0;
      state->reason = 0;
      state->smtp_reply = 0;
diff -cr /var/tmp/postfix-2.6.2/src/global/mail_params.h ./src/global/mail_params.h
*** /var/tmp/postfix-2.6.2/src/global/mail_params.h	Mon May 11 10:48:42 2009
--- ./src/global/mail_params.h	Thu Jun  4 18:31:03 2009
***************
*** 2982,2987 ****
--- 2982,2991 ----
  #define DEF_MILT_V			"$" VAR_MAIL_NAME " $" VAR_MAIL_VERSION
  extern char *var_milt_v;
  
+ #define VAR_MILT_HEAD_CHECKS		"milter_header_checks"
+ #define DEF_MILT_HEAD_CHECKS		""
+ extern char *var_milt_head_checks;
+ 
   /*
    * What internal mail do we inspect/stamp/etc.? This is not yet safe enough
    * to enable world-wide.