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