From: Wietse Z Venema Date: Thu, 14 May 2026 05:00:00 +0000 (-0500) Subject: postfix-3.12-20260514 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=thirdparty%2Fpostfix.git postfix-3.12-20260514 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index 53a20f718..c934825e9 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -31107,7 +31107,7 @@ Apologies for any names omitted. Bitrot fixes: deprecation warning with OpenSSL 4.0 (tls/tls_dane.c); race condition fix in a test script - (tls/dls_dane.sh). Viktor Dukhovni. + (tls/tls_dane.sh). Viktor Dukhovni. 20260510 @@ -31119,11 +31119,48 @@ Apologies for any names omitted. master/multi_server.c, master/single_server.c, src/master/trigger_server.c. -TODO - Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc. +20260513 + + Bitrot: builds with musl libc were using the obsolete + NO_SNPRINTF code path in vbuf_print.c. File: util/sys_defs.h. + +20260514 + + Bitrot: the obsolete NO_SNPRINTF code path in vbuf_print.c + wasn't updated for Claude Code findings. File: util/vbuf_print.c. + + Feature: TLS protocol trace via SSL_trace, enabled with + "trace" in *_tls_loglevel or *_tls_loglevel_maps. smtp(8), + smtpd(8), and tlsproxy(8) write the transcript to a + per-connection file under $queue_directory/tlstrace/, capped + at *_tls_trace_size_limit bytes per file. posttls-finger(1) + writes to a file in the current directory; one file per + run, with separator lines between reconnects. The path is + logged via msg_info() when the trace begins. Files: + proto/postconf.proto, global/mail_params.h, + postscreen/postscreen.c, postscreen/postscreen_tls_conf.c, + posttls-finger/posttls-finger.c, smtp/lmtp_params.c, + smtp/smtp.c, smtp/smtp_params.c, smtp/smtp_proto.c, + smtpd/smtpd.c, tls/tls.h, tls/tls_client.c, tls/tls_misc.c, + tls/tls_proxy_attr.h, tls/tls_proxy_client_start_proto.[hc], + tls/tls_proxy_server_start_proto.[hc], tls/tls_server.c, + tlsproxy/tlsproxy.c, tlsproxy/tlsproxy_client.c, + tlsproxy/tlsproxy_server.c. + + The above feature was implemented in three iterations. In + the first two, it was designed by Wietse and Viktor, with + preliminary implementations by Claude Code supervised by + Viktor. With minor changes, the Claude Code implementation + was finished by humans. + + Feature: Postfix daemons create at most tls_trace_rate_limit + trace files (default: 1) per anvil_rate_time_unit interval + (default: 60s). This limit applies to the combined trace + file output from all Postfix daemon processes. Files: + anvil/anvil.c, global/anvil_clnt.[hc], proto/postconf.proto, + mantools/postlink. - Document TLS parameters in tlsproxy(8) and postscreen(8). +TODO - Why are process_name and service_name implemented in different - ways? + Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc. diff --git a/postfix/INSTALL b/postfix/INSTALL index 623dabf31..649560d43 100644 --- a/postfix/INSTALL +++ b/postfix/INSTALL @@ -617,6 +617,14 @@ The following is an extensive list of names and values. ||-DNO_SNPRINTF |default, Postfix uses snprintf() except on | || |ancient systems. | ||______________________________|_____________________________________________| +|| |Do not build with support for OpenSSL TLS | +|| |traces. Some vendor OpenSSL runtime libraries| +|| |are built without support for tracing, and | +||-DNO_TLS_TRACE |Postfix software built on a system with TLS | +|| |trace support would not work when installed | +|| |on one without. See smtp_tls_loglevel in the | +|| |postconf(5) manual. | +||______________________________|_____________________________________________| | |Specifies a non-default compiler debugging | |DEBUG=debug_level |level. The default is "-g". Specify DEBUG= to| | |turn off debugging. | diff --git a/postfix/README_FILES/INSTALL b/postfix/README_FILES/INSTALL index 956e0ea4e..d11826fd9 100644 --- a/postfix/README_FILES/INSTALL +++ b/postfix/README_FILES/INSTALL @@ -617,6 +617,14 @@ The following is an extensive list of names and values. ||-DNO_SNPRINTF |default, Postfix uses snprintf() except on | || |ancient systems. | |_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | +|| |Do not build with support for OpenSSL TLS | +|| |traces. Some vendor OpenSSL runtime libraries| +|| |are built without support for tracing, and | +||-DNO_TLS_TRACE |Postfix software built on a system with TLS | +|| |trace support would not work when installed | +|| |on one without. See smtp_tls_loglevel in the | +|| |postconf(5) manual. | +|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | | |Specifies a non-default compiler debugging | |DEBUG=debug_level |level. The default is "-g". Specify DEBUG= to| | |turn off debugging. | diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 91e619056..db94d6490 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -26,6 +26,44 @@ now also distributed with the more recent Eclipse Public License license of their choice. Those who are more comfortable with the IPL can continue with that license. +Major changes with snapshot 20260514 +==================================== + +Support for per-session TLS traces with detailed TLS protocol +information but without any plaintext SMTP content. This feature +can be enabled by appending ",trace" to a TLS loglevel. + +Postfix daemons will record one trace per TLS session under +$queue_directory/tlstrace. A trace file name is the concatenation +of the application name ("smtpd", "smtp", etc.), a time stamp +formatted as yyyymmddhhmmss, the microsecond portion of system time, +the peer IP address, and a six-character unique string to avoid +file name collisions. + +Safety measures: the size of each trace file is limited with {lmtp, +postscreen, smtp_, smtpd}_tls_trace_size_limit (default: 102400), +and all Postfix daemons combined will create no more than +tls_trace_rate_limit trace files (default: 1) per anvil_rate_time_unit +interval (default: 60s). + +The posttls-finger command will create trace files (using the same +name format as daemons) in the current directory, for example: +"posttls-finger -L 1,trace example.com". It is not subject to the +tls_trace_rate_limit or trace file size constraints. + +This feature is based on a design by Wietse and Viktor, initially +implemented by Claude Code, then simplified and completed by Wietse +and Viktor. + +Incompatible changes with snapshot 20260514 +=========================================== + +The internal protocol between tlsproxy(8) and its clients (smtp(8), +postscreen(8), smtpd(8) in tlsproxy mode, and "posttls-finger -X") +gained a new attribute. Run "postfix reload" after the upgrade. If +this step is skipped, TLS sessions through tlsproxy(8) will fail, +because the old and new processes disagree on the protocol shape. + Incompatible changes with snapshot 20260312 =========================================== diff --git a/postfix/conf/postfix-files b/postfix/conf/postfix-files index 7db6b3c3b..68412e6a7 100644 --- a/postfix/conf/postfix-files +++ b/postfix/conf/postfix-files @@ -65,6 +65,7 @@ $queue_directory/maildrop:d:$mail_owner:$setgid_group:730:uc $queue_directory/public:d:$mail_owner:$setgid_group:710:uc $queue_directory/pid:d:root:-:755:uc $queue_directory/saved:d:$mail_owner:-:700:ucr +$queue_directory/tlstrace:d:$mail_owner:-:700:ucr $queue_directory/trace:d:$mail_owner:-:700:ucr # Update shared libraries and plugins before daemon or command-line programs. $shlib_directory/lib${LIB_PREFIX}util${LIB_SUFFIX}:f:root:-:755 diff --git a/postfix/html/INSTALL.html b/postfix/html/INSTALL.html index f20762361..c64f3bcff 100644 --- a/postfix/html/INSTALL.html +++ b/postfix/html/INSTALL.html @@ -891,6 +891,12 @@ they are known to be available. instead of snprintf(). By default, Postfix uses snprintf() except on ancient systems. + -DNO_TLS_TRACE Do not build with support +for OpenSSL TLS traces. Some vendor OpenSSL runtime libraries are built +without support for tracing, and Postfix software built on a system with +TLS trace support would not work when installed on one without. See +smtp_tls_loglevel in the postconf(5) manual. + DEBUG=debug_level Specifies a non-default compiler debugging level. The default is "-g". Specify DEBUG= to turn off debugging. diff --git a/postfix/html/anvil.8.html b/postfix/html/anvil.8.html index 0647075d3..d8a927d5e 100644 --- a/postfix/html/anvil.8.html +++ b/postfix/html/anvil.8.html @@ -120,6 +120,19 @@ ANVIL(8) ANVIL(8) status=0 rate=number +TLS TRACE RATE CONTROL + To register a TLS trace event send the following request to the + anvil(8) server: + + request=tlstr + ident=string + + The anvil(8) server answers with the number of TLS trace requests per + unit time for the (service, client) combination specified with ident: + + status=0 + rate=number + SECURITY The anvil(8) server does not talk to the network or to local users, and can run chrooted at fixed low privilege. diff --git a/postfix/html/lmtp.8.html b/postfix/html/lmtp.8.html index 04e8ed84a..97be3c227 100644 --- a/postfix/html/lmtp.8.html +++ b/postfix/html/lmtp.8.html @@ -794,6 +794,16 @@ SMTP(8) SMTP(8) Optional TLS loglevel override that depends on the remote peer host name or IP address. + smtp_tls_trace_size_limit (102400) + Size limit, in bytes, for the TLS protocol transcript that the + Postfix SMTP client writes when the "trace" keyword is included + in the TLS loglevel for a peer (smtp_tls_loglevel or + smtp_tls_loglevel_maps). + + tls_trace_rate_limit (1) + The maximum number of TLS traces per anvil_rate_time_unit that + all Postfix daemons combined will create. + OBSOLETE TLS CONTROLS The following configuration parameters exist for compatibility with Postfix versions before 2.3. Support for these will be removed in a diff --git a/postfix/html/makedefs.1.html b/postfix/html/makedefs.1.html index c5afd48f9..f9a3ba2e4 100644 --- a/postfix/html/makedefs.1.html +++ b/postfix/html/makedefs.1.html @@ -131,33 +131,36 @@ MAKEDEFS(1) MAKEDEFS(1) -DNO_STDBOOL Don't use <stdbool.h>. This is usually auto-detected. + -DNO_TLS_TRACE + Build without OpenSSL 3 (and later) debug trace support. + DEBUG=debug_level - Specifies a non-default debugging level. The default is -g. + Specifies a non-default debugging level. The default is -g. Specify DEBUG= to turn off debugging. OPT=optimization_level - Specifies a non-default optimization level. The default is -O. + Specifies a non-default optimization level. The default is -O. Specify OPT= to turn off optimization. POSTFIX_INSTALL_OPTS=-option... - Specifies options for the postfix-install command, separated by - whitespace. Currently, the only supported option is + Specifies options for the postfix-install command, separated by + whitespace. Currently, the only supported option is -keep-build-mtime. SHLIB_CFLAGS=flags - Override the compiler flags (typically, "-fPIC") for Postfix + Override the compiler flags (typically, "-fPIC") for Postfix dynamically-linked libraries and database plugins. This feature was introduced with Postfix 3.0. SHLIB_RPATH=rpath - Override the runpath (typically, "'-Wl,-rpath,${SHLIB_DIR}'") + Override the runpath (typically, "'-Wl,-rpath,${SHLIB_DIR}'") for Postfix dynamically-linked libraries. This feature was introduced with Postfix 3.0. SHLIB_SUFFIX=suffix - Override the filename suffix (typically, ".so") for Postfix + Override the filename suffix (typically, ".so") for Postfix dynamically-linked libraries and database plugins. This feature was introduced with Postfix 3.0. @@ -165,7 +168,7 @@ MAKEDEFS(1) MAKEDEFS(1) shared=yes shared=no - Enable (disable) Postfix builds with dynamically-linked + Enable (disable) Postfix builds with dynamically-linked libraries typically named $shlib_directory/libpostfix-*.so.*. This feature was introduced with Postfix 3.0. @@ -173,39 +176,39 @@ MAKEDEFS(1) MAKEDEFS(1) dynamicmaps=yes dynamicmaps=no - Enable (disable) Postfix builds with the configuration file + Enable (disable) Postfix builds with the configuration file $meta_directory/dynamicmaps.cf and dynamically-loadable database - plugins typically named postfix-*.so.*. The setting "dynam- - icmaps=yes" implicitly enables Postfix dynamically-linked + plugins typically named postfix-*.so.*. The setting "dynam- + icmaps=yes" implicitly enables Postfix dynamically-linked libraries. This feature was introduced with Postfix 3.0. pie=yes - pie=no Enable (disable) Postfix builds with position-independent exe- + pie=no Enable (disable) Postfix builds with position-independent exe- cutables, on platforms where this is supported. This feature was introduced with Postfix 3.0. installation_parameter=value... - Override the compiled-in default value of the specified instal- - lation parameter(s). The following parameters are supported in + Override the compiled-in default value of the specified instal- + lation parameter(s). The following parameters are supported in this context: - command_directory config_directory daemon_directory data_direc- - tory default_cache_db_type default_database_type html_directory + command_directory config_directory daemon_directory data_direc- + tory default_cache_db_type default_database_type html_directory mail_spool_directory mailq_path manpage_directory meta_directory - newaliases_path queue_directory readme_directory sendmail_path + newaliases_path queue_directory readme_directory sendmail_path shlib_directory openssl_path - See the postconf(5) manpage for a description of these parame- + See the postconf(5) manpage for a description of these parame- ters. This feature was introduced with Postfix 3.0. WARN=warning_flags - Specifies non-default gcc compiler warning options for use when + Specifies non-default gcc compiler warning options for use when "make" is invoked in a source subdirectory only. LICENSE diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 7631a7cbc..c71d03260 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -6130,6 +6130,16 @@ configuration parameter. See there for details.

