diff options
Diffstat (limited to '')
-rw-r--r-- | src/postscreen/postscreen_send.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/src/postscreen/postscreen_send.c b/src/postscreen/postscreen_send.c new file mode 100644 index 0000000..53714b1 --- /dev/null +++ b/src/postscreen/postscreen_send.c @@ -0,0 +1,293 @@ +/*++ +/* NAME +/* postscreen_send 3 +/* SUMMARY +/* postscreen low-level output +/* SYNOPSIS +/* #include <postscreen.h> +/* +/* void pcs_send_pre_jail_init(void) +/* +/* int psc_send_reply(state, text) +/* PSC_STATE *state; +/* const char *text; +/* +/* int PSC_SEND_REPLY(state, text) +/* PSC_STATE *state; +/* const char *text; +/* +/* void psc_send_socket(state) +/* PSC_STATE *state; +/* DESCRIPTION +/* pcs_send_pre_jail_init() performs one-time initialization. +/* +/* psc_send_reply() sends the specified text to the specified +/* remote SMTP client. In case of an immediate error, it logs +/* a warning (except EPIPE) with the client address and port, +/* and returns a non-zero result (all errors including EPIPE). +/* +/* psc_send_reply() does a best effort to send the reply, but +/* it won't block when the output is throttled by a hostile +/* peer. +/* +/* PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply(). +/* It will eventually be replaced by its expansion. +/* +/* psc_send_socket() sends the specified socket to the real +/* Postfix SMTP server. The socket is delivered in the background. +/* This function must be called after all other session-related +/* work is finished including postscreen cache updates. +/* +/* In case of an immediate error, psc_send_socket() sends a 421 +/* reply to the remote SMTP client and closes the connection. +/* If the 220- greeting was sent, sending 421 would be invalid; +/* instead, the client is redirected to the dummy SMTP engine +/* which sends the 421 reply at the first legitimate opportunity. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> +#include <connect.h> +#include <attr.h> +#include <vstream.h> + +/* Global library. */ + +#include <mail_params.h> +#include <smtp_reply_footer.h> +#include <mail_proto.h> +#include <maps.h> + +/* Application-specific. */ + +#include <postscreen.h> + +static MAPS *psc_rej_ftr_maps; + + /* + * This program screens all inbound SMTP connections, so it better not waste + * time. + */ +#define PSC_SEND_SOCK_CONNECT_TIMEOUT 1 +#define PSC_SEND_SOCK_NOTIFY_TIMEOUT 100 + +/* pcs_send_pre_jail_init - initialize */ + +void pcs_send_pre_jail_init(void) +{ + static int init_count = 0; + + if (init_count++ != 0) + msg_panic("pcs_send_pre_jail_init: multiple calls"); + + /* + * SMTP server reject footer. + */ + if (*var_psc_rej_ftr_maps) + psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS, + var_psc_rej_ftr_maps, + DICT_FLAG_LOCK); +} + +/* psc_get_footer - find that footer */ + +static const char *psc_get_footer(const char *text, ssize_t text_len) +{ + static VSTRING *footer_buf = 0; + + if (footer_buf == 0) + footer_buf = vstring_alloc(100); + /* Strip the \r\n for consistency with smtpd. */ + vstring_strncpy(footer_buf, text, text_len); + return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0)); +} + +/* psc_send_reply - send reply to remote SMTP client */ + +int psc_send_reply(PSC_STATE *state, const char *text) +{ + ssize_t start; + int ret; + const char *footer; + ssize_t text_len = strlen(text) - 2; + + if (msg_verbose) + msg_info("> [%s]:%s: %.*s", state->smtp_client_addr, + state->smtp_client_port, (int) text_len, text); + + /* + * Append the new text to earlier text that could not be sent because the + * output was throttled. + */ + start = VSTRING_LEN(state->send_buf); + vstring_strcat(state->send_buf, text); + + /* + * For soft_bounce support, we also fix the REJECT logging before the + * dummy SMTP engine calls the psc_send_reply() output routine. We do + * some double work, but it is for debugging only. + */ + if (var_soft_bounce) { + if (text[0] == '5') + STR(state->send_buf)[start + 0] = '4'; + if (text[4] == '5') + STR(state->send_buf)[start + 4] = '4'; + } + + /* + * Append the optional reply footer. + */ + if ((*text == '4' || *text == '5') + && ((psc_rej_ftr_maps != 0 + && (footer = psc_get_footer(text, text_len)) != 0) + || *(footer = var_psc_rej_footer) != 0)) + smtp_reply_footer(state->send_buf, start, footer, + STR(psc_expand_filter), psc_expand_lookup, + (void *) state); + + /* + * Do a best effort sending text, but don't block when the output is + * throttled by a hostile peer. + */ + ret = write(vstream_fileno(state->smtp_client_stream), + STR(state->send_buf), LEN(state->send_buf)); + if (ret > 0) + vstring_truncate(state->send_buf, ret - LEN(state->send_buf)); + if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET) + msg_warn("write [%s]:%s: %m", state->smtp_client_addr, + state->smtp_client_port); + return (ret < 0 && errno != EAGAIN); +} + +/* psc_send_socket_close_event - file descriptor has arrived or timeout */ + +static void psc_send_socket_close_event(int event, void *context) +{ + const char *myname = "psc_send_socket_close_event"; + PSC_STATE *state = (PSC_STATE *) context; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s", + myname, psc_post_queue_length, psc_check_queue_length, + event, state->smtp_server_fd, state->smtp_client_addr, + state->smtp_client_port); + + /* + * The real SMTP server has closed the local IPC channel, or we have + * reached the limit of our patience. In the latter case it is still + * possible that the real SMTP server will receive the socket so we + * should not interfere. + */ + PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event, + context); + if (event == EVENT_TIME) + msg_warn("timeout sending connection to service %s", + psc_smtpd_service_name); + psc_free_session_state(state); +} + +/* psc_send_socket - send socket to real SMTP server process */ + +void psc_send_socket(PSC_STATE *state) +{ + const char *myname = "psc_send_socket"; + int server_fd; + int pass_err; + VSTREAM *fp; + + 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 = + LOCAL_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; + } + /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */ + fp = vstream_fdopen(server_fd, O_RDWR); + pass_err = + (LOCAL_SEND_FD(server_fd, + vstream_fileno(state->smtp_client_stream)) < 0 + || (attr_print(fp, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port), + ATTR_TYPE_END) || vstream_fflush(fp))); + /* XXX Note: no read between attr_print() and vstream_fdclose(). */ + (void) vstream_fdclose(fp); + if (pass_err != 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, + (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT); + return; + } +} |