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