]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.10-20250117
authorWietse Z Venema <wietse@porcupine.org>
Fri, 17 Jan 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Fri, 24 Jan 2025 16:52:52 +0000 (03:52 +1100)
22 files changed:
postfix/HISTORY
postfix/README_FILES/TLSRPT_README
postfix/RELEASE_NOTES
postfix/html/TLSRPT_README.html
postfix/proto/TLSRPT_README.html
postfix/proto/stop.double-history
postfix/proto/stop.spell-history
postfix/src/cleanup/Makefile.in
postfix/src/cleanup/cleanup.h
postfix/src/cleanup/cleanup_envelope_test.c
postfix/src/cleanup/cleanup_message.c
postfix/src/global/Makefile.in
postfix/src/global/header_opts.c
postfix/src/global/header_opts.h
postfix/src/global/mail_version.h
postfix/src/global/sendopts.c
postfix/src/global/sendopts.h
postfix/src/global/sendopts_test.c [new file with mode: 0644]
postfix/src/smtp/Makefile.in
postfix/src/smtp/smtp_connect.c
postfix/src/util/argv.c
postfix/src/util/hex_code.c

index c6a38de56cf5da8ba1d61017795d5d2abae2aaca..6c81e8c9ec88eb9b3ba8921887999b36a2bcbd22 100644 (file)
@@ -28854,3 +28854,27 @@ Apologies for any names omitted.
        pickup/pickup.c, qmgr/qmgr.h, qmgr/qmgr_active.c,
        qmgr/qmgr_deliver.c, qmgr/qmgr_message.c, qmqpd/qmqpd.c,
        smtp/smtp_proto.c, smtpd/smtpd.c, verify/verify.c,
+
+20250117
+
+       Cleanup: factored out the sendopts.c unit test code, and
+       added two missing tests. File: global/sendopts_test.c.
+
+       Cleanup: increased the capacity to remember which types of
+       message headers have been seen, and encapsulated some set
+       operations. Files: cleanup/cleanup.h, cleanup/cleanup_message.c.
+
+       Feature: support for the RFC 8689 "TLS-Required: no" message
+       header. This limits the Postfix SMTP client TLS security
+       level to "smtp_tls_security = may", which does not authenticate
+       remote SMTP server TLS certificates, and which allows falling
+       back to plaintext. This is needed for the delivery of
+       messages such as TLSRPT summaries, which should be sent
+       even when the preferred TLS security policy cannot be
+       enforced. Support for the REQUIRETLS ESMTP extension remains
+       future work. Files: cleanup/cleanup_message.c,
+       global/header_opts.c, global/header_opts.h, smtp/smtp_connect.c,
+       proto/TLSRPT_README.html.
+
+       Cleanup: memory leaks in test code. Files: util/hex_code.c,
+       util/argv.c.
index 14ecf3b80f553189ebf87d6d4ceef044a0a67118..2553348e4b3cf3d1d66d1b30af35414c0ab794a1 100644 (file)
@@ -181,6 +181,12 @@ request that TLS enforcement will be disabled when submitting an email message.
 
 Options:
 
+  * Specify the "T\bTL\bLS\bS-\b-R\bRe\beq\bqu\bui\bir\bre\bed\bd:\b: n\bno\bo" message header, defined in RFC 8689, to
+    reduce the TLS security level to "m\bma\bay\by" (that is, do not verify remote SMTP
+    server certificates, and fall back to plaintext if TLS is unavailable).
+
+    This feature is available in Postfix 3.10 and later.
+
   * Do nothing. When TLS security enforcement is required but fails, a TLSRPT
     summary message will be delayed until the problem is addressed, or until
     the message expires in the mail queue. Keep in mind that TLSRPT is not a
index 7a9ea40c0eefcb61f061f67e05e73c2e719563d9..68e2ba84ab22229e7cf0ca345f017a78b329cc40 100644 (file)
@@ -25,6 +25,17 @@ now also distributed with the more recent Eclipse Public License
 (EPL) 2.0. Recipients can choose to take the software under the
 license of their choice. Those who are more comfortable with the
 IPL can continue with that license.
+[Feature 20250117]
+
+Support for the RFC 8689 "TLS-Required: no" message header to request
+delivery of messages such as TLSRPT summaries even if the preferred
+TLS security policy cannot be enforced. This limits the Postfix
+SMTP client to "smtp_tls_security_level = may" which does not
+authenticate server certificates and which allows falling back to
+plaintext.
+
+Support for the REQUIRETLS SMTP service extension remains future work.
 
 [Incompat 20250116]
 
index 783be6b8a8e1d3445fa3d7105ccb194e0d686cbc..af99332574a98a4b0d2fd3530b4d5aad95e2de52 100644 (file)
@@ -276,6 +276,12 @@ when submitting an email message. </p>
 
 <ul>
 
