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.
+
+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.
+
+20230314
+
+ Bugfix (introduced: Postfix 3.5): check_ccert_access did
+ not parse inline map specifications. Report and fix by Sean
+ Gallagher. File: global/map_search.c.
+
+20230330
+
+ Safety: the long form "{ name = value }" in import_environment
+ or export_environment is not documented, but accepted, and
+ it was stored in the process environment as the invalid
+ form "name = value", thus not setting or overriding an entry
+ for "name". This form is now stored as the expected
+ "name=value". Found during code maintenance. Also refined
+ the "missing attribute name" detection. Files: clean_env.c,
+ split_nameval.c.
+
+20230418
+
+ Bugfix (introduced: Postfix 3.2): the MySQL client could
+ return "not found" instead of "error" during the time that
+ all MySQL server connections were turned down after error.
+ Found during code maintenance. File: global/dict_mysql.c.
# 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 "$@"
{
HOST *host;
MYSQL_RES *first_result = 0;
- int query_error;
+ int query_error = 1;
/*
* Helper to avoid spamming the log with warnings.
* 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_VERSION_NUMBER "3.7.4"
+#define MAIL_RELEASE_DATE "20230418"
+#define MAIL_VERSION_NUMBER "3.7.5"
#ifdef SNAPSHOT
#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
if ((heap_err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
msg_warn("malformed map specification: '%s'", heap_err);
MAP_SEARCH_CREATE_RETURN(0);
- } else if ((map_type_name = mystrtok(&bp, CHARS_COMMA_SP)) == 0) {
+ } else if ((map_type_name = mystrtokq(&bp, CHARS_COMMA_SP,
+ CHARS_BRACE)) == 0) {
msg_warn("empty map specification: '%s'", map_spec);
MAP_SEARCH_CREATE_RETURN(0);
}
{"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"},
{"{type:name {search_order=one, two, bad}}", 0, 0, 0},
{"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"},
+ {"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"},
{0},
};
TEST_CASE *test_case;
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);
/* Utility library. */
#include <msg.h>
+#include <mymalloc.h>
#include <argv.h>
#include <safe.h>
#include <clean_env.h>
+#include <stringops.h>
/* clean_env - clean up the environment */
ARGV *save_list;
char *value;
char **cpp;
- char *eq;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
/*
* Preserve or specify selected environment variables.
*/
-#define STRING_AND_LENGTH(x, y) (x), (ssize_t) (y)
-
save_list = argv_alloc(10);
- for (cpp = preserve_list; *cpp; cpp++)
- if ((eq = strchr(*cpp, '=')) != 0)
- argv_addn(save_list, STRING_AND_LENGTH(*cpp, eq - *cpp),
- STRING_AND_LENGTH(eq + 1, strlen(eq + 1)), (char *) 0);
- else if ((value = safe_getenv(*cpp)) != 0)
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("clean_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ } else if ((value = safe_getenv(*cpp)) != 0) {
argv_add(save_list, *cpp, value, (char *) 0);
+ }
+ }
/*
* Truncate the process environment, if available. On some systems
{
char **cpp;
ARGV *save_list;
- char *eq;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
/*
* Extract name=value settings.
*/
save_list = argv_alloc(10);
- for (cpp = preserve_list; *cpp; cpp++)
- if ((eq = strchr(*cpp, '=')) != 0)
- argv_addn(save_list, STRING_AND_LENGTH(*cpp, eq - *cpp),
- STRING_AND_LENGTH(eq + 1, strlen(eq + 1)), (char *) 0);
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("update_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ }
+ }
/*
* Apply name=value settings.
} while (0)
SKIP(buf, np, ISSPACE(*np)); /* find name begin */
- if (*np == 0)
+ if (*np == 0 || *np == '=')
return ("missing attribute name");
SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */
SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */