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