+<li> <p> Specify the "<b>TLS-Required: no</b>" message header,
+defined in <a href="https://tools.ietf.org/html/rfc8689">RFC 8689</a>, to reduce the TLS security level to "<b>may</b>"
+(that is, do not verify remote SMTP server certificates, and fall
+back to plaintext if TLS is unavailable). <br> <br> This feature
+is available in Postfix 3.10 and later. </p>
+
 <li> <p> Do nothing. When TLS security enforcement is required but
 fails, a TLSRPT summary message will be delayed
 until the problem is addressed, or until the message expires
index 76c448539cd6ca6cc66316d0bfc9f364586b06b1..1d6820d4e7b0fffe38f13c5c3e7cecad80e19d71 100644 (file)
@@ -276,6 +276,12 @@ when submitting an email message. </p>
 
 <ul>
 
+<li> <p> Specify the "<b>TLS-Required: no</b>" message header,
+defined in RFC 8689, to reduce the TLS security level to "<b>may</b>"
+(that is, do not verify remote SMTP server certificates, and fall
+back to plaintext if TLS is unavailable). <br> <br> This feature
+is available in Postfix 3.10 and later. </p>
+
 <li> <p> Do nothing. When TLS security enforcement is required but
 fails, a TLSRPT summary message will be delayed
 until the problem is addressed, or until the message expires
index 0f36085d582d4ab196f0cfedb2e87d8cc456f938..ef83bc3c8c17fc0d8af3fbdaf74cfa901a66da61 100644 (file)
@@ -158,3 +158,4 @@ proto  proto socketmap_table
  pickup pickup c qmgr qmgr h qmgr qmgr_active c 
  qmgr qmgr_deliver c qmgr qmgr_message c qmqpd qmqpd c 
  smtp smtp_proto c smtpd smtpd c verify verify c 
+ operations Files cleanup cleanup h cleanup cleanup_message c 
index 8a854098547440843eb9be56021fed97da7c64ed..74e0858b7c4920bcb3758d4da1da5417c6169a35 100644 (file)
@@ -98,3 +98,4 @@ diffs
 CLOSEFROM
 Roessner
 bitflags
+Schulze
index adf21aa2189863de319e82a114c5530c5c4015d2..306d3b537587436044f2ffa9f561e9d73f84b3e0 100644 (file)
@@ -959,6 +959,42 @@ cleanup_envelope.o: ../../include/vstream.h
 cleanup_envelope.o: ../../include/vstring.h
 cleanup_envelope.o: cleanup.h
 cleanup_envelope.o: cleanup_envelope.c
+cleanup_envelope_test.o: ../../include/argv.h
+cleanup_envelope_test.o: ../../include/attr.h
+cleanup_envelope_test.o: ../../include/been_here.h
+cleanup_envelope_test.o: ../../include/check_arg.h
+cleanup_envelope_test.o: ../../include/cleanup_user.h
+cleanup_envelope_test.o: ../../include/dict.h
+cleanup_envelope_test.o: ../../include/dsn_mask.h
+cleanup_envelope_test.o: ../../include/header_body_checks.h
+cleanup_envelope_test.o: ../../include/header_opts.h
+cleanup_envelope_test.o: ../../include/htable.h
+cleanup_envelope_test.o: ../../include/mail_conf.h
+cleanup_envelope_test.o: ../../include/mail_params.h
+cleanup_envelope_test.o: ../../include/mail_stream.h
+cleanup_envelope_test.o: ../../include/maps.h
+cleanup_envelope_test.o: ../../include/match_list.h
+cleanup_envelope_test.o: ../../include/milter.h
+cleanup_envelope_test.o: ../../include/mime_state.h
+cleanup_envelope_test.o: ../../include/msg.h
+cleanup_envelope_test.o: ../../include/msg_vstream.h
+cleanup_envelope_test.o: ../../include/myflock.h
+cleanup_envelope_test.o: ../../include/mymalloc.h
+cleanup_envelope_test.o: ../../include/nvtable.h
+cleanup_envelope_test.o: ../../include/rec_type.h
+cleanup_envelope_test.o: ../../include/record.h
+cleanup_envelope_test.o: ../../include/resolve_clnt.h
+cleanup_envelope_test.o: ../../include/sendopts.h
+cleanup_envelope_test.o: ../../include/smtputf8.h
+cleanup_envelope_test.o: ../../include/string_list.h
+cleanup_envelope_test.o: ../../include/stringops.h
+cleanup_envelope_test.o: ../../include/sys_defs.h
+cleanup_envelope_test.o: ../../include/tok822.h
+cleanup_envelope_test.o: ../../include/vbuf.h
+cleanup_envelope_test.o: ../../include/vstream.h
+cleanup_envelope_test.o: ../../include/vstring.h
+cleanup_envelope_test.o: cleanup.h
+cleanup_envelope_test.o: cleanup_envelope_test.c
 cleanup_extracted.o: ../../include/argv.h
 cleanup_extracted.o: ../../include/attr.h
 cleanup_extracted.o: ../../include/been_here.h
