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