summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_early.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/postscreen/postscreen_early.c')
-rw-r--r--src/postscreen/postscreen_early.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/src/postscreen/postscreen_early.c b/src/postscreen/postscreen_early.c
new file mode 100644
index 0000000..c9d8faf
--- /dev/null
+++ b/src/postscreen/postscreen_early.c
@@ -0,0 +1,377 @@
+/*++
+/* NAME
+/* postscreen_early 3
+/* SUMMARY
+/* postscreen pre-handshake tests
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void psc_early_init(void)
+/*
+/* void psc_early_tests(state)
+/* PSC_STATE *state;
+/* DESCRIPTION
+/* psc_early_tests() performs protocol tests before the SMTP
+/* handshake: the pregreet test and the DNSBL test. Control
+/* is passed to the psc_smtpd_tests() routine as appropriate.
+/*
+/* psc_early_init() performs one-time initialization.
+/* 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 <sys/socket.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+static char *psc_teaser_greeting;
+static VSTRING *psc_escape_buf;
+
+/* psc_allowlist_non_dnsbl - allowlist pending non-dnsbl tests */
+
+static void psc_allowlist_non_dnsbl(PSC_STATE *state)
+{
+ time_t now;
+ int tindx;
+
+ /*
+ * If no tests failed (we can't undo those), and if the allowlist
+ * threshold is met, flag non-dnsbl tests that are pending or disabled as
+ * successfully completed, and set their expiration times equal to the
+ * DNSBL expiration time, except for tests that would expire later.
+ *
+ * Why flag disabled tests as passed? When a disabled test is turned on,
+ * postscreen should not apply that test to clients that are already
+ * allowlisted based on their combined DNSBL score.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->dnsbl_score < var_psc_dnsbl_thresh
+ && var_psc_dnsbl_althresh < 0
+ && state->dnsbl_score <= var_psc_dnsbl_althresh) {
+ now = event_time();
+ for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) {
+ if (tindx == PSC_TINDX_DNSBL)
+ continue;
+ if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx))
+ && !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) {
+ if (msg_verbose)
+ msg_info("skip %s test for [%s]:%s",
+ psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state));
+ /* Wrong for deep protocol tests, but we disable those. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx);
+ /* This also disables pending deep protocol tests. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
+ }
+ /* Update expiration even if the test was completed or disabled. */
+ if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl)
+ state->client_info->expire_time[tindx] = now + state->dnsbl_ttl;
+ }
+ }
+}
+
+/* psc_early_event - handle pre-greet, EOF, and DNSBL results. */
+
+static void psc_early_event(int event, void *context)
+{
+ const char *myname = "psc_early_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+ time_t *expire_time = state->client_info->expire_time;
+ char read_buf[PSC_READ_BUF_SIZE];
+ int read_count;
+ DELTA_TIME elapsed;
+
+ if (msg_verbose > 1)
+ msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
+ myname, psc_post_queue_length, psc_check_queue_length,
+ event, vstream_fileno(state->smtp_client_stream),
+ state->smtp_client_addr, state->smtp_client_port,
+ psc_print_state_flags(state->flags, myname));
+
+ PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, context);
+
+ /*
+ * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a
+ * memory leak.
+ *
+ * XXX We can avoid "forgetting" to do this by keeping a pointer to the
+ * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
+ * shave off a hash table lookup when retrieving the DNSBL result.
+ *
+ * A direct pointer increases the odds of dangling pointers. Hash-table
+ * lookup is safer, and that is why it's done that way.
+ */
+ switch (event) {
+
+ /*
+ * We either reached the end of the early tests time limit, or all
+ * early tests completed before the pregreet timer would go off.
+ */
+ case EVENT_TIME:
+
+ /*
+ * Check if the SMTP client spoke before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
+ expire_time[PSC_TINDX_PREGR] = event_time() + var_psc_pregr_ttl;
+ PSC_PASS_SESSION_STATE(state, "pregreet test",
+ PSC_STATE_FLAG_PREGR_PASS);
+ }
+ if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL)
+ && psc_pregr_action == PSC_ACT_IGNORE) {
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */
+ }
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ * Note: this score will be partial when some DNS lookup did not
+ * complete before the pregreet timer expired.
+ *
+ * If the client is DNS blocklisted, drop the connection, send the
+ * client to a dummy protocol engine, or continue to the next test.
+ */
+#define PSC_DNSBL_FORMAT \
+ "%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
+#define NO_DNSBL_SCORE INT_MAX
+
+ if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
+ if (state->dnsbl_score == NO_DNSBL_SCORE) {
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+ }
+ if (state->dnsbl_score < var_psc_dnsbl_thresh) {
+ expire_time[PSC_TINDX_DNSBL] = event_time() + state->dnsbl_ttl;
+ PSC_PASS_SESSION_STATE(state, "dnsbl test",
+ PSC_STATE_FLAG_DNSBL_PASS);
+ } else {
+ msg_info("DNSBL rank %d for [%s]:%s",
+ state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ switch (psc_dnsbl_action) {
+ case PSC_ACT_DROP:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "521",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
+ return;
+ case PSC_ACT_ENFORCE:
+ state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
+ PSC_DNSBL_FORMAT, "550",
+ state->smtp_client_addr,
+ state->dnsbl_name);
+ PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
+ break;
+ case PSC_ACT_IGNORE:
+ PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
+ /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */
+ break;
+ default:
+ msg_panic("%s: unknown dnsbl action value %d",
+ myname, psc_dnsbl_action);
+
+ }
+ }
+ }
+
+ /*
+ * Pass the connection to a real SMTP server, or enter the dummy
+ * engine for deep tests.
+ */
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
+ || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
+ != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
+ psc_smtpd_tests(state);
+ else
+ psc_conclude(state);
+ return;
+
+ /*
+ * EOF, or the client spoke before its turn. We simply drop the
+ * connection, or we continue waiting and allow DNS replies to
+ * trickle in.
+ */
+ default:
+ if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
+ read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ /* XXX Wait for DNS replies to come in. */
+ psc_hangup_event(state);
+ return;
+ }
+ read_buf[read_count] = 0;
+ escape(psc_escape_buf, read_buf, read_count);
+ msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count,
+ psc_format_delta_time(psc_temp, state->start_time, &elapsed),
+ PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf));
+ PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
+ switch (psc_pregr_action) {
+ case PSC_ACT_DROP:
+ /* Avoid memory leak. */
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index,
+ &state->dnsbl_ttl);
+ PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
+ return;
+ case PSC_ACT_ENFORCE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n");
+ break;
+ case PSC_ACT_IGNORE:
+ /* We call psc_dnsbl_retrieve() when the timer expires. */
+ /* We must handle this case after the timer expires. */
+ break;
+ default:
+ msg_panic("%s: unknown pregreet action value %d",
+ myname, psc_pregr_action);
+ }
+
+ /*
+ * Terminate the greet delay if we're just waiting for the pregreet
+ * test to complete. It is safe to call psc_early_event directly,
+ * since we are already in that function.
+ *
+ * XXX After this code passes all tests, swap around the two blocks in
+ * this switch statement and fall through from EVENT_READ into
+ * EVENT_TIME, instead of calling psc_early_event recursively.
+ */
+ state->flags |= PSC_STATE_FLAG_PREGR_DONE;
+ if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT
+ || ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)))
+ psc_early_event(EVENT_TIME, context);
+ else
+ event_request_timer(psc_early_event, context,
+ PSC_EFF_GREET_WAIT - elapsed.dt_sec);
+ return;
+ }
+}
+
+/* psc_early_dnsbl_event - cancel pregreet timer if waiting for DNS only */
+
+static void psc_early_dnsbl_event(int unused_event, void *context)
+{
+ const char *myname = "psc_early_dnsbl_event";
+ PSC_STATE *state = (PSC_STATE *) context;
+
+ if (msg_verbose)
+ msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state));
+
+ /*
+ * Collect the DNSBL score, and allowlist other tests if applicable.
+ */
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
+ state->dnsbl_index, &state->dnsbl_ttl);
+ if (var_psc_dnsbl_althresh < 0)
+ psc_allowlist_non_dnsbl(state);
+
+ /*
+ * Terminate the greet delay if we're just waiting for DNSBL lookup to
+ * complete. Don't call psc_early_event directly, that would result in a
+ * dangling pointer.
+ */
+ state->flags |= PSC_STATE_FLAG_DNSBL_DONE;
+ if ((state->flags & PSC_STATE_MASK_EARLY_DONE)
+ == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))
+ event_request_timer(psc_early_event, context, EVENT_NULL_DELAY);
+}
+
+/* psc_early_tests - start the early (before protocol) tests */
+
+void psc_early_tests(PSC_STATE *state)
+{
+ const char *myname = "psc_early_tests";
+
+ /*
+ * Report errors and progress in the context of this test.
+ */
+ PSC_BEGIN_TESTS(state, "tests before SMTP handshake");
+
+ /*
+ * Run a PREGREET test. Send half the greeting banner, by way of teaser,
+ * then wait briefly to see if the client speaks before its turn.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && psc_teaser_greeting != 0
+ && PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) {
+ psc_hangup_event(state);
+ return;
+ }
+
+ /*
+ * Run a DNS blocklist query.
+ */
+ if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0)
+ state->dnsbl_index =
+ psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event,
+ (void *) state);
+ else
+ state->dnsbl_index = -1;
+ state->dnsbl_score = NO_DNSBL_SCORE;
+
+ /*
+ * Wait for the client to respond or for DNS lookup to complete.
+ */
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0)
+ PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
+ psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+ else
+ event_request_timer(psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
+}
+
+/* psc_early_init - initialize early tests */
+
+void psc_early_init(void)
+{
+ if (*var_psc_pregr_banner) {
+ vstring_sprintf(psc_temp, "220-%s\r\n", var_psc_pregr_banner);
+ psc_teaser_greeting = mystrdup(STR(psc_temp));
+ psc_escape_buf = vstring_alloc(100);
+ }
+}