@@ -1172,6 +1208,7 @@ cleanup_message.o: ../../include/ascii_header_text.h
 cleanup_message.o: ../../include/attr.h
 cleanup_message.o: ../../include/been_here.h
 cleanup_message.o: ../../include/check_arg.h
+cleanup_message.o: ../../include/clean_ascii_cntrl_space.h
 cleanup_message.o: ../../include/cleanup_user.h
 cleanup_message.o: ../../include/conv_time.h
 cleanup_message.o: ../../include/dict.h
@@ -1206,12 +1243,12 @@ cleanup_message.o: ../../include/rec_type.h
 cleanup_message.o: ../../include/record.h
 cleanup_message.o: ../../include/resolve_clnt.h
 cleanup_message.o: ../../include/rfc2047_code.h
+cleanup_message.o: ../../include/sendopts.h
 cleanup_message.o: ../../include/split_at.h
 cleanup_message.o: ../../include/string_list.h
 cleanup_message.o: ../../include/stringops.h
 cleanup_message.o: ../../include/sys_defs.h
 cleanup_message.o: ../../include/tok822.h
-cleanup_message.o: ../../include/clean_ascii_cntrl_space.h
 cleanup_message.o: ../../include/vbuf.h
 cleanup_message.o: ../../include/vstream.h
 cleanup_message.o: ../../include/vstring.h
index 3565b6f167a13079babf452f22ee78fab5b4c573..82bd482efed858c34f083250c4a0b5034657816c 100644 (file)
@@ -12,6 +12,7 @@
   * System library.
   */
 #include <sys/time.h>
+#include <stdint.h>                    /* C99 uint64_t */
 
  /*
   * Utility library.
@@ -69,7 +70,7 @@ typedef struct CLEANUP_STATE {
     int     qmgr_opts;                 /* qmgr processing options */
     int     errs;                      /* any badness experienced */
     int     err_mask;                  /* allowed badness */
-    int     headers_seen;              /* which headers were seen */
+    uint64_t headers_seen;             /* which headers were seen */
     int     hop_count;                 /* count of received: headers */
     char   *resent;                    /* any resent- header seen */
     BH_TABLE *dups;                    /* recipient dup filter */