This feature is available in Postfix 2.3 and later.

+ + +
lmtp_tls_trace_size_limit +(default: 102400)
+ +

The lmtp(8) equivalent of smtp_tls_trace_size_limit.

+ +

This feature is available in Postfix 3.12 and later.

+ +
lmtp_tls_trust_anchor_file @@ -9912,6 +9922,18 @@ for details.

This feature is available in Postfix 2.8 and later.

+ + +
postscreen_tls_trace_size_limit +(default: $smtpd_tls_trace_size_limit)
+ +

The postscreen(8) equivalent of smtpd_tls_trace_size_limit. +postscreen(8) generates the trace via tlsproxy(8); the trace file +name starts with "tlsproxy-".

+ +

This feature is available in Postfix 3.12 and later.

+ +
postscreen_upstream_proxy_protocol @@ -15005,6 +15027,19 @@ transmission after STARTTLS.

Do not use "smtp_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged.

+

With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtp_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the content of application data messages) of the TLS +session to a per-connection file under $queue_directory/tlstrace/, +capped at smtp_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtp_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (find(1), logrotate(8), or similar).

+

This feature is available in Postfix 2.2 and later.

@@ -16029,6 +16064,23 @@ The default time unit is s (seconds).

This feature is available in Postfix 2.2 and later.

+ + +
smtp_tls_trace_size_limit +(default: 102400)
+ +

Size limit, in bytes, for the TLS protocol transcript that the +Postfix SMTP client writes when the "trace" keyword is included in +the TLS loglevel for a peer (smtp_tls_loglevel or +smtp_tls_loglevel_maps). The transcript is written to a per- +connection file under $queue_directory/tlstrace/, named +smtp-pid-time-peer.txt. Once the limit is reached the trace is +truncated with a one-line note. A value of 0 disables tracing. +

+ +

This feature is available in Postfix 3.12 and later.

+ +
smtp_tls_trust_anchor_file @@ -20600,6 +20652,19 @@ transmission after STARTTLS.

Do not use "smtpd_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged.

+

With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtpd_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the content of application data messages) of the TLS +session to a per-connection file under $queue_directory/tlstrace/, +capped at smtpd_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtpd_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (find(1), logrotate(8), or similar).

+

This feature is available in Postfix 2.2 and later.

@@ -20998,6 +21063,17 @@ The default time unit is s (seconds).

for TLS session ticket support in Postfix 2.11.

+ + +
smtpd_tls_trace_size_limit +(default: 102400)
+ +

The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. +File names start with "smtpd-" instead of "smtp-".

+ +

This feature is available in Postfix 3.12 and later.

+ +
smtpd_tls_wrappermode @@ -22451,6 +22527,18 @@ SSL_CTX_set_options(3).

This feature is available in Postfix 2.11 and later.

+ + +
tls_trace_rate_limit +(default: 1)
+ +

The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. Specify a value ≤0 to +disable the limit.

+ +

This feature is available in Postfix 3.12 and later.

