Home | History | Annotate | Line # | Download | only in postscreen
      1 /*	$NetBSD: postscreen_early.c,v 1.4 2022/10/08 16:12:48 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	postscreen_early 3
      6 /* SUMMARY
      7 /*	postscreen pre-handshake tests
      8 /* SYNOPSIS
      9 /*	#include <postscreen.h>
     10 /*
     11 /*	void	psc_early_init(void)
     12 /*
     13 /*	void	psc_early_tests(state)
     14 /*	PSC_STATE *state;
     15 /* DESCRIPTION
     16 /*	psc_early_tests() performs protocol tests before the SMTP
     17 /*	handshake: the pregreet test and the DNSBL test. Control
     18 /*	is passed to the psc_smtpd_tests() routine as appropriate.
     19 /*
     20 /*	psc_early_init() performs one-time initialization.
     21 /* LICENSE
     22 /* .ad
     23 /* .fi
     24 /*	The Secure Mailer license must be distributed with this software.
     25 /* AUTHOR(S)
     26 /*	Wietse Venema
     27 /*	IBM T.J. Watson Research
     28 /*	P.O. Box 704
     29 /*	Yorktown Heights, NY 10598, USA
     30 /*
     31 /*	Wietse Venema
     32 /*	Google, Inc.
     33 /*	111 8th Avenue
     34 /*	New York, NY 10011, USA
     35 /*--*/
     36 
     37 /* System library. */
     38 
     39 #include <sys_defs.h>
     40 #include <sys/socket.h>
     41 #include <limits.h>
     42 
     43 /* Utility library. */
     44 
     45 #include <msg.h>
     46 #include <stringops.h>
     47 #include <mymalloc.h>
     48 #include <vstring.h>
     49 
     50 /* Global library. */
     51 
     52 #include <mail_params.h>
     53 
     54 /* Application-specific. */
     55 
     56 #include <postscreen.h>
     57 
     58 static char *psc_teaser_greeting;
     59 static VSTRING *psc_escape_buf;
     60 
     61 /* psc_allowlist_non_dnsbl - allowlist pending non-dnsbl tests */
     62 
     63 static void psc_allowlist_non_dnsbl(PSC_STATE *state)
     64 {
     65     time_t  now;
     66     int     tindx;
     67 
     68     /*
     69      * If no tests failed (we can't undo those), and if the allowlist
     70      * threshold is met, flag non-dnsbl tests that are pending or disabled as
     71      * successfully completed, and set their expiration times equal to the
     72      * DNSBL expiration time, except for tests that would expire later.
     73      *
     74      * Why flag disabled tests as passed? When a disabled test is turned on,
     75      * postscreen should not apply that test to clients that are already
     76      * allowlisted based on their combined DNSBL score.
     77      */
     78     if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
     79 	&& state->dnsbl_score < var_psc_dnsbl_thresh
     80 	&& var_psc_dnsbl_althresh < 0
     81 	&& state->dnsbl_score <= var_psc_dnsbl_althresh) {
     82 	now = event_time();
     83 	for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) {
     84 	    if (tindx == PSC_TINDX_DNSBL)
     85 		continue;
     86 	    if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx))
     87 		&& !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) {
     88 		if (msg_verbose)
     89 		    msg_info("skip %s test for [%s]:%s",
     90 			 psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state));
     91 		/* Wrong for deep protocol tests, but we disable those. */
     92 		state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx);
     93 		/* This also disables pending deep protocol tests. */
     94 		state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
     95 	    }
     96 	    /* Update expiration even if the test was completed or disabled. */
     97 	    if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl)
     98 		state->client_info->expire_time[tindx] = now + state->dnsbl_ttl;
     99 	}
    100     }
    101 }
    102 
    103 /* psc_early_event - handle pre-greet, EOF, and DNSBL results. */
    104 
    105 static void psc_early_event(int event, void *context)
    106 {
    107     const char *myname = "psc_early_event";
    108     PSC_STATE *state = (PSC_STATE *) context;
    109     time_t *expire_time = state->client_info->expire_time;
    110     char    read_buf[PSC_READ_BUF_SIZE];
    111     int     read_count;
    112     DELTA_TIME elapsed;
    113 
    114     if (msg_verbose > 1)
    115 	msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
    116 		 myname, psc_post_queue_length, psc_check_queue_length,
    117 		 event, vstream_fileno(state->smtp_client_stream),
    118 		 state->smtp_client_addr, state->smtp_client_port,
    119 		 psc_print_state_flags(state->flags, myname));
    120 
    121     PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
    122 			    psc_early_event, context);
    123 
    124     /*
    125      * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a
    126      * memory leak.
    127      *
    128      * XXX We can avoid "forgetting" to do this by keeping a pointer to the
    129      * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
    130      * shave off a hash table lookup when retrieving the DNSBL result.
    131      *
    132      * A direct pointer increases the odds of dangling pointers. Hash-table
    133      * lookup is safer, and that is why it's done that way.
    134      */
    135     switch (event) {
    136 
    137 	/*
    138 	 * We either reached the end of the early tests time limit, or all
    139 	 * early tests completed before the pregreet timer would go off.
    140 	 */
    141     case EVENT_TIME:
    142 
    143 	/*
    144 	 * Check if the SMTP client spoke before its turn.
    145 	 */
    146 	if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
    147 	    && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
    148 	    expire_time[PSC_TINDX_PREGR] = event_time() + var_psc_pregr_ttl;
    149 	    PSC_PASS_SESSION_STATE(state, "pregreet test",
    150 				   PSC_STATE_FLAG_PREGR_PASS);
    151 	}
    152 	if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL)
    153 	    && psc_pregr_action == PSC_ACT_IGNORE) {
    154 	    PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
    155 	    /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */
    156 	}
    157 
    158 	/*
    159 	 * Collect the DNSBL score, and allowlist other tests if applicable.
    160 	 * Note: this score will be partial when some DNS lookup did not
    161 	 * complete before the pregreet timer expired.
    162 	 *
    163 	 * If the client is DNS blocklisted, drop the connection, send the
    164 	 * client to a dummy protocol engine, or continue to the next test.
    165 	 */
    166 #define PSC_DNSBL_FORMAT \
    167 	"%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
    168 #define NO_DNSBL_SCORE	INT_MAX
    169 
    170 	if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
    171 	    if (state->dnsbl_score == NO_DNSBL_SCORE) {
    172 		state->dnsbl_score =
    173 		    psc_dnsbl_retrieve(state->smtp_client_addr,
    174 				       &state->dnsbl_name,
    175 				       state->dnsbl_index,
    176 				       &state->dnsbl_ttl);
    177 		if (var_psc_dnsbl_althresh < 0)
    178 		    psc_allowlist_non_dnsbl(state);
    179 	    }
    180 	    if (state->dnsbl_score < var_psc_dnsbl_thresh) {
    181 		expire_time[PSC_TINDX_DNSBL] = event_time() + state->dnsbl_ttl;
    182 		PSC_PASS_SESSION_STATE(state, "dnsbl test",
    183 				       PSC_STATE_FLAG_DNSBL_PASS);
    184 	    } else {
    185 		msg_info("DNSBL rank %d for [%s]:%s",
    186 			 state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
    187 		PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
    188 		switch (psc_dnsbl_action) {
    189 		case PSC_ACT_DROP:
    190 		    state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
    191 						    PSC_DNSBL_FORMAT, "521",
    192 						    state->smtp_client_addr,
    193 							 state->dnsbl_name);
    194 		    PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
    195 		    return;
    196 		case PSC_ACT_ENFORCE:
    197 		    state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
    198 						    PSC_DNSBL_FORMAT, "550",
    199 						    state->smtp_client_addr,
    200 							 state->dnsbl_name);
    201 		    PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
    202 		    break;
    203 		case PSC_ACT_IGNORE:
    204 		    PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
    205 		    /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */
    206 		    break;
    207 		default:
    208 		    msg_panic("%s: unknown dnsbl action value %d",
    209 			      myname, psc_dnsbl_action);
    210 
    211 		}
    212 	    }
    213 	}
    214 
    215 	/*
    216 	 * Pass the connection to a real SMTP server, or enter the dummy
    217 	 * engine for deep tests.
    218 	 */
    219 	if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
    220 	    || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
    221 		!= PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
    222 	    psc_smtpd_tests(state);
    223 	else
    224 	    psc_conclude(state);
    225 	return;
    226 
    227 	/*
    228 	 * EOF, or the client spoke before its turn. We simply drop the
    229 	 * connection, or we continue waiting and allow DNS replies to
    230 	 * trickle in.
    231 	 */
    232     default:
    233 	if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
    234 			  read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
    235 	    /* Avoid memory leak. */
    236 	    if (state->dnsbl_score == NO_DNSBL_SCORE
    237 		&& (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
    238 		(void) psc_dnsbl_retrieve(state->smtp_client_addr,
    239 					  &state->dnsbl_name,
    240 					  state->dnsbl_index,
    241 					  &state->dnsbl_ttl);
    242 	    /* XXX Wait for DNS replies to come in. */
    243 	    psc_hangup_event(state);
    244 	    return;
    245 	}
    246 	read_buf[read_count] = 0;
    247 	escape(psc_escape_buf, read_buf, read_count);
    248 	msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count,
    249 	       psc_format_delta_time(psc_temp, state->start_time, &elapsed),
    250 		 PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf));
    251 	PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
    252 	switch (psc_pregr_action) {
    253 	case PSC_ACT_DROP:
    254 	    /* Avoid memory leak. */
    255 	    if (state->dnsbl_score == NO_DNSBL_SCORE
    256 		&& (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
    257 		(void) psc_dnsbl_retrieve(state->smtp_client_addr,
    258 					  &state->dnsbl_name,
    259 					  state->dnsbl_index,
    260 					  &state->dnsbl_ttl);
    261 	    PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
    262 	    return;
    263 	case PSC_ACT_ENFORCE:
    264 	    /* We call psc_dnsbl_retrieve() when the timer expires. */
    265 	    PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n");
    266 	    break;
    267 	case PSC_ACT_IGNORE:
    268 	    /* We call psc_dnsbl_retrieve() when the timer expires. */
    269 	    /* We must handle this case after the timer expires. */
    270 	    break;
    271 	default:
    272 	    msg_panic("%s: unknown pregreet action value %d",
    273 		      myname, psc_pregr_action);
    274 	}
    275 
    276 	/*
    277 	 * Terminate the greet delay if we're just waiting for the pregreet
    278 	 * test to complete. It is safe to call psc_early_event directly,
    279 	 * since we are already in that function.
    280 	 *
    281 	 * XXX After this code passes all tests, swap around the two blocks in
    282 	 * this switch statement and fall through from EVENT_READ into
    283 	 * EVENT_TIME, instead of calling psc_early_event recursively.
    284 	 */
    285 	state->flags |= PSC_STATE_FLAG_PREGR_DONE;
    286 	if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT
    287 	    || ((state->flags & PSC_STATE_MASK_EARLY_DONE)
    288 		== PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)))
    289 	    psc_early_event(EVENT_TIME, context);
    290 	else
    291 	    event_request_timer(psc_early_event, context,
    292 				PSC_EFF_GREET_WAIT - elapsed.dt_sec);
    293 	return;
    294     }
    295 }
    296 
    297 /* psc_early_dnsbl_event - cancel pregreet timer if waiting for DNS only */
    298 
    299 static void psc_early_dnsbl_event(int unused_event, void *context)
    300 {
    301     const char *myname = "psc_early_dnsbl_event";
    302     PSC_STATE *state = (PSC_STATE *) context;
    303 
    304     if (msg_verbose)
    305 	msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state));
    306 
    307     /*
    308      * Collect the DNSBL score, and allowlist other tests if applicable.
    309      */
    310     state->dnsbl_score =
    311 	psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
    312 			   state->dnsbl_index, &state->dnsbl_ttl);
    313     if (var_psc_dnsbl_althresh < 0)
    314 	psc_allowlist_non_dnsbl(state);
    315 
    316     /*
    317      * Terminate the greet delay if we're just waiting for DNSBL lookup to
    318      * complete. Don't call psc_early_event directly, that would result in a
    319      * dangling pointer.
    320      */
    321     state->flags |= PSC_STATE_FLAG_DNSBL_DONE;
    322     if ((state->flags & PSC_STATE_MASK_EARLY_DONE)
    323 	== PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))
    324 	event_request_timer(psc_early_event, context, EVENT_NULL_DELAY);
    325 }
    326 
    327 /* psc_early_tests - start the early (before protocol) tests */
    328 
    329 void    psc_early_tests(PSC_STATE *state)
    330 {
    331     const char *myname = "psc_early_tests";
    332 
    333     /*
    334      * Report errors and progress in the context of this test.
    335      */
    336     PSC_BEGIN_TESTS(state, "tests before SMTP handshake");
    337 
    338     /*
    339      * Run a PREGREET test. Send half the greeting banner, by way of teaser,
    340      * then wait briefly to see if the client speaks before its turn.
    341      */
    342     if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
    343 	&& psc_teaser_greeting != 0
    344 	&& PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) {
    345 	psc_hangup_event(state);
    346 	return;
    347     }
    348 
    349     /*
    350      * Run a DNS blocklist query.
    351      */
    352     if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0)
    353 	state->dnsbl_index =
    354 	    psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event,
    355 			      (void *) state);
    356     else
    357 	state->dnsbl_index = -1;
    358     state->dnsbl_score = NO_DNSBL_SCORE;
    359 
    360     /*
    361      * Wait for the client to respond or for DNS lookup to complete.
    362      */
    363     if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0)
    364 	PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
    365 		       psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
    366     else
    367 	event_request_timer(psc_early_event, (void *) state, PSC_EFF_GREET_WAIT);
    368 }
    369 
    370 /* psc_early_init - initialize early tests */
    371 
    372 void    psc_early_init(void)
    373 {
    374     if (*var_psc_pregr_banner) {
    375 	vstring_sprintf(psc_temp, "220-%s\r\n", var_psc_pregr_banner);
    376 	psc_teaser_greeting = mystrdup(STR(psc_temp));
    377 	psc_escape_buf = vstring_alloc(100);
    378     }
    379 }
    380