void    psc_conclude(PSC_STATE *state)
{
    const char *myname = "psc_conclude";

    if (msg_verbose)
	msg_info("flags for %s: %s",
		 myname, psc_print_state_flags(state->flags, myname));

    /*
     * Handle clients that passed at least one test other than permanent
     * whitelisting, and that didn't fail any test including permanent
     * blacklisting. There may still be unfinished tests; those tests will
     * need to be completed when the client returns in a later session.
     */
    if (state->flags & PSC_STATE_MASK_ANY_FAIL)
	state->flags &= ~PSC_STATE_MASK_ANY_PASS;

    /*
     * Log our final blessing when all unfinished tests were completed.
     */
    if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0
	&& (state->flags & PSC_STATE_MASK_ANY_PASS) ==
	PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO))
	msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0
		 || state->client_info->pass_new_count++ > 0 ?
		 "OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state));

    /*
     * Update the postscreen cache. This still supports a scenario where a
     * client gets whitelisted in the course of multiple sessions, as long as
     * that client does not "fail" any test. Don't try to optimize away cache
     * updates; we want cached information to be up-to-date even if a test
     * result is renewed during overlapping SMTP sessions, and even if
     * 'postfix reload' happens in the middle of that.
     */
    if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0
	&& psc_cache_map != 0) {
	psc_print_tests(psc_temp, state);
	psc_cache_update(psc_cache_map, state->smtp_client_addr, STR(psc_temp));
    }

    /*
     * Either hand off the socket to a real SMTP engine, or say bye-bye.
     */
    if ((state->flags & PSC_STATE_FLAG_NOFORWARD) == 0) {
	psc_send_socket(state);
    } else {
	if ((state->flags & PSC_STATE_FLAG_HANGUP) == 0)
	    (void) PSC_SEND_REPLY(state, state->final_reply);
	msg_info("DISCONNECT [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	psc_free_session_state(state);
    }
}
Example #2
0
static void psc_early_event(int event, char *context)
{
    const char *myname = "psc_early_event";
    PSC_STATE *state = (PSC_STATE *) context;
    char    read_buf[PSC_READ_BUF_SIZE];
    int     read_count;
    DELTA_TIME elapsed;

    if (msg_verbose > 1)
	msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
		 myname, psc_post_queue_length, psc_check_queue_length,
		 event, vstream_fileno(state->smtp_client_stream),
		 state->smtp_client_addr, state->smtp_client_port,
		 psc_print_state_flags(state->flags, myname));

    PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
			    psc_early_event, context);

    /*
     * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a
     * memory leak.
     * 
     * XXX We can avoid "forgetting" to do this by keeping a pointer to the
     * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
     * shave off a hash table lookup when retrieving the DNSBL result.
     * 
     * A direct pointer increases the odds of dangling pointers. Hash-table
     * lookup is safer, and that is why it's done that way.
     */
    switch (event) {

	/*
	 * We either reached the end of the early tests time limit, or all
	 * early tests completed before the pregreet timer would go off.
	 */
    case EVENT_TIME:

	/*
	 * Check if the SMTP client spoke before its turn.
	 */
	if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
	    && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
	    state->pregr_stamp = event_time() + var_psc_pregr_ttl;
	    PSC_PASS_SESSION_STATE(state, "pregreet test",
				   PSC_STATE_FLAG_PREGR_PASS);
	}
	if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL)
	    && psc_pregr_action == PSC_ACT_IGNORE) {
	    PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
	    /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */
	}

	/*
	 * Collect the DNSBL score, and whitelist other tests if applicable.
	 * Note: this score will be partial when some DNS lookup did not
	 * complete before the pregreet timer expired.
	 * 
	 * If the client is DNS blocklisted, drop the connection, send the
	 * client to a dummy protocol engine, or continue to the next test.
	 */
#define PSC_DNSBL_FORMAT \
	"%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
