20160515
Portability: OpenBSD 6.0. Files: makedefs, util/sys_defs.h.
+
+20160717
+
+ Bugfix (introduced: Postfix 1.1): the virtual(8) delivery
+ agent discarded the error result from vstream_fseek().
+ File: virtual/mailbox.c.
+
+20160730
+
+ Bugfix (introduced: 20090614): with concurrent connections
+ from the same client IP address, and after-220 tests enabled,
+ postscreen could overwrite the cached "all tests completed"
+ result of one connection that completed the after-220 tests,
+ with the "some tests not completed" result of a concurrent
+ connection where the client hung up later, without completing
+ the after-220 tests.
+
+20161105
+
+ Bugfix (introduced: Postfix 1.1): the postsuper command did
+ not count a successful rename operation after error recovery.
+ Problem reported by Markus Schönhaber. File: postsuper/postsuper.c.
+
+20161220
+
+ Bugfix (introduced: Postfix 2.1.0): the Postfix SMTP daemon
+ did not query sender_canonical_maps when rejecting unknown
+ senders with "smtpd_reject_unlisted_recipient = yes" or
+ with reject_unlisted_sender. Stephen R. van den Berg (Mr.
+ procmail). Files: smtpd/smtpd.c, smtpd/smtpd_check.c.
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20150515"
-#define MAIL_VERSION_NUMBER "2.11.8"
+#define MAIL_RELEASE_DATE "20170101"
+#define MAIL_VERSION_NUMBER "2.11.9"
#ifdef SNAPSHOT
#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
* valid.
*/
if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->client_info->concurrency == 1
&& psc_cache_map != 0
&& (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
saved_flags = state->flags;
psc_conclude(state);
return;
}
+ } else if (state->client_info->concurrency > 1) {
+ saved_flags = state->flags;
+ psc_todo_tests(state, event_time());
+ state->flags |= saved_flags;
+ if (msg_verbose)
+ msg_info("%s: new + recent flags: %s",
+ myname, psc_print_state_flags(state->flags, myname));
} else {
saved_flags = state->flags;
psc_new_tests(state);
char *unused_context)
{
PSC_STATE dummy;
+ PSC_CLIENT_INFO dummy_client_info;
/*
* This function is called by the cache cleanup pseudo thread.
* silly logging we remove the cache entry only after all tests have
* expired longer ago than the cache retention time.
*/
+ dummy.client_info = &dummy_client_info;
psc_parse_tests(&dummy, stamp_str, event_time() - var_psc_cache_ret);
return ((dummy.flags & PSC_STATE_MASK_ANY_TODO) == 0);
}
#define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname)
+ /*
+ * Per-client shared state.
+ */
+typedef struct {
+ int concurrency; /* per-client */
+ int pass_new_count; /* per-client */
+ time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */
+} PSC_CLIENT_INFO;
+
/*
* Per-session state.
*/
char *smtp_client_port; /* client port */
char *smtp_server_addr; /* server address */
char *smtp_server_port; /* server port */
- int client_concurrency; /* per-client */
const char *final_reply; /* cause for hanging up */
VSTRING *send_buf; /* pending output */
/* Test context. */
struct timeval start_time; /* start of current test */
const char *test_name; /* name of current test */
- time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */
+ PSC_CLIENT_INFO *client_info; /* shared client state */
VSTRING *dnsbl_reply; /* dnsbl reject text */
int dnsbl_score; /* saved DNSBL score */
const char *dnsbl_name; /* DNSBL name with largest weight */
* Emulate legacy ad-hoc variables on top of indexable time stamps. This
* avoids massive scar tissue during initial feature development.
*/
-#define pregr_stamp expire_time[PSC_TINDX_PREGR]
-#define dnsbl_stamp expire_time[PSC_TINDX_DNSBL]
-#define pipel_stamp expire_time[PSC_TINDX_PIPEL]
-#define nsmtp_stamp expire_time[PSC_TINDX_NSMTP]
-#define barlf_stamp expire_time[PSC_TINDX_BARLF]
+#define pregr_stamp client_info->expire_time[PSC_TINDX_PREGR]
+#define dnsbl_stamp client_info->expire_time[PSC_TINDX_DNSBL]
+#define pipel_stamp client_info->expire_time[PSC_TINDX_PIPEL]
+#define nsmtp_stamp client_info->expire_time[PSC_TINDX_NSMTP]
+#define barlf_stamp client_info->expire_time[PSC_TINDX_BARLF]
+
+ /* Minize the patch size for stable releases. */
+#define client_concurrency client_info->concurrency
/*
* Special expiration time values.
#define PSC_INIT_TESTS(dst) do { \
time_t *_it_stamp_p; \
(dst)->flags = 0; \
- for (_it_stamp_p = (dst)->expire_time; \
- _it_stamp_p < (dst)->expire_time + PSC_TINDX_COUNT; \
+ for (_it_stamp_p = (dst)->client_info->expire_time; \
+ _it_stamp_p < (dst)->client_info->expire_time + PSC_TINDX_COUNT; \
_it_stamp_p++) \
*_it_stamp_p = PSC_TIME_STAMP_INVALID; \
} while (0)
+#define PSC_INIT_TEST_FLAGS_ONLY(dst) do { \
+ (dst)->flags = 0; \
+ } while (0)
#define PSC_BEGIN_TESTS(state, name) do { \
(state)->test_name = (name); \
GETTIMEOFDAY(&(state)->start_time); \
} while (0)
extern void psc_new_tests(PSC_STATE *);
extern void psc_parse_tests(PSC_STATE *, const char *, time_t);
+extern void psc_todo_tests(PSC_STATE *, time_t);
extern char *psc_print_tests(VSTRING *, PSC_STATE *);
extern char *psc_print_grey_key(VSTRING *, const char *, const char *,
const char *, const char *);
state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
}
/* Update expiration even if the test was completed or disabled. */
- if (state->expire_time[tindx] < now + var_psc_dnsbl_ttl)
- state->expire_time[tindx] = now + var_psc_dnsbl_ttl;
+ if (state->client_info->expire_time[tindx] < now + var_psc_dnsbl_ttl)
+ state->client_info->expire_time[tindx] = now + var_psc_dnsbl_ttl;
}
}
}
if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0
&& (state->flags & PSC_STATE_MASK_ANY_PASS) ==
PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO))
- msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0 ?
+ msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0
+ || state->client_info->pass_new_count++ > 0 ?
"OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state));
/*
* Update the postscreen cache. This still supports a scenario where a
* client gets whitelisted in the course of multiple sessions, as long as
- * that client does not "fail" any test.
+ * that client does not "fail" any test. Don't try to optimize away cache
+ * updates; we want cached information to be up-to-date even if a test
+ * result is renewed during overlapping SMTP sessions, and even if
+ * 'postfix reload' happens in the middle of that.
*/
if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0
&& psc_cache_map != 0) {
/* psc_new_session_state() creates a new session state object
/* for the specified client stream, and increments the
/* psc_check_queue_length counter. The flags and per-test time
-/* stamps are initialized with PSC_INIT_TESTS(). The addr and
+/* stamps are initialized with PSC_INIT_TESTS(), or for concurrent
+/* sessions, with PSC_INIT_TEST_FLAGS_ONLY(). The addr and
/* port arguments are null-terminated strings with the remote
/* SMTP client endpoint. The _reply members are set to
/* polite "try again" SMTP replies. The protocol member is set
const char *server_port)
{
PSC_STATE *state;
- HTABLE_INFO *ht;
state = (PSC_STATE *) mymalloc(sizeof(*state));
- PSC_INIT_TESTS(state);
if ((state->smtp_client_stream = stream) != 0)
psc_check_queue_length++;
state->smtp_server_fd = (-1);
/*
* Update the per-client session count.
*/
- if ((ht = htable_locate(psc_client_concurrency, client_addr)) == 0)
- ht = htable_enter(psc_client_concurrency, client_addr, (char *) 0);
- ht->value += 1;
- state->client_concurrency = CAST_CHAR_PTR_TO_INT(ht->value);
+ if ((state->client_info = (PSC_CLIENT_INFO *)
+ htable_find(psc_client_concurrency, client_addr)) == 0) {
+ state->client_info = (PSC_CLIENT_INFO *)
+ mymalloc(sizeof(state->client_info[0]));
+ (void) htable_enter(psc_client_concurrency, client_addr,
+ (void *) state->client_info);
+ PSC_INIT_TESTS(state);
+ state->client_info->concurrency = 1;
+ state->client_info->pass_new_count = 0;
+ } else {
+ PSC_INIT_TEST_FLAGS_ONLY(state);
+ state->client_info->concurrency += 1;
+ }
return (state);
}
state->smtp_client_addr)) == 0)
msg_panic("%s: unknown client address: %s",
myname, state->smtp_client_addr);
- if (--(ht->value) == 0)
- htable_delete(psc_client_concurrency, state->smtp_client_addr,
- (void (*) (char *)) 0);
+ if (--(state->client_info->concurrency) == 0)
+ htable_delete(psc_client_concurrency, state->smtp_client_addr, myfree);
if (state->smtp_client_stream != 0) {
event_server_disconnect(state->smtp_client_stream);
/* const char *stamp_text;
/* time_t time_value;
/*
+/* void psc_todo_tests(state, time_value)
+/* PSC_STATE *state;
+/* const char *stamp_text;
+/* time_t time_value;
+/*
/* char *psc_print_tests(buffer, state)
/* VSTRING *buffer;
/* PSC_STATE *state;
/* zeroes all the flags bits. These values are not meant to
/* be stored into the postscreen(8) cache.
/*
+/* PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits. It
+/* should be used when the time stamps are already initialized.
+/*
/* psc_new_tests() sets all test expiration time stamps to
-/* PSC_TIME_STAMP_NEW, and overwrites all flags bits. Only
-/* enabled tests are flagged with PSC_STATE_FLAG_TODO; the
-/* object is flagged with PSC_STATE_FLAG_NEW.
+/* PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
+/*
+/* psc_parse_tests() parses a cache file record and invokes
+/* psc_todo_tests().
/*
-/* psc_parse_tests() parses a cache file record and overwrites
-/* all flags bits. Tests are considered "expired" when they
+/* psc_todo_tests() overwrites all per-session flag bits, and
+/* populates the flags based on test expiration time stamp
+/* information. Tests are considered "expired" when they
/* would be expired at the specified time value. Only enabled
/* tests are flagged as "expired"; the object is flagged as
/* "new" if some enabled tests have "new" time stamps.
void psc_new_tests(PSC_STATE *state)
{
- /*
- * We know this client is brand new.
- */
- state->flags = PSC_STATE_FLAG_NEW;
-
/*
* Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
* recognize cache entries that haven't passed all enabled tests. When we
state->barlf_stamp = PSC_TIME_STAMP_NEW;
/*
- * Don't flag disabled tests as "todo", because there would be no way to
- * make those bits go away.
+ * Determine what tests need to be completed.
*/
- if (PSC_PREGR_TEST_ENABLE())
- state->flags |= PSC_STATE_FLAG_PREGR_TODO;
- if (PSC_DNSBL_TEST_ENABLE())
- state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
- if (var_psc_pipel_enable)
- state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
- if (var_psc_nsmtp_enable)
- state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
- if (var_psc_barlf_enable)
- state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+ psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
}
/* psc_parse_tests - parse test results from cache */
{
const char *start = stamp_str;
char *cp;
- time_t *time_stamps = state->expire_time;
+ time_t *time_stamps = state->client_info->expire_time;
time_t *sp;
- /*
- * We don't know what tests have expired or have never passed.
- */
- state->flags = 0;
-
/*
* Parse the cache entry, and allow for older postscreen versions that
* implemented fewer tests. We pretend that the newer tests were disabled
* at the time that the cache entry was written.
- *
- * Flag the cache entry as "new" when the cache entry has fields for all
- * enabled tests, but the remote SMTP client has not yet passed all those
- * tests.
*/
for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
*sp = strtoul(start, &cp, 10);
if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
*sp = PSC_TIME_STAMP_DISABLED;
- if (*sp == PSC_TIME_STAMP_NEW)
- state->flags |= PSC_STATE_FLAG_NEW;
if (msg_verbose)
msg_info("%s -> %lu", start, (unsigned long) *sp);
if (*cp == ';')
start = cp;
}
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, time_value);
+}
+
+/* psc_todo_tests - determine what tests to perform */
+
+void psc_todo_tests(PSC_STATE *state, time_t time_value)
+{
+ time_t *time_stamps = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * Reset all per-session flags.
+ */
+ state->flags = 0;
+
+ /*
+ * Flag the tests as "new" when the cache entry has fields for all
+ * enabled tests, but the remote SMTP client has not yet passed all those
+ * tests.
+ */
+ for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+ if (*sp == PSC_TIME_STAMP_NEW)
+ state->flags |= PSC_STATE_FLAG_NEW;
+ }
+
/*
* Don't flag disabled tests as "todo", because there would be no way to
* make those bits go away.
if ((ret = sane_rename(old, new)) < 0) {
if (errno != ENOENT
|| mail_queue_mkdirs(new) < 0
- || sane_rename(old, new) < 0)
+ || (ret = sane_rename(old, new)) < 0)
if (errno != ENOENT)
msg_fatal("rename file %s as %s: %m", old, new);
} else {
int var_strict_rfc821_env;
bool var_disable_vrfy_cmd;
char *var_canonical_maps;
+char *var_send_canon_maps;
char *var_rcpt_canon_maps;
char *var_virt_alias_maps;
char *var_virt_mailbox_maps;
VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
* trivial-rewrite resolver.
*/
static MAPS *local_rcpt_maps;
+static MAPS *send_canon_maps;
static MAPS *rcpt_canon_maps;
static MAPS *canonical_maps;
static MAPS *virt_alias_maps;
*/
local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+ send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps,
DSN_SPLIT dp;
if (msg_verbose)
- msg_info(">>> CHECKING RECIPIENT MAPS <<<");
+ msg_info(">>> CHECKING %s VALIDATION MAPS <<<", reply_class);
/*
* Resolve the address.
* domains.
*/
if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient))
+ || (strcmp(reply_class, SMTPD_NAME_SENDER) == 0
+ && MATCH(send_canon_maps, CONST_STR(reply->recipient)))
|| MATCH(canonical_maps, CONST_STR(reply->recipient))
|| MATCH(virt_alias_maps, CONST_STR(reply->recipient)))
return (0);
char *var_rcpt_delim;
char *var_rest_classes;
char *var_alias_maps;
+char *var_send_canon_maps;
char *var_rcpt_canon_maps;
char *var_canonical_maps;
char *var_virt_alias_maps;
VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim,
VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes,
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps,
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps,
VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps,
resp = 0;
break;
}
+ if (strcasecmp(args->argv[0], VAR_SEND_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_send_canon_maps, args->argv[1]);
+ UPDATE_MAPS(send_canon_maps, VAR_SEND_CANON_MAPS,
+ var_send_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX);
+ resp = 0;
+ break;
+ }
+ if (strcasecmp(args->argv[0], VAR_RCPT_CANON_MAPS) == 0) {
+ UPDATE_STRING(var_rcpt_canon_maps, args->argv[1]);
+ UPDATE_MAPS(rcpt_canon_maps, VAR_RCPT_CANON_MAPS,
+ var_rcpt_canon_maps, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX);
+ resp = 0;
+ break;
+ }
if (strcasecmp(args->argv[0], VAR_RBL_REPLY_MAPS) == 0) {
UPDATE_STRING(var_rbl_reply_maps, args->argv[1]);
UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS,
int mail_copy_status;
int deliver_status;
int copy_flags;
- long end;
struct stat st;
/*
msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
VAR_STRICT_MBOX_OWNER);
} else {
- end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END);
+ if (vstream_fseek(mp->fp, (off_t) 0, SEEK_END) < 0)
+ msg_fatal("%s: seek queue file %s: %m",
+ myname, VSTREAM_PATH(mp->fp));
mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
copy_flags, "\n", why);
}