add an extra newline character before appending the new
setting, causing information to become garbled. Fix by
Michael Tokarev. File: postconf/postconf_edit.c.
+
+20250710
+
+ Bugfix (defect introduced: postfix-2.2, date 20050203):
+ after detecting a lookup table change, and after starting
+ a new postscreen process, the old postscreen process logged
+ an ENOTSOCK error while attempting to accept a connection
+ on a socket that it was no longer listening on. This error
+ was introduced first in the multi_server skeleton code, and
+ was five years later duplicated in the event_server skeleton
+ that was created for postscreen. Problem reported by Florian
+ Piekert. Files: master/multi_server.c, master/event_server.c.
+
+20250714
+
+ Deleted an <openssl/engine.h> dependency, because the feature is
+ being removed from OpenSSL, and Postfix no longer needs it. File:
+ posttls-finger/posttls-finger.c.
+
+20250716
+
+ Bugfix (defect introduced: Postfix 2.8, date 20101230):
+ after detecting a cache table change and before starting a
+ new postscreen process, the old postscreen process did not
+ close the postscreen_cache_map, and therefore kept an
+ exclusive lock that could prevent a new postscreen process
+ from starting. Problem reported by Florian Piekert. File:
+ postscreen/postscreen.c.
+
+20250717
+
+ Workaround: Postfix daemons no longer automatically restart
+ after a btree:, dbm:, hash:, lmdb:, or sdbm: table file
+ modification time change, when they opened that table for
+ writing. Files: util/dict.c, util/dict_db.c, util/dict_dbm.c,
+ util/dict_lmdb.c, util/dict_sdbm.c.
+
+20250801
+
+ Bugfix (defect introduced: Postfix 3.7): incorrect backwards
+ compatible support for the legacy configuration parameters
+ tlsproxy_client_level and tlsproxy_client_policy. This
+ disabled the tlsproxy TLS client role when a legacy parameter
+ was set. Reported by John Doe, diagnosed by Viktor Dukhovni.
+ File: global/mail_params.h.
+
+ Bugfix (defect introduced: Postfix 3.4): with the TLS client
+ role disabled by configuration, the tlsproxy daemon
+ dereferenced a null pointer while handling a tlsproxy client
+ request. Reported by John Doe. File: tlsproxy/tlsproxy.c.
/* Migrate an incorrect name. */
#define OBS_TLSP_CLNT_LEVEL "tlsproxy_client_level"
#define VAR_TLSP_CLNT_LEVEL "tlsproxy_client_security_level"
-#define DEF_TLSP_CLNT_LEVEL "${" OBS_TLSP_CLNT_LEVEL ":$" VAR_SMTP_TLS_LEVEL "}"
+#define DEF_TLSP_CLNT_LEVEL "${" OBS_TLSP_CLNT_LEVEL "?{$" \
+ OBS_TLSP_CLNT_LEVEL "}:{$" \
+ VAR_SMTP_TLS_LEVEL "}}"
extern char *var_tlsp_clnt_level;
#define VAR_TLSP_CLNT_PER_SITE "tlsproxy_client_per_site"
/* Migrate an incorrect name. */
#define OBS_TLSP_CLNT_POLICY "tlsproxy_client_policy"
#define VAR_TLSP_CLNT_POLICY "tlsproxy_client_policy_maps"
-#define DEF_TLSP_CLNT_POLICY "${" OBS_TLSP_CLNT_POLICY ":$" VAR_SMTP_TLS_POLICY "}"
+#define DEF_TLSP_CLNT_POLICY "${" OBS_TLSP_CLNT_POLICY "?{$" \
+ OBS_TLSP_CLNT_POLICY "}:{$" \
+ VAR_SMTP_TLS_POLICY "}}"
extern char *var_tlsp_clnt_policy;
/*
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20250422"
-#define MAIL_VERSION_NUMBER "3.9.4"
+#define MAIL_RELEASE_DATE "20250818"
+#define MAIL_VERSION_NUMBER "3.9.5"
#ifdef SNAPSHOT
#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
static void (*event_server_pre_disconn) (VSTREAM *, char *, char **);
static void (*event_server_slow_exit) (char *, char **);
static int event_server_watchdog = 1000;
+static int event_server_drain_was_called = 0;
/* event_server_exit - normal termination */
const char *myname = "event_server_drain";
int fd;
+ if (event_server_drain_was_called)
+ return (0);
+
switch (fork()) {
/* Try again later. */
case -1:
msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
}
var_use_limit = 1;
+ event_server_drain_was_called = 1;
return (0);
/* Let the master start a new process. */
default:
int time_left = -1;
int fd;
+ if (event_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (event_server_pre_accept)
event_server_pre_accept(event_server_name, event_server_argv);
+ if (event_server_drain_was_called)
+ return;
fd = LOCAL_ACCEPT(listen_fd);
if (event_server_lock != 0
&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
int fd;
HTABLE *attr = 0;
+ if (event_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (event_server_pre_accept)
event_server_pre_accept(event_server_name, event_server_argv);
+ if (event_server_drain_was_called)
+ return;
fd = pass_accept_attr(listen_fd, &attr);
if (event_server_lock != 0
&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
int time_left = -1;
int fd;
+ if (event_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (event_server_pre_accept)
event_server_pre_accept(event_server_name, event_server_argv);
+ if (event_server_drain_was_called)
+ return;
fd = inet_accept(listen_fd);
if (event_server_lock != 0
&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
static int multi_server_in_flow_delay;
static unsigned multi_server_generation;
static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **);
+static int multi_server_drain_was_called = 0;
/* multi_server_exit - normal termination */
const char *myname = "multi_server_drain";
int fd;
+ if (multi_server_drain_was_called)
+ return (0);
+
switch (fork()) {
/* Try again later. */
case -1:
msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
}
var_use_limit = 1;
+ multi_server_drain_was_called = 1;
return (0);
/* Let the master start a new process. */
default:
int time_left = -1;
int fd;
+ if (multi_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (multi_server_pre_accept)
multi_server_pre_accept(multi_server_name, multi_server_argv);
+ if (multi_server_drain_was_called)
+ return;
fd = LOCAL_ACCEPT(listen_fd);
if (multi_server_lock != 0
&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
int fd;
HTABLE *attr = 0;
+ if (multi_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (multi_server_pre_accept)
multi_server_pre_accept(multi_server_name, multi_server_argv);
+ if (multi_server_drain_was_called)
+ return;
fd = pass_accept_attr(listen_fd, &attr);
if (multi_server_lock != 0
&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
int time_left = -1;
int fd;
+ if (multi_server_drain_was_called)
+ return;
+
/*
* Be prepared for accept() to fail because some other process already
* got the connection (the number of processes competing for clients is
if (multi_server_pre_accept)
multi_server_pre_accept(multi_server_name, multi_server_argv);
+ if (multi_server_drain_was_called)
+ return;
fd = inet_accept(listen_fd);
if (multi_server_lock != 0
&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
if (new_event_time >= last_event_time + 1
&& (name = dict_changed_name()) != 0) {
msg_info("table %s has changed - finishing in the background", name);
- event_server_drain();
+ psc_drain(unused_name, unused_argv);
} else {
last_event_time = new_event_time;
}
#ifdef USE_TLS
#include <tls_proxy.h>
-#include <openssl/engine.h>
#endif
/*
init_buf = vstring_alloc(100);
init_key = tls_proxy_client_init_serialize(attr_print_plain, init_buf,
init_props);
+#define TLSP_CLIENT_INIT_RETURN(retval) do { \
+ vstring_free(init_buf); \
+ vstring_free(param_buf); \
+ return (retval); \
+ } while (0)
+
if (tlsp_pre_jail_done == 0) {
if (tlsp_pre_jail_client_param_key == 0
|| tlsp_pre_jail_client_init_key == 0) {
* TLS_APPL_STATE instance; this makes a mismatch of TLS_CLIENT_PARAMS
* settings problematic.
*/
- if (tlsp_pre_jail_done
- && !been_here_fixed(tlsp_params_mismatch_filter, param_key)
- && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) {
+ else if (tlsp_pre_jail_client_param_key == 0
+ || tlsp_pre_jail_client_init_key == 0) {
+ msg_warn("TLS client role is disabled by configuration");
+ TLSP_CLIENT_INIT_RETURN(0);
+ } else if (!been_here_fixed(tlsp_params_mismatch_filter, param_key)
+ && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) {
msg_warn("request from tlsproxy client with unexpected settings");
tlsp_log_config_diff(tlsp_pre_jail_client_param_key, param_key);
log_hints = 1;
SSL_MODE_ENABLE_PARTIAL_WRITE
| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
- vstring_free(init_buf);
- vstring_free(param_buf);
- return (appl_state);
+ TLSP_CLIENT_INIT_RETURN(appl_state);
}
/* tlsp_close_event - pre-handshake plaintext-client close event */
TLSP_INIT_TIMEOUT, (void *) state);
return;
} else {
+ state->flags |= TLSP_FLAG_DO_HANDSHAKE;
tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event,
TLSP_INIT_TIMEOUT, (void *) state);
return;
{
TLSP_STATE *state = (TLSP_STATE *) mymalloc(sizeof(*state));
- state->flags = TLSP_FLAG_DO_HANDSHAKE;
+ state->flags = 0;
state->service = mystrdup(service);
state->plaintext_stream = plaintext_stream;
state->plaintext_buf = 0;
/* .IP "char *context"
/* Application context from the caller.
/* .PP
-/* dict_changed_name() returns non-zero when any dictionary needs to
-/* be re-opened because it has changed or because it was unlinked.
+/* dict_changed_name() returns non-zero when any dictionary is
+/* opened read-only and has changed, or because it was unlinked.
/* A non-zero result is the name of a changed dictionary.
/*
/* dict_load_file_xt() reads name-value entries from the named file.
dict = ((DICT_NODE *) h->value)->dict;
if (dict->stat_fd < 0) /* not file-based */
continue;
- if (dict->mtime == 0) /* not bloody likely */
- msg_warn("%s: table %s: null time stamp", myname, h->key);
+ if (dict->mtime < 0) /* not bloody likely */
+ msg_warn("%s: table %s: negative time stamp", myname, h->key);
if (fstat(dict->stat_fd, &st) < 0)
msg_fatal("%s: fstat: %m", myname);
if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0
+ && dict->mtime > 0
&& st.st_mtime != dict->mtime)
|| st.st_nlink == 0)
status = h->key;
dict_db->dict.stat_fd = dbfd;
if (fstat(dict_db->dict.stat_fd, &st) < 0)
msg_fatal("dict_db_open: fstat: %m");
- dict_db->dict.mtime = st.st_mtime;
+ if (open_flags == O_RDONLY)
+ dict_db->dict.mtime = st.st_mtime;
dict_db->dict.owner.uid = st.st_uid;
dict_db->dict.owner.status = (st.st_uid != 0);
msg_fatal("open database %s: cannot support GDBM", path);
if (fstat(dict_dbm->dict.stat_fd, &st) < 0)
msg_fatal("dict_dbm_open: fstat: %m");
- dict_dbm->dict.mtime = st.st_mtime;
+ if (open_mode == O_RDONLY)
+ dict_dbm->dict.mtime = st.st_mtime;
dict_dbm->dict.owner.uid = st.st_uid;
dict_dbm->dict.owner.status = (st.st_uid != 0);
msg_fatal("dict_lmdb_open: fstat: %m");
dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd;
dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL;
- dict_lmdb->dict.mtime = st.st_mtime;
+ if (open_flags == O_RDONLY)
+ dict_lmdb->dict.mtime = st.st_mtime;
dict_lmdb->dict.owner.uid = st.st_uid;
dict_lmdb->dict.owner.status = (st.st_uid != 0);
dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
msg_fatal("dict_sdbm_open: fstat: %m");
- dict_sdbm->dict.mtime = st.st_mtime;
+ if (open_flags == O_RDONLY)
+ dict_sdbm->dict.mtime = st.st_mtime;
dict_sdbm->dict.owner.uid = st.st_uid;
dict_sdbm->dict.owner.status = (st.st_uid != 0);