summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp/smtp-server-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-smtp/smtp-server-connection.c')
-rw-r--r--src/lib-smtp/smtp-server-connection.c1648
1 files changed, 1648 insertions, 0 deletions
diff --git a/src/lib-smtp/smtp-server-connection.c b/src/lib-smtp/smtp-server-connection.c
new file mode 100644
index 0000000..f301e82
--- /dev/null
+++ b/src/lib-smtp/smtp-server-connection.c
@@ -0,0 +1,1648 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "array.h"
+#include "str.h"
+#include "guid.h"
+#include "base64.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream.h"
+#include "connection.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "master-service.h"
+#include "master-service-ssl.h"
+
+#include "smtp-syntax.h"
+#include "smtp-reply-parser.h"
+#include "smtp-command-parser.h"
+#include "smtp-server-private.h"
+
+const char *const smtp_server_state_names[] = {
+ "GREETING",
+ "XCLIENT",
+ "HELO",
+ "STARTTLS",
+ "AUTH",
+ "READY",
+ "MAIL FROM",
+ "RCPT TO",
+ "DATA"
+};
+
+/*
+ * Connection
+ */
+
+static void smtp_server_connection_input(struct connection *_conn);
+static int smtp_server_connection_output(struct smtp_server_connection *conn);
+static void
+smtp_server_connection_disconnect(struct smtp_server_connection *conn,
+ const char *reason) ATTR_NULL(2);
+
+static void
+smtp_server_connection_update_stats(struct smtp_server_connection *conn)
+{
+ if (conn->conn.input != NULL)
+ conn->stats.input = conn->conn.input->v_offset;
+ if (conn->conn.output != NULL)
+ conn->stats.output = conn->conn.output->offset;
+}
+
+const struct smtp_server_stats *
+smtp_server_connection_get_stats(struct smtp_server_connection *conn)
+{
+ smtp_server_connection_update_stats(conn);
+ return &conn->stats;
+}
+
+static bool
+smtp_server_connection_check_pipeline(struct smtp_server_connection *conn)
+{
+ unsigned int pipeline = conn->command_queue_count;
+
+ if (conn->command_queue_tail != NULL) {
+ i_assert(pipeline > 0);
+ if (conn->command_queue_tail->state ==
+ SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY)
+ pipeline--;
+ }
+
+ if (pipeline >= conn->set.max_pipelined_commands) {
+ e_debug(conn->event, "Command pipeline is full "
+ "(pipelined commands %u > limit %u)",
+ pipeline, conn->set.max_pipelined_commands);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void smtp_server_connection_input_halt(struct smtp_server_connection *conn)
+{
+ connection_input_halt(&conn->conn);
+}
+
+void smtp_server_connection_input_resume(struct smtp_server_connection *conn)
+{
+ struct smtp_server_command *cmd;
+ bool cmd_locked = FALSE;
+
+ if (conn->conn.io == NULL) {
+ /* Only resume when we actually can */
+ if (conn->input_locked || conn->input_broken ||
+ conn->disconnected)
+ return;
+ if (!smtp_server_connection_check_pipeline(conn))
+ return;
+
+ /* Is queued command still blocking input? */
+ cmd = conn->command_queue_head;
+ while (cmd != NULL) {
+ if (cmd->input_locked || cmd->pipeline_blocked) {
+ cmd_locked = TRUE;
+ break;
+ }
+ cmd = cmd->next;
+ }
+ if (cmd_locked)
+ return;
+
+ /* Restore input handler */
+ connection_input_resume(&conn->conn);
+ }
+
+ if (conn->conn.io != NULL &&
+ i_stream_have_bytes_left(conn->conn.input)) {
+ io_set_pending(conn->conn.io);
+ }
+}
+
+void smtp_server_connection_input_lock(struct smtp_server_connection *conn)
+{
+ conn->input_locked = TRUE;
+ smtp_server_connection_input_halt(conn);
+}
+
+void smtp_server_connection_input_unlock(struct smtp_server_connection *conn)
+{
+ conn->input_locked = FALSE;
+ smtp_server_connection_input_resume(conn);
+}
+
+#undef smtp_server_connection_input_capture
+void smtp_server_connection_input_capture(struct smtp_server_connection *conn,
+ smtp_server_input_callback_t *callback, void *context)
+{
+ i_assert(!conn->input_broken && !conn->disconnected);
+ connection_input_halt(&conn->conn);
+ conn->conn.io = io_add_istream(conn->conn.input, *callback, context);
+}
+
+static void
+smtp_server_connection_update_rawlog(struct smtp_server_connection *conn)
+{
+ struct stat st;
+
+ if (conn->set.rawlog_dir == NULL)
+ return;
+
+ if (!conn->rawlog_checked) {
+ conn->rawlog_checked = TRUE;
+ if (stat(conn->set.rawlog_dir, &st) == 0)
+ conn->rawlog_enabled = TRUE;
+ }
+ if (conn->rawlog_enabled) {
+ iostream_rawlog_create(conn->set.rawlog_dir,
+ &conn->conn.input, &conn->conn.output);
+ }
+}
+
+static void
+smtp_server_connection_streams_changed(struct smtp_server_connection *conn)
+{
+ smtp_server_connection_update_rawlog(conn);
+ smtp_command_parser_set_stream(conn->smtp_parser, conn->conn.input);
+
+ o_stream_set_flush_callback(conn->conn.output,
+ smtp_server_connection_output, conn);
+ o_stream_set_flush_pending(conn->conn.output, TRUE);
+}
+
+void smtp_server_connection_set_streams(struct smtp_server_connection *conn,
+ struct istream *input,
+ struct ostream *output)
+{
+ struct istream *old_input = conn->conn.input;
+ struct ostream *old_output = conn->conn.output;
+
+ i_assert(conn->created_from_streams);
+
+ conn->conn.input = input;
+ i_stream_ref(conn->conn.input);
+
+ conn->conn.output = output;
+ o_stream_ref(conn->conn.output);
+ o_stream_set_no_error_handling(conn->conn.output, TRUE);
+
+ i_stream_unref(&old_input);
+ o_stream_unref(&old_output);
+
+ smtp_server_connection_streams_changed(conn);
+}
+
+void smtp_server_connection_set_ssl_streams(struct smtp_server_connection *conn,
+ struct istream *input,
+ struct ostream *output)
+{
+ conn->ssl_secured = TRUE;
+ conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS);
+
+ smtp_server_connection_set_streams(conn, input, output);
+}
+
+static void
+smtp_server_connection_idle_timeout(struct smtp_server_connection *conn)
+{
+ smtp_server_connection_terminate(
+ &conn, "4.4.2", "Disconnected for inactivity");
+}
+
+void smtp_server_connection_timeout_stop(struct smtp_server_connection *conn)
+{
+ if (conn->to_idle != NULL) {
+ e_debug(conn->event, "Timeout stop");
+
+ timeout_remove(&conn->to_idle);
+ }
+}
+
+void smtp_server_connection_timeout_start(struct smtp_server_connection *conn)
+{
+ if (conn->disconnected)
+ return;
+
+ if (conn->to_idle == NULL &&
+ conn->set.max_client_idle_time_msecs > 0) {
+ e_debug(conn->event, "Timeout start");
+
+ conn->to_idle = timeout_add(
+ conn->set.max_client_idle_time_msecs,
+ smtp_server_connection_idle_timeout, conn);
+ }
+}
+
+void smtp_server_connection_timeout_reset(struct smtp_server_connection *conn)
+{
+ if (conn->to_idle != NULL)
+ timeout_reset(conn->to_idle);
+}
+
+static void
+smtp_server_connection_timeout_update(struct smtp_server_connection *conn)
+{
+ struct smtp_server_command *cmd = conn->command_queue_head;
+
+ if (cmd == NULL) {
+ smtp_server_connection_timeout_start(conn);
+ return;
+ }
+
+ switch (cmd->state) {
+ case SMTP_SERVER_COMMAND_STATE_NEW:
+ smtp_server_connection_timeout_start(conn);
+ break;
+ case SMTP_SERVER_COMMAND_STATE_PROCESSING:
+ if (cmd->input_captured) {
+ /* Command updates timeout internally */
+ return;
+ }
+ smtp_server_connection_timeout_stop(conn);
+ break;
+ case SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY:
+ case SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY:
+ smtp_server_connection_timeout_stop(conn);
+ break;
+ case SMTP_SERVER_COMMAND_STATE_FINISHED:
+ case SMTP_SERVER_COMMAND_STATE_ABORTED:
+ i_unreached();
+ }
+}
+
+static void smtp_server_connection_ready(struct smtp_server_connection *conn)
+{
+ conn->raw_input = conn->conn.input;
+ conn->raw_output = conn->conn.output;
+
+ smtp_server_connection_update_rawlog(conn);
+
+ conn->smtp_parser = smtp_command_parser_init(conn->conn.input,
+ &conn->set.command_limits);
+ o_stream_set_flush_callback(conn->conn.output,
+ smtp_server_connection_output, conn);
+
+ o_stream_cork(conn->conn.output);
+ if (conn->set.no_greeting) {
+ /* Don't send greeting or login reply. */
+ } else if (conn->authenticated) {
+ /* RFC 4954, Section 4:
+ Should the client successfully complete the exchange, the
+ SMTP server issues a 235 reply. */
+ smtp_server_connection_send_line(
+ conn, "235 2.7.0 Logged in.");
+ } else {
+ smtp_server_connection_send_line(
+ conn, "220 %s %s", conn->set.hostname,
+ conn->set.login_greeting);
+ }
+ if (!conn->corked)
+ o_stream_uncork(conn->conn.output);
+}
+
+static void smtp_server_connection_destroy(struct connection *_conn)
+{
+ struct smtp_server_connection *conn =
+ (struct smtp_server_connection *)_conn;
+
+ smtp_server_connection_disconnect(conn, NULL);
+ smtp_server_connection_unref(&conn);
+}
+
+static bool
+smtp_server_connection_handle_command(struct smtp_server_connection *conn,
+ const char *cmd_name, const char *cmd_params)
+{
+ struct smtp_server_connection *tmp_conn = conn;
+ struct smtp_server_command *cmd;
+ bool finished;
+
+ cmd = smtp_server_command_new(tmp_conn, cmd_name);
+
+ smtp_server_command_ref(cmd);
+
+ smtp_server_connection_ref(tmp_conn);
+ smtp_server_command_execute(cmd, cmd_params);
+ if (!smtp_server_connection_unref(&tmp_conn)) {
+ /* The command start callback managed to get this connection
+ destroyed */
+ smtp_server_command_unref(&cmd);
+ return FALSE;
+ }
+
+ if (conn->command_queue_head == cmd)
+ (void)smtp_server_command_next_to_reply(&cmd);
+
+ smtp_server_connection_timeout_update(conn);
+
+ finished = !cmd->input_locked;
+ return (!smtp_server_command_unref(&cmd) || finished);
+}
+
+static int
+smtp_server_connection_init_ssl_ctx(struct smtp_server_connection *conn,
+ const char **error_r)
+{
+ struct smtp_server *server = conn->server;
+ const char *error;
+
+ if (conn->ssl_ctx != NULL || conn->set.ssl == NULL)
+ return 0;
+ if (conn->set.ssl == server->set.ssl) {
+ if (smtp_server_init_ssl_ctx(server, error_r) < 0)
+ return -1;
+ conn->ssl_ctx = server->ssl_ctx;
+ ssl_iostream_context_ref(conn->ssl_ctx);
+ return 0;
+ }
+
+ if (ssl_iostream_server_context_cache_get(conn->set.ssl, &conn->ssl_ctx,
+ &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't initialize SSL context: %s", error);
+ return -1;
+ }
+ return 0;
+}
+
+int smtp_server_connection_ssl_init(struct smtp_server_connection *conn)
+{
+ const char *error;
+ int ret;
+
+ if (smtp_server_connection_init_ssl_ctx(conn, &error) < 0) {
+ e_error(conn->event, "Couldn't initialize SSL: %s", error);
+ return -1;
+ }
+
+ e_debug(conn->event, "Starting SSL handshake");
+
+ if (conn->raw_input != conn->conn.input) {
+ /* Recreate rawlog after STARTTLS */
+ i_stream_ref(conn->raw_input);
+ o_stream_ref(conn->raw_output);
+ i_stream_destroy(&conn->conn.input);
+ o_stream_destroy(&conn->conn.output);
+ conn->conn.input = conn->raw_input;
+ conn->conn.output = conn->raw_output;
+ }
+
+ smtp_server_connection_input_halt(conn);
+ if (conn->ssl_ctx == NULL) {
+ ret = master_service_ssl_init(
+ master_service, &conn->conn.input, &conn->conn.output,
+ &conn->ssl_iostream, &error);
+ } else {
+ ret = io_stream_create_ssl_server(
+ conn->ssl_ctx, conn->set.ssl,
+ &conn->conn.input, &conn->conn.output,
+ &conn->ssl_iostream, &error);
+ }
+ if (ret < 0) {
+ e_error(conn->event,
+ "Couldn't initialize SSL server for %s: %s",
+ conn->conn.name, error);
+ return -1;
+ }
+ smtp_server_connection_input_resume(conn);
+
+ conn->ssl_secured = TRUE;
+ conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS);
+
+ if (conn->ssl_start)
+ smtp_server_connection_ready(conn);
+ else
+ smtp_server_connection_streams_changed(conn);
+ return 0;
+}
+
+static void
+smtp_server_connection_handle_input(struct smtp_server_connection *conn)
+{
+ struct smtp_server_command *pending_command;
+ enum smtp_command_parse_error error_code;
+ const char *cmd_name, *cmd_params, *error;
+ int ret;
+
+ /* Check whether we are continuing a command */
+ pending_command = NULL;
+ if (conn->command_queue_tail != NULL) {
+ pending_command =
+ ((conn->command_queue_tail->state ==
+ SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) ?
+ conn->command_queue_tail : NULL);
+ }
+
+ smtp_server_connection_timeout_reset(conn);
+
+ /* Parse commands */
+ ret = 1;
+ while (!conn->closing && !conn->input_locked && ret != 0) {
+ while ((ret = smtp_command_parse_next(
+ conn->smtp_parser, &cmd_name, &cmd_params,
+ &error_code, &error)) > 0) {
+
+ if (pending_command != NULL) {
+ /* Previous command is now fully read and ready
+ to reply */
+ smtp_server_command_ready_to_reply(pending_command);
+ pending_command = NULL;
+ }
+
+ e_debug(conn->event, "Received new command: %s %s",
+ cmd_name, cmd_params);
+
+ conn->stats.command_count++;
+
+ /* Handle command (cmd may be destroyed after this) */
+ if (!smtp_server_connection_handle_command(conn,
+ cmd_name, cmd_params))
+ return;
+
+ if (conn->disconnected)
+ return;
+ /* Last command locked the input; stop trying to read
+ more. */
+ if (conn->input_locked)
+ break;
+ /* Client indicated it will close after this command;
+ stop trying to read more. */
+ if (conn->closing)
+ break;
+
+ if (!smtp_server_connection_check_pipeline(conn)) {
+ smtp_server_connection_input_halt(conn);
+ return;
+ }
+
+ if (conn->command_queue_tail != NULL) {
+ pending_command =
+ ((conn->command_queue_tail->state ==
+ SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) ?
+ conn->command_queue_tail : NULL);
+ }
+ }
+
+ if (ret < 0 && conn->conn.input->eof) {
+ const char *error =
+ i_stream_get_disconnect_reason(conn->conn.input);
+ e_debug(conn->event, "Remote closed connection: %s",
+ error);
+
+ if (conn->command_queue_head == NULL ||
+ conn->command_queue_head->state <
+ SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY) {
+ /* No pending commands or unfinished
+ command; close */
+ smtp_server_connection_close(&conn, error);
+ } else {
+ /* A command is still processing;
+ only drop input io for now */
+ conn->input_broken = TRUE;
+ smtp_server_connection_input_halt(conn);
+ }
+ return;
+ }
+
+ if (ret < 0) {
+ struct smtp_server_command *cmd;
+
+ e_debug(conn->event,
+ "Client sent invalid command: %s", error);
+
+ switch (error_code) {
+ case SMTP_COMMAND_PARSE_ERROR_BROKEN_COMMAND:
+ conn->input_broken = TRUE;
+ /* fall through */
+ case SMTP_COMMAND_PARSE_ERROR_BAD_COMMAND:
+ cmd = smtp_server_command_new_invalid(conn);
+ smtp_server_command_fail(
+ cmd, 500, "5.5.2",
+ "Invalid command syntax");
+ break;
+ case SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG:
+ cmd = smtp_server_command_new_invalid(conn);
+ smtp_server_command_fail(
+ cmd, 500, "5.5.2", "Line too long");
+ break;
+ case SMTP_COMMAND_PARSE_ERROR_DATA_TOO_LARGE:
+ /* Command data size exceeds the absolute limit;
+ i.e. beyond which we don't even want to skip
+ data anymore. The command error is usually
+ already submitted by the application and sent
+ to the client. */
+ smtp_server_connection_close(&conn,
+ "Command data size exceeds absolute limit");
+ return;
+ case SMTP_COMMAND_PARSE_ERROR_BROKEN_STREAM:
+ smtp_server_connection_close(&conn, error);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ if (conn->disconnected)
+ return;
+ if (conn->input_broken || conn->closing) {
+ smtp_server_connection_input_halt(conn);
+ return;
+ }
+
+ if (ret == 0 && pending_command != NULL &&
+ !smtp_command_parser_pending_data(conn->smtp_parser)) {
+ /* Previous command is now fully read and ready to
+ reply */
+ smtp_server_command_ready_to_reply(pending_command);
+ }
+ }
+}
+
+static void smtp_server_connection_input(struct connection *_conn)
+{
+ struct smtp_server_connection *conn =
+ (struct smtp_server_connection *)_conn;
+
+ i_assert(!conn->input_broken);
+
+ if (conn->handling_input)
+ return;
+
+ smtp_server_connection_timeout_reset(conn);
+
+ if (conn->ssl_start && conn->ssl_iostream == NULL) {
+ if (smtp_server_connection_ssl_init(conn) < 0) {
+ smtp_server_connection_close(&conn,
+ "SSL Initialization failed");
+ return;
+ }
+ if (conn->halted) {
+ smtp_server_connection_input_lock(conn);
+ return;
+ }
+ }
+ i_assert(!conn->halted);
+
+
+ if (!smtp_server_connection_check_pipeline(conn)) {
+ smtp_server_connection_input_halt(conn);
+ return;
+ }
+
+ smtp_server_connection_ref(conn);
+ conn->handling_input = TRUE;
+ if (conn->callbacks != NULL &&
+ conn->callbacks->conn_cmd_input_pre != NULL)
+ conn->callbacks->conn_cmd_input_pre(conn->context);
+ smtp_server_connection_handle_input(conn);
+ if (conn->callbacks != NULL &&
+ conn->callbacks->conn_cmd_input_post != NULL)
+ conn->callbacks->conn_cmd_input_post(conn->context);
+ conn->handling_input = FALSE;
+ smtp_server_connection_unref(&conn);
+}
+
+bool smtp_server_connection_pending_command_data(
+ struct smtp_server_connection *conn)
+{
+ if (conn->smtp_parser == NULL)
+ return FALSE;
+ return smtp_command_parser_pending_data(conn->smtp_parser);
+}
+
+/*
+ * Command reply handling
+ */
+
+void smtp_server_connection_handle_output_error(
+ struct smtp_server_connection *conn)
+{
+ smtp_server_connection_close(&conn,
+ o_stream_get_disconnect_reason(conn->conn.output));
+}
+
+static bool
+smtp_server_connection_next_reply(struct smtp_server_connection *conn)
+{
+ struct smtp_server_command *cmd;
+
+ cmd = conn->command_queue_head;
+ if (cmd == NULL) {
+ /* No commands pending */
+ e_debug(conn->event, "No more commands pending");
+ return FALSE;
+ }
+
+ return smtp_server_command_send_replies(cmd);
+}
+
+void smtp_server_connection_cork(struct smtp_server_connection *conn)
+{
+ conn->corked = TRUE;
+ if (conn->conn.output != NULL)
+ o_stream_cork(conn->conn.output);
+}
+
+void smtp_server_connection_uncork(struct smtp_server_connection *conn)
+{
+ conn->corked = FALSE;
+ if (conn->conn.output != NULL) {
+ if (o_stream_uncork_flush(conn->conn.output) < 0) {
+ smtp_server_connection_handle_output_error(conn);
+ return;
+ }
+ smtp_server_connection_trigger_output(conn);
+ }
+}
+
+static void
+smtp_server_connection_send_replies(struct smtp_server_connection *conn)
+{
+ /* Send more replies until no more replies remain, the output
+ blocks again, or the connection is closed */
+ while (!conn->disconnected && smtp_server_connection_next_reply(conn));
+
+ smtp_server_connection_timeout_update(conn);
+
+ /* Accept more commands if possible */
+ smtp_server_connection_input_resume(conn);
+}
+
+int smtp_server_connection_flush(struct smtp_server_connection *conn)
+{
+ struct ostream *output = conn->conn.output;
+ int ret;
+
+ if ((ret = o_stream_flush(output)) <= 0) {
+ if (ret < 0)
+ smtp_server_connection_handle_output_error(conn);
+ return ret;
+ }
+ return 1;
+}
+
+static int smtp_server_connection_output(struct smtp_server_connection *conn)
+{
+ int ret;
+
+ e_debug(conn->event, "Sending replies");
+
+ smtp_server_connection_ref(conn);
+ o_stream_cork(conn->conn.output);
+ ret = smtp_server_connection_flush(conn);
+ if (ret > 0) {
+ smtp_server_connection_timeout_reset(conn);
+ smtp_server_connection_send_replies(conn);
+ }
+ if (ret >= 0 && !conn->corked && conn->conn.output != NULL)
+ ret = o_stream_uncork_flush(conn->conn.output);
+ if (conn->conn.output != NULL && conn->conn.output->closed) {
+ smtp_server_connection_handle_output_error(conn);
+ ret = -1;
+ }
+ smtp_server_connection_unref(&conn);
+ return ret;
+}
+
+void smtp_server_connection_trigger_output(struct smtp_server_connection *conn)
+{
+ if (conn->conn.output != NULL) {
+ e_debug(conn->event, "Trigger output");
+ o_stream_set_flush_pending(conn->conn.output, TRUE);
+ }
+}
+
+/*
+ *
+ */
+
+static struct connection_settings smtp_server_connection_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE,
+ .log_connection_id = TRUE,
+};
+
+static const struct connection_vfuncs smtp_server_connection_vfuncs = {
+ .destroy = smtp_server_connection_destroy,
+ .input = smtp_server_connection_input,
+};
+
+struct connection_list *smtp_server_connection_list_init(void)
+{
+ return connection_list_init(&smtp_server_connection_set,
+ &smtp_server_connection_vfuncs);
+}
+
+static struct event *
+smtp_server_connection_event_create(struct smtp_server *server,
+ const struct smtp_server_settings *set)
+{
+ struct event *conn_event;
+
+ if (set != NULL && set->event_parent != NULL) {
+ conn_event = event_create(set->event_parent);
+ smtp_server_event_init(server, conn_event);
+ } else
+ conn_event = event_create(server->event);
+ event_set_append_log_prefix(conn_event, t_strdup_printf(
+ "%s-server: ", smtp_protocol_name(server->set.protocol)));
+ event_set_forced_debug(conn_event, (set != NULL && set->debug));
+
+ return conn_event;
+}
+
+static void
+smtp_server_connection_update_event(struct smtp_server_connection *conn)
+{
+ event_add_str(conn->event, "connection_id", conn->session_id);
+ event_add_str(conn->event, "session", conn->session_id);
+}
+
+static void
+smtp_server_connection_init_session(struct smtp_server_connection *conn)
+{
+ guid_128_t guid;
+ string_t *session_id;
+
+ session_id = t_str_new(30);
+ guid_128_generate(guid);
+ base64_encode(guid, sizeof(guid), session_id);
+
+ /* drop trailing "==" */
+ i_assert(str_c(session_id)[str_len(session_id)-2] == '=');
+ str_truncate(session_id, str_len(session_id)-2);
+
+ conn->session_id = i_strdup(str_c(session_id));
+}
+
+static struct smtp_server_connection * ATTR_NULL(5, 6)
+smtp_server_connection_alloc(struct smtp_server *server,
+ const struct smtp_server_settings *set,
+ int fd_in, int fd_out,
+ const struct smtp_server_callbacks *callbacks,
+ void *context)
+{
+ struct smtp_server_connection *conn;
+ pool_t pool;
+
+ pool = pool_alloconly_create("smtp server", 1024);
+ conn = p_new(pool, struct smtp_server_connection, 1);
+ conn->pool = pool;
+ conn->refcount = 1;
+ conn->server = server;
+ conn->callbacks = callbacks;
+ conn->context = context;
+
+ /* Merge settings with global server settings */
+ conn->set = server->set;
+ if (set != NULL) {
+ conn->set.protocol = server->set.protocol;
+ if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0')
+ conn->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);
+
+ if (set->ssl != NULL)
+ conn->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);
+
+ if (set->hostname != NULL && *set->hostname != '\0')
+ conn->set.hostname = p_strdup(pool, set->hostname);
+ if (set->login_greeting != NULL &&
+ *set->login_greeting != '\0') {
+ conn->set.login_greeting =
+ p_strdup(pool, set->login_greeting);
+ }
+ if (set->capabilities != 0)
+ conn->set.capabilities = set->capabilities;
+ conn->set.workarounds |= set->workarounds;
+
+ if (set->max_client_idle_time_msecs > 0) {
+ conn->set.max_client_idle_time_msecs =
+ set->max_client_idle_time_msecs;
+ }
+ if (set->max_pipelined_commands > 0) {
+ conn->set.max_pipelined_commands =
+ set->max_pipelined_commands;
+ }
+ if (set->max_bad_commands > 0) {
+ conn->set.max_bad_commands = set->max_bad_commands;
+ }
+ if (set->max_recipients > 0)
+ conn->set.max_recipients = set->max_recipients;
+ smtp_command_limits_merge(&conn->set.command_limits,
+ &set->command_limits);
+
+ conn->set.max_message_size = set->max_message_size;
+ if (set->max_message_size == 0 ||
+ set->max_message_size == UOFF_T_MAX) {
+ conn->set.command_limits.max_data_size = UOFF_T_MAX;
+ } else if (conn->set.command_limits.max_data_size != 0) {
+ /* Explicit limit given */
+ } else if (set->max_message_size >
+ (UOFF_T_MAX - SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT)) {
+ /* Very high limit */
+ conn->set.command_limits.max_data_size = UOFF_T_MAX;
+ } else {
+ /* Absolute maximum before connection is closed in DATA
+ command */
+ conn->set.command_limits.max_data_size =
+ set->max_message_size +
+ SMTP_SERVER_DEFAULT_MAX_SIZE_EXCESS_LIMIT;
+ }
+
+ if (set->mail_param_extensions != NULL) {
+ conn->set.mail_param_extensions =
+ p_strarray_dup(pool, set->mail_param_extensions);
+ }
+ if (set->rcpt_param_extensions != NULL) {
+ conn->set.rcpt_param_extensions =
+ p_strarray_dup(pool, set->rcpt_param_extensions);
+ }
+ if (set->xclient_extensions != NULL) {
+ conn->set.xclient_extensions =
+ p_strarray_dup(pool, set->xclient_extensions);
+ }
+
+ if (set->socket_send_buffer_size > 0) {
+ conn->set.socket_send_buffer_size =
+ set->socket_send_buffer_size;
+ }
+ if (set->socket_recv_buffer_size > 0) {
+ conn->set.socket_recv_buffer_size =
+ set->socket_recv_buffer_size;
+ }
+
+ conn->set.tls_required =
+ conn->set.tls_required || set->tls_required;
+ conn->set.auth_optional =
+ conn->set.auth_optional || set->auth_optional;
+ conn->set.mail_path_allow_broken =
+ conn->set.mail_path_allow_broken ||
+ set->mail_path_allow_broken;
+ conn->set.rcpt_domain_optional =
+ conn->set.rcpt_domain_optional ||
+ set->rcpt_domain_optional;
+ conn->set.no_greeting =
+ conn->set.no_greeting || set->no_greeting;
+ conn->set.debug = conn->set.debug || set->debug;
+ }
+
+ if (set != NULL && set->mail_param_extensions != NULL) {
+ const char *const *extp;
+
+ p_array_init(&conn->mail_param_extensions, pool,
+ str_array_length(set->mail_param_extensions) + 8);
+ for (extp = set->mail_param_extensions; *extp != NULL; extp++) {
+ const char *ext = p_strdup(pool, *extp);
+ array_push_back(&conn->mail_param_extensions, &ext);
+ }
+ array_append_zero(&conn->mail_param_extensions);
+ }
+ if (set != NULL && set->rcpt_param_extensions != NULL) {
+ const char *const *extp;
+
+ p_array_init(&conn->rcpt_param_extensions, pool,
+ str_array_length(set->rcpt_param_extensions) + 8);
+ for (extp = set->rcpt_param_extensions; *extp != NULL; extp++) {
+ const char *ext = p_strdup(pool, *extp);
+ array_push_back(&conn->rcpt_param_extensions, &ext);
+ }
+ array_append_zero(&conn->rcpt_param_extensions);
+ }
+
+ net_set_nonblock(fd_in, TRUE);
+ if (fd_in != fd_out)
+ net_set_nonblock(fd_out, TRUE);
+ (void)net_set_tcp_nodelay(fd_out, TRUE);
+
+ set = &conn->set;
+ if (set->socket_send_buffer_size > 0 &&
+ net_set_send_buffer_size(fd_out,
+ set->socket_send_buffer_size) < 0) {
+ e_error(conn->event,
+ "net_set_send_buffer_size(%zu) failed: %m",
+ set->socket_send_buffer_size);
+ }
+ if (set->socket_recv_buffer_size > 0 &&
+ net_set_recv_buffer_size(fd_in,
+ set->socket_recv_buffer_size) < 0) {
+ e_error(conn->event,
+ "net_set_recv_buffer_size(%zu) failed: %m",
+ set->socket_recv_buffer_size);
+ }
+
+ smtp_server_connection_init_session(conn);
+
+ return conn;
+}
+
+struct smtp_server_connection *
+smtp_server_connection_create(
+ struct smtp_server *server, int fd_in, int fd_out,
+ const struct ip_addr *remote_ip, in_port_t remote_port,
+ bool ssl_start, const struct smtp_server_settings *set,
+ const struct smtp_server_callbacks *callbacks, void *context)
+{
+ struct smtp_server_connection *conn;
+ struct event *conn_event;
+
+ conn = smtp_server_connection_alloc(server, set, fd_in, fd_out,
+ callbacks, context);
+ conn_event = smtp_server_connection_event_create(server, set);
+ conn->conn.event_parent = conn_event;
+ connection_init_server_ip(server->conn_list, &conn->conn, NULL,
+ fd_in, fd_out, remote_ip, remote_port);
+ conn->event = conn->conn.event;
+ smtp_server_connection_update_event(conn);
+ event_unref(&conn_event);
+
+ conn->ssl_start = ssl_start;
+ if (ssl_start)
+ conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS);
+
+ /* Halt input until started */
+ smtp_server_connection_halt(conn);
+
+ e_debug(conn->event, "Connection created");
+
+ return conn;
+}
+
+struct smtp_server_connection *
+smtp_server_connection_create_from_streams(
+ struct smtp_server *server,
+ struct istream *input, struct ostream *output,
+ const struct ip_addr *remote_ip, in_port_t remote_port,
+ const struct smtp_server_settings *set,
+ const struct smtp_server_callbacks *callbacks, void *context)
+{
+ struct smtp_server_connection *conn;
+ struct event *conn_event;
+ int fd_in, fd_out;
+
+ fd_in = i_stream_get_fd(input);
+ fd_out = o_stream_get_fd(output);
+ i_assert(fd_in >= 0);
+ i_assert(fd_out >= 0);
+
+ conn = smtp_server_connection_alloc(server, set, fd_in, fd_out,
+ callbacks, context);
+ if (remote_ip != NULL && remote_ip->family != 0)
+ conn->conn.remote_ip = *remote_ip;
+ if (remote_port != 0)
+ conn->conn.remote_port = remote_port;
+ conn_event = smtp_server_connection_event_create(server, set);
+ conn->conn.event_parent = conn_event;
+ connection_init_from_streams(server->conn_list, &conn->conn, NULL,
+ input, output);
+ conn->created_from_streams = TRUE;
+ conn->event = conn->conn.event;
+ smtp_server_connection_update_event(conn);
+ event_unref(&conn_event);
+
+ /* Halt input until started */
+ smtp_server_connection_halt(conn);
+
+ e_debug(conn->event, "Connection created");
+
+ return conn;
+}
+
+void smtp_server_connection_ref(struct smtp_server_connection *conn)
+{
+ conn->refcount++;
+}
+
+static const char *
+smtp_server_connection_get_disconnect_reason(
+ struct smtp_server_connection *conn)
+{
+ const char *err;
+
+ if (conn->ssl_iostream != NULL &&
+ !ssl_iostream_is_handshaked(conn->ssl_iostream)) {
+ err = ssl_iostream_get_last_error(conn->ssl_iostream);
+ if (err != NULL) {
+ return t_strdup_printf(
+ "TLS handshaking failed: %s", err);
+ }
+ }
+
+ return io_stream_get_disconnect_reason(conn->conn.input,
+ conn->conn.output);
+}
+
+static void
+smtp_server_connection_disconnect(struct smtp_server_connection *conn,
+ const char *reason)
+{
+ struct smtp_server_command *cmd, *cmd_next;
+
+ if (conn->disconnected)
+ return;
+ conn->disconnected = TRUE;
+
+ if (reason == NULL)
+ reason = smtp_server_connection_get_disconnect_reason(conn);
+ else
+ reason = t_str_oneline(reason);
+
+ cmd = conn->command_queue_head;
+ if (cmd != NULL && cmd->reg != NULL) {
+ /* Unfinished command - include it in the reason string */
+ reason = t_strdup_printf("%s (unfinished %s command)",
+ reason, cmd->reg->name);
+ }
+ if (!conn->set.no_state_in_reason) {
+ reason = t_strdup_printf("%s (state=%s)", reason,
+ smtp_server_state_names[conn->state.state]);
+ }
+
+ e_debug(conn->event, "Disconnected: %s", reason);
+
+ /* Preserve statistics */
+ smtp_server_connection_update_stats(conn);
+
+ /* Drop transaction */
+ smtp_server_connection_reset_state(conn);
+
+ /* Clear command queue */
+ cmd = conn->command_queue_head;
+ while (cmd != NULL) {
+ cmd_next = cmd->next;
+ smtp_server_command_abort(&cmd);
+ cmd = cmd_next;
+ }
+
+ smtp_server_connection_timeout_stop(conn);
+ if (conn->conn.output != NULL)
+ o_stream_uncork(conn->conn.output);
+ if (conn->smtp_parser != NULL)
+ smtp_command_parser_deinit(&conn->smtp_parser);
+ ssl_iostream_destroy(&conn->ssl_iostream);
+ if (conn->ssl_ctx != NULL)
+ ssl_iostream_context_unref(&conn->ssl_ctx);
+
+ if (conn->callbacks != NULL &&
+ conn->callbacks->conn_disconnect != NULL) {
+ /* The callback may close the fd, so remove IO before that */
+ io_remove(&conn->conn.io);
+ conn->callbacks->conn_disconnect(conn->context, reason);
+ }
+
+ if (!conn->created_from_streams)
+ connection_disconnect(&conn->conn);
+ else {
+ conn->conn.fd_in = conn->conn.fd_out = -1;
+ io_remove(&conn->conn.io);
+ i_stream_unref(&conn->conn.input);
+ o_stream_unref(&conn->conn.output);
+ }
+}
+
+bool smtp_server_connection_unref(struct smtp_server_connection **_conn)
+{
+ struct smtp_server_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ i_assert(conn->refcount > 0);
+ if (--conn->refcount > 0)
+ return TRUE;
+
+ smtp_server_connection_disconnect(conn, NULL);
+
+ e_debug(conn->event, "Connection destroy");
+
+ if (conn->callbacks != NULL && conn->callbacks->conn_free != NULL)
+ conn->callbacks->conn_free(conn->context);
+
+ connection_deinit(&conn->conn);
+
+ i_free(conn->proxy_helo);
+ i_free(conn->helo_domain);
+ i_free(conn->username);
+ i_free(conn->session_id);
+ event_unref(&conn->next_trans_event);
+ pool_unref(&conn->pool);
+ return FALSE;
+}
+
+void smtp_server_connection_send_line(struct smtp_server_connection *conn,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+
+ T_BEGIN {
+ string_t *str;
+
+ str = t_str_new(256);
+ str_vprintfa(str, fmt, args);
+
+ e_debug(conn->event, "Sent: %s", str_c(str));
+
+ str_append(str, "\r\n");
+ o_stream_nsend(conn->conn.output, str_data(str), str_len(str));
+ } T_END;
+ va_end(args);
+}
+
+void smtp_server_connection_reply_lines(struct smtp_server_connection *conn,
+ unsigned int status,
+ const char *enh_code,
+ const char *const *text_lines)
+{
+ struct smtp_reply reply;
+
+ i_zero(&reply);
+ reply.status = status;
+ reply.text_lines = text_lines;
+
+ if (!smtp_reply_parse_enhanced_code(
+ enh_code, &reply.enhanced_code, NULL))
+ reply.enhanced_code = SMTP_REPLY_ENH_CODE(status / 100, 0, 0);
+
+ T_BEGIN {
+ string_t *str;
+
+ e_debug(conn->event, "Sent: %s", smtp_reply_log(&reply));
+
+ str = t_str_new(256);
+ smtp_reply_write(str, &reply);
+ o_stream_nsend(conn->conn.output, str_data(str), str_len(str));
+ } T_END;
+}
+
+void smtp_server_connection_reply_immediate(
+ struct smtp_server_connection *conn,
+ unsigned int status, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ T_BEGIN {
+ string_t *str;
+
+ str = t_str_new(256);
+ str_printfa(str, "%03u ", status);
+ str_vprintfa(str, fmt, args);
+
+ e_debug(conn->event, "Sent: %s", str_c(str));
+
+ str_append(str, "\r\n");
+ o_stream_nsend(conn->conn.output, str_data(str), str_len(str));
+ } T_END;
+ va_end(args);
+
+ /* Send immediately */
+ if (o_stream_is_corked(conn->conn.output)) {
+ o_stream_uncork(conn->conn.output);
+ o_stream_cork(conn->conn.output);
+ }
+}
+
+void smtp_server_connection_login(struct smtp_server_connection *conn,
+ const char *username, const char *helo,
+ const unsigned char *pdata,
+ unsigned int pdata_len, bool ssl_secured)
+{
+ i_assert(!conn->started);
+
+ conn->set.capabilities &= ENUM_NEGATE(SMTP_CAPABILITY_STARTTLS);
+ i_free(conn->username);
+ conn->username = i_strdup(username);
+ if (helo != NULL && *helo != '\0') {
+ i_free(conn->helo_domain);
+ conn->helo_domain = i_strdup(helo);
+ conn->helo.domain = conn->helo_domain;
+ conn->helo.domain_valid = TRUE;
+ }
+ conn->authenticated = TRUE;
+ conn->ssl_secured = ssl_secured;
+
+ if (pdata_len > 0) {
+ if (!i_stream_add_data(conn->conn.input, pdata, pdata_len))
+ i_panic("Couldn't add client input to stream");
+ }
+}
+
+void smtp_server_connection_start_pending(struct smtp_server_connection *conn)
+{
+ i_assert(!conn->started);
+ conn->started = TRUE;
+
+ conn->raw_input = conn->conn.input;
+ conn->raw_output = conn->conn.output;
+
+ if (!conn->ssl_start)
+ smtp_server_connection_ready(conn);
+ else if (conn->ssl_iostream == NULL)
+ smtp_server_connection_input_unlock(conn);
+}
+
+void smtp_server_connection_start(struct smtp_server_connection *conn)
+{
+ smtp_server_connection_start_pending(conn);
+ smtp_server_connection_resume(conn);
+}
+
+void smtp_server_connection_abort(struct smtp_server_connection **_conn,
+ unsigned int status, const char *enh_code,
+ const char *reason)
+{
+ struct smtp_server_connection *conn = *_conn;
+ const char **reason_lines;
+
+ if (conn == NULL)
+ return;
+ *_conn = NULL;
+
+ i_assert(!conn->started);
+ conn->started = TRUE;
+
+ if (conn->authenticated) {
+ reason_lines = t_strsplit_spaces(reason, "\r\n");
+ smtp_server_connection_reply_lines(
+ conn, status, enh_code, reason_lines);
+ smtp_server_connection_terminate(
+ &conn, "4.3.2", "Shutting down due to fatal error");
+ } else {
+ smtp_server_connection_terminate(&conn, enh_code, reason);
+ }
+}
+
+void smtp_server_connection_halt(struct smtp_server_connection *conn)
+{
+ conn->halted = TRUE;
+ smtp_server_connection_timeout_stop(conn);
+ if (!conn->started || !conn->ssl_start || conn->ssl_iostream != NULL)
+ smtp_server_connection_input_lock(conn);
+}
+
+void smtp_server_connection_resume(struct smtp_server_connection *conn)
+{
+ smtp_server_connection_input_unlock(conn);
+ smtp_server_connection_timeout_update(conn);
+ conn->halted = FALSE;
+}
+
+void smtp_server_connection_close(struct smtp_server_connection **_conn,
+ const char *reason)
+{
+ struct smtp_server_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (conn->closed)
+ return;
+ conn->closed = TRUE;
+
+ smtp_server_connection_disconnect(conn, reason);
+ smtp_server_connection_unref(&conn);
+}
+
+void smtp_server_connection_terminate(struct smtp_server_connection **_conn,
+ const char *enh_code, const char *reason)
+{
+ struct smtp_server_connection *conn = *_conn;
+ const char **reason_lines;
+
+ *_conn = NULL;
+
+ if (conn->closed)
+ return;
+
+ i_assert(enh_code[0] == '4' && enh_code[1] == '.');
+
+ T_BEGIN {
+ /* Add hostname prefix */
+ reason_lines = t_strsplit_spaces(reason, "\r\n");
+ reason_lines[0] = t_strconcat(conn->set.hostname, " ",
+ reason_lines[0], NULL);
+
+ smtp_server_connection_reply_lines(conn, 421, enh_code,
+ reason_lines);
+
+ smtp_server_connection_close(&conn, reason);
+ } T_END;
+}
+
+struct smtp_server_helo_data *
+smtp_server_connection_get_helo_data(struct smtp_server_connection *conn)
+{
+ return &conn->helo;
+}
+
+enum smtp_server_state
+smtp_server_connection_get_state(struct smtp_server_connection *conn,
+ const char **args_r)
+{
+ if (args_r != NULL)
+ *args_r = conn->state.args;
+ return conn->state.state;
+}
+
+void smtp_server_connection_set_state(struct smtp_server_connection *conn,
+ enum smtp_server_state state,
+ const char *args)
+{
+ bool changed = FALSE;
+
+ if (conn->state.state != state) {
+ conn->state.state = state;
+ changed = TRUE;
+ }
+ if (null_strcmp(args, conn->state.args) != 0) {
+ i_free(conn->state.args);
+ conn->state.args = i_strdup(args);
+ changed = TRUE;
+ }
+
+ if (changed && conn->callbacks != NULL &&
+ conn->callbacks->conn_state_changed != NULL)
+ conn->callbacks->conn_state_changed(conn->context, state, args);
+}
+
+const char *
+smtp_server_connection_get_security_string(struct smtp_server_connection *conn)
+{
+ if (conn->ssl_iostream == NULL)
+ return NULL;
+ return ssl_iostream_get_security_string(conn->ssl_iostream);
+}
+
+void smtp_server_connection_reset_state(struct smtp_server_connection *conn)
+{
+ e_debug(conn->event, "Connection state reset");
+
+ i_free(conn->state.args);
+
+ if (conn->state.trans != NULL)
+ smtp_server_transaction_free(&conn->state.trans);
+
+ /* RFC 3030, Section 2:
+ The RSET command, when issued after the first BDAT and before the
+ BDAT LAST, clears all segments sent during that transaction and resets
+ the session.
+ */
+ i_stream_destroy(&conn->state.data_input);
+ i_stream_destroy(&conn->state.data_chain_input);
+ conn->state.data_chain = NULL;
+
+ /* Reset state */
+ i_zero(&conn->state);
+ smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_READY, NULL);
+}
+
+void smtp_server_connection_clear(struct smtp_server_connection *conn)
+{
+ e_debug(conn->event, "Connection clear");
+
+ i_free(conn->helo_domain);
+ i_zero(&conn->helo);
+ smtp_server_connection_reset_state(conn);
+}
+
+void smtp_server_connection_set_capabilities(
+ struct smtp_server_connection *conn, enum smtp_capability capabilities)
+{
+ conn->set.capabilities = capabilities;
+}
+
+void smtp_server_connection_add_extra_capability(
+ struct smtp_server_connection *conn,
+ const struct smtp_capability_extra *cap)
+{
+ const struct smtp_capability_extra *cap_idx;
+ struct smtp_capability_extra cap_new;
+ unsigned int insert_idx;
+ pool_t pool = conn->pool;
+
+ /* Avoid committing protocol errors */
+ i_assert(smtp_ehlo_keyword_is_valid(cap->name));
+ i_assert(smtp_ehlo_params_are_valid(cap->params));
+
+ /* Cannot override standard capabiltiies */
+ i_assert(smtp_capability_find_by_name(cap->name)
+ == SMTP_CAPABILITY_NONE);
+
+ if (!array_is_created(&conn->extra_capabilities))
+ p_array_init(&conn->extra_capabilities, pool, 4);
+
+ /* Keep array sorted */
+ insert_idx = array_count(&conn->extra_capabilities);
+ array_foreach(&conn->extra_capabilities, cap_idx) {
+ int cmp = strcasecmp(cap_idx->name, cap->name);
+
+ /* Prohibit duplicates */
+ i_assert(cmp != 0);
+
+ if (cmp > 0) {
+ insert_idx = array_foreach_idx(
+ &conn->extra_capabilities, cap_idx);
+ break;
+ }
+ }
+
+ i_zero(&cap_new);
+ cap_new.name = p_strdup(pool, cap->name);
+ if (cap->params != NULL)
+ cap_new.params = p_strarray_dup(pool, cap->params);
+
+ array_insert(&conn->extra_capabilities, insert_idx, &cap_new, 1);
+}
+
+void *smtp_server_connection_get_context(struct smtp_server_connection *conn)
+{
+ return conn->context;
+}
+
+bool smtp_server_connection_is_ssl_secured(struct smtp_server_connection *conn)
+{
+ return conn->ssl_secured;
+}
+
+bool smtp_server_connection_is_trusted(struct smtp_server_connection *conn)
+{
+ if (conn->callbacks == NULL || conn->callbacks->conn_is_trusted == NULL)
+ return FALSE;
+ return conn->callbacks->conn_is_trusted(conn->context);
+}
+
+enum smtp_protocol
+smtp_server_connection_get_protocol(struct smtp_server_connection *conn)
+{
+ return conn->set.protocol;
+}
+
+const char *
+smtp_server_connection_get_protocol_name(struct smtp_server_connection *conn)
+{
+ string_t *pname = t_str_new(16);
+
+ switch (conn->set.protocol) {
+ case SMTP_PROTOCOL_SMTP:
+ if (conn->helo.old_smtp)
+ str_append(pname, "SMTP");
+ else
+ str_append(pname, "ESMTP");
+ break;
+ case SMTP_PROTOCOL_LMTP:
+ str_append(pname, "LMTP");
+ break;
+ default:
+ i_unreached();
+ }
+ if (conn->ssl_secured)
+ str_append_c(pname, 'S');
+ if (conn->authenticated)
+ str_append_c(pname, 'A');
+ return str_c(pname);
+}
+
+struct smtp_server_transaction *
+smtp_server_connection_get_transaction(struct smtp_server_connection *conn)
+{
+ return conn->state.trans;
+}
+
+const char *
+smtp_server_connection_get_transaction_id(struct smtp_server_connection *conn)
+{
+ if (conn->state.trans == NULL)
+ return NULL;
+ return conn->state.trans->id;
+}
+
+void smtp_server_connection_get_proxy_data(struct smtp_server_connection *conn,
+ struct smtp_proxy_data *proxy_data)
+{
+ i_zero(proxy_data);
+ proxy_data->source_ip = conn->conn.remote_ip;
+ proxy_data->source_port = conn->conn.remote_port;
+ if (conn->proxy_helo != NULL)
+ proxy_data->helo = conn->proxy_helo;
+ else if (conn->helo.domain_valid)
+ proxy_data->helo = conn->helo.domain;
+ proxy_data->login = conn->username;
+ proxy_data->session = conn->session_id;
+
+ if (conn->proxy_proto != SMTP_PROXY_PROTOCOL_UNKNOWN)
+ proxy_data->proto = conn->proxy_proto;
+ else if (conn->set.protocol == SMTP_PROTOCOL_LMTP)
+ proxy_data->proto = SMTP_PROXY_PROTOCOL_LMTP;
+ else if (conn->helo.old_smtp)
+ proxy_data->proto = SMTP_PROXY_PROTOCOL_SMTP;
+ else
+ proxy_data->proto = SMTP_PROXY_PROTOCOL_ESMTP;
+
+ proxy_data->ttl_plus_1 = conn->proxy_ttl_plus_1;
+ proxy_data->timeout_secs = conn->proxy_timeout_secs;
+}
+
+void smtp_server_connection_set_proxy_data(
+ struct smtp_server_connection *conn,
+ const struct smtp_proxy_data *proxy_data)
+{
+ if (proxy_data->source_ip.family != 0)
+ conn->conn.remote_ip = proxy_data->source_ip;
+ if (proxy_data->source_port != 0)
+ conn->conn.remote_port = proxy_data->source_port;
+ if (proxy_data->helo != NULL) {
+ i_free(conn->helo_domain);
+ conn->helo_domain = i_strdup(proxy_data->helo);
+ conn->helo.domain = conn->helo_domain;
+ conn->helo.domain_valid = TRUE;
+ if (conn->helo.domain_valid) {
+ i_free(conn->proxy_helo);
+ conn->proxy_helo = i_strdup(proxy_data->helo);
+ }
+ }
+ if (proxy_data->login != NULL) {
+ i_free(conn->username);
+ conn->username = i_strdup(proxy_data->login);
+ }
+ if (proxy_data->proto != SMTP_PROXY_PROTOCOL_UNKNOWN)
+ conn->proxy_proto = proxy_data->proto;
+ if (proxy_data->session != NULL &&
+ strcmp(proxy_data->session, conn->session_id) != 0) {
+ e_debug(conn->event, "Updated session ID from %s to %s",
+ conn->session_id, proxy_data->session);
+ i_free(conn->session_id);
+ conn->session_id = i_strdup(proxy_data->session);
+ }
+
+ if (proxy_data->ttl_plus_1 > 0)
+ conn->proxy_ttl_plus_1 = proxy_data->ttl_plus_1;
+ if (conn->proxy_timeout_secs > 0)
+ conn->proxy_timeout_secs = proxy_data->timeout_secs;
+
+ connection_update_properties(&conn->conn);
+ smtp_server_connection_update_event(conn);
+
+ if (conn->callbacks != NULL &&
+ conn->callbacks->conn_proxy_data_updated != NULL) {
+ struct smtp_proxy_data full_data;
+
+ smtp_server_connection_get_proxy_data(conn, &full_data);
+
+ conn->callbacks->
+ conn_proxy_data_updated(conn->context, &full_data);
+ }
+}
+
+void smtp_server_connection_register_mail_param(
+ struct smtp_server_connection *conn, const char *param)
+{
+ param = p_strdup(conn->pool, param);
+
+ if (!array_is_created(&conn->mail_param_extensions)) {
+ p_array_init(&conn->mail_param_extensions, conn->pool, 8);
+ array_push_back(&conn->mail_param_extensions, &param);
+ } else {
+ unsigned int count = array_count(&conn->mail_param_extensions);
+
+ i_assert(count > 0);
+ array_idx_set(&conn->mail_param_extensions,
+ count - 1, &param);
+ }
+ array_append_zero(&conn->mail_param_extensions);
+}
+
+void smtp_server_connection_register_rcpt_param(
+ struct smtp_server_connection *conn, const char *param)
+{
+ param = p_strdup(conn->pool, param);
+
+ if (!array_is_created(&conn->rcpt_param_extensions)) {
+ p_array_init(&conn->rcpt_param_extensions, conn->pool, 8);
+ array_push_back(&conn->rcpt_param_extensions, &param);
+ } else {
+ unsigned int count = array_count(&conn->rcpt_param_extensions);
+
+ i_assert(count > 0);
+ array_idx_set(&conn->rcpt_param_extensions,
+ count - 1, &param);
+ }
+ array_append_zero(&conn->rcpt_param_extensions);
+}
+
+void smtp_server_connection_switch_ioloop(struct smtp_server_connection *conn)
+{
+ if (conn->to_idle != NULL)
+ conn->to_idle = io_loop_move_timeout(&conn->to_idle);
+ connection_switch_ioloop(&conn->conn);
+}
+
+struct event_reason *
+smtp_server_connection_reason_begin(struct smtp_server_connection *conn,
+ const char *name)
+{
+ if (conn->set.reason_code_module == NULL)
+ return NULL;
+ const char *reason_code =
+ event_reason_code(conn->set.reason_code_module, name);
+ return event_reason_begin(reason_code);
+}