#define NO_DNSBL_SCORE	INT_MAX

	if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
	    if (state->dnsbl_score == NO_DNSBL_SCORE) {
		state->dnsbl_score =
		    psc_dnsbl_retrieve(state->smtp_client_addr,
				       &state->dnsbl_name,
				       state->dnsbl_index);
		if (var_psc_dnsbl_wthresh < 0)
		    psc_whitelist_non_dnsbl(state);
	    }
	    if (state->dnsbl_score < var_psc_dnsbl_thresh) {
		state->dnsbl_stamp = event_time() + var_psc_dnsbl_ttl;
		PSC_PASS_SESSION_STATE(state, "dnsbl test",
				       PSC_STATE_FLAG_DNSBL_PASS);
	    } else {
		msg_info("DNSBL rank %d for [%s]:%s",
			 state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
		PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
		switch (psc_dnsbl_action) {
		case PSC_ACT_DROP:
		    state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
						    PSC_DNSBL_FORMAT, "521",
						    state->smtp_client_addr,
							 state->dnsbl_name);
		    PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
		    return;
		case PSC_ACT_ENFORCE:
		    state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
						    PSC_DNSBL_FORMAT, "550",
						    state->smtp_client_addr,
							 state->dnsbl_name);
		    PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
		    break;
		case PSC_ACT_IGNORE:
		    PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
		    /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */
		    break;
		default:
		    msg_panic("%s: unknown dnsbl action value %d",
			      myname, psc_dnsbl_action);

		}
	    }
	}

	/*
	 * Pass the connection to a real SMTP server, or enter the dummy
	 * engine for deep tests.
	 */
	if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
	    || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
		!= PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
	    psc_smtpd_tests(state);
	else
	    psc_conclude(state);
	return;

	/*
	 * EOF, or the client spoke before its turn. We simply drop the
	 * connection, or we continue waiting and allow DNS replies to
	 * trickle in.
	 */
    default:
	if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
			  read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
	    /* Avoid memory leak. */
	    if (state->dnsbl_score == NO_DNSBL_SCORE
		&& (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
		(void) psc_dnsbl_retrieve(state->smtp_client_addr,
					  &state->dnsbl_name,
					  state->dnsbl_index);
	    /* XXX Wait for DNS replies to come in. */
	    psc_hangup_event(state);
	    return;
	}
	read_buf[read_count] = 0;
	escape(psc_escape_buf, read_buf, read_count);
	msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count,
	       psc_format_delta_time(psc_temp, state->start_time, &elapsed),
		 PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf));
	PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL);
	switch (psc_pregr_action) {
	case PSC_ACT_DROP:
	    /* Avoid memory leak. */
	    if (state->dnsbl_score == NO_DNSBL_SCORE
		&& (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
		(void) psc_dnsbl_retrieve(state->smtp_client_addr,
					  &state->dnsbl_name,
					  state->dnsbl_index);
	    PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
	    return;
	case PSC_ACT_ENFORCE:
	    /* We call psc_dnsbl_retrieve() when the timer expires. */
	    PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n");
	    break;
	case PSC_ACT_IGNORE:
	    /* We call psc_dnsbl_retrieve() when the timer expires. */
	    /* We must handle this case after the timer expires. */
	    break;
	default:
	    msg_panic("%s: unknown pregreet action value %d",
		      myname, psc_pregr_action);
	}

	/*
	 * Terminate the greet delay if we're just waiting for the pregreet
	 * test to complete. It is safe to call psc_early_event directly,
	 * since we are already in that function.
	 * 
	 * XXX After this code passes all tests, swap around the two blocks in
	 * this switch statement and fall through from EVENT_READ into
	 * EVENT_TIME, instead of calling psc_early_event recursively.
	 */
	state->flags |= PSC_STATE_FLAG_PREGR_DONE;
	if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT
	    || ((state->flags & PSC_STATE_MASK_EARLY_DONE)
		== PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)))
	    psc_early_event(EVENT_TIME, context);
	else
	    event_request_timer(psc_early_event, context,
				PSC_EFF_GREET_WAIT - elapsed.dt_sec);
	return;
    }
}