]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11-20251118
authorWietse Z Venema <wietse@porcupine.org>
Tue, 18 Nov 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sun, 23 Nov 2025 01:49:01 +0000 (12:49 +1100)
postfix/HISTORY
postfix/html/postconf.5.html
postfix/man/man5/postconf.5
postfix/mantools/postconf2man
postfix/proto/postconf.proto
postfix/src/cleanup/Makefile.in
postfix/src/cleanup/cleanup_message.c
postfix/src/cleanup/cleanup_message_test.c [new file with mode: 0644]
postfix/src/global/mail_version.h

index 15c184b4ff9b05e0555f82e561ac3f91a0409a24..9d0223734c5c2236da56e250421a15ea98a90c54 100644 (file)
@@ -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.
index f0d6899399ba3c66af361723d8b99696c2bd8e02..950bd6defdea7c14a03bd5fe61883d48f9c7f18d 100644 (file)
@@ -12398,7 +12398,7 @@ used in plaintext as permitted by the opportunistic TLS policy. </dd>
 
 <dt> tls=dane </dt> <dd> DANE policy compliant, no downgrade. </dd>
 
-<dt> tls=(dane:halfdane) </dt> <dd> Opportunistic DANE. The connection
+<dt> tls=dane:halfdane </dt> <dd> Opportunistic DANE. The connection
 security was downgraded to '<tt>halfdane</tt>' to indicate that
 mail server records were not DNSSEC-signed. </dd>
 
index 06c4e8bcc81b239b961ae877681dbf92baf87d35..d95a6b2276c7988cb68ea85882aac151ad9d8a1a 100644 (file)
@@ -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.
index 189e84e859ca07341a343e00ec1d6282d24c6709..7433c91b166dce802ac19d00e31ecc6b9ed039a5 100755 (executable)
@@ -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 =~ /<H2>/) {
        $block =~ s/<H2><a[^>]+>([^<]+)<\/a><\/H2>/\n.SH \1\n/g;
        $block =~ tr/a-z/A-Z/;
index b642166b435bddb67c9512b1b954fa52659cda70..ff3ed381d1cf06ed184322b26fc5e2c93539b673 100644 (file)
@@ -20032,7 +20032,7 @@ used in plaintext as permitted by the opportunistic TLS policy. </dd>
 
 <dt> tls=dane </dt> <dd> DANE policy compliant, no downgrade. </dd>
 
-<dt> tls=(dane:halfdane) </dt> <dd> Opportunistic DANE. The connection
+<dt> tls=dane:halfdane </dt> <dd> Opportunistic DANE. The connection
 security was downgraded to '<tt>halfdane</tt>' to indicate that
 mail server records were not DNSSEC-signed. </dd>
 
index 859a030cb66a2145bbef10b5cab1fc2ec09468c1..38fe077437af5041245dc07506b9a80af606d1a8 100644 (file)
@@ -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 \
index 91a7e225a33e5e414ee9b009086882c33434624a..32408a9fa1c54a2ae6663dc5910a4ee2d57d371c 100644 (file)
@@ -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 (file)
index 0000000..bef2b9d
--- /dev/null
@@ -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 <sys_defs.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <cleanup_user.h>
+#include <hfrom_format.h>
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+
+ /*
+  * Application-specific.
+  */
+#include <cleanup.h>
+
+ /*
+  * 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);
+}
index 6fd5d9510a83f67c2f94f8eb7226acc5429ded04..0eb572e7d8f9d22a99b7d71a5c9930cd64e46197 100644 (file)
@@ -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