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: + + milter_header_checks (empty) + Optional lookup tables for content inspection of + message headers that are produced by Milter appli- + cations. + MIME PROCESSING CONTROLS 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 ---- +
milter_header_checks + (default: empty)
+ +

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. +

+ +

The following example sends all mail that is marked as SPAM to + a spam handling machine. Note that matches are case-insensitive + by default.

+ +
+
+ /etc/postfix/main.cf:
+     milter_header_checks = pcre:/etc/postfix/milter_header_checks
+ 
+
+ /etc/postfix/milter_header_checks:
+     /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+ 
+
+ +

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.

+ +

This feature is available in Postfix 2.7, and as an optional + patch for Postfix 2.6.

+ + +
+
milter_helo_macros (default: see "postconf -d" output)
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;$&;g; s;\bmilter_end_of_data_macros\b;$&;g; s;\bmilter_end_of_header_macros\b;$&;g; + s;\bmilter_header_checks\b;$&;g; # Multi-instance support s;\bmulti_instance_directo[-]*\n*[ ]*ries\b;$&;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.

+ + %PARAM milter_header_checks + +

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. +

+ +

The following example sends all mail that is marked as SPAM to + a spam handling machine. Note that matches are case-insensitive + by default.

+ +
+
+ /etc/postfix/main.cf:
+     milter_header_checks = pcre:/etc/postfix/milter_header_checks
+ 
+
+ /etc/postfix/milter_header_checks:
+     /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+ 
+
+ +

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.

+ +

This feature is available in Postfix 2.7, and as an optional + patch for Postfix 2.6.

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 #include #include + #include /* * 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 #include #include + #include /* 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.