@@ -141,6 +142,11 @@ typedef struct CLEANUP_STATE {
 #define CLEANUP_FLAG_WARN_SEEN (1<<17) /* REC_TYPE_WARN record seen */
 #define CLEANUP_FLAG_END_SEEN  (1<<18) /* REC_TYPE_END record seen */
 
+ /*
+  * Bit mask for the CLEANUP_STATE.headers_seen member.
+  */
+#define HDRS_SEEN_MASK(hval)           ((uint64_t) 1 << (hval))
+
  /*
   * Mappings.
   */
index cc0a098b105cd74d73ee9a9329883575505576c4..2fa1689611ddb30f221adbf4263b637b73530b9c 100644 (file)
@@ -132,8 +132,8 @@ static int overrides_size_fields(const TEST_CASE *tp)
     CLEANUP_STATE saved_state = *state;
 
     /*
-     * Process the test SIZE record payload and write an place-holder SIZE
-     * record that will be overwritten later with final information.
+     * Process the test SIZE record payload, clear some bits from the
+     * sendopts field, and write an all-zeroes preliminary SIZE record.
      */
     VSTRING *output_stream_buf = vstring_alloc(100);
 
@@ -153,7 +153,8 @@ static int overrides_size_fields(const TEST_CASE *tp)
     input_buf = 0;
 
     /*
-     * Write an updated SIZE record to the output stream.
+     * Overwrite the SIZE record with an updated version that includes the
+     * modified sendopts field.
      */
     cleanup_final(state);
     if (state->errs != CLEANUP_STAT_OK) {
@@ -166,7 +167,8 @@ static int overrides_size_fields(const TEST_CASE *tp)
     state->dst = 0;
 
     /*
-     * Compare the stored record content against the expected content.
+     * Read the final SIZE record content. This normally happens in the queue
+     * manager, and in the pickup daemon after a message is re-queued.
      */
     VSTREAM *fp;
 
@@ -185,6 +187,13 @@ static int overrides_size_fields(const TEST_CASE *tp)
     (void) vstream_fclose(fp);
     vstring_free(output_stream_buf);
 
+    /*
+     * Compare the stored SIZE record content against the expected content.
+     * We expect that the fields for data_size, data_offset, rcpt_count,
+     * qmgr_opts, and cont_length, are consistent with the saved
+     * CLEANUP_STATE, and we expect to see a specific value for the sendopts
+     * field that was made by cleanup_envelope().
+     */
     int     got_conv;
     long    data_size, data_offset, cont_length;
     int     rcpt_count, qmgr_opts, sendopts;
index 21d94431d41561607feddbe15ff47e311cce33d2..b9a7e93600d17d1dfb6148095e00c19153d530eb 100644 (file)
@@ -96,6 +96,7 @@
 #include <info_log_addr_form.h>
 #include <hfrom_format.h>
 #include <rfc2047_code.h>
+#include <sendopts.h>
 
 /* Application-specific. */
 
@@ -631,7 +632,7 @@ static void cleanup_header_callback(void *context, int header_class,
      * MAY remove Return-path headers before adding their own.
      */
     else {
-       state->headers_seen |= (1 << hdr_opts->type);
+       state->headers_seen |= HDRS_SEEN_MASK(hdr_opts->type);
        if (hdr_opts->type == HDR_MESSAGE_ID) {
            ssize_t len;
 
@@ -652,6 +653,17 @@ static void cleanup_header_callback(void *context, int header_class,
            if (state->hop_count == 1)
                argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END);
        }
+       if (hdr_opts->type == HDR_TLS_REQUIRED) {
+           char   *cp = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
+
+           while (ISSPACE(*cp))
+               cp++;
+           if (strcasecmp(cp, "no") == 0)
+               state->sendopts |= SOPT_REQUIRETLS_HEADER;
+           else
+               msg_warn("ignoring malformed header: '%.100s'",
+                        vstring_str(header_buf));
+       }
        if (CLEANUP_OUT_OK(state)) {
            if (hdr_opts->flags & HDR_OPT_RR)
                state->resent = "Resent-";
@@ -758,8 +770,8 @@ static void cleanup_header_done_callback(void *context)
      * complicate future code that wants to log more name=value attributes.
      */
     if ((state->hdr_rewrite_context || var_always_add_hdrs)
-       && (state->headers_seen & (1 << (state->resent[0] ?
-                          HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
+       && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
+                           HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID)) == 0) {
        if (var_long_queue_ids) {
            vstring_sprintf(state->temp1, "%s@%s",
                            state->queue_id, var_myhostname);
@@ -776,14 +788,14 @@ static void cleanup_header_done_callback(void *context)
        msg_info("%s: %smessage-id=<%s>",
                 state->queue_id, *state->resent ? "resent-" : "",
                 vstring_str(state->temp1));
-       state->headers_seen |= (1 << (state->resent[0] ?
-                                  HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID));
+       state->headers_seen |= HDRS_SEEN_MASK(state->resent[0] ?
+                                   HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID);
        if (state->resent[0] == 0 && state->message_id == 0)
            state->message_id = concatenate("<", vstring_str(state->temp1),
                                            ">", (char *) 0);
 
     }
-    if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0)
+    if ((state->headers_seen & HDRS_SEEN_MASK(HDR_MESSAGE_ID)) == 0)
        msg_info("%s: message-id=<>", state->queue_id);
 
     /*
@@ -791,8 +803,8 @@ static void cleanup_header_done_callback(void *context)
      * with the GMT offset at the end.
      */
     if ((state->hdr_rewrite_context || var_always_add_hdrs)
-       && (state->headers_seen & (1 << (state->resent[0] ?
-                                      HDR_RESENT_DATE : HDR_DATE))) == 0) {
+       && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
+                                       HDR_RESENT_DATE : HDR_DATE)) == 0) {
        vstring_sprintf(state->temp2, "%sDate: %s",
                      state->resent, mail_date(state->arrival_time.tv_sec));
        cleanup_out_header(state, state->temp2);
@@ -802,8 +814,8 @@ static void cleanup_header_done_callback(void *context)
      * Add a missing (Resent-)From: header.
      */
     if ((state->hdr_rewrite_context || var_always_add_hdrs)
-       && (state->headers_seen & (1 << (state->resent[0] ?
-                                      HDR_RESENT_FROM : HDR_FROM))) == 0) {
+       && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
+                                       HDR_RESENT_FROM : HDR_FROM)) == 0) {
        char   *fullname;
 
        quote_822_local(state->temp1, *state->sender ?
@@ -869,8 +881,10 @@ static void cleanup_header_done_callback(void *context)
     /*
      * Add a missing destination header.
      */
-#define VISIBLE_RCPT   ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \
-                       | (1 << HDR_CC) | (1 << HDR_RESENT_CC))
+#define VISIBLE_RCPT   (HDRS_SEEN_MASK(HDR_TO) \
+                       | HDRS_SEEN_MASK(HDR_RESENT_TO) \
+                       | HDRS_SEEN_MASK(HDR_CC) \
+                       | HDRS_SEEN_MASK(HDR_RESENT_CC))
 
     if ((state->hdr_rewrite_context || var_always_add_hdrs)
        && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) {
index 84f10c40ba42926ff6a13b19db953666de246961..67520c7e2fb85d2cda3e49a77a40c9e22aa7ec00 100644 (file)
@@ -131,7 +131,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
        fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
        haproxy_srvr map_search delivered_hdr login_sender_match \
        compat_level config_known_tcp_ports hfrom_format rfc2047_code \
-       ascii_header_text sendopts
+       ascii_header_text sendopts_test
 
 LIBS   = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 LIB_DIR        = ../../lib
@@ -405,7 +405,7 @@ rfc2047_code: rfc2047_code.c $(LIB) $(LIBS)
 ascii_header_text: ascii_header_text.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-sendopts: sendopts.c $(LIB) $(LIBS)
+sendopts_test: sendopts_test.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
 config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS)
@@ -421,7 +421,7 @@ tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
        normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
        delivered_hdr_test login_sender_match_test compat_level_test \
        config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \
-       ascii_header_text_test sendopts_test
+       ascii_header_text_test test_sendopts
 
 mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \
        mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4
@@ -793,8 +793,8 @@ rfc2047_code_test: update rfc2047_code
 ascii_header_text_test: update ascii_header_text 
        $(SHLIB_ENV) $(VALGRIND) ./ascii_header_text
 
-sendopts_test: update sendopts
-       -$(SHLIB_ENV) $(VALGRIND) ./sendopts
+test_sendopts: update sendopts_test
+       -$(SHLIB_ENV) $(VALGRIND) ./sendopts_test
 
 clean:
        rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAPS)
@@ -2679,6 +2679,24 @@ scache_single.o: ../../include/vbuf.h
 scache_single.o: ../../include/vstring.h
 scache_single.o: scache.h
 scache_single.o: scache_single.c
+sendopts.o: ../../include/check_arg.h
+sendopts.o: ../../include/msg.h
+sendopts.o: ../../include/name_mask.h
+sendopts.o: ../../include/sys_defs.h
+sendopts.o: ../../include/vbuf.h
+sendopts.o: ../../include/vstring.h
+sendopts.o: sendopts.c
+sendopts.o: sendopts.h
+sendopts_test.o: ../../include/check_arg.h
+sendopts_test.o: ../../include/msg.h
+sendopts_test.o: ../../include/msg_vstream.h
+sendopts_test.o: ../../include/stringops.h
+sendopts_test.o: ../../include/sys_defs.h
+sendopts_test.o: ../../include/vbuf.h
+sendopts_test.o: ../../include/vstream.h
+sendopts_test.o: ../../include/vstring.h
+sendopts_test.o: sendopts.h
+sendopts_test.o: sendopts_test.c
 sent.o: ../../include/attr.h
 sent.o: ../../include/check_arg.h
 sent.o: ../../include/htable.h
index c0c4d5ceb81027960c4ad4e91a04458202a90fb2..1be71a4eda3b1aa19e4c007bc3670f6e9a7ad4ae 100644 (file)
@@ -26,6 +26,9 @@
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 /* System library. */
@@ -87,6 +90,7 @@ static HEADER_OPTS header_opts[] = {
     "Return-Receipt-To", HDR_RETURN_RECEIPT_TO, HDR_OPT_SENDER,
     "Sender", HDR_SENDER, HDR_OPT_SENDER,
     "To", HDR_TO, HDR_OPT_XRECIP,
+    "TLS-Required", HDR_TLS_REQUIRED, 0,
 };
 
 #define HEADER_OPTS_SIZE (sizeof(header_opts) / sizeof(header_opts[0]))
index b03cc5d87eced94565615af38648b4369b7ed747..1af23b22ca2c14d43d2f446abc2cb832b77280df 100644 (file)
@@ -20,8 +20,7 @@ typedef struct {
 } HEADER_OPTS;
 
  /*
-  * Header types. If we reach 31, we must group the headers we need to
-  * remember at the beginning, or we should use fd_set bit sets.
+  * Header types.
   */
 #define HDR_OTHER                      0
 #define HDR_APPARENTLY_TO              1
@@ -55,6 +54,7 @@ typedef struct {
 #define HDR_CONTENT_ID                 29
 #define HDR_MIME_VERSION               30
 #define HDR_DISP_NOTIFICATION          31
+#define HDR_TLS_REQUIRED               32      /* RFC 8689 */
 
  /*
   * Header flags.
@@ -79,6 +79,9 @@ extern const HEADER_OPTS *header_opts_find(const char *);
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 #endif
index d772922401f4b834fcb63b885808e43e1612011a..0d155f8e2062c044d2fc213aff7e6c33c1711aab 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      "20250116"
+#define MAIL_RELEASE_DATE      "20250117"
 #define MAIL_VERSION_NUMBER    "3.10"
 
 #ifdef SNAPSHOT
index 0dc3ddc155ce401b6911a3db0ad7075a12810445..f8570db275804fef34232657d8c3b2d75208b7e8 100644 (file)
@@ -6,8 +6,9 @@
 /* SYNOPSIS
 /*     #include <sendopts.h>
 /*
-/*     const char *sendopts_strflags(code)
-/*     int     code;
+/*     const char *sendopts_strflags(
+/*     int     flags,
+/*     int     delim)
 /* DESCRIPTION
 /*     Postfix queue files and IPC messages contain a sendopts field
 /*     with flags that control SMTPUTF8, REQUIRETLS, etc. support. The
 /*
 /*     sendopts_strflags() maps a sendopts flag value to printable
 /*     string. The result is overwritten upon each call.
+/*
+/*     Arguments:
+/* .IP flags
+/*     A bitmask that is to be converted to text.
+/* .IP delim
+/*     The character to separate output words with: one of ' ,|'.
+/* DIAGNOSTICS
+/*     Panic: invalid delimiter. Fatal error: invalid flag.
 /* LICENSE
 /* .ad
 /* .fi
@@ -30,6 +39,7 @@
 /* System library. */
 
 #include <sys_defs.h>
+#include <string.h>
 
  /*
   * Utility library.
@@ -44,7 +54,7 @@
 #include <sendopts.h>
 
  /*
-  * Mapping from flags code to printable string.
+  * Mapping from flags to printable string.
   */
 static NAME_MASK sendopts_flag_map[] = {
     "smtputf8_requested", SOPT_SMTPUTF8_REQUESTED,
@@ -58,9 +68,13 @@ static NAME_MASK sendopts_flag_map[] = {
 
 /* sendopts_strflags - map flags code to printable string */
 
-const char *sendopts_strflags(unsigned flags)
+const char *sendopts_strflags(unsigned flags, int delim)
 {
+    const char myname[] = "sendopts_strflags";
+    static const char delims[] = " ,|";
+    static const int dflags[] = {0, NAME_MASK_COMMA, NAME_MASK_PIPE};
     static VSTRING *result;
+    const char *cp;
 
     if (flags == 0)
        return ("none");
@@ -70,92 +84,10 @@ const char *sendopts_strflags(unsigned flags)
     else
        VSTRING_RESET(result);
 
-    return (str_name_mask_opt(result, "sendopts_strflags", sendopts_flag_map,
-                             flags, NAME_MASK_FATAL));
-}
-
-#ifdef TEST
-#include <stdlib.h>
-#include <string.h>
-#include <stringops.h>
-#include <msg_vstream.h>
+    if ((cp = strchr(delims, delim)) == 0)
+       msg_panic("%s: bad delimiter: '%c'", myname, delim);
 
- /*
-  * Tests and test cases.
-  */
-typedef struct TEST_CASE {
-    const char *label;                 /* identifies test case */
-    int     mask;
-    const char *want;
-} TEST_CASE;
-
-static const TEST_CASE test_cases[] = {
-    {"SOPT_SMTPUTF8_ALL",
-       SOPT_SMTPUTF8_ALL,
-       "smtputf8_requested smtputf8_header smtputf8_sender smtputf8_recipient"
-    },
-    {"SOPT_SMTPUTF8_DERIVED",
-       SOPT_SMTPUTF8_DERIVED,
-       "smtputf8_header smtputf8_sender smtputf8_recipient"
-    },
-    {"SOPT_SMTPUTF8_REQUESTED",
-       SOPT_SMTPUTF8_REQUESTED,
-       "smtputf8_requested"
-    },
-    {"SOPT_SMTPUTF8_HEADER",
-       SOPT_SMTPUTF8_HEADER,
-       "smtputf8_header"
-    },
-    {"SOPT_SMTPUTF8_SENDER",
-       SOPT_SMTPUTF8_SENDER,
-       "smtputf8_sender"
-    },
-    {"SOPT_SMTPUTF8_RECIPIENT",
-       SOPT_SMTPUTF8_RECIPIENT,
-       "smtputf8_recipient"
-    },
-    {"SOPT_REQUIRETLS_ALL",
-       SOPT_REQUIRETLS_ALL,
-       "requiretls_header requiretls_esmtp"
-    },
-    {"SOPT_REQUIRETLS_DERIVED",
-       SOPT_REQUIRETLS_DERIVED,
-       "requiretls_header"
-    },
-    {"SOPT_REQUIRETLS_HEADER",
-       SOPT_REQUIRETLS_HEADER,
-       "requiretls_header"
-    },
-    {"SOPT_REQUIRETLS_ESMTP",
-       SOPT_REQUIRETLS_ESMTP,
-       "requiretls_esmtp"
-    },
-    {0},
-};
-
-int     main(int argc, char **argv)
-{
-    const TEST_CASE *tp;
-    int     pass = 0;
-    int     fail = 0;
-    const char *got;
-
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    for (tp = test_cases; tp->label != 0; tp++) {
-       msg_info("RUN  %s", tp->label);
-       got = sendopts_strflags(tp->mask);
-       if (strcmp(got, tp->want) != 0) {
-           msg_warn("got result '%s', want: '%s'", got, tp->want);
-           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);
+    return (str_name_mask_opt(result, "sendopts_strflags", sendopts_flag_map,
+                             flags, NAME_MASK_FATAL | dflags[cp - delims]));
 }
 
-#endif
index c80ea5a33ab97f18f5d25bbe3261622e321f21ca..bcc54809b78e7ed9ee56732cfbd460dfcbc29073 100644 (file)
@@ -44,7 +44,7 @@
  /*
   * Debug helper.
   */
-extern const char *sendopts_strflags(unsigned flags);
+extern const char *sendopts_strflags(unsigned flags, int delim);
 
 /* LICENSE
 /* .ad
diff --git a/postfix/src/global/sendopts_test.c b/postfix/src/global/sendopts_test.c
new file mode 100644 (file)
index 0000000..e2df6b7
--- /dev/null
@@ -0,0 +1,108 @@
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stringops.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <sendopts.h>
+
+ /*
+  * Tests and test cases.
+  */
+typedef struct TEST_CASE {
+    const char *label;                 /* identifies test case */
+    int     mask;
+    const char *want;
+} TEST_CASE;
+
+static const TEST_CASE test_cases[] = {
+    {"SOPT_SMTPUTF8_ALL",
+       SOPT_SMTPUTF8_ALL,
+       "smtputf8_requested smtputf8_header smtputf8_sender smtputf8_recipient"
+    },
+    {"SOPT_SMTPUTF8_DERIVED",
+       SOPT_SMTPUTF8_DERIVED,
+       "smtputf8_header smtputf8_sender smtputf8_recipient"
+    },
+    {"SOPT_SMTPUTF8_REQUESTED",
+       SOPT_SMTPUTF8_REQUESTED,
+       "smtputf8_requested"
+    },
+    {"SOPT_SMTPUTF8_HEADER",
+       SOPT_SMTPUTF8_HEADER,
+       "smtputf8_header"
+    },
+    {"SOPT_SMTPUTF8_SENDER",
+       SOPT_SMTPUTF8_SENDER,
+       "smtputf8_sender"
+    },
+    {"SOPT_SMTPUTF8_RECIPIENT",
+       SOPT_SMTPUTF8_RECIPIENT,
+       "smtputf8_recipient"
+    },
+    {"SOPT_REQUIRETLS_ALL",
+       SOPT_REQUIRETLS_ALL,
+       "requiretls_header requiretls_esmtp"
+    },
+    {"SOPT_REQUIRETLS_DERIVED",
+       SOPT_REQUIRETLS_DERIVED,
+       "requiretls_header"
+    },
+    {"SOPT_REQUIRETLS_HEADER",
+       SOPT_REQUIRETLS_HEADER,
+       "requiretls_header"
+    },
+    {"SOPT_REQUIRETLS_ESMTP",
+       SOPT_REQUIRETLS_ESMTP,
+       "requiretls_esmtp"
+    },
+    {"SOPT_FLAG_ALL",
+       SOPT_FLAG_ALL,
+       "smtputf8_requested smtputf8_header smtputf8_sender smtputf8_recipient"
+       " requiretls_header requiretls_esmtp"
+    },
+    {"SOPT_FLAG_DERIVED",
+       SOPT_FLAG_DERIVED,
+       "smtputf8_header smtputf8_sender smtputf8_recipient"
+       " requiretls_header"
+    },
+    {0},
+};
+
+int     main(int argc, char **argv)
+{
+    const TEST_CASE *tp;
+    int     pass = 0;
+    int     fail = 0;
+    const char *got;
+
+    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+    for (tp = test_cases; tp->label != 0; tp++) {
+       msg_info("RUN  %s", tp->label);
+       got = sendopts_strflags(tp->mask, ' ');
+       if (strcmp(got, tp->want) != 0) {
+           msg_warn("got result '%s', want: '%s'", got, tp->want);
+           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 f416b071a175592943260d3bf857da6c8856c03b..44add46b25cf306dc3e40fe44041456bcf42d522 100644 (file)
@@ -259,6 +259,7 @@ smtp_connect.o: ../../include/recipient_list.h
 smtp_connect.o: ../../include/resolve_clnt.h
 smtp_connect.o: ../../include/sane_connect.h
 smtp_connect.o: ../../include/scache.h
+smtp_connect.o: ../../include/sendopts.h
 smtp_connect.o: ../../include/sock_addr.h
 smtp_connect.o: ../../include/split_at.h
 smtp_connect.o: ../../include/string_list.h
@@ -804,7 +805,6 @@ smtp_tlsrpt.o: ../../include/header_body_checks.h
 smtp_tlsrpt.o: ../../include/header_opts.h
 smtp_tlsrpt.o: ../../include/hex_code.h
 smtp_tlsrpt.o: ../../include/htable.h
-smtp_tlsrpt.o: ../../include/inet_proto.h
 smtp_tlsrpt.o: ../../include/mail_params.h
 smtp_tlsrpt.o: ../../include/maps.h
 smtp_tlsrpt.o: ../../include/match_list.h
index e60450f21a7a05c45fec3882ba6a8ebfcbc7240a..88ea18eb0d51a283f6bc225b6c23e7a4e42dd445 100644 (file)
 #include <dsn_buf.h>
 #include <mail_addr.h>
 #include <valid_hostname.h>
+#include <sendopts.h>
 
 /* DNS library. */
 
@@ -497,6 +498,51 @@ static void smtp_cache_policy(SMTP_STATE *state, const char *dest)
     }
 }
 
+/* smtp_get_effective_tls_level - get the effective TLS security level */
+
+static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
+{
+    SMTP_ITERATOR *iter = state->iterator;
+    SMTP_TLS_POLICY *tls = state->tls;
+
+    /*
+     * Determine the TLS level for this destination.
+     */
+    if (!smtp_tls_policy_cache_query(why, tls, iter)) {
+       return (0);
+    }
+
+    /*
+     * If the sender requires verified TLS, the TLS level must enforce a
+     * server certificate match.
+     */
+#if 0
+    else if ((state->request->sendopts & SOPT_REQUIRETLS_ESMTP)) {
+       if (TLS_MUST_MATCH(tls->level) == 0) {
+           dsb_simple(why, "5.7.10", "Sender requires verified TLS, "
+                      " but my configured TLS security level is '%s %s'",
+                      var_mail_name, str_tls_level(tls->level));
+           return (0);
+       }
+    }
+#endif
+
+    /*
+     * Otherwise, if the TLS level is not TLS_LEV_NONE or some non-level, and
+     * the message contains a "TLS-Required: no" header, limit the level to
+     * TLS_LEV_MAY.
+     */
+    else if (tls->level > TLS_LEV_NONE
+            && (state->request->sendopts & SOPT_REQUIRETLS_HEADER)) {
+       tls->level = TLS_LEV_MAY;
+    }
+
+    /*
+     * Success.
+     */
+    return (1);
+}
+
 /* smtp_connect_local - connect to local server */
 
 static void smtp_connect_local(SMTP_STATE *state, const char *path)
@@ -552,7 +598,7 @@ static void smtp_connect_local(SMTP_STATE *state, const char *path)
      * of SASL-unauthenticated connections.
      */
 #ifdef USE_TLS
-    if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+    if (!smtp_get_effective_tls_level(why, state)) {
        msg_warn("TLS policy lookup error for %s/%s: %s",
                 STR(iter->host), STR(iter->addr), STR(why->reason));
        return;
@@ -777,7 +823,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
        }
        SMTP_ITER_UPDATE_HOST(iter, SMTP_HNAME(addr), hostaddr.buf, addr);
 #ifdef USE_TLS
-       if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+       if (!smtp_get_effective_tls_level(why, state)) {
            msg_warn("TLS policy lookup error for %s/%s: %s",
                     STR(iter->dest), STR(iter->host), STR(why->reason));
            continue;
@@ -1065,7 +1111,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
            }
            SMTP_ITER_UPDATE_HOST(iter, SMTP_HNAME(addr), hostaddr.buf, addr);
 #ifdef USE_TLS
-           if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
+           if (!smtp_get_effective_tls_level(why, state)) {
                msg_warn("TLS policy lookup for %s/%s: %s",
                         STR(iter->dest), STR(iter->host), STR(why->reason));
                continue;
index 0816430bc1bac8210b452eebd3d25d2d544ee84b..555a35ef9e3ae5bc17e0d03d9cf0c8c793bf397a 100644 (file)
@@ -807,7 +807,7 @@ int     main(int argc, char **argv)
 
        argvp = argv_alloc(1);
        if (setjmp(test_panic_jbuf) == 0)
-           tp->populate_fn(tp, argvp);
+           argvp = tp->populate_fn(tp, argvp);
        test_failed = test_argv_verify(tp, argvp);
        if (test_failed) {
            msg_info("%s: FAIL", tp->label);
index feed9f1cd6716592128db360d07c4edfaaa69359..07d2248957c174c60de0a2a011451a212e42ff8e 100644 (file)
@@ -296,6 +296,7 @@ int     main(int unused_argc, char **unused_argv)
            fail++;
        }
     }
+    vstring_free(buf);
     msg_info("PASS=%d FAIL=%d", pass, fail);
     return (fail > 0);
 }