From: Wietse Z Venema Date: Tue, 18 Nov 2025 05:00:00 +0000 (-0500) Subject: postfix-3.11-20251118 X-Git-Tag: v3.11.0-RC1~13 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f2fb1f32e6e0bd2cda7a3eb6e5d9563cd093b9ea;p=thirdparty%2Fpostfix.git postfix-3.11-20251118 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index 15c184b4f..9d0223734 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -30044,3 +30044,11 @@ Apologies for any names omitted. Bugfix: garbage in the postconf.5 text. Perl numbers () regexp patterns in unexpected ways. File: mantools/postconf2man. + +20251118 + + Code health: unit tests for the non_empty_end_of_header_action + feature. File: cleanup_message_test.c. + + Miscellaneous documentation fixes. Files: proto/postconf.proto, + mantools/postconf2html. diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index f0d689939..950bd6def 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -12398,7 +12398,7 @@ used in plaintext as permitted by the opportunistic TLS policy.
tls=dane
DANE policy compliant, no downgrade.
-
tls=(dane:halfdane)
Opportunistic DANE. The connection +
tls=dane:halfdane
Opportunistic DANE. The connection security was downgraded to 'halfdane' to indicate that mail server records were not DNSSEC-signed.
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 06c4e8bcc..d95a6b227 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -7878,7 +7878,7 @@ used in plaintext as permitted by the opportunistic TLS policy. .IP "tls=dane" DANE policy compliant, no downgrade. .br -.IP "tls=(dane:halfdane)" +.IP "tls=dane:halfdane" Opportunistic DANE. The connection security was downgraded to 'halfdane' to indicate that mail server records were not DNSSEC\-signed. diff --git a/postfix/mantools/postconf2man b/postfix/mantools/postconf2man index 189e84e85..7433c91b1 100755 --- a/postfix/mantools/postconf2man +++ b/postfix/mantools/postconf2man @@ -39,8 +39,7 @@ while(<>) { #$block =~ s/\n\./\n\\\&./g; $block =~ s/\n\./\n\134\&./g; $block =~ s/\n'/\n\134\&'/g; - $block =~ s/(<(p|dd|br)+>\s*)'/$1\134\&'/g; - $block =~ s/(<(p|dd|br)+>\s*)\./$1\134\&./g; + $block =~ s/(<(p|dd|br)+>\s*)([.'])/$1\134\&$3/g; if ($block =~ /

/) { $block =~ s/

]+>([^<]+)<\/a><\/H2>/\n.SH \1\n/g; $block =~ tr/a-z/A-Z/; diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index b642166b4..ff3ed381d 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -20032,7 +20032,7 @@ used in plaintext as permitted by the opportunistic TLS policy.
tls=dane
DANE policy compliant, no downgrade.
-
tls=(dane:halfdane)
Opportunistic DANE. The connection +
tls=dane:halfdane
Opportunistic DANE. The connection security was downgraded to 'halfdane' to indicate that mail server records were not DNSSEC-signed.
diff --git a/postfix/src/cleanup/Makefile.in b/postfix/src/cleanup/Makefile.in index 859a030cb..38fe07743 100644 --- a/postfix/src/cleanup/Makefile.in +++ b/postfix/src/cleanup/Makefile.in @@ -15,7 +15,8 @@ HDRS = TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) -TESTPROG= cleanup_masquerade cleanup_milter cleanup_envelope_test +TESTPROG= cleanup_masquerade cleanup_milter cleanup_envelope_test \ + cleanup_message_test PROG = cleanup INC_DIR = ../../include LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ @@ -62,11 +63,19 @@ CLEANUP_SIZE_TEST_OBJS = cleanup_envelope.o cleanup_state.o cleanup_out.o \ cleanup_envelope_test: cleanup_envelope_test.o $(CLEANUP_SIZE_TEST_OBJS) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(CLEANUP_SIZE_TEST_OBJS) $(LIBS) $(SYSLIBS) -tests: cleanup_masquerade_test milter_tests test_cleanup_envelope +CLEANUP_MESG_TEST_OBJS = cleanup_message.o cleanup_state.o cleanup_out.o +cleanup_message_test: cleanup_message_test.o $(CLEANUP_MESG_TEST_OBJS) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(CLEANUP_MESG_TEST_OBJS) $(LIBS) $(SYSLIBS) + +tests: cleanup_masquerade_test milter_tests test_cleanup_envelope \ + test_cleanup_message test_cleanup_envelope: cleanup_envelope_test $(SHLIB_ENV) $(VALGRIND) ./cleanup_envelope_test +test_cleanup_message: cleanup_message_test + $(SHLIB_ENV) $(VALGRIND) ./cleanup_message_test + milter_tests: cleanup_milter_test bug_tests \ cleanup_milter_test2 cleanup_milter_test3 cleanup_milter_test4 \ cleanup_milter_test5 cleanup_milter_test6 cleanup_milter_test7 \ diff --git a/postfix/src/cleanup/cleanup_message.c b/postfix/src/cleanup/cleanup_message.c index 91a7e225a..32408a9fa 100644 --- a/postfix/src/cleanup/cleanup_message.c +++ b/postfix/src/cleanup/cleanup_message.c @@ -928,7 +928,7 @@ static void cleanup_header_done_callback(void *context) * Get the current error state before mime_state_update() can return it. */ mime_errs = mime_state_status(state->mime_state); - if ((mime_errs && MIME_ERR_NON_EMPTY_EOH) + if ((mime_errs & MIME_ERR_NON_EMPTY_EOH) && cleanup_non_empty_eoh_action == NON_EMPTY_EOH_CODE_ADD_HDR) cleanup_out_string(state, REC_TYPE_NORM, "MIME-Error: message header was not terminated by empty line"); diff --git a/postfix/src/cleanup/cleanup_message_test.c b/postfix/src/cleanup/cleanup_message_test.c new file mode 100644 index 000000000..bef2b9dc7 --- /dev/null +++ b/postfix/src/cleanup/cleanup_message_test.c @@ -0,0 +1,342 @@ +/*++ +/* NAME +/* cleanup_message_test 1t +/* SUMMARY +/* cleanup_message unit tests +/* SYNOPSIS +/* ./cleanup_message_test +/* DESCRIPTION +/* cleanup_message_test runs and logs each configured test, reports if +/* a test is a PASS or FAIL, and returns an exit status of zero if +/* all tests are a PASS. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ +#include + + /* + * Stubs for configuration parameter dependencies. + */ +int var_always_add_hdrs; +int var_auto_8bit_enc_hdr; +int var_body_check_len; +int var_cleanup_mask_stray_cr_lf; +int var_dup_filter_limit; +int var_force_mime_iconv; +char *var_full_name_encoding_charset; +char *var_header_checks; +int var_hopcount_limit; +char *var_mimehdr_checks; +char *var_nesthdr_checks; +char *var_rcpt_witheld; +int var_reqtls_esmtp_hdr; + +MAPS *cleanup_comm_canon_maps; +MAPS *cleanup_send_canon_maps; +MAPS *cleanup_rcpt_canon_maps; +MAPS *cleanup_virt_alias_maps; +int cleanup_comm_canon_flags; +int cleanup_send_canon_flags; +int cleanup_rcpt_canon_flags; +ARGV *cleanup_masq_domains; +MAPS *cleanup_header_checks; +MAPS *cleanup_mimehdr_checks; +MAPS *cleanup_nesthdr_checks; +MAPS *cleanup_body_checks; +STRING_LIST *cleanup_masq_exceptions; +char *var_masq_classes; +MAPS *cleanup_send_bcc_maps; +MAPS *cleanup_rcpt_bcc_maps; +MILTERS *cleanup_milters; +int cleanup_ext_prop_mask; +int cleanup_hfrom_format; +int cleanup_masq_flags; +int cleanup_non_empty_eoh_action; +char *cleanup_path; +VSTRING *cleanup_reject_chars; +VSTRING *cleanup_strip_chars; + +static void test_setup(void) +{ + var_always_add_hdrs = DEF_ALWAYS_ADD_HDRS; + var_auto_8bit_enc_hdr = DEF_AUTO_8BIT_ENC_HDR; + var_body_check_len = DEF_BODY_CHECK_LEN; + var_cleanup_mask_stray_cr_lf = DEF_CLEANUP_MASK_STRAY_CR_LF; + var_dup_filter_limit = DEF_DUP_FILTER_LIMIT; + var_force_mime_iconv = DEF_FORCE_MIME_ICONV; + var_full_name_encoding_charset = DEF_FULL_NAME_ENCODING_CHARSET; + var_header_checks = DEF_HEADER_CHECKS; + var_hopcount_limit = DEF_HOPCOUNT_LIMIT; + var_mimehdr_checks = DEF_MIMEHDR_CHECKS; + var_nesthdr_checks = DEF_NESTHDR_CHECKS; + var_rcpt_witheld = DEF_RCPT_WITHELD; + cleanup_hfrom_format = HFROM_FORMAT_CODE_STD; + cleanup_masq_flags = (CLEANUP_MASQ_FLAG_ENV_FROM | \ + CLEANUP_MASQ_FLAG_HDR_FROM | CLEANUP_MASQ_FLAG_HDR_RCPT); + cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_FIX_QUIETLY; + var_masq_classes = DEF_MASQ_CLASSES; + var_drop_hdrs = DEF_DROP_HDRS; + var_header_limit = 2000; + var_line_limit = DEF_LINE_LIMIT; + var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM; + var_reqtls_esmtp_hdr = 1; +} + + /* + * Stubs for cleanup_message.c dependencies. + */ +void cleanup_extracted(CLEANUP_STATE *state, int type, const char *data, + ssize_t len) +{ + msg_panic("cleanup_extracted"); +} + +int cleanup_map11_tree(CLEANUP_STATE *state, TOK822 *tree, + MAPS *maps, int propagate) +{ + return (0); +} + +int cleanup_masquerade_tree(CLEANUP_STATE *state, TOK822 *tree, + ARGV *masq_domains) +{ + return (0); +} + +int cleanup_rewrite_tree(const char *context_name, TOK822 *tree) +{ + return (0); +} + + /* + * Stubs for cleanup_state.c dependencies. + */ +void cleanup_envelope(CLEANUP_STATE *state, int type, const char *data, + ssize_t len) +{ + msg_panic("cleanup_envelope"); +} + +void cleanup_region_done(CLEANUP_STATE *state) +{ +} + + /* + * Intercept cleanup_out_header(), cleanup_out_string(), + * cleanup_out_format() + */ +int rec_put(VSTREAM *stream, int type, const char *data, ssize_t len) +{ + if (msg_verbose) + msg_info("fake_%s: %c '%.*s' %d", + __func__, type, (int) len, data, (int) len); + + /* + * TOO(wietse) envelope records. + */ if (type == REC_TYPE_NORM) + vstream_fprintf(stream, "%.*s\n", (int) len, data); + else if (type == REC_TYPE_CONT) + vstream_fprintf(stream, "%.*s", (int) len, data); + return (type); +} + + /* + * Tests and test cases. + */ +typedef struct TEST_CASE { + const char *label; /* identifies test case */ + int (*action) (const struct TEST_CASE *); +} TEST_CASE; + +#define PASS 1 +#define FAIL 0 + +static int test_action(const TEST_CASE *tp, const char *inputs[], + int want_errs, const char *want_text) +{ + VSTRING *got_text = vstring_alloc(100); + VSTREAM *dst = vstream_memopen(got_text, O_WRONLY); + CLEANUP_STATE *state = 0; + int ret = PASS; + const char *const * cpp; + + if (dst == 0) { + msg_warn("vstream_memopen: %m"); + ret = FAIL; + } else { + state = cleanup_state_alloc((VSTREAM *) 0); + state->queue_id = mystrdup("queue_id"); + state->flags |= CLEANUP_FLAG_FILTER; + state->sender = mystrdup("sender"); + state->recip = mystrdup("recip"); + /* Don't add 'missing' headers. */ + state->headers_seen = ~0; + + state->dst = dst; + state->action = cleanup_message; + for (cpp = inputs; *cpp; cpp++) + state->action(state, REC_TYPE_NORM, *cpp, strlen(*cpp)); + state->action(state, REC_TYPE_XTRA, "", 0); + (void) vstream_fclose(state->dst); + state->dst = 0; + if (state->errs != want_errs) { + msg_warn("cleanup_envelope: got: '%s', want: '%s'", + cleanup_strerror(state->errs), + cleanup_strerror(want_errs)); + ret = FAIL; + } else if (want_errs == CLEANUP_STAT_OK + && strcmp(vstring_str(got_text), want_text) != 0) { + msg_warn("got '%s', want: '%s'", vstring_str(got_text), + want_text); + ret = FAIL; + } + } + + /* + * Cleanup. + */ + vstring_free(got_text); + if (state) + cleanup_state_free(state); + return (ret); +} + +static int silently_adds_empty_line(const TEST_CASE *tp) +{ + static const char *inputs[] = { + "Received: text", + "bad header: text", + 0, + }; + int want_errs = CLEANUP_STAT_OK; + const char *want_text = { + "Received: text\n" + "\n" + "bad header: text\n" + }; + + cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_FIX_QUIETLY; + return (test_action(tp, inputs, want_errs, want_text)); +} + +static int adds_informative_header(const TEST_CASE *tp) +{ + static const char *inputs[] = { + "Received: text", + "bad header: text", + 0, + }; + int want_errs = CLEANUP_STAT_OK; + const char *want_text = { + "Received: text\n" + "MIME-Error: message header was not terminated by empty line\n" + "\n" + "bad header: text\n" + }; + + cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_ADD_HDR; + return (test_action(tp, inputs, want_errs, want_text)); +} + +static int rejects_non_empty_header_end(const TEST_CASE *tp) +{ + static const char *inputs[] = { + "Received: text", + "bad header text", + 0, + }; + int want_errs = CLEANUP_STAT_CONT; + const char *want_text = 0; + + cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_REJECT; + return (test_action(tp, inputs, want_errs, want_text)); +} + +static const TEST_CASE test_cases[] = { + {"silently_adds_empty_line", + silently_adds_empty_line, + }, + {"adds_informative_header", + adds_informative_header, + }, + {"rejects_non_empty_header_end", + rejects_non_empty_header_end, + }, + {0}, +}; + +static NORETURN usage(const char *myname) +{ + msg_fatal("usage: %s [-v]", myname); +} + +int main(int argc, char **argv) +{ + const char *myname = sane_basename((VSTRING *) 0, argv[0]); + const TEST_CASE *tp; + int pass = 0; + int fail = 0; + int ch; + + /* XXX How to avoid linking in mail_params.o? */ + + msg_vstream_init(myname, VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + default: + usage(myname); + case 'v': + msg_verbose++; + break; + } + } + + for (tp = test_cases; tp->label != 0; tp++) { + test_setup(); + msg_info("RUN %s", tp->label); + if (tp->action(tp) != PASS) { + fail++; + msg_info("FAIL %s", tp->label); + } else { + msg_info("PASS %s", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + exit(fail != 0); +} diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 6fd5d9510..0eb572e7d 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20251117" +#define MAIL_RELEASE_DATE "20251118" #define MAIL_VERSION_NUMBER "3.11" #ifdef SNAPSHOT