xref: /netbsd-src/external/ibm-public/postfix/dist/src/postscreen/postscreen_early.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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 
psc_allowlist_non_dnsbl(PSC_STATE * state)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 
psc_early_event(int event,void * context)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 
psc_early_dnsbl_event(int unused_event,void * context)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 
psc_early_tests(PSC_STATE * state)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 
psc_early_init(void)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