+ +
tls_trust_server_ccerts diff --git a/postfix/html/postscreen.8.html b/postfix/html/postscreen.8.html index 1f603e910..034a7e506 100644 --- a/postfix/html/postscreen.8.html +++ b/postfix/html/postscreen.8.html @@ -459,6 +459,9 @@ POSTSCREEN(8) POSTSCREEN(8) Optional TLS loglevel override that depends on the remote peer host name or IP address. + postscreen_tls_trace_size_limit ($smtpd_tls_trace_size_limit) + The postscreen(8) equivalent of smtpd_tls_trace_size_limit. + postscreen_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers) The postscreen(8) equivalent of smtpd_tls_mandatory_ciphers. @@ -476,12 +479,16 @@ POSTSCREEN(8) POSTSCREEN(8) postscreen_tls_req_ccert ($smtpd_tls_req_ccert) The postscreen(8) equivalent of smtpd_tls_req_ccert. + tls_trace_rate_limit (1) + The maximum number of TLS traces per anvil_rate_time_unit that + all Postfix daemons combined will create. + OBSOLETE STARTTLS SUPPORT CONTROLS - These parameters are supported for compatibility with smtpd(8) legacy + These parameters are supported for compatibility with smtpd(8) legacy parameters. postscreen_use_tls ($smtpd_use_tls) - Opportunistic TLS: announce STARTTLS support to remote SMTP + Opportunistic TLS: announce STARTTLS support to remote SMTP clients, but do not require that clients use TLS encryption. postscreen_enforce_tls ($smtpd_enforce_tls) @@ -490,18 +497,18 @@ POSTSCREEN(8) POSTSCREEN(8) MISCELLANEOUS CONTROLS config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal point when log- + The maximal number of digits after the decimal point when log- ging delay values. command_directory (see 'postconf -d' output) The location of all postfix administrative commands. max_idle (100s) - The maximum amount of time that an idle Postfix daemon process + The maximum amount of time that an idle Postfix daemon process waits for an incoming connection before terminating voluntarily. process_id (read-only) @@ -514,7 +521,7 @@ POSTSCREEN(8) POSTSCREEN(8) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - A prefix that is prepended to the process name in syslog + A prefix that is prepended to the process name in syslog records, so that, for example, "smtpd" becomes "prefix/smtpd". Available in Postfix 3.3 and later: @@ -525,7 +532,7 @@ POSTSCREEN(8) POSTSCREEN(8) Available in Postfix 3.5 and later: info_log_address_format (external) - The email address form that will be used in non-debug logging + The email address form that will be used in non-debug logging (info, warning, etc.). SEE ALSO @@ -544,7 +551,7 @@ POSTSCREEN(8) POSTSCREEN(8) HISTORY This service was introduced with Postfix version 2.8. - Many ideas in postscreen(8) were explored in earlier work by Michael + Many ideas in postscreen(8) were explored in earlier work by Michael Tokarev, in OpenBSD spamd, and in MailChannels Traffic Control. AUTHOR(S) diff --git a/postfix/html/posttls-finger.1.html b/postfix/html/posttls-finger.1.html index d52b306fb..5c27d704f 100644 --- a/postfix/html/posttls-finger.1.html +++ b/postfix/html/posttls-finger.1.html @@ -20,7 +20,7 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) a domainname; with LMTP it is either a domainname prefixed with inet: or a pathname prefixed with unix:. If Postfix is built without TLS support, the resulting posttls-finger(1) program has very limited func- - tionality, and only the -a, -c, -h, -o, -S, -t, -T and -v options are + tionality, and only the -a, -c, -h, -S, -t, -T and -v options are available. Note: this is an unsupported test program. No attempt is made to main- @@ -206,85 +206,98 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) only useful to those who can debug SSL protocol problems from hex dumps. + trace Available with Postfix 3.12 and later. Write a + human-readable protocol transcript of the TLS session to + a file in the current directory. The file is named + process_name-date_time.usec-peer-XXXXXX, where XXXXXX is + replaced with a unique string to avoid filename con- + flicts. All reconnect attempts in a single command-line + run share the same trace file, separated by "=== post- + tls-finger reconnect ===" lines. When -X is in effect, + tlsproxy(8) generates the trace file under $queue_direc- + tory/tlstrace/ instead. The keyword is ignored with a + warning if Postfix or OpenSSL was built without TLS trace + support. + untrusted - Logs trust chain verification problems. This is turned - on automatically at security levels that use peer names - signed by Certification Authorities to validate certifi- - cates. So while this setting is recognized, you should + Logs trust chain verification problems. This is turned + on automatically at security levels that use peer names + signed by Certification Authorities to validate certifi- + cates. So while this setting is recognized, you should never need to set it explicitly. peercert - This logs a one line summary of the remote SMTP server + This logs a one line summary of the remote SMTP server certificate subject, issuer, and fingerprints. certmatch - This logs remote SMTP server certificate matching, show- + This logs remote SMTP server certificate matching, show- ing the CN and each subjectAltName and which name - matched. With DANE, logs matching of TLSA record + matched. With DANE, logs matching of TLSA record trust-anchor and end-entity certificates. - cache This logs session cache operations, showing whether ses- - sion caching is effective with the remote SMTP server. - Automatically used when reconnecting with the -r option; + cache This logs session cache operations, showing whether ses- + sion caching is effective with the remote SMTP server. + Automatically used when reconnecting with the -r option; rarely needs to be set explicitly. verbose Enables verbose logging in the Postfix TLS driver; includes all of peercert..cache and more. - The default is routine,certmatch. After a reconnect, peercert, + The default is routine,certmatch. After a reconnect, peercert, certmatch and verbose are automatically disabled while cache and summary are enabled. -m count (default: 5) - When the -r delay option is specified, the -m option determines - the maximum number of reconnect attempts to use with a server - behind a load balancer, to see whether connection caching is - likely to be effective for this destination. Some MTAs don't - expose the underlying server identity in their EHLO response; - with these servers there will never be more than 1 reconnection + When the -r delay option is specified, the -m option determines + the maximum number of reconnect attempts to use with a server + behind a load balancer, to see whether connection caching is + likely to be effective for this destination. Some MTAs don't + expose the underlying server identity in their EHLO response; + with these servers there will never be more than 1 reconnection attempt. -M insecure_mx_policy (default: dane) - The TLS policy for MX hosts with "secure" TLSA records when the - nexthop destination security level is dane, but the MX record + The TLS policy for MX hosts with "secure" TLSA records when the + nexthop destination security level is dane, but the MX record was found via an "insecure" MX lookup. See the main.cf documen- tation for smtp_tls_dane_insecure_mx_policy for details. -o name=value - Specify zero or more times to override the value of the main.cf - parameter name with value. Possible use-cases include overrid- - ing the values of TLS library parameters, or "myhostname" to + Specify zero or more times to override the value of the main.cf + parameter name with value. Possible use-cases include overrid- + ing the values of TLS library parameters, or "myhostname" to configure the SMTP EHLO name sent to the remote server. -p protocols (default: >=TLSv1) - TLS protocols that posttls-finger(1) will exclude or include. + TLS protocols that posttls-finger(1) will exclude or include. See smtp_tls_mandatory_protocols for details. -P CApath/ (default: none) - The OpenSSL CApath/ directory (indexed via c_rehash(1)) for + The OpenSSL CApath/ directory (indexed via c_rehash(1)) for remote SMTP server certificate verification. By default no CAp- ath is used and no public CAs are trusted. -r delay - With a cacheable TLS session, disconnect and reconnect after + With a cacheable TLS session, disconnect and reconnect after delay seconds. Report whether the session is re-used. Retry if a - new server is encountered, up to 5 times or as specified with - the -m option. By default reconnection is disabled, specify a + new server is encountered, up to 5 times or as specified with + the -m option. By default reconnection is disabled, specify a positive delay to enable this behavior. -R Use SRV lookup instead of MX. -s servername - The server name to send with the TLS Server Name Indication - (SNI) extension. When the server has DANE TLSA records, this - parameter is ignored and the TLSA base domain is used instead. - Otherwise, SNI is not used by default, but can be enabled by + The server name to send with the TLS Server Name Indication + (SNI) extension. When the server has DANE TLSA records, this + parameter is ignored and the TLSA base domain is used instead. + Otherwise, SNI is not used by default, but can be enabled by specifying the desired value with this option. - -S Disable SMTP; that is, connect to an LMTP server. The default - port for LMTP over TCP is 24. Alternative ports can specified - by appending ":servicename" or ":portnumber" to the destination + -S Disable SMTP; that is, connect to an LMTP server. The default + port for LMTP over TCP is 24. Alternative ports can specified + by appending ":servicename" or ":portnumber" to the destination argument. -t timeout (default: 30) @@ -292,41 +305,41 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) reading the remote server's 220 banner. -T timeout (default: 30) - The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT. + The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT. - -v Enable verbose Postfix logging. Specify more than once to + -v Enable verbose Postfix logging. Specify more than once to increase the level of verbose logging. - -w Enable outgoing TLS wrapper mode, or SUBMISSIONS/SMTPS support. - This is typically provided on port 465 by servers that are com- - patible with the SMTP-in-SSL protocol, rather than the STARTTLS - protocol. The destination domain:port must of course provide + -w Enable outgoing TLS wrapper mode, or SUBMISSIONS/SMTPS support. + This is typically provided on port 465 by servers that are com- + patible with the SMTP-in-SSL protocol, rather than the STARTTLS + protocol. The destination domain:port must of course provide such a service. - -x Prefer RFC7250 non-X.509 raw public key (RPK) server creden- - tials. By default only X.509 certificates are accepted. This + -x Prefer RFC7250 non-X.509 raw public key (RPK) server creden- + tials. By default only X.509 certificates are accepted. This is analogous to setting smtp_tls_enable_rpk = yes in the smtp(8) client. At the fingerprint security level, when raw public keys - are enabled, only public key (and not certificate) fingerprints - will be compared against the specified list of match arguments. - Certificate fingerprints are fragile when raw public keys are - solicited, the server may at some point in time start returning + are enabled, only public key (and not certificate) fingerprints + will be compared against the specified list of match arguments. + Certificate fingerprints are fragile when raw public keys are + solicited, the server may at some point in time start returning only the public key. - -X Enable tlsproxy(8) mode. This is an unsupported mode, for pro- + -X Enable tlsproxy(8) mode. This is an unsupported mode, for pro- gram development only. [inet:]domain[:port] Connect via TCP to domain domain, port port. The default port is - smtp (or 24 with LMTP). With SMTP an MX lookup is performed to - resolve the domain to a host, unless the domain is enclosed in - []. If you want to connect to a specific MX host, for instance - mx1.example.com, specify [mx1.example.com] as the destination + smtp (or 24 with LMTP). With SMTP an MX lookup is performed to + resolve the domain to a host, unless the domain is enclosed in + []. If you want to connect to a specific MX host, for instance + mx1.example.com, specify [mx1.example.com] as the destination and example.com as a match argument. When using DNS, the desti- - nation domain is assumed fully qualified and no default domain - or search suffixes are applied; you must use fully-qualified - names or also enable native host lookups (these don't support - dane or dane-only as no DNSSEC validation information is avail- + nation domain is assumed fully qualified and no default domain + or search suffixes are applied; you must use fully-qualified + names or also enable native host lookups (these don't support + dane or dane-only as no DNSSEC validation information is avail- able via native lookups). unix:pathname @@ -335,8 +348,8 @@ POSTTLS-FINGER(1) POSTTLS-FINGER(1) match ... With no match arguments specified, certificate peername matching uses the compiled-in default strategies for each security level. - If you specify one or more arguments, these will be used as the - list of certificate or public-key digests to match for the fin- + If you specify one or more arguments, these will be used as the + list of certificate or public-key digests to match for the fin- gerprint level, or as the list of DNS names to match in the cer- tificate at the verify and secure levels. If the security level is dane, or dane-only the match names are ignored, and hostname, diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index 04e8ed84a..97be3c227 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -794,6 +794,16 @@ SMTP(8) SMTP(8) Optional TLS loglevel override that depends on the remote peer host name or IP address. + smtp_tls_trace_size_limit (102400) + Size limit, in bytes, for the TLS protocol transcript that the + Postfix SMTP client writes when the "trace" keyword is included + in the TLS loglevel for a peer (smtp_tls_loglevel or + smtp_tls_loglevel_maps). + + tls_trace_rate_limit (1) + The maximum number of TLS traces per anvil_rate_time_unit that + all Postfix daemons combined will create. + OBSOLETE TLS CONTROLS The following configuration parameters exist for compatibility with Postfix versions before 2.3. Support for these will be removed in a diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index 75800d0e9..34d70a154 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -678,6 +678,13 @@ SMTPD(8) SMTPD(8) Optional TLS loglevel override that depends on the remote peer host name or IP address. + smtpd_tls_trace_size_limit (102400) + The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. + + tls_trace_rate_limit (1) + The maximum number of TLS traces per anvil_rate_time_unit that + all Postfix daemons combined will create. + OBSOLETE TLS CONTROLS The following configuration parameters exist for compatibility with Postfix versions before 2.3. Support for these will be removed in a diff --git a/postfix/makedefs b/postfix/makedefs index d21749a23..2ec81ccc4 100644 --- a/postfix/makedefs +++ b/postfix/makedefs @@ -109,6 +109,8 @@ # uses snprintf() except on ancient systems. # .IP \fB-DNO_STDBOOL\fR # Don't use . This is usually auto-detected. +# .IP \fB-DNO_TLS_TRACE\fR +# Build without OpenSSL 3 (and later) debug trace support. # .RE # .IP \fBDEBUG=\fIdebug_level\fR # Specifies a non-default debugging level. The default is \fB-g\fR. @@ -885,7 +887,7 @@ CCARGS="$CCARGS -DSNAPSHOT" # Non-production: needs thorough testing, or major changes are still # needed before the code stabilizes. -#CCARGS="$CCARGS -DNONPROD" +#CCARGS="$CCARGS -DNONPROD='\"featurename-nonprod\"'" # Workaround: prepend Postfix include files before other include files. CCARGS="-I. -I../../include $CCARGS" diff --git a/postfix/man/man1/makedefs.1 b/postfix/man/man1/makedefs.1 index d479e80a5..15e8a0b6f 100644 --- a/postfix/man/man1/makedefs.1 +++ b/postfix/man/man1/makedefs.1 @@ -112,6 +112,8 @@ Use sprintf() instead of snprintf(). By default, Postfix uses snprintf() except on ancient systems. .IP \fB\-DNO_STDBOOL\fR Don't use . This is usually auto\-detected. +.IP \fB\-DNO_TLS_TRACE\fR +Build without OpenSSL 3 (and later) debug trace support. .RE .IP \fBDEBUG=\fIdebug_level\fR Specifies a non\-default debugging level. The default is \fB\-g\fR. diff --git a/postfix/man/man1/posttls-finger.1 b/postfix/man/man1/posttls-finger.1 index ee8bd8a68..66cb9ea32 100644 --- a/postfix/man/man1/posttls-finger.1 +++ b/postfix/man/man1/posttls-finger.1 @@ -20,7 +20,7 @@ destination is a domainname; with LMTP it is either a domainname prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR. If Postfix is built without TLS support, the resulting \fBposttls\-finger\fR(1) program has very limited functionality, and only the \fB\-a\fR, \fB\-c\fR, -\fB\-h\fR, \fB\-o\fR, \fB\-S\fR, \fB\-t\fR, \fB\-T\fR and \fB\-v\fR options +\fB\-h\fR, \fB\-S\fR, \fB\-t\fR, \fB\-T\fR and \fB\-v\fR options are available. Note: this is an unsupported test program. No attempt is made @@ -187,6 +187,19 @@ Log hexadecimal packet dumps of the SSL handshake; for experts only. .IP "\fBssl\-session\-packet\-dump\fR" Log hexadecimal packet dumps of the entire SSL session; only useful to those who can debug SSL protocol problems from hex dumps. +.IP "\fBtrace\fR" +Available with Postfix 3.12 and later. Write a human\-readable +protocol transcript of the TLS session to a file in the current +directory. The file is named +\fIprocess_name\fR\-\fIdate_time\fR.\fIusec\fR\-\fIpeer\fR\-\fIXXXXXX\fR, +where \fIXXXXXX\fR is replaced with a unique string to avoid +filename conflicts. All reconnect attempts in a single +command\-line run share the same trace file, separated by "=== +posttls\-finger reconnect ===" lines. When \fB\-X\fR is in effect, +tlsproxy(8) generates the trace file under +\fI$queue_directory\fR/tlstrace/ instead. The keyword is +ignored with a warning if Postfix or OpenSSL was built without +TLS trace support. .IP "\fBuntrusted\fR" Logs trust chain verification problems. This is turned on automatically at security levels that use peer names signed diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index fc5997b19..a5a7a28a4 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -3702,6 +3702,10 @@ The LMTP\-specific version of the smtp_tls_session_cache_timeout configuration parameter. See there for details. .PP This feature is available in Postfix 2.3 and later. +.SH lmtp_tls_trace_size_limit (default: 102400) +The \fBlmtp\fR(8) equivalent of smtp_tls_trace_size_limit. +.PP +This feature is available in Postfix 3.12 and later. .SH lmtp_tls_trust_anchor_file (default: empty) The LMTP\-specific version of the smtp_tls_trust_anchor_file configuration parameter. See there for details. @@ -6143,6 +6147,12 @@ postscreen_use_tls and postscreen_enforce_tls. See smtpd_tls_security_level for details. .PP This feature is available in Postfix 2.8 and later. +.SH postscreen_tls_trace_size_limit (default: $smtpd_tls_trace_size_limit) +The \fBpostscreen\fR(8) equivalent of smtpd_tls_trace_size_limit. +\fBpostscreen\fR(8) generates the trace via \fBtlsproxy\fR(8); the trace file +name starts with "tlsproxy\-". +.PP +This feature is available in Postfix 3.12 and later. .SH postscreen_upstream_proxy_protocol (default: empty) The name of the proxy protocol used by an optional before\-postscreen proxy agent. When a proxy agent is used, this protocol conveys local @@ -9894,6 +9904,19 @@ transmission after STARTTLS. Do not use "smtp_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged. .PP +With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtp_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the \fBcontent\fR of application data messages) of the TLS +session to a per\-connection file under $queue_directory/tlstrace/, +capped at smtp_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtp_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (\fBfind\fR(1), \fBlogrotate\fR(8), or similar). +.PP This feature is available in Postfix 2.2 and later. .SH smtp_tls_loglevel_maps (default: empty) Optional TLS loglevel override that depends on the remote peer @@ -10833,6 +10856,16 @@ one\-letter suffix that specifies the time unit). Time units: s The default time unit is s (seconds). .PP This feature is available in Postfix 2.2 and later. +.SH smtp_tls_trace_size_limit (default: 102400) +Size limit, in bytes, for the TLS protocol transcript that the +Postfix SMTP client writes when the "trace" keyword is included in +the TLS loglevel for a peer (smtp_tls_loglevel or +smtp_tls_loglevel_maps). The transcript is written to a per\- +connection file under $queue_directory/tlstrace/, named +smtp\-pid\-time\-peer.txt. Once the limit is reached the trace is +truncated with a one\-line note. A value of 0 disables tracing. +.PP +This feature is available in Postfix 3.12 and later. .SH smtp_tls_trust_anchor_file (default: empty) Zero or more PEM\-format files with trust\-anchor certificates and/or public keys. If the parameter is not empty the root CAs in @@ -14422,6 +14455,19 @@ transmission after STARTTLS. Do not use "smtpd_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged. .PP +With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtpd_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the \fBcontent\fR of application data messages) of the TLS +session to a per\-connection file under $queue_directory/tlstrace/, +capped at smtpd_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtpd_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (\fBfind\fR(1), \fBlogrotate\fR(8), or similar). +.PP This feature is available in Postfix 2.2 and later. .SH smtpd_tls_loglevel_maps (default: empty) Optional TLS loglevel override that depends on the remote peer @@ -14768,6 +14814,11 @@ The default time unit is s (seconds). .PP This feature is available in Postfix 2.2 and later, and updated for TLS session ticket support in Postfix 2.11. +.SH smtpd_tls_trace_size_limit (default: 102400) +The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. +File names start with "smtpd\-" instead of "smtp\-". +.PP +This feature is available in Postfix 3.12 and later. .SH smtpd_tls_wrappermode (default: no) Run the Postfix SMTP server in TLS "wrapper" mode, instead of using the STARTTLS command. @@ -15894,6 +15945,12 @@ Postfix >= 3.4. See \fBSSL_CTX_set_options\fR(3). .br .PP This feature is available in Postfix 2.11 and later. +.SH tls_trace_rate_limit (default: 1) +The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. Specify a value <=0 to +disable the limit. +.PP +This feature is available in Postfix 3.12 and later. .SH tls_trust_server_ccerts (default: no) Whether to trust client certificates whose extended key usage (EKU) lists only \fBserverAuth\fR and not \fBclientAuth\fR as valid TLS client diff --git a/postfix/man/man8/anvil.8 b/postfix/man/man8/anvil.8 index 89ea9a6c3..a98503b42 100644 --- a/postfix/man/man8/anvil.8 +++ b/postfix/man/man8/anvil.8 @@ -161,6 +161,27 @@ The \fBanvil\fR(8) server answers with the number of auth requests per unit time for the (service, client) combination specified with \fBident\fR: +.nf + \fBstatus=0\fR + \fBrate=\fInumber\fR +.fi +.SH "TLS TRACE RATE CONTROL" +.na +.nf +.ad +.fi +To register a TLS trace event send the following request +to the \fBanvil\fR(8) server: + +.nf + \fBrequest=tlstr\fR + \fBident=\fIstring\fR +.fi + +The \fBanvil\fR(8) server answers with the number of TLS trace +requests per unit time for the (service, client) combination +specified with \fBident\fR: + .nf \fBstatus=0\fR \fBrate=\fInumber\fR diff --git a/postfix/man/man8/postscreen.8 b/postfix/man/man8/postscreen.8 index 73a338e77..0adf0c110 100644 --- a/postfix/man/man8/postscreen.8 +++ b/postfix/man/man8/postscreen.8 @@ -434,6 +434,8 @@ The \fBpostscreen\fR(8) equivalent of smtpd_tls_loglevel. .IP "\fBpostscreen_tls_loglevel_maps ($smtpd_tls_loglevel_maps)\fR" Optional TLS loglevel override that depends on the remote peer host name or IP address. +.IP "\fBpostscreen_tls_trace_size_limit ($smtpd_tls_trace_size_limit)\fR" +The \fBpostscreen\fR(8) equivalent of smtpd_tls_trace_size_limit. .IP "\fBpostscreen_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR" The \fBpostscreen\fR(8) equivalent of smtpd_tls_mandatory_ciphers. .IP "\fBpostscreen_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR" @@ -444,6 +446,9 @@ The \fBpostscreen\fR(8) equivalent of smtpd_tls_mandatory_protocols. The \fBpostscreen\fR(8) equivalent of smtpd_tls_protocols. .IP "\fBpostscreen_tls_req_ccert ($smtpd_tls_req_ccert)\fR" The \fBpostscreen\fR(8) equivalent of smtpd_tls_req_ccert. +.IP "\fBtls_trace_rate_limit (1)\fR" +The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. .SH "OBSOLETE STARTTLS SUPPORT CONTROLS" .na .nf diff --git a/postfix/man/man8/smtp.8 b/postfix/man/man8/smtp.8 index 11da44d31..e8de6fdb9 100644 --- a/postfix/man/man8/smtp.8 +++ b/postfix/man/man8/smtp.8 @@ -709,6 +709,14 @@ Available in Postfix version 3.12 and later: .IP "\fBsmtp_tls_loglevel_maps (empty)\fR" Optional TLS loglevel override that depends on the remote peer host name or IP address. +.IP "\fBsmtp_tls_trace_size_limit (102400)\fR" +Size limit, in bytes, for the TLS protocol transcript that the +Postfix SMTP client writes when the "trace" keyword is included in +the TLS loglevel for a peer (smtp_tls_loglevel or +smtp_tls_loglevel_maps). +.IP "\fBtls_trace_rate_limit (1)\fR" +The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. .SH "OBSOLETE TLS CONTROLS" .na .nf diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index f79feab31..03c24b45b 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -596,6 +596,11 @@ Available in Postfix version 3.12 and later: .IP "\fBsmtpd_tls_loglevel_maps (empty)\fR" Optional TLS loglevel override that depends on the remote peer host name or IP address. +.IP "\fBsmtpd_tls_trace_size_limit (102400)\fR" +The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. +.IP "\fBtls_trace_rate_limit (1)\fR" +The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. .SH "OBSOLETE TLS CONTROLS" .na .nf diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 159e05a12..064684fa6 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -820,6 +820,12 @@ while (<>) { s;\btls_fast_shutdown_enable\b;$&;g; s;\btls_trust_server_ccerts\b;$&;g; + s;\blmtp_tls_trace_size_limit\b;$&;g; + s;\bpostscreen_tls_trace_size_limit\b;$&;g; + s;\bsmtp_tls_trace_size_limit\b;$&;g; + s;\bsmtpd_tls_trace_size_limit\b;$&;g; + s;\btls_trace_rate_limit\b;$&;g; + s;\bfrozen_delivered_to\b;$&;g; s;\breset_owner_alias\b;$&;g; s;\benable_long_queue_ids\b;$&;g; diff --git a/postfix/proto/INSTALL.html b/postfix/proto/INSTALL.html index 898d5298b..cd6180470 100644 --- a/postfix/proto/INSTALL.html +++ b/postfix/proto/INSTALL.html @@ -891,6 +891,12 @@ they are known to be available. instead of snprintf(). By default, Postfix uses snprintf() except on ancient systems. + -DNO_TLS_TRACE Do not build with support +for OpenSSL TLS traces. Some vendor OpenSSL runtime libraries are built +without support for tracing, and Postfix software built on a system with +TLS trace support would not work when installed on one without. See +smtp_tls_loglevel in the postconf(5) manual. + DEBUG=debug_level Specifies a non-default compiler debugging level. The default is "-g". Specify DEBUG= to turn off debugging. diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 77683ff39..7dfaf4845 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -10102,6 +10102,19 @@ transmission after STARTTLS.

Do not use "smtpd_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged.

+

With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtpd_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the content of application data messages) of the TLS +session to a per-connection file under $queue_directory/tlstrace/, +capped at smtpd_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtpd_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (find(1), logrotate(8), or similar).

