summaryrefslogtreecommitdiffstats
path: root/src/lmtp/lmtp-proxy.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lmtp/lmtp-proxy.c860
1 files changed, 860 insertions, 0 deletions
diff --git a/src/lmtp/lmtp-proxy.c b/src/lmtp/lmtp-proxy.c
new file mode 100644
index 0000000..bf03c75
--- /dev/null
+++ b/src/lmtp/lmtp-proxy.c
@@ -0,0 +1,860 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lmtp-common.h"
+#include "istream.h"
+#include "istream-sized.h"
+#include "ostream.h"
+#include "iostream-ssl.h"
+#include "str.h"
+#include "strescape.h"
+#include "time-util.h"
+#include "smtp-common.h"
+#include "smtp-params.h"
+#include "smtp-address.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+#include "smtp-client-transaction.h"
+#include "auth-master.h"
+#include "master-service-ssl-settings.h"
+#include "mail-storage-service.h"
+#include "lda-settings.h"
+#include "lmtp-recipient.h"
+#include "lmtp-proxy.h"
+
+#define LMTP_MAX_REPLY_SIZE 4096
+#define LMTP_PROXY_DEFAULT_TIMEOUT_MSECS (1000*125)
+
+enum lmtp_proxy_ssl_flags {
+ /* Use SSL/TLS enabled */
+ PROXY_SSL_FLAG_YES = 0x01,
+ /* Don't do SSL handshake immediately after connected */
+ PROXY_SSL_FLAG_STARTTLS = 0x02,
+ /* Don't require that the received certificate is valid */
+ PROXY_SSL_FLAG_ANY_CERT = 0x04
+};
+
+struct lmtp_proxy_rcpt_settings {
+ enum smtp_protocol protocol;
+ const char *host;
+ struct ip_addr hostip, source_ip;
+ in_port_t port;
+ enum lmtp_proxy_ssl_flags ssl_flags;
+ unsigned int timeout_msecs;
+ struct smtp_params_rcpt params;
+
+ bool proxy_not_trusted:1;
+};
+
+struct lmtp_proxy_recipient {
+ struct lmtp_recipient *rcpt;
+ struct lmtp_proxy_connection *conn;
+
+ struct smtp_address *address;
+
+ const unsigned char *forward_fields;
+ size_t forward_fields_size;
+
+ bool rcpt_to_failed:1;
+ bool data_reply_received:1;
+};
+
+struct lmtp_proxy_connection {
+ struct lmtp_proxy *proxy;
+ struct lmtp_proxy_rcpt_settings set;
+ char *host;
+
+ struct smtp_client_connection *lmtp_conn;
+ struct smtp_client_transaction *lmtp_trans;
+ struct istream *data_input;
+ struct timeout *to;
+
+ bool finished:1;
+ bool failed:1;
+};
+
+struct lmtp_proxy {
+ struct client *client;
+
+ struct smtp_server_transaction *trans;
+
+ struct smtp_client *lmtp_client;
+
+ ARRAY(struct lmtp_proxy_connection *) connections;
+ ARRAY(struct lmtp_proxy_recipient *) rcpt_to;
+ unsigned int next_data_reply_idx;
+
+ struct timeout *to_finish;
+ struct istream *data_input;
+
+ unsigned int max_timeout_msecs;
+ unsigned int proxy_session_seq;
+
+ bool finished:1;
+};
+
+static void
+lmtp_proxy_data_cb(const struct smtp_reply *reply,
+ struct lmtp_proxy_recipient *lprcpt);
+
+/*
+ * LMTP proxy
+ */
+
+static struct lmtp_proxy *
+lmtp_proxy_init(struct client *client,
+ struct smtp_server_transaction *trans)
+{
+ const char *extra_capabilities[] = {
+ LMTP_RCPT_FORWARD_CAPABILITY,
+ NULL };
+ struct smtp_client_settings lmtp_set;
+ struct lmtp_proxy *proxy;
+
+ proxy = i_new(struct lmtp_proxy, 1);
+ proxy->client = client;
+ proxy->trans = trans;
+ i_array_init(&proxy->rcpt_to, 32);
+ i_array_init(&proxy->connections, 32);
+
+ i_zero(&lmtp_set);
+ lmtp_set.my_hostname = client->my_domain;
+ lmtp_set.extra_capabilities = extra_capabilities;
+ lmtp_set.dns_client_socket_path = dns_client_socket_path;
+ lmtp_set.max_reply_size = LMTP_MAX_REPLY_SIZE;
+ lmtp_set.rawlog_dir = client->lmtp_set->lmtp_proxy_rawlog_dir;
+
+ smtp_server_connection_get_proxy_data(client->conn,
+ &lmtp_set.proxy_data);
+ lmtp_set.proxy_data.source_ip = client->remote_ip;
+ lmtp_set.proxy_data.source_port = client->remote_port;
+ /* This initial session_id is used only locally by lib-smtp. Each LMTP
+ proxy connection gets a more specific updated session_id. */
+ lmtp_set.proxy_data.session = trans->id;
+ if (lmtp_set.proxy_data.ttl_plus_1 == 0)
+ lmtp_set.proxy_data.ttl_plus_1 = LMTP_PROXY_DEFAULT_TTL + 1;
+ else
+ lmtp_set.proxy_data.ttl_plus_1--;
+ lmtp_set.event_parent = client->event;
+
+ proxy->lmtp_client = smtp_client_init(&lmtp_set);
+
+ return proxy;
+}
+
+static void lmtp_proxy_connection_deinit(struct lmtp_proxy_connection *conn)
+{
+ if (conn->lmtp_trans != NULL)
+ smtp_client_transaction_destroy(&conn->lmtp_trans);
+ if (conn->lmtp_conn != NULL)
+ smtp_client_connection_close(&conn->lmtp_conn);
+ timeout_remove(&conn->to);
+ i_stream_unref(&conn->data_input);
+ i_free(conn->host);
+ i_free(conn);
+}
+
+void lmtp_proxy_deinit(struct lmtp_proxy **_proxy)
+{
+ struct lmtp_proxy *proxy = *_proxy;
+ struct lmtp_proxy_connection *conn;
+
+ *_proxy = NULL;
+
+ array_foreach_elem(&proxy->connections, conn)
+ lmtp_proxy_connection_deinit(conn);
+
+ smtp_client_deinit(&proxy->lmtp_client);
+ i_stream_unref(&proxy->data_input);
+ timeout_remove(&proxy->to_finish);
+ array_free(&proxy->rcpt_to);
+ array_free(&proxy->connections);
+ i_free(proxy);
+}
+
+static void
+lmtp_proxy_mail_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED,
+ struct lmtp_proxy_connection *conn ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+static void lmtp_proxy_connection_finish(struct lmtp_proxy_connection *conn)
+{
+ conn->finished = TRUE;
+ conn->lmtp_trans = NULL;
+}
+
+static void
+lmtp_proxy_connection_init_ssl(struct lmtp_proxy_connection *conn,
+ struct ssl_iostream_settings *ssl_set_r,
+ enum smtp_client_connection_ssl_mode *ssl_mode_r)
+{
+ const struct master_service_ssl_settings *master_ssl_set;
+
+ *ssl_mode_r = SMTP_CLIENT_SSL_MODE_NONE;
+
+ if ((conn->set.ssl_flags & PROXY_SSL_FLAG_YES) == 0) {
+ i_zero(ssl_set_r);
+ return;
+ }
+
+ master_ssl_set = master_service_ssl_settings_get(master_service);
+ master_service_ssl_client_settings_to_iostream_set(
+ master_ssl_set, pool_datastack_create(), ssl_set_r);
+ if ((conn->set.ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0)
+ ssl_set_r->allow_invalid_cert = TRUE;
+
+ if ((conn->set.ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0)
+ *ssl_mode_r = SMTP_CLIENT_SSL_MODE_IMMEDIATE;
+ else
+ *ssl_mode_r = SMTP_CLIENT_SSL_MODE_STARTTLS;
+}
+
+static bool
+lmtp_proxy_connection_has_rcpt_forward(struct lmtp_proxy_connection *conn)
+{
+ const struct smtp_capability_extra *cap_extra =
+ smtp_client_connection_get_extra_capability(
+ conn->lmtp_conn, LMTP_RCPT_FORWARD_CAPABILITY);
+
+ return (cap_extra != NULL);
+}
+
+static struct lmtp_proxy_connection *
+lmtp_proxy_get_connection(struct lmtp_proxy *proxy,
+ const struct lmtp_proxy_rcpt_settings *set)
+{
+ static const char *rcpt_param_extensions[] =
+ { LMTP_RCPT_FORWARD_PARAMETER, NULL };
+ static const struct smtp_client_capability_extra cap_rcpt_forward = {
+ .name = LMTP_RCPT_FORWARD_CAPABILITY,
+ .rcpt_param_extensions = rcpt_param_extensions,
+ };
+ struct smtp_client_settings lmtp_set;
+ struct smtp_server_transaction *trans = proxy->trans;
+ struct client *client = proxy->client;
+ struct lmtp_proxy_connection *conn;
+ enum smtp_client_connection_ssl_mode ssl_mode;
+ struct ssl_iostream_settings ssl_set;
+
+ i_assert(set->timeout_msecs > 0);
+
+ array_foreach_elem(&proxy->connections, conn) {
+ if (conn->set.protocol == set->protocol &&
+ conn->set.port == set->port &&
+ strcmp(conn->set.host, set->host) == 0 &&
+ net_ip_compare(&conn->set.hostip, &set->hostip) &&
+ net_ip_compare(&conn->set.source_ip, &set->source_ip) &&
+ conn->set.ssl_flags == set->ssl_flags)
+ return conn;
+ }
+
+ conn = i_new(struct lmtp_proxy_connection, 1);
+ conn->proxy = proxy;
+ conn->set.protocol = set->protocol;
+ conn->set.hostip = set->hostip;
+ conn->host = i_strdup(set->host);
+ conn->set.host = conn->host;
+ conn->set.source_ip = set->source_ip;
+ conn->set.port = set->port;
+ conn->set.ssl_flags = set->ssl_flags;
+ conn->set.timeout_msecs = set->timeout_msecs;
+ array_push_back(&proxy->connections, &conn);
+
+ lmtp_proxy_connection_init_ssl(conn, &ssl_set, &ssl_mode);
+
+ i_zero(&lmtp_set);
+ lmtp_set.my_ip = conn->set.source_ip;
+ lmtp_set.ssl = &ssl_set;
+ lmtp_set.peer_trusted = !conn->set.proxy_not_trusted;
+ lmtp_set.forced_capabilities = SMTP_CAPABILITY__ORCPT;
+ lmtp_set.mail_send_broken_path = TRUE;
+ lmtp_set.verbose_user_errors = client->lmtp_set->lmtp_verbose_replies;
+
+ if (conn->set.hostip.family != 0) {
+ conn->lmtp_conn = smtp_client_connection_create_ip(
+ proxy->lmtp_client, set->protocol,
+ &conn->set.hostip, conn->set.port,
+ conn->set.host, ssl_mode, &lmtp_set);
+ } else {
+ conn->lmtp_conn = smtp_client_connection_create(
+ proxy->lmtp_client, set->protocol,
+ conn->set.host, conn->set.port,
+ ssl_mode, &lmtp_set);
+ }
+ struct smtp_proxy_data proxy_data = {
+ .session = t_strdup_printf("%s:P%u", proxy->trans->id,
+ ++proxy->proxy_session_seq),
+ };
+ smtp_client_connection_update_proxy_data(conn->lmtp_conn, &proxy_data);
+ smtp_client_connection_accept_extra_capability(conn->lmtp_conn,
+ &cap_rcpt_forward);
+ smtp_client_connection_connect(conn->lmtp_conn, NULL, NULL);
+
+ conn->lmtp_trans = smtp_client_transaction_create(
+ conn->lmtp_conn, trans->mail_from, &trans->params, 0,
+ lmtp_proxy_connection_finish, conn);
+
+ smtp_client_transaction_start(conn->lmtp_trans,
+ lmtp_proxy_mail_cb, conn);
+
+ if (proxy->max_timeout_msecs < set->timeout_msecs)
+ proxy->max_timeout_msecs = set->timeout_msecs;
+ return conn;
+}
+
+static void
+lmtp_proxy_handle_connection_error(struct lmtp_proxy_recipient *lprcpt,
+ const struct smtp_reply *reply)
+{
+ struct lmtp_recipient *lrcpt = lprcpt->rcpt;
+ struct client *client = lrcpt->client;
+ struct smtp_server_recipient *rcpt = lrcpt->rcpt;
+ const char *detail = "";
+
+ if (client->lmtp_set->lmtp_verbose_replies) {
+ smtp_server_command_fail(rcpt->cmd->cmd, 451, "4.4.0",
+ "Proxy failed: %s (session=%s)",
+ smtp_reply_log(reply),
+ lrcpt->session_id);
+ return;
+ }
+
+ switch (reply->status) {
+ case SMTP_CLIENT_COMMAND_ERROR_ABORTED:
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED:
+ detail = "DNS lookup, ";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED:
+ case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED:
+ detail = "connect, ";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST:
+ case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED:
+ detail = "connection lost, ";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY:
+ detail = "bad reply, ";
+ break;
+ case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT:
+ detail = "timed out, ";
+ break;
+ default:
+ break;
+ }
+
+ smtp_server_command_fail(rcpt->cmd->cmd, 451, "4.4.0",
+ "Proxy failed (%ssession=%s)",
+ detail, lrcpt->session_id);
+}
+
+static bool
+lmtp_proxy_handle_reply(struct lmtp_proxy_recipient *lprcpt,
+ const struct smtp_reply *reply,
+ struct smtp_reply *reply_r)
+{
+ *reply_r = *reply;
+
+ if (!smtp_reply_is_remote(reply) ||
+ reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED) {
+ lmtp_proxy_handle_connection_error(lprcpt, reply);
+ return FALSE;
+ }
+
+ if (!smtp_reply_has_enhanced_code(reply)) {
+ reply_r->enhanced_code =
+ SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0);
+ }
+ return TRUE;
+}
+
+/*
+ * RCPT command
+ */
+
+static bool
+lmtp_proxy_rcpt_parse_fields(struct lmtp_recipient *lrcpt,
+ struct lmtp_proxy_rcpt_settings *set,
+ const char *const *args, const char **address)
+{
+ struct smtp_server_recipient *rcpt = lrcpt->rcpt;
+ const char *p, *key, *value;
+ bool proxying = FALSE, port_set = FALSE;
+
+ for (; *args != NULL; args++) {
+ p = strchr(*args, '=');
+ if (p == NULL) {
+ key = *args;
+ value = "";
+ } else {
+ key = t_strdup_until(*args, p);
+ value = p + 1;
+ }
+
+ if (strcmp(key, "proxy") == 0)
+ proxying = TRUE;
+ else if (strcmp(key, "host") == 0)
+ set->host = value;
+ else if (strcmp(key, "hostip") == 0) {
+ if (net_addr2ip(value, &set->hostip) < 0) {
+ e_error(rcpt->event,
+ "proxy: Invalid hostip %s", value);
+ return FALSE;
+ }
+ } else if (strcmp(key, "source_ip") == 0) {
+ if (net_addr2ip(value, &set->source_ip) < 0) {
+ e_error(rcpt->event,
+ "proxy: Invalid source_ip %s", value);
+ return FALSE;
+ }
+ } else if (strcmp(key, "port") == 0) {
+ if (net_str2port(value, &set->port) < 0) {
+ e_error(rcpt->event,
+ "proxy: Invalid port number %s", value);
+ return FALSE;
+ }
+ port_set = TRUE;
+ } else if (strcmp(key, "proxy_timeout") == 0) {
+ if (str_to_uint(value, &set->timeout_msecs) < 0) {
+ e_error(rcpt->event,"proxy: "
+ "Invalid proxy_timeout value %s", value);
+ return FALSE;
+ }
+ set->timeout_msecs *= 1000;
+ } else if (strcmp(key, "proxy_not_trusted") == 0) {
+ set->proxy_not_trusted = TRUE;
+ } else if (strcmp(key, "protocol") == 0) {
+ if (strcmp(value, "lmtp") == 0) {
+ set->protocol = SMTP_PROTOCOL_LMTP;
+ if (!port_set)
+ set->port = 24;
+ } else if (strcmp(value, "smtp") == 0) {
+ set->protocol = SMTP_PROTOCOL_SMTP;
+ if (!port_set)
+ set->port = 25;
+ } else {
+ e_error(rcpt->event,
+ "proxy: Unknown protocol %s", value);
+ return FALSE;
+ }
+ } else if (strcmp(key, "ssl") == 0) {
+ set->ssl_flags |= PROXY_SSL_FLAG_YES;
+ if (strcmp(value, "any-cert") == 0)
+ set->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ } else if (strcmp(key, "starttls") == 0) {
+ set->ssl_flags |= PROXY_SSL_FLAG_YES |
+ PROXY_SSL_FLAG_STARTTLS;
+ if (strcmp(value, "any-cert") == 0)
+ set->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT;
+ } else if (strcmp(key, "user") == 0 ||
+ strcmp(key, "destuser") == 0) {
+ /* Changing the username */
+ *address = value;
+ } else {
+ /* Just ignore it */
+ }
+ }
+ if (proxying && set->host == NULL) {
+ e_error(rcpt->event, "proxy: host not given");
+ return FALSE;
+ }
+ return proxying;
+}
+
+static bool
+lmtp_proxy_is_ourself(const struct client *client,
+ const struct lmtp_proxy_rcpt_settings *set)
+{
+ struct ip_addr ip;
+
+ if (set->port != client->local_port)
+ return FALSE;
+
+ if (set->hostip.family != 0)
+ ip = set->hostip;
+ else {
+ if (net_addr2ip(set->host, &ip) < 0)
+ return FALSE;
+ }
+ if (!net_ip_compare(&ip, &client->local_ip))
+ return FALSE;
+ return TRUE;
+}
+
+static void
+lmtp_proxy_rcpt_approved(struct smtp_server_recipient *rcpt ATTR_UNUSED,
+ struct lmtp_proxy_recipient *lprcpt)
+{
+ struct client *client = lprcpt->rcpt->client;
+
+ /* Add to proxy recipients */
+ array_push_back(&client->proxy->rcpt_to, &lprcpt);
+}
+
+static void
+lmtp_proxy_rcpt_cb(const struct smtp_reply *proxy_reply,
+ struct lmtp_proxy_recipient *lprcpt)
+{
+ struct smtp_server_recipient *rcpt = lprcpt->rcpt->rcpt;
+ struct smtp_reply reply;
+
+ if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply))
+ return;
+
+ if (smtp_reply_is_success(proxy_reply)) {
+ /* If backend accepts it, we accept it too */
+
+ /* The default 2.0.0 code won't do */
+ if (!smtp_reply_has_enhanced_code(proxy_reply))
+ reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0);
+ }
+
+ /* Forward reply */
+ smtp_server_recipient_reply_forward(rcpt, &reply);
+}
+
+static void
+lmtp_proxy_rcpt_login_cb(const struct smtp_reply *proxy_reply, void *context)
+{
+ struct lmtp_proxy_recipient *lprcpt = context;
+ struct lmtp_recipient *lrcpt = lprcpt->rcpt;
+ struct lmtp_proxy_connection *conn = lprcpt->conn;
+ struct smtp_server_recipient *rcpt = lrcpt->rcpt;
+ struct smtp_reply reply;
+ struct smtp_client_transaction_rcpt *relay_rcpt;
+ struct smtp_params_rcpt *rcpt_params = &rcpt->params;
+ bool add_orcpt_param = FALSE, add_xrcptforward_param = FALSE;
+ pool_t param_pool;
+
+ if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply))
+ return;
+ if (!smtp_reply_is_success(proxy_reply)) {
+ smtp_server_recipient_reply_forward(rcpt, &reply);
+ return;
+ }
+
+ /* Add an ORCPT parameter when passdb changed the username (and
+ therefore the RCPT address changed) and there is no ORCPT parameter
+ yet. */
+ if (!smtp_params_rcpt_has_orcpt(rcpt_params) &&
+ !smtp_address_equals(lprcpt->address, rcpt->path))
+ add_orcpt_param = TRUE;
+
+ /* Add forward fields parameter when passdb returned forward_* fields */
+ if (lprcpt->forward_fields != NULL &&
+ lmtp_proxy_connection_has_rcpt_forward(conn))
+ add_xrcptforward_param = TRUE;
+
+ /* Copy params when changes are pending */
+ param_pool = NULL;
+ if (add_orcpt_param || add_xrcptforward_param) {
+ param_pool = pool_datastack_create();
+ rcpt_params = p_new(param_pool, struct smtp_params_rcpt, 1);
+ smtp_params_rcpt_copy(param_pool, rcpt_params, &rcpt->params);
+ }
+
+ /* Add ORCPT */
+ if (add_orcpt_param) {
+ smtp_params_rcpt_set_orcpt(rcpt_params, param_pool,
+ rcpt->path);
+ }
+ /* Add forward fields parameter */
+ if (add_xrcptforward_param) {
+ smtp_params_rcpt_encode_extra(
+ rcpt_params, param_pool, LMTP_RCPT_FORWARD_PARAMETER,
+ lprcpt->forward_fields, lprcpt->forward_fields_size);
+ }
+
+ smtp_server_recipient_add_hook(
+ rcpt, SMTP_SERVER_RECIPIENT_HOOK_APPROVED,
+ lmtp_proxy_rcpt_approved, lprcpt);
+
+ relay_rcpt = smtp_client_transaction_add_pool_rcpt(
+ conn->lmtp_trans, rcpt->pool, lprcpt->address, rcpt_params,
+ lmtp_proxy_rcpt_cb, lprcpt);
+ smtp_client_transaction_rcpt_set_data_callback(
+ relay_rcpt, lmtp_proxy_data_cb, lprcpt);
+}
+
+int lmtp_proxy_rcpt(struct client *client,
+ struct smtp_server_cmd_ctx *cmd,
+ struct lmtp_recipient *lrcpt,
+ const char *username, const char *detail,
+ char delim)
+{
+ struct auth_master_connection *auth_conn;
+ struct lmtp_proxy_rcpt_settings set;
+ struct lmtp_proxy_connection *conn;
+ struct smtp_server_recipient *rcpt = lrcpt->rcpt;
+ struct lmtp_proxy_recipient *lprcpt;
+ struct smtp_server_transaction *trans;
+ struct smtp_address *address = rcpt->path;
+ struct auth_user_info info;
+ struct mail_storage_service_input input;
+ const char *const *fields, *errstr, *orig_username = username;
+ struct smtp_proxy_data proxy_data;
+ struct smtp_address *user;
+ string_t *fwfields;
+ pool_t auth_pool;
+ int ret;
+
+ trans = smtp_server_connection_get_transaction(cmd->conn);
+ i_assert(trans != NULL); /* MAIL command is synchronous */
+
+ i_zero(&input);
+ input.module = input.service = "lmtp";
+ mail_storage_service_init_settings(storage_service, &input);
+
+ i_zero(&info);
+ info.service = master_service_get_name(master_service);
+ info.local_ip = client->local_ip;
+ info.real_local_ip = client->real_local_ip;
+ info.remote_ip = client->remote_ip;
+ info.real_remote_ip = client->real_remote_ip;
+ info.local_port = client->local_port;
+ info.real_local_port = client->real_local_port;
+ info.remote_port = client->remote_port;
+ info.real_remote_port = client->real_remote_port;
+ info.forward_fields = lrcpt->forward_fields;
+
+ // FIXME: make this async
+ auth_pool = pool_alloconly_create("auth lookup", 1024);
+ auth_conn = mail_storage_service_get_auth_conn(storage_service);
+ ret = auth_master_pass_lookup(auth_conn, username, &info,
+ auth_pool, &fields);
+ if (ret <= 0) {
+ errstr = (ret < 0 && fields[0] != NULL ?
+ t_strdup(fields[0]) :
+ "Temporary user lookup failure");
+ pool_unref(&auth_pool);
+ if (ret < 0) {
+ smtp_server_recipient_reply(rcpt, 451, "4.3.0", "%s",
+ errstr);
+ return -1;
+ } else {
+ /* User not found from passdb: revert to local delivery.
+ */
+ return 0;
+ }
+ }
+
+ i_zero(&set);
+ set.port = client->local_port;
+ set.protocol = SMTP_PROTOCOL_LMTP;
+ set.timeout_msecs = LMTP_PROXY_DEFAULT_TIMEOUT_MSECS;
+
+ if (!lmtp_proxy_rcpt_parse_fields(lrcpt, &set, fields, &username)) {
+ /* Not proxying this user */
+ pool_unref(&auth_pool);
+ return 0;
+ }
+ if (strcmp(username, orig_username) != 0) {
+ /* The existing "user" event field is overridden with the new
+ user name, while old username is available as "orig_user" */
+ event_add_str(rcpt->event, "user", username);
+ event_add_str(rcpt->event, "original_user", orig_username);
+
+ if (smtp_address_parse_username(pool_datastack_create(),
+ username, &user, &errstr) < 0) {
+ e_error(rcpt->event, "%s: "
+ "Username `%s' returned by passdb lookup is not a valid SMTP address",
+ orig_username, username);
+ smtp_server_recipient_reply(
+ rcpt, 550, "5.3.5",
+ "Internal user lookup failure");
+ pool_unref(&auth_pool);
+ return -1;
+ }
+ /* Username changed. change the address as well */
+ if (*detail == '\0') {
+ address = user;
+ } else {
+ address = smtp_address_add_detail_temp(
+ user, detail, delim);
+ }
+ } else if (lmtp_proxy_is_ourself(client, &set)) {
+ e_error(rcpt->event, "Proxying to <%s> loops to itself",
+ username);
+ smtp_server_recipient_reply(rcpt, 554, "5.4.6",
+ "Proxying loops to itself");
+ pool_unref(&auth_pool);
+ return -1;
+ }
+
+ smtp_server_connection_get_proxy_data(cmd->conn, &proxy_data);
+ if (proxy_data.ttl_plus_1 == 1) {
+ e_error(rcpt->event,
+ "Proxying to <%s> appears to be looping (TTL=0)",
+ username);
+ smtp_server_recipient_reply(rcpt, 554, "5.4.6",
+ "Proxying appears to be looping "
+ "(TTL=0)");
+ pool_unref(&auth_pool);
+ return -1;
+ }
+
+ if (client->proxy == NULL)
+ client->proxy = lmtp_proxy_init(client, trans);
+
+ conn = lmtp_proxy_get_connection(client->proxy, &set);
+
+ lprcpt = p_new(rcpt->pool, struct lmtp_proxy_recipient, 1);
+ lprcpt->rcpt = lrcpt;
+ lprcpt->address = smtp_address_clone(rcpt->pool, address);
+ lprcpt->conn = conn;
+
+ lrcpt->type = LMTP_RECIPIENT_TYPE_PROXY;
+ lrcpt->backend_context = lprcpt;
+
+ /* Copy forward fields returned from passdb */
+ fwfields = NULL;
+ for (const char *const *ptr = fields; *ptr != NULL; ptr++) {
+ if (strncasecmp(*ptr, "forward_", 8) != 0)
+ continue;
+
+ if (fwfields == NULL)
+ fwfields = t_str_new(128);
+ else
+ str_append_c(fwfields, '\t');
+
+ str_append_tabescaped(fwfields, (*ptr) + 8);
+ }
+ if (fwfields != NULL) {
+ lprcpt->forward_fields = p_memdup(
+ rcpt->pool, str_data(fwfields), str_len(fwfields));
+ lprcpt->forward_fields_size = str_len(fwfields);
+ }
+
+ pool_unref(&auth_pool);
+
+ smtp_client_connection_connect(conn->lmtp_conn,
+ lmtp_proxy_rcpt_login_cb, lprcpt);
+ return 1;
+}
+
+/*
+ * DATA command
+ */
+
+static void
+lmtp_proxy_data_cb(const struct smtp_reply *proxy_reply,
+ struct lmtp_proxy_recipient *lprcpt)
+{
+ struct lmtp_proxy_connection *conn = lprcpt->conn;
+ struct lmtp_recipient *lrcpt = lprcpt->rcpt;
+ struct smtp_server_recipient *rcpt = lrcpt->rcpt;
+ struct lmtp_proxy *proxy = conn->proxy;
+ struct smtp_server_transaction *trans = proxy->trans;
+ struct smtp_address *address = lprcpt->address;
+ const struct smtp_client_transaction_times *times =
+ smtp_client_transaction_get_times(conn->lmtp_trans);
+ unsigned int rcpt_index = rcpt->index;
+ struct smtp_reply reply;
+ string_t *msg;
+
+ /* Compose log message */
+ msg = t_str_new(128);
+ str_printfa(msg, "<%s>: ", lrcpt->session_id);
+ if (smtp_reply_is_success(proxy_reply))
+ str_append(msg, "Sent message to");
+ else
+ str_append(msg, "Failed to send message to");
+ str_printfa(msg, " <%s> at %s:%u: %s (%u/%u at %u ms)",
+ smtp_address_encode(address),
+ conn->set.host, conn->set.port,
+ smtp_reply_log(proxy_reply),
+ rcpt_index + 1, array_count(&trans->rcpt_to),
+ timeval_diff_msecs(&ioloop_timeval, &times->started));
+
+ /* Handle reply */
+ if (smtp_reply_is_success(proxy_reply)) {
+ /* If backend accepts it, we accept it too */
+ e_info(rcpt->event, "%s", str_c(msg));
+
+ /* Substitute our own success message */
+ smtp_reply_printf(&reply, 250, "%s Saved", lrcpt->session_id);
+ /* Do let the enhanced code through */
+ if (!smtp_reply_has_enhanced_code(proxy_reply))
+ reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 0, 0);
+ else
+ reply.enhanced_code = proxy_reply->enhanced_code;
+
+ } else {
+ if (smtp_reply_is_remote(proxy_reply)) {
+ /* The problem isn't with the proxy, it's with the
+ remote side. so the remote side will log an error,
+ while for us this is just an info event */
+ e_info(rcpt->event, "%s", str_c(msg));
+ } else {
+ e_error(rcpt->event, "%s", str_c(msg));
+ }
+
+ if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply))
+ return;
+ }
+
+ /* Forward reply */
+ smtp_server_recipient_reply_forward(rcpt, &reply);
+}
+
+static void
+lmtp_proxy_data_dummy_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED,
+ struct lmtp_proxy_connection *conn ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+void lmtp_proxy_data(struct client *client,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input)
+{
+ struct lmtp_proxy *proxy = client->proxy;
+ struct lmtp_proxy_connection *conn;
+ uoff_t size;
+
+ i_assert(data_input->seekable);
+ i_assert(proxy->data_input == NULL);
+
+ client_update_data_state(client, "proxying");
+
+ proxy->data_input = data_input;
+ i_stream_ref(proxy->data_input);
+ if (i_stream_get_size(proxy->data_input, TRUE, &size) < 0) {
+ e_error(client->event,
+ "i_stream_get_size(data_input) failed: %s",
+ i_stream_get_error(proxy->data_input));
+ size = UOFF_T_MAX;
+ }
+
+ /* Create the data_input streams first */
+ array_foreach_elem(&proxy->connections, conn) {
+ if (conn->finished) {
+ /* This connection had already failed */
+ continue;
+ }
+
+ if (size == UOFF_T_MAX) {
+ conn->data_input =
+ i_stream_create_limit(data_input, UOFF_T_MAX);
+ } else {
+ conn->data_input =
+ i_stream_create_sized(data_input, size);
+ }
+ }
+ /* Now that all the streams are created, start reading them
+ (reading them earlier could have caused the data_input parent's
+ offset to change) */
+ array_foreach_elem(&proxy->connections, conn) {
+ if (conn->finished) {
+ /* This connection had already failed */
+ continue;
+ }
+
+ smtp_client_transaction_set_timeout(conn->lmtp_trans,
+ proxy->max_timeout_msecs);
+ smtp_client_transaction_send(conn->lmtp_trans, conn->data_input,
+ lmtp_proxy_data_dummy_cb, conn);
+ }
+}