void psc_early_tests(PSC_STATE *state) { const char *myname = "psc_early_tests"; /* * Report errors and progress in the context of this test. */ PSC_BEGIN_TESTS(state, "tests before SMTP handshake"); /* * Run a PREGREET test. Send half the greeting banner, by way of teaser, * then wait briefly to see if the client speaks before its turn. */ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0 && psc_teaser_greeting != 0 && PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) { psc_hangup_event(state); return; } /* * Run a DNS blocklist query. */ if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0) state->dnsbl_index = psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event, (char *) state); else state->dnsbl_index = -1; state->dnsbl_score = NO_DNSBL_SCORE; /* * Wait for the client to respond or for DNS lookup to complete. */ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0) PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream), psc_early_event, (char *) state, PSC_EFF_GREET_WAIT); else event_request_timer(psc_early_event, (char *) state, PSC_EFF_GREET_WAIT); }
void psc_endpt_haproxy_lookup(VSTREAM *stream, PSC_ENDPT_LOOKUP_FN notify) { const char *myname = "psc_endpt_haproxy_lookup"; PSC_HAPROXY_STATE *state; /* * Prepare the per-session state. XXX To improve overload behavior, * maintain a pool of these so that we can reduce memory allocator * activity. */ state = (PSC_HAPROXY_STATE *) mymalloc(sizeof(*state)); state->stream = stream; state->notify = notify; state->buffer = vstring_alloc(100); /* * Read the haproxy line. */ PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_endpt_haproxy_event, (char *) state, var_psc_uproxy_tmout); }
int psc_dnsbl_request(const char *client_addr, void (*callback) (int, void *), void *context) { const char *myname = "psc_dnsbl_request"; int fd; VSTREAM *stream; HTABLE_INFO **ht; PSC_DNSBL_SCORE *score; HTABLE_INFO *hash_node; static int request_count; /* * Some spambots make several connections at nearly the same time, * causing their pregreet delays to overlap. Such connections can share * the efforts of DNSBL lookup. * * We store a reference-counted DNSBL score under its client IP address. We * increment the reference count with each score request, and decrement * the reference count with each score retrieval. * * Do not notify the requestor NOW when the DNS replies are already in. * Reason: we must not make a backwards call while we are still in the * middle of executing the corresponding forward call. Instead we create * a zero-delay timer request and call the notification function from * there. * * psc_dnsbl_request() could instead return a result value to indicate that * the DNSBL score is already available, but that would complicate the * caller with two different notification code paths: one asynchronous * code path via the callback invocation, and one synchronous code path * via the psc_dnsbl_request() result value. That would be a source of * future bugs. */ if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) { score = (PSC_DNSBL_SCORE *) hash_node->value; score->refcount += 1; PSC_CALL_BACK_EXTEND(hash_node, score); PSC_CALL_BACK_ENTER(score, callback, context); if (msg_verbose > 1) msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d", myname, client_addr, score->refcount, score->pending_lookups); if (score->pending_lookups == 0) event_request_timer(callback, context, EVENT_NULL_DELAY); return (PSC_CALL_BACK_INDEX_OF_LAST(score)); } if (msg_verbose > 1) msg_info("%s: create blocklist score for %s", myname, client_addr); score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score)); score->request_id = request_count++; score->dnsbl_name = 0; score->dnsbl_weight = 0; /* As with dnsblog(8), a value < 0 means no reply TTL. */ score->pass_ttl = -1; score->fail_ttl = -1; score->total = 0; score->refcount = 1; score->pending_lookups = 0; PSC_CALL_BACK_INIT(score); PSC_CALL_BACK_ENTER(score, callback, context); (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score); /* * Send a query to all DNSBL servers. Later, DNSBL lookup will be done * with an UDP-based DNS client that is built directly into Postfix code. * We therefore do not optimize the maximum out of this temporary * implementation. */ for (ht = dnsbl_site_list; *ht; ht++) { if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) { msg_warn("%s: connect to %s service: %m", myname, psc_dnsbl_service); continue; } stream = vstream_fdopen(fd, O_RDWR); vstream_control(stream, CA_VSTREAM_CTL_CONTEXT(ht[0]->key), CA_VSTREAM_CTL_END); attr_print(stream, ATTR_FLAG_NONE, SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key), SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr), SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id), ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("%s: error sending to %s service: %m", myname, psc_dnsbl_service); vstream_fclose(stream); continue; } PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, (void *) stream, var_psc_dnsbl_tmout); score->pending_lookups += 1; } return (PSC_CALL_BACK_INDEX_OF_LAST(score)); }
void psc_send_socket(PSC_STATE *state) { const char *myname = "psc_send_socket"; int server_fd; if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s", myname, psc_post_queue_length, psc_check_queue_length, vstream_fileno(state->smtp_client_stream), state->smtp_client_addr, state->smtp_client_port); /* * Connect to the real SMTP service over a local IPC channel, send the * file descriptor, and close the file descriptor to save resources. * Experience has shown that some systems will discard information when * we close a channel immediately after writing. Thus, we waste resources * waiting for the remote side to close the local IPC channel first. The * good side of waiting is that we learn when the real SMTP server is * falling behind. * * This is where we would forward the connection to an SMTP server that * provides an appropriate level of service for this client class. For * example, a server that is more forgiving, or one that is more * suspicious. Alternatively, we could send attributes along with the * socket with client reputation information, making everything even more * Postfix-specific. */ if ((server_fd = PASS_CONNECT(psc_smtpd_service_name, NON_BLOCKING, PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) { msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name); if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); } else { PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n"); psc_free_session_state(state); } return; } if (LOCAL_SEND_FD(server_fd, vstream_fileno(state->smtp_client_stream)) < 0) { msg_warn("cannot pass connection to service %s: %m", psc_smtpd_service_name); (void) close(server_fd); if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); } else { PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n"); psc_free_session_state(state); } return; } else { /* * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug * where smtp-source sometimes sees the connection being closed after * it has already received the real SMTP server's 220 greeting! */ #if 0 PSC_DEL_CLIENT_STATE(state); #endif PSC_ADD_SERVER_STATE(state, server_fd); PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event, (char *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT); return; } }