summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_send.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/postscreen/postscreen_send.c293
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;
+ }
+}