+

This feature is available in Postfix 2.2 and later.

%PARAM smtpd_tls_received_header no @@ -10610,6 +10623,19 @@ transmission after STARTTLS.

Do not use "smtp_tls_loglevel = 2" or higher except in case of problems. Use of loglevel 4 is strongly discouraged.

+

With Postfix 3.12 and later, any of the levels above may be +followed by ",trace" (for example "smtp_tls_loglevel = 1,trace"). +The "trace" keyword writes a protocol message trace (this does not +include the content of application data messages) of the TLS +session to a per-connection file under $queue_directory/tlstrace/, +capped at smtp_tls_trace_size_limit bytes per file. The path is +written to the system log at the start of each trace. The "trace" +keyword is intended for occasional use via smtp_tls_loglevel_maps +for a specific peer; setting it globally will produce a trace file +for every TLS session. Old trace files are not removed +automatically; operators who enable the feature should arrange +periodic cleanup (find(1), logrotate(8), or similar).

+

This feature is available in Postfix 2.2 and later.

%PARAM smtp_tls_session_cache_database @@ -20956,3 +20982,45 @@ until a match is found or until all subnetworks have been tried.

The lmtp(8) equivalent of smtp_tls_loglevel_maps.

This feature is available in Postfix 3.12 and later.

+ +%PARAM smtp_tls_trace_size_limit 102400 + +

Size limit, in bytes, for the TLS protocol transcript that the +Postfix SMTP client writes when the "trace" keyword is included in +the TLS loglevel for a peer (smtp_tls_loglevel or +smtp_tls_loglevel_maps). The transcript is written to a per- +connection file under $queue_directory/tlstrace/, named +smtp-pid-time-peer.txt. Once the limit is reached the trace is +truncated with a one-line note. A value of 0 disables tracing. +

+ +

This feature is available in Postfix 3.12 and later.

+ +%PARAM lmtp_tls_trace_size_limit 102400 + +

The lmtp(8) equivalent of smtp_tls_trace_size_limit.

+ +

This feature is available in Postfix 3.12 and later.

+ +%PARAM smtpd_tls_trace_size_limit 102400 + +

The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. +File names start with "smtpd-" instead of "smtp-".

+ +

This feature is available in Postfix 3.12 and later.

+ +%PARAM postscreen_tls_trace_size_limit $smtpd_tls_trace_size_limit + +

The postscreen(8) equivalent of smtpd_tls_trace_size_limit. +postscreen(8) generates the trace via tlsproxy(8); the trace file +name starts with "tlsproxy-".

+ +

This feature is available in Postfix 3.12 and later.

+ +%PARAM tls_trace_rate_limit 1 + +

The maximum number of TLS traces per anvil_rate_time_unit that +all Postfix daemons combined will create. Specify a value ≤0 to +disable the limit.

+ +

This feature is available in Postfix 3.12 and later.

