summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_tests.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/postscreen/postscreen_tests.c')
-rw-r--r--src/postscreen/postscreen_tests.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/src/postscreen/postscreen_tests.c b/src/postscreen/postscreen_tests.c
new file mode 100644
index 0000000..5e18622
--- /dev/null
+++ b/src/postscreen/postscreen_tests.c
@@ -0,0 +1,341 @@
+/*++
+/* NAME
+/* postscreen_tests 3
+/* SUMMARY
+/* postscreen tests timestamp/flag bulk support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void PSC_INIT_TESTS(state)
+/* PSC_STATE *state;
+/*
+/* void psc_new_tests(state)
+/* PSC_STATE *state;
+/*
+/* void psc_parse_tests(state, stamp_text, time_value)
+/* PSC_STATE *state;
+/* 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;
+/*
+/* char *psc_print_grey_key(buffer, client, helo, sender, rcpt)
+/* VSTRING *buffer;
+/* const char *client;
+/* const char *helo;
+/* const char *sender;
+/* const char *rcpt;
+/*
+/* const char *psc_test_name(tindx)
+/* int tindx;
+/* DESCRIPTION
+/* The functions in this module overwrite the per-test expiration
+/* time stamps and all flags bits. Some functions are implemented
+/* as unsafe macros, meaning they evaluate one or more arguments
+/* multiple times.
+/*
+/* PSC_INIT_TESTS() is an unsafe macro that sets the per-test
+/* expiration time stamps to PSC_TIME_STAMP_INVALID, and that
+/* 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 invokes psc_todo_tests().
+/*
+/* psc_parse_tests() parses a cache file record and invokes
+/* psc_todo_tests().
+/*
+/* 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.
+/*
+/* psc_print_tests() creates a cache file record for the
+/* specified flags and per-test expiration time stamps.
+/* This may modify the time stamps for disabled tests.
+/*
+/* psc_print_grey_key() prints a greylist lookup key.
+/*
+/* psc_test_name() returns the name for the specified text
+/* index.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h> /* sscanf */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <name_code.h>
+#include <sane_strtol.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Kludge to detect if some test is enabled.
+ */
+#define PSC_PREGR_TEST_ENABLE() (*var_psc_pregr_banner != 0)
+#define PSC_DNSBL_TEST_ENABLE() (*var_psc_dnsbl_sites != 0)
+
+ /*
+ * Format of a persistent cache entry (which is almost but not quite the
+ * same as the in-memory representation).
+ *
+ * Each cache entry has one time stamp for each test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It
+ * is reserved for in-memory objects that are still being initialized.
+ *
+ * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed.
+ * Postscreen will log the client with "pass new" when it passes the final
+ * test.
+ *
+ * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never
+ * passed, and that the test was disabled when the cache entry was written.
+ *
+ * - Otherwise, the test was passed, and the time stamp indicates when that
+ * test result expires.
+ *
+ * A cache entry is expired when the time stamps of all passed tests are
+ * expired.
+ */
+
+/* psc_new_tests - initialize new test results from scratch */
+
+void psc_new_tests(PSC_STATE *state)
+{
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * 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
+ * write a cache entry to the database, any new-but-disabled tests will
+ * get a PSC_TIME_STAMP_DISABLED time stamp.
+ */
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW;
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW;
+
+ /*
+ * Determine what tests need to be completed.
+ */
+ psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
+}
+
+/* psc_parse_tests - parse test results from cache */
+
+void psc_parse_tests(PSC_STATE *state,
+ const char *stamp_str,
+ time_t time_value)
+{
+ const char *start = stamp_str;
+ char *cp;
+ time_t *time_stamps = state->client_info->expire_time;
+ time_t *sp;
+
+ /*
+ * 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.
+ */
+ for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+ *sp = sane_strtoul(start, &cp, 10);
+ if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
+ *sp = PSC_TIME_STAMP_DISABLED;
+ if (msg_verbose)
+ msg_info("%s -> %lu", start, (unsigned long) *sp);
+ if (*cp == ';')
+ start = cp + 1;
+ else
+ 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 *expire_time = 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 = expire_time; sp < expire_time + 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 (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+
+ /*
+ * If any test has expired, proactively refresh tests that will expire
+ * soon. This can increase the occurrence of client-visible delays, but
+ * avoids questions about why a client can pass some test and then fail
+ * within seconds. The proactive refresh time is really a surrogate for
+ * the user's curiosity level, and therefore hard to choose optimally.
+ */
+#ifdef VAR_PSC_REFRESH_TIME
+ if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0
+ && var_psc_refresh_time > 0) {
+ time_t refresh_time = time_value + var_psc_refresh_time;
+
+ if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR])
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL])
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL])
+ state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
+ if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP])
+ state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
+ if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF])
+ state->flags |= PSC_STATE_FLAG_BARLF_TODO;
+ }
+#endif
+
+ /*
+ * Gratuitously make postscreen logging more useful by turning on all
+ * enabled pre-handshake tests when any pre-handshake test is turned on.
+ *
+ * XXX Don't enable PREGREET gratuitously before the test expires. With a
+ * short TTL for DNSBL allowlisting, turning on PREGREET would force a
+ * full postscreen_greet_wait too frequently.
+ */
+#if 0
+ if (state->flags & PSC_STATE_MASK_EARLY_TODO) {
+ if (PSC_PREGR_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_PREGR_TODO;
+ if (PSC_DNSBL_TEST_ENABLE())
+ state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
+ }
+#endif
+}
+
+/* psc_print_tests - print postscreen cache record */
+
+char *psc_print_tests(VSTRING *buf, PSC_STATE *state)
+{
+ const char *myname = "psc_print_tests";
+ time_t *expire_time = state->client_info->expire_time;
+
+ /*
+ * Sanity check.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
+ msg_panic("%s: attempt to save a no-update record", myname);
+
+ /*
+ * Give disabled tests a dummy time stamp so that we don't log a client
+ * with "pass new" when some disabled test becomes enabled at some later
+ * time.
+ */
+ if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED;
+ if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED;
+ if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW)
+ expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED;
+
+ vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
+ (unsigned long) expire_time[PSC_TINDX_PREGR],
+ (unsigned long) expire_time[PSC_TINDX_DNSBL],
+ (unsigned long) expire_time[PSC_TINDX_PIPEL],
+ (unsigned long) expire_time[PSC_TINDX_NSMTP],
+ (unsigned long) expire_time[PSC_TINDX_BARLF]);
+ return (STR(buf));
+}
+
+/* psc_print_grey_key - print postscreen cache record */
+
+char *psc_print_grey_key(VSTRING *buf, const char *client,
+ const char *helo, const char *sender,
+ const char *rcpt)
+{
+ return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
+ client, helo, sender, rcpt)));
+}
+
+/* psc_test_name - map test index to symbolic name */
+
+const char *psc_test_name(int tindx)
+{
+ const char *myname = "psc_test_name";
+ const NAME_CODE test_name_map[] = {
+ PSC_TNAME_PREGR, PSC_TINDX_PREGR,
+ PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
+ PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
+ PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
+ PSC_TNAME_BARLF, PSC_TINDX_BARLF,
+ 0, -1,
+ };
+ const char *result;
+
+ if ((result = str_name_code(test_name_map, tindx)) == 0)
+ msg_panic("%s: bad index %d", myname, tindx);
+ return (result);
+}