warning message tls.tls_dh.c.
20230115
+
Workaround for a breaking change in OpenSSL 3: always turn
on SSL_OP_IGNORE_UNEXPECTED_EOF, to avoid warning messages
and missed opportunities for TLS session reuse. This is
framing, and is therefore not affected by TLS truncation
attacks. Fix by Viktor Dukhovni. Files: tls/tls.h, tls_client.c,
tls/tls_server.c.
+
+20230121
+
+ Documentation: describe when Postfix and Milters inspect
+ SMTP commands or header/body content. File:
+ proto/MILTER_README.html.
+
+20230127
+
+ Bugfix (introduced: Postfix 3.4): the posttls-finger command
+ failed to detect that a connection was resumed in the case
+ that a server did not return a certificate. Viktor Dukhovni.
+ File: posttls-finger/posttls-finger.c.
+
+ Workaround: OpenSSL 3.x EVP_get_cipherbyname() can return
+ lazily-bound handles. Postfix now checks that the expected
+ functionality will be available instead of failing later.
+ Fix by Viktor Dukhovni. File: tls/tls_server.c.
+
+ Portability: MacOS support for the postfix-env.sh test
+ script.
This document provides information on the following topics:
* How Milter applications plug into Postfix
+ * When Postfix and Milters inspect an SMTP session
* Building Milter applications
* Running Milter applications
* Configuring Postfix
Local -> sendmail(1)
+W\bWh\bhe\ben\bn P\bPo\bos\bst\btf\bfi\bix\bx a\ban\bnd\bd M\bMi\bil\blt\bte\ber\brs\bs i\bin\bns\bsp\bpe\bec\bct\bt a\ban\bn S\bSM\bMT\bTP\bP s\bse\bes\bss\bsi\bio\bon\bn
+
+Generally, Postfix inspects information first, then the first configured
+Milter, the second configured Milter, and so on.
+
+ * With most SMTP commands: Postfix reviews one SMTP command, and if Postfix
+ does not reject it, Postfix passes the command to the first configured
+ Milter. If the first Milter does not reject the command, Postfix passes it
+ to the second configured Milter, and so on. This includes commands with an
+ envelope sender (MAIL FROM) or envelope recipient (RCPT TO). Postfix stores
+ the same envelope records in a queue file as when no Milters are
+ configured, including rewritten envelope addresses, expanded virtual
+ aliases, BCC addresses from sender/recipient_bcc_maps, and so on.
+
+ * With header/body content: Postfix may rewrite or reject header/body content
+ before it stores that content in the queue file; Postfix stores the same
+ header/body content as when no Milters are configured. If Postfix does not
+ reject the header/body content, Postfix passes it to the first configured
+ Milter which may modify or reject that content or may modify the stored
+ envelope. If the first Milter does not reject the header/body content,
+ Postfix passes it to the second configured Milter, and so on.
+
+Details:
+
+ * Postfix hides its own Postfix-prepended Received: header, for compatibility
+ with Sendmail. Postfix does not hide other headers that Postfix or Milters
+ added or modified.
+
+ * When the Postfix SMTP server receives a sequence of one or more valid BDAT
+ commands, it generates one DATA command for the Milters.
+
+ * The Milter API does not support inspection of SMTP commands such as QUIT,
+ NOOP, or VRFY; the API supports only commands that are needed for email
+ delivery.
+
B\bBu\bui\bil\bld\bdi\bin\bng\bg M\bMi\bil\blt\bte\ber\br a\bap\bpp\bpl\bli\bic\bca\bat\bti\bio\bon\bns\bs
Milter applications have been written in C, Haskell, Java, Perl, Python, Rust,
<li><a href="#plumbing">How Milter applications plug into Postfix </a>
+<li><a href="#when-inspect">When Postfix and Milters inspect an
+SMTP session </a>
+
<li><a href="#building">Building Milter applications</a>
<li><a href="#running">Running Milter applications</a>
</blockquote>
+<h2><a name="when-inspect">When Postfix and Milters inspect an SMTP
+session </a></h2>
+
+<p> Generally, Postfix inspects information first, then the first
+configured Milter, the second configured Milter, and so on. </p>
+
+<ul>
+
+<li><p> With most SMTP commands: Postfix reviews one SMTP command,
+and if Postfix does not reject it, Postfix passes the command to
+the first configured Milter. If the first Milter does not reject
+the command, Postfix passes it to the second configured Milter, and
+so on. This includes commands with an envelope sender (MAIL FROM)
+or envelope recipient (RCPT TO). Postfix stores the same envelope
+records in a queue file as when no Milters are configured, including
+rewritten envelope addresses, expanded virtual aliases, BCC addresses
+from sender/recipient_bcc_maps, and so on. </p>
+
+<li><p> With header/body content: Postfix may rewrite or reject
+header/body content before it stores that content in the queue file;
+Postfix stores the same header/body content as when no Milters are
+configured. If Postfix does not reject the header/body content,
+Postfix passes it to the first configured Milter which may modify
+or reject that content or may modify the stored envelope. If the
+first Milter does not reject the header/body content, Postfix passes
+it to the second configured Milter, and so on. </p>
+
+</ul>
+
+<p> Details: </p>
+
+<ul>
+
+<li><p> Postfix hides its own Postfix-prepended Received: header, for
+compatibility with Sendmail. Postfix does not hide other headers that
+Postfix or Milters added or modified. </p>
+
+
+<li><p> When the Postfix SMTP server receives a sequence of one or
+more valid BDAT commands, it generates one DATA command for the
+Milters. </p>
+
+<li><p> The Milter API does not support inspection of SMTP commands
+such as QUIT, NOOP, or VRFY; the API supports only commands that are
+needed for email delivery. <p>
+
+</ul>
+
<h2><a name="building">Building Milter applications</a></h2>
<p> Milter applications have been written in C, Haskell, Java, Perl,
# Run a program with the new shared libraries instead of the installed ones.
-LD_LIBRARY_PATH=`pwd`/lib exec "$@"
+LD_LIBRARY_PATH=`pwd`/lib DYLD_LIBRARY_PATH=`pwd`/lib exec "$@"
<li><a href="#plumbing">How Milter applications plug into Postfix </a>
+<li><a href="#when-inspect">When Postfix and Milters inspect an
+SMTP session </a>
+
<li><a href="#building">Building Milter applications</a>
<li><a href="#running">Running Milter applications</a>
</blockquote>
+<h2><a name="when-inspect">When Postfix and Milters inspect an SMTP
+session </a></h2>
+
+<p> Generally, Postfix inspects information first, then the first
+configured Milter, the second configured Milter, and so on. </p>
+
+<ul>
+
+<li><p> With most SMTP commands: Postfix reviews one SMTP command,
+and if Postfix does not reject it, Postfix passes the command to
+the first configured Milter. If the first Milter does not reject
+the command, Postfix passes it to the second configured Milter, and
+so on. This includes commands with an envelope sender (MAIL FROM)
+or envelope recipient (RCPT TO). Postfix stores the same envelope
+records in a queue file as when no Milters are configured, including
+rewritten envelope addresses, expanded virtual aliases, BCC addresses
+from sender/recipient_bcc_maps, and so on. </p>
+
+<li><p> With header/body content: Postfix may rewrite or reject
+header/body content before it stores that content in the queue file;
+Postfix stores the same header/body content as when no Milters are
+configured. If Postfix does not reject the header/body content,
+Postfix passes it to the first configured Milter which may modify
+or reject that content or may modify the stored envelope. If the
+first Milter does not reject the header/body content, Postfix passes
+it to the second configured Milter, and so on. </p>
+
+</ul>
+
+<p> Details: </p>
+
+<ul>
+
+<li><p> Postfix hides its own Postfix-prepended Received: header, for
+compatibility with Sendmail. Postfix does not hide other headers that
+Postfix or Milters added or modified. </p>
+
+
+<li><p> When the Postfix SMTP server receives a sequence of one or
+more valid BDAT commands, it generates one DATA command for the
+Milters. </p>
+
+<li><p> The Milter API does not support inspection of SMTP commands
+such as QUIT, NOOP, or VRFY; the API supports only commands that are
+needed for email delivery. <p>
+
+</ul>
+
<h2><a name="building">Building Milter applications</a></h2>
<p> Milter applications have been written in C, Haskell, Java, Perl,
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20230121"
+#define MAIL_RELEASE_DATE "20230128"
#define MAIL_VERSION_NUMBER "3.8"
#ifdef SNAPSHOT
print_trust_info(state);
state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT |
TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED);
- state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY;
- tls_update_app_logmask(state->tls_ctx, state->log_mask);
}
+ state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY;
+ tls_update_app_logmask(state->tls_ctx, state->log_mask);
}
return (0);
}
*/
static const char server_session_id_context[] = "Postfix/TLS";
+#ifndef OPENSSL_NO_TLSEXT
+ /*
+ * We retain the cipher handle for the lifetime of the process.
+ */
+static const EVP_CIPHER *tkt_cipher;
+#endif
+
#define GET_SID(s, v, lptr) ((v) = SSL_SESSION_get_id((s), (lptr)))
typedef const unsigned char *session_id_t;
#define TLS_TKT_ACCEPT 1 /* Ticket decryptable and re-usable */
#define TLS_TKT_REISSUE 2 /* Ticket decryptable, not re-usable */
-#if defined(SSL_OP_NO_TICKET) && !defined(OPENSSL_NO_TLSEXT)
+#if !defined(OPENSSL_NO_TLSEXT)
#if OPENSSL_VERSION_PREREQ(3,0)
EVP_CIPHER_CTX *ctx, EVP_MAC_CTX *hctx, int create)
{
OSSL_PARAM params[3];
- static const EVP_CIPHER *ciph;
TLS_TICKET_KEY *key;
TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
- if ((!ciph && (ciph = EVP_get_cipherbyname(var_tls_tkt_cipher)) == 0)
- || (key = tls_mgr_key(create ? 0 : name, timeout)) == 0
+ if ((key = tls_mgr_key(create ? 0 : name, timeout)) == 0
|| (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
if (create) {
- EVP_EncryptInit_ex(ctx, ciph, NOENGINE, key->bits, iv);
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
if (TLScontext->log_mask & TLS_LOG_CACHE)
msg_info("%s: Issuing session ticket, key expiration: %ld",
TLScontext->namaddr, (long) key->tout);
} else {
- EVP_DecryptInit_ex(ctx, ciph, NOENGINE, key->bits, iv);
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
if (TLScontext->log_mask & TLS_LOG_CACHE)
msg_info("%s: Decrypting session ticket, key expiration: %ld",
TLScontext->namaddr, (long) key->tout);
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int create)
{
static const EVP_MD *sha256;
- static const EVP_CIPHER *ciph;
TLS_TICKET_KEY *key;
TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index);
int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2;
if ((!sha256 && (sha256 = EVP_sha256()) == 0)
- || (!ciph && (ciph = EVP_get_cipherbyname(var_tls_tkt_cipher)) == 0)
|| (key = tls_mgr_key(create ? 0 : name, timeout)) == 0
|| (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0))
return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE);
HMAC_Init_ex(hctx, key->hmac, TLS_TICKET_MACLEN, sha256, NOENGINE);
if (create) {
- EVP_EncryptInit_ex(ctx, ciph, NOENGINE, key->bits, iv);
+ EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN);
if (TLScontext->log_mask & TLS_LOG_CACHE)
msg_info("%s: Issuing session ticket, key expiration: %ld",
TLScontext->namaddr, (long) key->tout);
} else {
- EVP_DecryptInit_ex(ctx, ciph, NOENGINE, key->bits, iv);
+ EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv);
if (TLScontext->log_mask & TLS_LOG_CACHE)
msg_info("%s: Decrypting session ticket, key expiration: %ld",
TLScontext->namaddr, (long) key->tout);
* Add SSL_OP_NO_TICKET when the timeout is zero or library support is
* incomplete.
*/
-#ifdef SSL_OP_NO_TICKET
#ifndef OPENSSL_NO_TLSEXT
ticketable = (*var_tls_tkt_cipher && scache_timeout > 0
&& !(off & SSL_OP_NO_TICKET));
if (ticketable) {
- const EVP_CIPHER *ciph;
-
- if ((ciph = EVP_get_cipherbyname(var_tls_tkt_cipher)) == 0
- || EVP_CIPHER_mode(ciph) != EVP_CIPH_CBC_MODE
- || EVP_CIPHER_iv_length(ciph) != TLS_TICKET_IVLEN
- || EVP_CIPHER_key_length(ciph) < TLS_TICKET_IVLEN
- || EVP_CIPHER_key_length(ciph) > TLS_TICKET_KEYLEN) {
+#if OPENSSL_VERSION_PREREQ(3,0)
+ tkt_cipher = EVP_CIPHER_fetch(NULL, var_tls_tkt_cipher, NULL);
+#else
+ tkt_cipher = EVP_get_cipherbyname(var_tls_tkt_cipher);
+#endif
+ if (tkt_cipher == 0
+ || EVP_CIPHER_mode(tkt_cipher) != EVP_CIPH_CBC_MODE
+ || EVP_CIPHER_iv_length(tkt_cipher) != TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) < TLS_TICKET_IVLEN
+ || EVP_CIPHER_key_length(tkt_cipher) > TLS_TICKET_KEYLEN) {
msg_warn("%s: invalid value: %s; session tickets disabled",
VAR_TLS_TKT_CIPHER, var_tls_tkt_cipher);
ticketable = 0;
#endif
if (!ticketable)
off |= SSL_OP_NO_TICKET;
-#endif
SSL_CTX_set_options(server_ctx, off);