diff --git a/postfix/proto/stop b/postfix/proto/stop index f1ed8411c..e7e6efe2f 100644 --- a/postfix/proto/stop +++ b/postfix/proto/stop @@ -1718,3 +1718,5 @@ yana YANA substrings Substring +tlstrace +yyyymmddhhmmss diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history index 7b282060f..c463b4d8f 100644 --- a/postfix/proto/stop.double-history +++ b/postfix/proto/stop.double-history @@ -254,3 +254,8 @@ proto proto stop proto stop double cc postfix postfix c postmap postmap c postmulti postmulti c stutter File postmulti postmulti c with other command line tools File postlog postlog c + auxiliary collate collate pl + global mail_params h postscreen postscreen c + smtpd smtpd c tls tls h tls tls_client c tls tls_misc c + smtp smtp c smtp smtp_params c smtp smtp_proto c + tlsproxy tlsproxy c tlsproxy tlsproxy_client c diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc index 77f4e7417..0fb87a2ca 100644 --- a/postfix/proto/stop.spell-cc +++ b/postfix/proto/stop.spell-cc @@ -1982,3 +1982,14 @@ openUTS xff nameN valueN +XXXXXX +cwd +mkstemp +tlstr +tlstrace +tlstrs +yyyymmddhhmmss +datetime +getpeername +overshift +Sayre diff --git a/postfix/src/anvil/anvil.c b/postfix/src/anvil/anvil.c index 884be28b9..c8abec76c 100644 --- a/postfix/src/anvil/anvil.c +++ b/postfix/src/anvil/anvil.c @@ -149,6 +149,25 @@ /* \fBstatus=0\fR /* \fBrate=\fInumber\fR /* .fi +/* TLS TRACE RATE CONTROL +/* .ad +/* .fi +/* To register a TLS trace event send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=tlstr\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of TLS trace +/* requests per unit time for the (service, client) combination +/* specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi /* SECURITY /* .ad /* .fi @@ -318,6 +337,7 @@ typedef struct { int rcpt; /* recipient rate */ int ntls; /* new TLS session rate */ int auth; /* AUTH request rate */ + int tlstr; /* TLS trace event rate */ time_t start; /* time of first rate sample */ } ANVIL_REMOTE; @@ -349,6 +369,7 @@ typedef struct { (remote)->rcpt = 0; \ (remote)->ntls = 0; \ (remote)->auth = 0; \ + (remote)->tlstr = 0; \ (remote)->start = event_time(); \ } while(0) @@ -369,6 +390,7 @@ typedef struct { (remote)->rcpt = 0; \ (remote)->ntls = 0; \ (remote)->auth = 0; \ + (remote)->tlstr = 0; \ (remote)->start = _start; \ } while(0) @@ -399,6 +421,8 @@ typedef struct { #define ANVIL_REMOTE_INCR_AUTH(remote) ANVIL_REMOTE_INCR_RATE((remote), auth) +#define ANVIL_REMOTE_INCR_TLSTR(remote) ANVIL_REMOTE_INCR_RATE((remote), tlstr) + /* Drop connection from (service, client) state. */ #define ANVIL_REMOTE_DROP_ONE(remote) \ @@ -476,6 +500,7 @@ static ANVIL_MAX max_mail_rate; /* peak message rate */ static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */ static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */ static ANVIL_MAX max_auth_rate; /* peak AUTH request rate */ +static ANVIL_MAX max_tlstr_rate; /* peak TLS trace request rate */ static int max_cache_size; /* peak cache size */ static time_t max_cache_time; /* time of peak size */ @@ -584,6 +609,7 @@ static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident) SEND_ATTR_INT(ANVIL_ATTR_RCPT, anvil_remote->rcpt), SEND_ATTR_INT(ANVIL_ATTR_NTLS, anvil_remote->ntls), SEND_ATTR_INT(ANVIL_ATTR_AUTH, anvil_remote->auth), + SEND_ATTR_INT(ANVIL_ATTR_AUTH, anvil_remote->tlstr), ATTR_TYPE_END); } } @@ -755,6 +781,35 @@ static void anvil_remote_auth(VSTREAM *client_stream, const char *ident) ANVIL_MAX_UPDATE(max_auth_rate, anvil_remote->auth, anvil_remote->ident); } +/* anvil_remote_tlstr - register TLS trace request event */ + +static void anvil_remote_tlstr(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update TLS trace request rate and respond to requesting server. + */ + ANVIL_REMOTE_INCR_TLSTR(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->tlstr), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->tlstr > max_tlstr_rate.value) + ANVIL_MAX_UPDATE(max_tlstr_rate, anvil_remote->tlstr, anvil_remote->ident); +} + /* anvil_remote_newtls - register newtls event */ static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident) @@ -893,6 +948,7 @@ static void anvil_status_dump(char *unused_name, char **unused_argv) ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient"); ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls"); ANVIL_MAX_RATE_REPORT(max_auth_rate, "auth"); + ANVIL_MAX_RATE_REPORT(max_tlstr_rate, "tlstrace"); if (max_cache_size > 0) { msg_info("statistics: max cache size %d at %.15s", @@ -923,6 +979,7 @@ static void anvil_service(VSTREAM *client_stream, char *unused_service, char **a ANVIL_REQ_DISC, anvil_remote_disconnect, ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat, ANVIL_REQ_AUTH, anvil_remote_auth, + ANVIL_REQ_TLSTR, anvil_remote_tlstr, ANVIL_REQ_LOOKUP, anvil_remote_lookup, 0, 0, }; diff --git a/postfix/src/global/anvil_clnt.c b/postfix/src/global/anvil_clnt.c index fff9ec7f4..e536c52d0 100644 --- a/postfix/src/global/anvil_clnt.c +++ b/postfix/src/global/anvil_clnt.c @@ -49,13 +49,20 @@ /* const char *addr; /* int *auths; /* +/* int anvil_clnt_tlstr(anvil_clnt, service, addr, tlstrs) +/* ANVIL_CLNT *anvil_clnt; +/* const char *service; +/* const char *addr; +/* int *tlstrs; +/* /* int anvil_clnt_disconnect(anvil_clnt, service, addr) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; /* /* int anvil_clnt_lookup(anvil_clnt, service, addr, count, -/* rate, msgs, rcpts, ntls, auths) +/* rate, msgs, rcpts, ntls, auths, +/* tlstrs) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; @@ -65,6 +72,7 @@ /* int *rcpts; /* int *ntls; /* int *auths; +/* int *tlstrs; /* DESCRIPTION /* anvil_clnt_create() instantiates a local anvil service /* client endpoint. @@ -91,6 +99,9 @@ /* anvil_clnt_auth() registers an AUTH event and returns the /* current AUTH event rate for the specified remote client. /* +/* anvil_clnt_tlstr() registers a TLS trace event and returns the +/* current TLS trace event rate for the specified service and peer. +/* /* anvil_clnt_disconnect() informs the anvil server that a remote /* client has disconnected. /* @@ -212,7 +223,8 @@ void anvil_clnt_free(ANVIL_CLNT *anvil_clnt) int anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service, const char *addr, int *count, int *rate, - int *msgs, int *rcpts, int *newtls, int *auths) + int *msgs, int *rcpts, int *newtls, + int *auths, int *tlstrs) { char *ident = ANVIL_IDENT(service, addr); int status; @@ -230,7 +242,8 @@ int anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service, RECV_ATTR_INT(ANVIL_ATTR_RCPT, rcpts), RECV_ATTR_INT(ANVIL_ATTR_NTLS, newtls), RECV_ATTR_INT(ANVIL_ATTR_AUTH, auths), - ATTR_TYPE_END) != 7) + RECV_ATTR_INT(ANVIL_ATTR_TLSTR, tlstrs), + ATTR_TYPE_END) != 8) status = ANVIL_STAT_FAIL; else if (status != ANVIL_STAT_OK) status = ANVIL_STAT_FAIL; @@ -383,6 +396,30 @@ int anvil_clnt_auth(ANVIL_CLNT *anvil_clnt, const char *service, return (status); } +/* anvil_clnt_tlstr - heads-up and status query */ + +int anvil_clnt_tlstr(ANVIL_CLNT *anvil_clnt, const char *service, + const char *addr, int *tlstrs) +{ + char *ident = ANVIL_IDENT(service, addr); + int status; + + if (attr_clnt_request((ATTR_CLNT *) anvil_clnt, + ATTR_FLAG_NONE, /* Query attributes. */ + SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_TLSTR), + SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes. */ + RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status), + RECV_ATTR_INT(ANVIL_ATTR_RATE, tlstrs), + ATTR_TYPE_END) != 2) + status = ANVIL_STAT_FAIL; + else if (status != ANVIL_STAT_OK) + status = ANVIL_STAT_FAIL; + myfree(ident); + return (status); +} + /* anvil_clnt_disconnect - heads-up only */ int anvil_clnt_disconnect(ANVIL_CLNT *anvil_clnt, const char *service, diff --git a/postfix/src/global/anvil_clnt.h b/postfix/src/global/anvil_clnt.h index d060155a6..ca93812b9 100644 --- a/postfix/src/global/anvil_clnt.h +++ b/postfix/src/global/anvil_clnt.h @@ -36,6 +36,7 @@ #define ANVIL_REQ_NTLS_STAT "newtls_status" #define ANVIL_REQ_AUTH "auth" #define ANVIL_REQ_LOOKUP "lookup" +#define ANVIL_REQ_TLSTR "tlstr" #define ANVIL_ATTR_IDENT "ident" #define ANVIL_ATTR_COUNT "count" #define ANVIL_ATTR_RATE "rate" @@ -43,6 +44,7 @@ #define ANVIL_ATTR_RCPT "rcpt" #define ANVIL_ATTR_NTLS "newtls" #define ANVIL_ATTR_AUTH "auth" +#define ANVIL_ATTR_TLSTR "tlstr" #define ANVIL_ATTR_STATUS "status" #define ANVIL_STAT_OK 0 @@ -60,7 +62,8 @@ extern int anvil_clnt_rcpt(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls_stat(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_auth(ANVIL_CLNT *, const char *, const char *, int *); -extern int anvil_clnt_lookup(ANVIL_CLNT *, const char *, const char *, int *, int *, int *, int *, int *, int *); +extern int anvil_clnt_tlstr(ANVIL_CLNT *, const char *, const char *, int *); +extern int anvil_clnt_lookup(ANVIL_CLNT *, const char *, const char *, int *, int *, int *, int *, int *, int *, int *); extern int anvil_clnt_disconnect(ANVIL_CLNT *, const char *, const char *); extern void anvil_clnt_free(ANVIL_CLNT *); diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index f35f81faa..ec934d4a1 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1445,6 +1445,10 @@ extern char *var_smtpd_tls_loglevel; #define DEF_SMTPD_TLS_LOGLEVEL_MAPS "" extern char *var_smtpd_tls_loglevel_maps; +#define VAR_SMTPD_TLS_TRACE_SIZE_LIMIT "smtpd_tls_trace_size_limit" +#define DEF_SMTPD_TLS_TRACE_SIZE_LIMIT 102400 +extern int var_smtpd_tls_trace_size_limit; + #define VAR_SMTPD_TLS_RECHEAD "smtpd_tls_received_header" #define DEF_SMTPD_TLS_RECHEAD 0 extern bool var_smtpd_tls_received_header; @@ -1626,6 +1630,12 @@ extern char *var_lmtp_tls_loglevel; /* In tlsmgr(8) */ extern char *var_smtp_tls_loglevel_maps; extern char *var_lmtp_tls_loglevel_maps; +#define VAR_SMTP_TLS_TRACE_SIZE_LIMIT "smtp_tls_trace_size_limit" +#define DEF_SMTP_TLS_TRACE_SIZE_LIMIT 102400 +#define VAR_LMTP_TLS_TRACE_SIZE_LIMIT "lmtp_tls_trace_size_limit" +#define DEF_LMTP_TLS_TRACE_SIZE_LIMIT 102400 +extern int var_smtp_tls_trace_size_limit; + #define VAR_SMTP_TLS_NOTEOFFER "smtp_tls_note_starttls_offer" #define DEF_SMTP_TLS_NOTEOFFER 0 #define VAR_LMTP_TLS_NOTEOFFER "lmtp_tls_note_starttls_offer" @@ -4757,6 +4767,10 @@ extern char *var_psc_tls_loglevel; #define DEF_PSC_TLS_LOGLEVEL_MAPS "$" VAR_SMTPD_TLS_LOGLEVEL_MAPS extern char *var_psc_tls_loglevel_maps; +#define VAR_PSC_TLS_TRACE_SIZE_LIMIT "postscreen_tls_trace_size_limit" +#define DEF_PSC_TLS_TRACE_SIZE_LIMIT "$" VAR_SMTPD_TLS_TRACE_SIZE_LIMIT +extern int var_psc_tls_trace_size_limit; + #define VAR_PSC_TLS_MAND_CIPH "postscreen_tls_mandatory_ciphers" #define DEF_PSC_TLS_MAND_CIPH "$" VAR_SMTPD_TLS_MAND_CIPH extern char *var_psc_tls_mand_ciph; @@ -4781,6 +4795,13 @@ extern int var_psc_tls_ccert_vd; #define DEF_PSC_STARTTLS_TMOUT "$" VAR_SMTPD_STARTTLS_TMOUT extern int var_psc_starttls_tmout; + /* + * How many TLS traces per anvil(8) time unit. + */ +#define VAR_TLS_TRACE_ANVIL_RATE "tls_trace_rate_limit" +#define DEF_TLS_TRACE_ANVIL_RATE 1 +extern int var_tls_trace_anvil_rate; + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 96e4ad8b9..a53144448 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 "20260510" +#define MAIL_RELEASE_DATE "20260514" #define MAIL_VERSION_NUMBER "3.12" #ifdef SNAPSHOT @@ -30,7 +30,7 @@ #endif #ifdef NONPROD -#define MAIL_VERSION_PROD "-nonprod" +#define MAIL_VERSION_PROD "-" NONPROD #else #define MAIL_VERSION_PROD "" #endif diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c index 9614608da..22ab219fd 100644 --- a/postfix/src/postscreen/postscreen.c +++ b/postfix/src/postscreen/postscreen.c @@ -398,6 +398,8 @@ /* .IP "\fBpostscreen_tls_loglevel_maps ($smtpd_tls_loglevel_maps)\fR" /* Optional TLS loglevel override that depends on the remote peer /* host name or IP address. +/* .IP "\fBpostscreen_tls_trace_size_limit ($smtpd_tls_trace_size_limit)\fR" +/* The \fBpostscreen\fR(8) equivalent of smtpd_tls_trace_size_limit. /* .IP "\fBpostscreen_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR" /* The \fBpostscreen\fR(8) equivalent of smtpd_tls_mandatory_ciphers. /* .IP "\fBpostscreen_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR" @@ -408,6 +410,9 @@ /* The \fBpostscreen\fR(8) equivalent of smtpd_tls_protocols. /* .IP "\fBpostscreen_tls_req_ccert ($smtpd_tls_req_ccert)\fR" /* The \fBpostscreen\fR(8) equivalent of smtpd_tls_req_ccert. +/* .IP "\fBtls_trace_rate_limit (1)\fR" +/* The maximum number of TLS traces per anvil_rate_time_unit that +/* all Postfix daemons combined will create. /* OBSOLETE STARTTLS SUPPORT CONTROLS /* .ad /* .fi @@ -646,6 +651,7 @@ char *var_smtpd_tls_proto; int var_smtpd_tls_ccert_vd; int var_smtpd_starttls_tmout; +int var_smtpd_tls_trace_size_limit; bool var_psc_tls_ask_ccert; bool var_psc_tls_enable_rpk; @@ -676,6 +682,7 @@ char *var_psc_tls_proto; int var_psc_tls_ccert_vd; int var_psc_starttls_tmout; +int var_psc_tls_trace_size_limit; /* * Global variables. @@ -1369,6 +1376,7 @@ int main(int argc, char **argv) VAR_PSC_CMD_COUNT, DEF_PSC_CMD_COUNT, &var_psc_cmd_count, 1, 0, VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0, VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, + VAR_SMTPD_TLS_TRACE_SIZE_LIMIT, DEF_SMTPD_TLS_TRACE_SIZE_LIMIT, &var_smtpd_tls_trace_size_limit, 0, 0, 0, }; static const CONFIG_NINT_TABLE nint_table[] = { @@ -1377,6 +1385,7 @@ int main(int argc, char **argv) VAR_PSC_CCONN_LIMIT, DEF_PSC_CCONN_LIMIT, &var_psc_cconn_limit, 0, 0, VAR_PSC_DNSBL_ALTHRESH, DEF_PSC_DNSBL_ALTHRESH, &var_psc_dnsbl_althresh, 0, 0, VAR_PSC_TLS_CCERT_VD, DEF_PSC_TLS_CCERT_VD, &var_psc_tls_ccert_vd, 0, 0, + VAR_PSC_TLS_TRACE_SIZE_LIMIT, DEF_PSC_TLS_TRACE_SIZE_LIMIT, &var_psc_tls_trace_size_limit, 0, 0, 0, }; static const CONFIG_TIME_TABLE time_table[] = { diff --git a/postfix/src/postscreen/postscreen_tls_conf.c b/postfix/src/postscreen/postscreen_tls_conf.c index 331d29826..3ba6d0908 100644 --- a/postfix/src/postscreen/postscreen_tls_conf.c +++ b/postfix/src/postscreen/postscreen_tls_conf.c @@ -310,7 +310,9 @@ bool psc_tls_pre_start(const PSC_STATE *state, namaddr = state->smtp_client_addr_port, cipher_grade = cipher_grade, cipher_exclusions = STR(cipher_exclusions), - mdalg = var_psc_tls_fpt_dgst); + mdalg = var_psc_tls_fpt_dgst, + trace_size_limit = var_psc_tls_trace_size_limit, + trace_peer = state->smtp_client_addr); return (true); } diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c index da3cfe469..690aedb40 100644 --- a/postfix/src/posttls-finger/posttls-finger.c +++ b/postfix/src/posttls-finger/posttls-finger.c @@ -14,7 +14,7 @@ /* prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR. If /* Postfix is built without TLS support, the resulting \fBposttls-finger\fR(1) /* program has very limited functionality, and only the \fB-a\fR, \fB-c\fR, -/* \fB-h\fR, \fB-o\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options +/* \fB-h\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options /* are available. /* /* Note: this is an unsupported test program. No attempt is made @@ -181,6 +181,19 @@ /* .IP "\fBssl-session-packet-dump\fR" /* Log hexadecimal packet dumps of the entire SSL session; only useful /* to those who can debug SSL protocol problems from hex dumps. +/* .IP "\fBtrace\fR" +/* Available with Postfix 3.12 and later. Write a human-readable +/* protocol transcript of the TLS session to a file in the current +/* directory. The file is named +/* \fIprocess_name\fR-\fIdate_time\fR.\fIusec\fR-\fIpeer\fR-\fIXXXXXX\fR, +/* where \fIXXXXXX\fR is replaced with a unique string to avoid +/* filename conflicts. All reconnect attempts in a single +/* command-line run share the same trace file, separated by "=== +/* posttls-finger reconnect ===" lines. When \fB-X\fR is in effect, +/* tlsproxy(8) generates the trace file under +/* \fI$queue_directory\fR/tlstrace/ instead. The keyword is +/* ignored with a warning if Postfix or OpenSSL was built without +/* TLS trace support. /* .IP "\fBuntrusted\fR" /* Logs trust chain verification problems. This is turned on /* automatically at security levels that use peer names signed @@ -352,6 +365,7 @@ #include #include #include +#include /* INT_MAX */ #include #include #include @@ -509,12 +523,18 @@ typedef struct STATE { char *protocols; /* Protocol inclusion/exclusion */ int mxinsec_level; /* DANE for insecure MX RRs? */ int tlsproxy_mode; + char *trace_file; /* full path; built once per run */ + int trace_failed; /* give up after first open failure */ #endif OPTIONS options; /* JCL */ } STATE; static DNS_RR *host_addr(STATE *, const char *); +#if defined(USE_TLS) && defined(HAVE_SSL_TRACE) +static BIO *posttls_trace_open(void *, const char *); +#endif + #define HNAME(addr) (addr->qname) /* @@ -840,7 +860,9 @@ static int starttls(STATE *state) tlsrpt = 0, ffail_type = 0, dane = state->ddane ? - state->ddane : state->dane); + state->ddane : state->dane, + trace_size_limit = INT_MAX, + trace_peer = state->paddr); #define PROXY_OPEN_FLAGS \ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) @@ -947,7 +969,15 @@ static int starttls(STATE *state) mdalg = state->mdalg, tlsrpt = 0, ffail_type = 0, - dane = state->ddane ? state->ddane : state->dane); + dane = state->ddane ? state->ddane : state->dane, + trace_size_limit = INT_MAX, +#ifdef HAVE_SSL_TRACE + trace_open = posttls_trace_open, +#else + trace_open = 0, +#endif + trace_arg = (void *) state, + trace_peer = state->paddr); } /* tlsproxy_mode */ vstring_free(cipher_exclusions); if (state->helo) { @@ -1831,6 +1861,8 @@ static void cleanup(STATE *state) myfree(state->certfile); myfree(state->keyfile); myfree(state->sni); + if (state->trace_file) + myfree(state->trace_file); if (state->options.level) myfree(state->options.level); myfree(state->options.logopts); @@ -1873,20 +1905,66 @@ static void usage(void) exit(1); } -#ifdef USE_TLS -#ifndef OPENSSL_NO_SSL_TRACE -static void ssl_trace(int write_p, int version, int content_type, - const void *buf, size_t msglen, SSL *ssl, void *arg) +#if defined(USE_TLS) && defined(HAVE_SSL_TRACE) + +/* posttls_trace_open - open or reopen the SSL_trace destination file */ + +static BIO *posttls_trace_open(void *arg, const char *trace_peer) { - BIO *out = (BIO *) arg; + STATE *state = (STATE *) arg; + struct timeval tv; + FILE *fp; + BIO *bio; + + /* + * If the destination became non-writable on a previous attempt, do not + * keep retrying: the resulting trace file would silently miss the + * earlier sessions. + */ + if (state->trace_failed) + return (0); + + /* + * Build the path lazily on the first connection of a posttls-finger run. + * All subsequent reconnects (-r/-m) reuse the same file, so one CLI + * invocation produces one trace file with each handshake's transcript + * appended after a separator line. + */ + if (state->trace_file == 0) { + VSTRING *path = vstring_alloc(64); + + bio = tls_trace_create_file(path, trace_peer); + state->trace_file = vstring_export(path); + if (bio == 0) { + /* Warning is already logged. */ + state->trace_failed = 1; + return (0); + } + msg_info("TLS protocol trace saved to %s", state->trace_file); + } - /* Avoid mixing BIO and vstream/stdio buffers */ - vstream_fflush(VSTREAM_OUT); - SSL_trace(write_p, version, content_type, buf, msglen, ssl, out); - (void) BIO_flush(out); + /* + * Truncate on the first connection so the file reflects only the current + * invocation; append on subsequent reconnects with a delimiter line in + * between. + */ + else { + if ((fp = fopen(state->trace_file, "a")) == 0) { + msg_warn("TLS trace: cannot open %s: %m", state->trace_file); + state->trace_failed = 1; + return (0); + } + if ((bio = BIO_new_fp(fp, BIO_CLOSE)) == 0) { + msg_warn("TLS trace: BIO_new_fp() failed for %s", state->trace_file); + (void) fclose(fp); + state->trace_failed = 1; + return (0); + } + (void) fputs("\n=== posttls-finger reconnect ===\n\n", fp); + } + return (bio); } -#endif #endif /* tls_init - initialize application TLS library context */ @@ -1916,13 +1994,6 @@ static void tls_init(STATE *state) CAfile = state->CAfile, CApath = state->CApath, mdalg = state->mdalg); -#ifndef OPENSSL_NO_SSL_TRACE - if (state->tls_ctx != 0 - && (state->log_mask & TLS_LOG_DEBUG)) { - SSL_CTX_set_msg_callback(state->tls_ctx->ssl_ctx, ssl_trace); - SSL_CTX_set_msg_callback_arg(state->tls_ctx->ssl_ctx, state->tls_bio); - } -#endif #endif } @@ -1976,6 +2047,7 @@ static void parse_options(STATE *state, int argc, char *argv[]) state->level = TLS_LEV_DANE; state->mxinsec_level = TLS_LEV_DANE; state->tlsproxy_mode = 0; + state->trace_file = 0; /* lazily constructed at trace_open */ #else #define TLSOPTS "" state->level = TLS_LEV_NONE; diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c index 7e76bf56b..5c7d9c8e9 100644 --- a/postfix/src/smtp/lmtp_params.c +++ b/postfix/src/smtp/lmtp_params.c @@ -103,6 +103,7 @@ VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, #ifdef USE_TLS VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, + VAR_LMTP_TLS_TRACE_SIZE_LIMIT, DEF_LMTP_TLS_TRACE_SIZE_LIMIT, &var_smtp_tls_trace_size_limit, 0, 0, #endif VAR_LMTP_MIN_DATA_RATE, DEF_LMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0, 0, diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index b2d38d4a9..5f9be87d4 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -675,6 +675,14 @@ /* .IP "\fBsmtp_tls_loglevel_maps (empty)\fR" /* Optional TLS loglevel override that depends on the remote peer /* host name or IP address. +/* .IP "\fBsmtp_tls_trace_size_limit (102400)\fR" +/* Size limit, in bytes, for the TLS protocol transcript that the +/* Postfix SMTP client writes when the "trace" keyword is included in +/* the TLS loglevel for a peer (smtp_tls_loglevel or +/* smtp_tls_loglevel_maps). +/* .IP "\fBtls_trace_rate_limit (1)\fR" +/* The maximum number of TLS traces per anvil_rate_time_unit that +/* all Postfix daemons combined will create. /* OBSOLETE TLS CONTROLS /* .ad /* .fi @@ -1143,6 +1151,7 @@ bool var_smtp_tls_enforce_peername; char *var_smtp_tls_key_file; char *var_smtp_tls_loglevel; char *var_smtp_tls_loglevel_maps; +int var_smtp_tls_trace_size_limit; bool var_smtp_tls_note_starttls_offer; char *var_smtp_tls_mand_proto; char *var_smtp_tls_sec_cmatch; diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c index e206e4201..a866fa949 100644 --- a/postfix/src/smtp/smtp_params.c +++ b/postfix/src/smtp/smtp_params.c @@ -104,6 +104,7 @@ VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, #ifdef USE_TLS VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, + VAR_SMTP_TLS_TRACE_SIZE_LIMIT, DEF_SMTP_TLS_TRACE_SIZE_LIMIT, &var_smtp_tls_trace_size_limit, 0, 0, #endif VAR_SMTP_MIN_DATA_RATE, DEF_SMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0, 0, diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index f67360882..1e7a7521e 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -1097,7 +1097,9 @@ static int smtp_start_tls(SMTP_STATE *state) tlsrpt = 0, #endif ffail_type = 0, - dane = state->tls->dane); + dane = state->tls->dane, + trace_size_limit = var_smtp_tls_trace_size_limit, + trace_peer = STR(iter->addr)); /* * The tlsproxy(8) server enforces timeouts that are larger than @@ -1228,7 +1230,11 @@ static int smtp_start_tls(SMTP_STATE *state) tlsrpt = 0, #endif ffail_type = state->tls->ext_policy_failure, - dane = state->tls->dane); + dane = state->tls->dane, + trace_size_limit = var_smtp_tls_trace_size_limit, + trace_open = 0, + trace_arg = 0, + trace_peer = STR(iter->addr)); /* * At this point there must not be any pending data in the stream diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 4480eb954..091f0f549 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -562,6 +562,11 @@ /* .IP "\fBsmtpd_tls_loglevel_maps (empty)\fR" /* Optional TLS loglevel override that depends on the remote peer /* host name or IP address. +/* .IP "\fBsmtpd_tls_trace_size_limit (102400)\fR" +/* The Postfix SMTP server equivalent of smtp_tls_trace_size_limit. +/* .IP "\fBtls_trace_rate_limit (1)\fR" +/* The maximum number of TLS traces per anvil_rate_time_unit that +/* all Postfix daemons combined will create. /* OBSOLETE TLS CONTROLS /* .ad /* .fi @@ -1529,6 +1534,7 @@ char *var_smtpd_tls_dkey_file; char *var_smtpd_tls_key_file; char *var_smtpd_tls_loglevel; char *var_smtpd_tls_loglevel_maps; +int var_smtpd_tls_trace_size_limit; char *var_smtpd_tls_mand_proto; bool var_smtpd_tls_received_header; bool var_smtpd_tls_req_ccert; @@ -5345,7 +5351,9 @@ static void smtpd_start_tls(SMTPD_STATE *state) namaddr = state->namaddr, cipher_grade = cipher_grade, cipher_exclusions = STR(cipher_exclusions), - mdalg = var_smtpd_tls_fpt_dgst); + mdalg = var_smtpd_tls_fpt_dgst, + trace_size_limit = var_smtpd_tls_trace_size_limit, + trace_peer = state->addr); /* * Note: state->tlsproxy is left open when smtp_flush() calls longjmp(), @@ -5394,7 +5402,11 @@ static void smtpd_start_tls(SMTPD_STATE *state) namaddr = state->namaddr, cipher_grade = cipher_grade, cipher_exclusions = STR(cipher_exclusions), - mdalg = var_smtpd_tls_fpt_dgst); + mdalg = var_smtpd_tls_fpt_dgst, + trace_size_limit = var_smtpd_tls_trace_size_limit, + trace_open = 0, + trace_arg = 0, + trace_peer = state->addr); #endif /* USE_TLSPROXY */ @@ -6885,6 +6897,7 @@ int main(int argc, char **argv) VAR_SMTPD_CIPV6_PREFIX, DEF_SMTPD_CIPV6_PREFIX, &var_smtpd_cipv6_prefix, 0, MAX_SMTPD_CIPV6_PREFIX, #ifdef USE_TLS VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, + VAR_SMTPD_TLS_TRACE_SIZE_LIMIT, DEF_SMTPD_TLS_TRACE_SIZE_LIMIT, &var_smtpd_tls_trace_size_limit, 0, 0, #endif VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0, VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0, diff --git a/postfix/src/tls/Makefile.in b/postfix/src/tls/Makefile.in index aa11f78fd..d443c3832 100644 --- a/postfix/src/tls/Makefile.in +++ b/postfix/src/tls/Makefile.in @@ -329,6 +329,7 @@ tls_mgr.o: tls_mgr.c tls_mgr.o: tls_mgr.h tls_mgr.o: tls_scache.h tls_misc.o: ../../include/argv.h +tls_misc.o: ../../include/been_here.h tls_misc.o: ../../include/check_arg.h tls_misc.o: ../../include/dict.h tls_misc.o: ../../include/dns.h diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index 8ce9c213c..80f4bc57e 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -141,6 +141,16 @@ extern const char *str_tls_level(int); #else #define TLS_ADD1_HOST SSL_add1_host #define TLS_SET1_HOST SSL_set1_host +#endif + + /* + * SSL_trace() is built into OpenSSL only when the library is configured + * with "enable-ssl-trace". This is the upstream default, but some + * distributions disable it. Postfix can also opt out at build time with + * CCARGS=-DNO_TLS_TRACE. + */ +#if !defined(OPENSSL_NO_SSL_TRACE) && !defined(NO_TLS_TRACE) +#define HAVE_SSL_TRACE #endif /* @@ -281,6 +291,9 @@ typedef struct { int errorcode; /* First error at error depth */ int must_fail; /* Failed to load trust settings */ char *ffail_type; /* Forced verification failure */ + /* SSL protocol trace; populated when log_mask has TLS_LOG_TRACE. */ + BIO *trace_bio; /* destination BIO, or NULL */ + int trace_size_limit; /* size cap; <= 0 means "stop now" */ /* End of Private members. */ } TLS_SESS_STATE; @@ -330,6 +343,7 @@ extern int tls_log_mask(const char *, const char *); #define TLS_LOG_TLSPKTS (1<<8) #define TLS_LOG_ALLPKTS (1<<9) #define TLS_LOG_DANE (1<<10) +#define TLS_LOG_TRACE (1<<11) /* * Client and Server application contexts @@ -517,6 +531,10 @@ typedef struct { const TLS_DANE *dane; /* DANE TLSA verification */ struct TLSRPT_WRAPPER *tlsrpt; /* RFC 8460 reporting */ char *ffail_type; /* Forced verification failure */ + int trace_size_limit; /* TLS protocol trace size limit */ + BIO *(*trace_open) (void *, const char *); /* override dest */ + void *trace_arg; /* opaque trace_open argument */ + char *trace_peer; /* Printable peer IP address */ } TLS_CLIENT_START_PROPS; extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *); @@ -540,13 +558,15 @@ extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *, a6, a7, a8, a9, a10, a11, a12, a13, a14)) #define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ - a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) \ + a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, \ + a23, a24, a25, a26) \ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \ - ((props)->a20), ((props)->a21), ((props)->a22), (props))) + ((props)->a20), ((props)->a21), ((props)->a22), ((props)->a23), \ + ((props)->a24), ((props)->a25), ((props)->a26), (props))) /* * tls_server.c @@ -588,6 +608,10 @@ typedef struct { const char *cipher_grade; const char *cipher_exclusions; const char *mdalg; /* default message digest algorithm */ + int trace_size_limit; /* TLS protocol trace size limit */ + BIO *(*trace_open) (void *, const char *); /* override dest */ + void *trace_arg; /* opaque trace_open argument */ + char *trace_peer; /* Printable peer IP address */ } TLS_SERVER_START_PROPS; extern TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *); @@ -612,11 +636,12 @@ extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *); a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)) #define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ - a10, a11, a12, a13) \ + a10, a11, a12, a13, a14, a15, a16, a17) \ tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ - ((props)->a12), ((props)->a13), (props))) + ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ + ((props)->a16), ((props)->a17), (props))) /* * tls_session.c @@ -743,6 +768,28 @@ extern const EVP_MD *tls_validate_digest(const char *); extern void tls_enable_client_rpk(SSL_CTX *, SSL *); extern void tls_enable_server_rpk(SSL_CTX *, SSL *); +#ifdef HAVE_SSL_TRACE +extern void tls_msg_callback(int, int, int, const void *, size_t, + SSL *, void *); + + /* + * Default trace destination: a file under tlstrace/. This is the default + * for daemon programs that do not supply start_props->trace_open. The real + * work is done in tls_trace_create_file(). + */ +#define TLS_TRACE_QDIR "tlstrace" +extern bool tls_trace_rate_ok(int); +extern BIO *tls_trace_create_qfile(const char *); + + /* + * Append -.--XXXXXX to the path + * buffer, then create the named file with mkstemp() and wrap it in an + * OpenSSL file BIO. + */ +extern BIO *tls_trace_create_file(VSTRING *, const char *); + +#endif + /* * tls_seed.c */ diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index 23e66964b..fc33c5e34 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -1286,6 +1286,38 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) if (log_mask & TLS_LOG_TLSPKTS) tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); +#ifdef HAVE_SSL_TRACE + + /* + * If "trace" is included in the TLS loglevel, open a destination BIO + * (either the application's override or the libtls default file under + * $queue_directory/tlstrace/) and install the SSL message callback. + * SSL_trace() writes records straight into the BIO; the budget is + * enforced via BIO_tell() in tls_msg_callback(). Enforce a global trace + * creation rate limit for requests from a daemon process. + */ + if ((log_mask & TLS_LOG_TRACE) && props->trace_size_limit > 0) { + BIO *bio; + + if (props->trace_open != 0) { + bio = props->trace_open(props->trace_arg, props->trace_peer); + } else if (tls_trace_rate_ok(var_tls_trace_anvil_rate)) { + bio = tls_trace_create_qfile(props->trace_peer); + } else { + msg_info("skipping TLS trace output - rate limit (%d) exceeded", + var_tls_trace_anvil_rate); + bio = 0; + } + + if (bio != 0) { + TLScontext->trace_bio = bio; + TLScontext->trace_size_limit = props->trace_size_limit; + SSL_set_msg_callback(TLScontext->con, tls_msg_callback); + SSL_set_msg_callback_arg(TLScontext->con, TLScontext); + } + } +#endif + /* * An external (STS) policy signaled a failure. Prevent false (PKI) * certificate matches in tls_verify.c. TODO(wietse) how was this handled diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index dad8cb54f..1c5a5a6aa 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -122,6 +122,14 @@ /* void tls_enable_server_rpk(ctx, ssl) /* SSL_CTX *ctx; /* SSL *ssl; +/* +/* bool tls_trace_rate_ok(int tls_trace_rate_limit) +/* +/* BIO *tls_trace_create_file( +/* VSTRING *path, +/* const char *trace_peer) +/* +/* BIO *tls_trace_create_qfile(const char *trace_peer) /* DESCRIPTION /* This module implements public and internal routines that /* support the TLS client and server. @@ -225,6 +233,20 @@ /* /* tls_enable_server_rpk() enables the use of raw public keys in the /* server to client direction, if supported by the OpenSSL library. +/* +/* tls_trace_create_file() fills in a TLS trace file name template +/* -.--XXXXXX, appends +/* the result to the path argument, opens the named file with +/* mkstemp(), and returns it as a file BIO with BIO_CLOSE +/* enabled. The result value is null in case of failure; all errors +/* are logged. +/* +/* tls_trace_rate_ok() queries the anvil(8) service and enforces the +/* tls_trace_rate_limit value. +/* +/* tls_trace_create_qfile() creates a TLS trace file for a daemon +/* process. This function initializes a path buffer with "tlstrace/", +/* and delegates the remaining work to tls_trace_create_file(). /* LICENSE /* .ad /* .fi @@ -259,6 +281,8 @@ #include #include #include +#include /* O_WRONLY etc. */ +#include /* Utility library. */ @@ -276,6 +300,8 @@ /* * Global library. */ +#include +#include #include #include #include @@ -299,6 +325,7 @@ char *var_tls_low_ignored; char *var_tls_export_ignored; char *var_tls_null_clist; int var_tls_daemon_rand_bytes; +int var_tls_trace_anvil_rate; char *var_tls_eecdh_auto; char *var_tls_eecdh_strong; char *var_tls_eecdh_ultra; @@ -556,6 +583,7 @@ static const NAME_MASK tls_log_table[] = { "ssl-debug", TLS_LOG_DEBUG, /* SSL library debug/verbose */ "ssl-handshake-packet-dump", TLS_LOG_TLSPKTS, "ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS, + "trace", TLS_LOG_TRACE, /* SSL_trace() to a file or email */ 0, 0, }; @@ -578,9 +606,144 @@ int tls_log_mask(const char *log_param, const char *log_level) mask = name_mask_opt(log_param, tls_log_table, log_level, NAME_MASK_ANY_CASE | NAME_MASK_RETURN); +#ifndef HAVE_SSL_TRACE + if (mask & TLS_LOG_TRACE) { + static BH_TABLE *log_spam_filter; + + if (log_spam_filter == 0) + log_spam_filter = been_here_init(BH_BOUND_NONE, BH_FLAG_NONE); + if (!been_here(log_spam_filter, "%s=%s", log_param, log_level)) { + msg_warn("%s: ignoring \"trace\" log level: " + "SSL_trace() is not available in this build", + log_param); + mask &= ~TLS_LOG_TRACE; + } + } +#endif return (mask); } +#ifdef HAVE_SSL_TRACE + +/* tls_msg_callback - SSL message callback for TLS_LOG_TRACE */ + +void tls_msg_callback(int write_p, int version, int content_type, + const void *buf, size_t msglen, + SSL *ssl, void *arg) +{ + TLS_SESS_STATE *TLScontext = (TLS_SESS_STATE *) arg; + BIO *bio = TLScontext->trace_bio; + + /* + * trace_size_limit <= 0 means either tracing is off (never started) + * or we already crossed the budget and emitted the truncation marker. + * Either way, do not write further. + */ + if (bio == 0 || TLScontext->trace_size_limit <= 0) + return; + + SSL_trace(write_p, version, content_type, buf, msglen, ssl, bio); + + if (BIO_tell(bio) >= TLScontext->trace_size_limit) { + static const char trailer[] = + "\n[TLS trace truncated: size limit reached]\n"; + + (void) BIO_write(bio, trailer, sizeof(trailer) - 1); + TLScontext->trace_size_limit = -1; + } +} + +/* tls_trace_rate_ok - enforce tls_trace_rate_limit */ + +bool tls_trace_rate_ok(int tls_trace_rate_limit) +{ + ANVIL_CLNT *client; + int rate; + int ret; + + if (tls_trace_rate_limit > 0) { + client = anvil_clnt_create(); + if (anvil_clnt_tlstr(client, "any", "any", &rate) == ANVIL_STAT_OK) { + ret = rate <= tls_trace_rate_limit; + } else { + ret = true; + } + anvil_clnt_free(client); + } else { + ret = true; + } + return (ret); +} + +/* tls_trace_create_qfile - default trace destination for daemons */ + +BIO *tls_trace_create_qfile(const char *trace_peer) +{ + VSTRING *path; + BIO *bio; + + /* + * Create the file under "tlstrace/". The cwd of every Postfix daemon is + * $queue_directory, so the relative path resolves correctly with or + * without chroot. The directory itself is expected to exist; + * postfix-script and postfix-files arrange for it, and most Postfix + * daemons would not have sufficient privileges to create it. + */ + path = vstring_alloc(100); + vstring_strcpy(path, TLS_TRACE_QDIR "/"); + bio = tls_trace_create_file(path, trace_peer); + msg_info("TLS protocol trace for %s saved to %s", + trace_peer, vstring_str(path)); + vstring_free(path); + return (bio); +} + +/* tls_trace_create_file - trace destination for CLI or daemon */ + +BIO *tls_trace_create_file(VSTRING *path, const char *trace_peer) +{ + struct timeval tv; + struct tm *lt; + FILE *fp; + BIO *bio; + + int newfd; + + /* + * Append "-.--XXXXXX" to the path. Open + * with mkstemp() so that this will never clobber an existing file; + * fdopen(3) hands the descriptor to stdio, and BIO_new_fp() with + * BIO_CLOSE() wraps the stream so that BIO_free_all() at session + * teardown also fclose()s it. + */ + vstring_sprintf_append(path, "%s-", var_procname); + GETTIMEOFDAY(&tv); + lt = localtime(&tv.tv_sec); + while (strftime(vstring_end(path), vstring_avail(path), + "%Y%m%d%H%M%S", lt) == 0) + VSTRING_SPACE(path, vstring_avail(path) + 100); + VSTRING_SKIP(path); + vstring_sprintf_append(path, ".%06d-%s-XXXXXX", + (int) tv.tv_usec, trace_peer); + if ((newfd = mkstemp(vstring_str(path))) < 0) { + msg_warn("TLS trace: cannot open %s: %m", vstring_str(path)); + return (0); + } + if ((fp = fdopen(newfd, "w")) == 0) { + msg_warn("TLS trace: fdopen(%s) failed: %m", vstring_str(path)); + (void) close(newfd); + return (0); + } + if ((bio = BIO_new_fp(fp, BIO_CLOSE)) == 0) { + msg_warn("TLS trace: BIO_new_fp() failed for %s", vstring_str(path)); + (void) fclose(fp); + return (0); + } + return (bio); +} + +#endif /* HAVE_SSL_TRACE */ + /* tls_update_app_logmask - update log level after init */ void tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask) @@ -685,6 +848,7 @@ void tls_param_init(void) /* If this changes, update TLS_*_PARAMS* in tls_*.h. */ static const CONFIG_INT_TABLE int_table[] = { VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0, + VAR_TLS_TRACE_ANVIL_RATE, DEF_TLS_TRACE_ANVIL_RATE, &var_tls_trace_anvil_rate, 0, 0, 0, }; @@ -1372,6 +1536,8 @@ TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr) TLScontext->errorcert = 0; TLScontext->rpt_reported = 0; TLScontext->ffail_type = 0; + TLScontext->trace_bio = 0; + TLScontext->trace_size_limit = 0; return (TLScontext); } @@ -1389,6 +1555,24 @@ void tls_free_context(TLS_SESS_STATE *TLScontext) if (TLScontext->con != 0) SSL_free(TLScontext->con); + /* + * Release the protocol trace, if one was opened. Order matters: + * SSL_free() above can still drive the message callback during + * teardown (close-notify processing in particular), so trace_bio + * must stay valid until after SSL_free() returns. Closing it + * first would let tls_msg_callback() write through a freed BIO. + * + * Always BIO_flush() before close, so any stdio buffering inside + * BIO_new_fp() lands on disk before the FILE * is fclose()d. An + * application override may release the BIO; otherwise BIO_free_all + * walks the chain and (with BIO_CLOSE) closes the underlying file. + */ + if (TLScontext->trace_bio != 0) { + (void) BIO_flush(TLScontext->trace_bio); + BIO_free_all(TLScontext->trace_bio); + TLScontext->trace_bio = 0; + } + if (TLScontext->namaddr) myfree(TLScontext->namaddr); if (TLScontext->serverid) diff --git a/postfix/src/tls/tls_proxy_attr.h b/postfix/src/tls/tls_proxy_attr.h index f539de6a4..1fd02fc5f 100644 --- a/postfix/src/tls/tls_proxy_attr.h +++ b/postfix/src/tls/tls_proxy_attr.h @@ -59,6 +59,7 @@ #define TLS_ATTR_SRVR_SIG_DGST "srvr_signature_digest" #define TLS_ATTR_NAMADDR "namaddr" #define TLS_ATTR_RPT_REPORTED "rpt_reported" +#define TLS_ATTR_TRACE_PEER "trace_peer" /* * TLS_SERVER_INIT_PROPS attributes. @@ -95,6 +96,7 @@ #define TLS_ATTR_CIPHER_GRADE "cipher_grade" #define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions" #define TLS_ATTR_MDALG "mdalg" +#define TLS_ATTR_TRACE_PEER "trace_peer" /* * TLS_CLIENT_INIT_PROPS attributes. @@ -136,6 +138,8 @@ #define TLS_ATTR_DANE "dane" #define TLS_ATTR_TLSRPT "tlsrpt" #define TLS_ATTR_FFAIL_TYPE "forced_failure_type" +#define TLS_ATTR_TRACE_SIZE_LIMIT "trace_size_limit" +#define TLS_ATTR_TRACE_PEER "trace_peer" /* * TLS_TLSA attributes. diff --git a/postfix/src/tls/tls_proxy_client_start_proto.c b/postfix/src/tls/tls_proxy_client_start_proto.c index beae3a7c6..b34264e03 100644 --- a/postfix/src/tls/tls_proxy_client_start_proto.c +++ b/postfix/src/tls/tls_proxy_client_start_proto.c @@ -267,6 +267,9 @@ int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn, #endif SEND_ATTR_STR(TLS_ATTR_FFAIL_TYPE, STRING_OR_EMPTY(props->ffail_type)), + SEND_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + props->trace_size_limit), + SEND_ATTR_STR(TLS_ATTR_TRACE_PEER, props->trace_peer), ATTR_TYPE_END); /* Do not flush the stream. */ if (msg_verbose) @@ -289,6 +292,7 @@ void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props) myfree((void *) props->protocols); myfree((void *) props->cipher_grade); myfree((void *) props->cipher_exclusions); + myfree((void *) props->trace_peer); if (props->matchargv) argv_free((ARGV *) props->matchargv); myfree((void *) props->mdalg); @@ -497,11 +501,12 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, VSTRING *cipher_exclusions = vstring_alloc(25); VSTRING *mdalg = vstring_alloc(25); VSTRING *ffail_type = vstring_alloc(25); + VSTRING *trace_peer = vstring_alloc(25); #ifdef USE_TLSRPT -#define EXPECT_START_SCAN_RETURN 19 +#define EXPECT_START_SCAN_RETURN 21 #else -#define EXPECT_START_SCAN_RETURN 18 +#define EXPECT_START_SCAN_RETURN 20 #endif if (msg_verbose) @@ -540,6 +545,9 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, &props->tlsrpt), #endif RECV_ATTR_STR(TLS_ATTR_FFAIL_TYPE, ffail_type), + RECV_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + &props->trace_size_limit), + RECV_ATTR_STR(TLS_ATTR_TRACE_PEER, trace_peer), ATTR_TYPE_END); /* Always construct a well-formed structure. */ props->log_param = vstring_export(log_param); @@ -555,6 +563,9 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, props->cipher_exclusions = vstring_export(cipher_exclusions); props->mdalg = vstring_export(mdalg); EXPORT_OR_NULL(props->ffail_type, ffail_type); + props->trace_open = 0; + props->trace_arg = 0; + props->trace_peer = vstring_export(trace_peer); ret = (ret == EXPECT_START_SCAN_RETURN ? 1 : -1); if (ret != 1) { tls_proxy_client_start_free(props); diff --git a/postfix/src/tls/tls_proxy_client_start_proto.h b/postfix/src/tls/tls_proxy_client_start_proto.h index f1859ff7d..613180a0f 100644 --- a/postfix/src/tls/tls_proxy_client_start_proto.h +++ b/postfix/src/tls/tls_proxy_client_start_proto.h @@ -25,12 +25,13 @@ #ifdef USE_TLS #define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) \ + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) \ (((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ - ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19)) + ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \ + ((props)->a20), ((props)->a21)) extern int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *); diff --git a/postfix/src/tls/tls_proxy_context_print.c b/postfix/src/tls/tls_proxy_context_print.c index 808abbe73..fb94673c8 100644 --- a/postfix/src/tls/tls_proxy_context_print.c +++ b/postfix/src/tls/tls_proxy_context_print.c @@ -112,6 +112,8 @@ int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, STRING_OR_EMPTY(tp->namaddr)), SEND_ATTR_INT(TLS_ATTR_RPT_REPORTED, tp->rpt_reported), + SEND_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + tp->trace_size_limit), ATTR_TYPE_END); /* Do not flush the stream. */ return (ret); diff --git a/postfix/src/tls/tls_proxy_context_scan.c b/postfix/src/tls/tls_proxy_context_scan.c index fc23c528a..949c90831 100644 --- a/postfix/src/tls/tls_proxy_context_scan.c +++ b/postfix/src/tls/tls_proxy_context_scan.c @@ -126,6 +126,8 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), RECV_ATTR_INT(TLS_ATTR_RPT_REPORTED, &tls_context->rpt_reported), + RECV_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + &tls_context->trace_size_limit), ATTR_TYPE_END); /* Always construct a well-formed structure. */ tls_context->peer_CN = vstring_export(peer_CN); @@ -143,7 +145,7 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve); tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst); tls_context->namaddr = vstring_export(namaddr); - ret = (ret == 25 ? 1 : -1); + ret = (ret == 26 ? 1 : -1); if (ret != 1) { tls_proxy_context_free(tls_context); tls_context = 0; diff --git a/postfix/src/tls/tls_proxy_server_start_proto.c b/postfix/src/tls/tls_proxy_server_start_proto.c index db2f6c4ce..21ce1e079 100644 --- a/postfix/src/tls/tls_proxy_server_start_proto.c +++ b/postfix/src/tls/tls_proxy_server_start_proto.c @@ -118,6 +118,10 @@ int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, STRING_OR_EMPTY(props->cipher_exclusions)), SEND_ATTR_STR(TLS_ATTR_MDALG, STRING_OR_EMPTY(props->mdalg)), + SEND_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + props->trace_size_limit), + SEND_ATTR_STR(TLS_ATTR_TRACE_PEER, + STRING_OR_EMPTY(props->trace_peer)), ATTR_TYPE_END); /* Do not flush the stream. */ return (ret); @@ -138,6 +142,7 @@ int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, VSTRING *cipher_grade = vstring_alloc(25); VSTRING *cipher_exclusions = vstring_alloc(25); VSTRING *mdalg = vstring_alloc(25); + VSTRING *trace_peer = vstring_alloc(25); /* * Note: memset() is not a portable way to initialize non-integer types. @@ -158,6 +163,9 @@ int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, cipher_exclusions), RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + RECV_ATTR_INT(TLS_ATTR_TRACE_SIZE_LIMIT, + &props->trace_size_limit), + RECV_ATTR_STR(TLS_ATTR_TRACE_PEER, trace_peer), ATTR_TYPE_END); /* Always construct a well-formed structure. */ props->log_param = vstring_export(log_param); @@ -167,7 +175,10 @@ int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, props->cipher_grade = vstring_export(cipher_grade); props->cipher_exclusions = vstring_export(cipher_exclusions); props->mdalg = vstring_export(mdalg); - ret = (ret == 10 ? 1 : -1); + props->trace_open = 0; + props->trace_arg = 0; + props->trace_peer = vstring_export(trace_peer); + ret = (ret == 12 ? 1 : -1); if (ret != 1) { tls_proxy_server_start_free(props); props = 0; @@ -188,6 +199,7 @@ void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props) myfree((void *) props->cipher_grade); myfree((void *) props->cipher_exclusions); myfree((void *) props->mdalg); + myfree((void *) props->trace_peer); myfree((void *) props); } diff --git a/postfix/src/tls/tls_proxy_server_start_proto.h b/postfix/src/tls/tls_proxy_server_start_proto.h index 2062d78fb..fb3d8f6f9 100644 --- a/postfix/src/tls/tls_proxy_server_start_proto.h +++ b/postfix/src/tls/tls_proxy_server_start_proto.h @@ -25,10 +25,11 @@ #ifdef USE_TLS #define TLS_PROXY_SERVER_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10) \ + a9, a10, a11, a12) \ (((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ - ((props)->a8), ((props)->a9), ((props)->a10)) + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12)) extern int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *); diff --git a/postfix/src/tls/tls_server.c b/postfix/src/tls/tls_server.c index 122628501..04066142c 100644 --- a/postfix/src/tls/tls_server.c +++ b/postfix/src/tls/tls_server.c @@ -931,6 +931,38 @@ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props) if (log_mask & TLS_LOG_TLSPKTS) tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); +#ifdef HAVE_SSL_TRACE + + /* + * If "trace" is included in the TLS loglevel, open a destination BIO + * (either the application's override or the libtls default file under + * $queue_directory/tlstrace/) and install the SSL message callback. + * SSL_trace() writes records straight into the BIO; the budget is + * enforced via BIO_tell() in tls_msg_callback(). Enforce a global trace + * creation rate limit for requests from a daemon process. + */ + if ((log_mask & TLS_LOG_TRACE) && props->trace_size_limit > 0) { + BIO *bio; + + if (props->trace_open != 0) { + bio = props->trace_open(props->trace_arg, props->trace_peer); + } else if (tls_trace_rate_ok(var_tls_trace_anvil_rate)) { + bio = tls_trace_create_qfile(props->trace_peer); + } else { + msg_info("skipping TLS trace output - rate limit (%d) exceeded", + var_tls_trace_anvil_rate); + bio = 0; + } + + if (bio != 0) { + TLScontext->trace_bio = bio; + TLScontext->trace_size_limit = props->trace_size_limit; + SSL_set_msg_callback(TLScontext->con, tls_msg_callback); + SSL_set_msg_callback_arg(TLScontext->con, TLScontext); + } + } +#endif + /* * If we don't trigger the handshake in the library, leave control over * SSL_accept/read/write/etc with the application. diff --git a/postfix/src/util/sys_defs.h b/postfix/src/util/sys_defs.h index ebda90a69..871e48c5e 100644 --- a/postfix/src/util/sys_defs.h +++ b/postfix/src/util/sys_defs.h @@ -797,8 +797,6 @@ extern int initgroups(const char *, int); #if HAVE_GLIBC_API_VERSION_SUPPORT(2, 1) #define SOCKADDR_SIZE socklen_t #define SOCKOPT_SIZE socklen_t -#else -#define NO_SNPRINTF #endif #ifndef NO_IPV6 #define HAS_IPV6 diff --git a/postfix/src/util/vbuf_print.c b/postfix/src/util/vbuf_print.c index 6e14cc0e9..368d0da74 100644 --- a/postfix/src/util/vbuf_print.c +++ b/postfix/src/util/vbuf_print.c @@ -129,8 +129,11 @@ VBUF_SKIP(bp); \ } while (0) #else -#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ - if (VBUF_SPACE((bp), (sz)) != 0) \ +#define VBUF_SNPRINTF(bp, width_or_prec, type_space, fmt, arg) do { \ + if ((width_or_prec) > INT_MAX - (type_space)) \ + msg_panic("vbuf_print: field width (%d + %lu) > INT_MAX", \ + (width_or_prec), (unsigned long) (type_space)); \ + if (VBUF_SPACE((bp), (width_or_prec) + (type_space)) != 0) \ return (bp); \ sprintf((char *) (bp)->ptr, (fmt), (arg)); \ VBUF_SKIP(bp); \