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