diff options
Diffstat (limited to 'src/submission/submission-client.c')
-rw-r--r-- | src/submission/submission-client.c | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/src/submission/submission-client.c b/src/submission/submission-client.c new file mode 100644 index 0000000..2ced163 --- /dev/null +++ b/src/submission/submission-client.c @@ -0,0 +1,555 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "submission-common.h" +#include "array.h" +#include "ioloop.h" +#include "base64.h" +#include "str.h" +#include "llist.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "hostpid.h" +#include "var-expand.h" +#include "settings-parser.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "mail-namespace.h" +#include "mail-storage.h" +#include "mail-storage-service.h" +#include "raw-storage.h" +#include "imap-urlauth.h" +#include "smtp-syntax.h" +#include "smtp-client-connection.h" + +#include "submission-backend-relay.h" +#include "submission-recipient.h" +#include "submission-commands.h" +#include "submission-settings.h" + +#include <unistd.h> + +/* max. length of input command line */ +#define MAX_INBUF_SIZE 4096 + +/* Stop reading input when output buffer has this many bytes. Once the buffer + size has dropped to half of it, start reading input again. */ +#define OUTBUF_THROTTLE_SIZE 4096 + +/* Disconnect client when it sends too many bad commands in a row */ +#define CLIENT_MAX_BAD_COMMANDS 20 + +/* Disconnect client after idling this many milliseconds */ +#define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) + +static const struct smtp_server_callbacks smtp_callbacks; +static const struct submission_client_vfuncs submission_client_vfuncs; + +struct submission_module_register submission_module_register = { 0 }; + +struct client *submission_clients; +unsigned int submission_client_count; + +static void client_input_pre(void *context) +{ + struct client *client = context; + + submission_backends_client_input_pre(client); +} +static void client_input_post(void *context) +{ + struct client *client = context; + + submission_backends_client_input_post(client); +} + +static void client_parse_backend_capabilities(struct client *client) +{ + const struct submission_settings *set = client->set; + const char *const *str; + + client->backend_capabilities = SMTP_CAPABILITY_NONE; + if (set->submission_backend_capabilities == NULL) + return; + + str = t_strsplit_spaces(set->submission_backend_capabilities, " ,"); + for (; *str != NULL; str++) { + enum smtp_capability cap = smtp_capability_find_by_name(*str); + + if (cap == SMTP_CAPABILITY_NONE) { + i_warning("Unknown SMTP capability in submission_backend_capabilities: " + "%s", *str); + continue; + } + + client->backend_capabilities |= cap; + } + + /* Make sure CHUNKING support is always enabled when BINARYMIME is + enabled by explicit configuration. */ + if (HAS_ALL_BITS(client->backend_capabilities, + SMTP_CAPABILITY_BINARYMIME)) { + client->backend_capabilities |= SMTP_CAPABILITY_CHUNKING; + } + + client->backend_capabilities_configured = TRUE; +} + +void client_apply_backend_capabilities(struct client *client) +{ + enum smtp_capability caps = client->backend_capabilities; + + /* propagate capabilities */ + caps |= SMTP_CAPABILITY_AUTH | SMTP_CAPABILITY_PIPELINING | + SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | + SMTP_CAPABILITY_CHUNKING | SMTP_CAPABILITY_BURL; + caps &= SUBMISSION_SUPPORTED_SMTP_CAPABILITIES; + smtp_server_connection_set_capabilities(client->conn, caps); +} + +void client_default_backend_started(struct client *client, + enum smtp_capability caps) +{ + /* propagate capabilities from backend to frontend */ + if (!client->backend_capabilities_configured) { + client->backend_capabilities = caps; + client_apply_backend_capabilities(client); + + /* resume the server now that we have the backend + capabilities */ + smtp_server_connection_resume(client->conn); + } +} + +static void +client_create_backend_default(struct client *client, + const struct submission_settings *set) +{ + struct submision_backend_relay_settings relay_set; + + i_zero(&relay_set); + relay_set.my_hostname = set->hostname; + relay_set.protocol = SMTP_PROTOCOL_SMTP; + relay_set.host = set->submission_relay_host; + relay_set.port = set->submission_relay_port; + relay_set.user = set->submission_relay_user; + relay_set.master_user = set->submission_relay_master_user; + relay_set.password = set->submission_relay_password; + relay_set.rawlog_dir = set->submission_relay_rawlog_dir; + relay_set.max_idle_time = set->submission_relay_max_idle_time; + relay_set.connect_timeout_msecs = set->submission_relay_connect_timeout; + relay_set.command_timeout_msecs = set->submission_relay_command_timeout; + relay_set.trusted = set->submission_relay_trusted; + + if (strcmp(set->submission_relay_ssl, "smtps") == 0) + relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE; + else if (strcmp(set->submission_relay_ssl, "starttls") == 0) + relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS; + else + relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_NONE; + relay_set.ssl_verify = set->submission_relay_ssl_verify; + + client->backend_default_relay = + submission_backend_relay_create(client, &relay_set); + client->backend_default = + submission_backend_relay_get(client->backend_default_relay); +} + +static void client_init_urlauth(struct client *client) +{ + static const char *access_apps[] = { "submit+", NULL }; + struct imap_urlauth_config config; + + i_zero(&config); + config.url_host = client->set->imap_urlauth_host; + config.url_port = client->set->imap_urlauth_port; + config.socket_path = t_strconcat(client->user->set->base_dir, + "/"IMAP_URLAUTH_SOCKET_NAME, NULL); + config.session_id = client->user->session_id; + config.access_anonymous = client->user->anonymous; + config.access_user = client->user->username; + config.access_service = "submission"; + config.access_applications = access_apps; + + client->urlauth_ctx = imap_urlauth_init(client->user, &config); +} + +struct client * +client_create(int fd_in, int fd_out, struct mail_user *user, + struct mail_storage_service_user *service_user, + const struct submission_settings *set, const char *helo, + const struct smtp_proxy_data *proxy_data, + const unsigned char *pdata, unsigned int pdata_len, + bool no_greeting) +{ + enum submission_client_workarounds workarounds = + set->parsed_workarounds; + const struct mail_storage_settings *mail_set; + struct smtp_server_settings smtp_set; + const char *ident; + struct client *client; + pool_t pool; + + /* always use nonblocking I/O */ + net_set_nonblock(fd_in, TRUE); + net_set_nonblock(fd_out, TRUE); + + pool = pool_alloconly_create("submission client", 2048); + client = p_new(pool, struct client, 1); + client->pool = pool; + client->v = submission_client_vfuncs; + client->user = user; + client->service_user = service_user; + client->set = set; + + i_array_init(&client->pending_backends, 4); + i_array_init(&client->rcpt_to, 8); + i_array_init(&client->rcpt_backends, 8); + + i_zero(&smtp_set); + smtp_set.hostname = set->hostname; + smtp_set.login_greeting = set->login_greeting; + smtp_set.max_recipients = set->submission_max_recipients; + smtp_set.max_client_idle_time_msecs = CLIENT_IDLE_TIMEOUT_MSECS; + smtp_set.max_message_size = set->submission_max_mail_size; + smtp_set.rawlog_dir = set->rawlog_dir; + smtp_set.no_greeting = no_greeting; + smtp_set.debug = user->mail_debug; + + if ((workarounds & SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { + smtp_set.workarounds |= + SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH; + } + if ((workarounds & SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH) != 0) { + smtp_set.workarounds |= + SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH; + } + + client_parse_backend_capabilities(client); + + p_array_init(&client->module_contexts, client->pool, 5); + + client->conn = smtp_server_connection_create(smtp_server, + fd_in, fd_out, user->conn.remote_ip, user->conn.remote_port, + FALSE, &smtp_set, &smtp_callbacks, client); + smtp_server_connection_set_proxy_data(client->conn, proxy_data); + smtp_server_connection_login(client->conn, client->user->username, helo, + pdata, pdata_len, user->conn.ssl_secured); + + client_create_backend_default(client, set); + + mail_set = mail_user_set_get_storage_set(user); + if (*set->imap_urlauth_host != '\0' && + *mail_set->mail_attribute_dict != '\0') { + /* Enable BURL capability only when urlauth dict is + configured correctly */ + client_init_urlauth(client); + } + + submission_client_count++; + DLLIST_PREPEND(&submission_clients, client); + + ident = mail_user_get_anvil_userip_ident(client->user); + if (ident != NULL) { + master_service_anvil_send( + master_service, t_strconcat( + "CONNECT\t", my_pid, "\tsubmission/", ident, + "\n", NULL)); + client->anvil_sent = TRUE; + } + + if (hook_client_created != NULL) + hook_client_created(&client); + + if (user->anonymous && !client->anonymous_allowed) { + smtp_server_connection_abort( + &client->conn, 534, "5.7.9", + "Anonymous login is not allowed for submission"); + } else if (client->backend_capabilities_configured) { + client_apply_backend_capabilities(client); + smtp_server_connection_start(client->conn); + } else { + submission_backend_start(client->backend_default); + smtp_server_connection_start_pending(client->conn); + } + + submission_refresh_proctitle(); + return client; +} + +static void client_state_reset(struct client *client) +{ + i_free(client->state.args); + i_stream_unref(&client->state.data_input); + pool_unref(&client->state.pool); + + i_zero(&client->state); +} + +void client_destroy(struct client **_client, const char *prefix, + const char *reason) +{ + struct client *client = *_client; + struct smtp_server_connection *conn = client->conn; + + *_client = NULL; + + smtp_server_connection_terminate( + &conn, (prefix == NULL ? "4.0.0" : prefix), reason); +} + +static void +client_default_destroy(struct client *client) +{ + i_assert(client->disconnected); + + if (client->destroyed) + return; + client->destroyed = TRUE; + + submission_backends_destroy_all(client); + array_free(&client->pending_backends); + array_free(&client->rcpt_to); + array_free(&client->rcpt_backends); + + submission_client_count--; + DLLIST_REMOVE(&submission_clients, client); + + if (client->anvil_sent) { + master_service_anvil_send( + master_service, t_strconcat( + "DISCONNECT\t", my_pid, "\tsubmission/", + mail_user_get_anvil_userip_ident(client->user), + "\n", NULL)); + } + + if (client->urlauth_ctx != NULL) + imap_urlauth_deinit(&client->urlauth_ctx); + + mail_user_deinit(&client->user); + mail_storage_service_user_unref(&client->service_user); + + client_state_reset(client); + + pool_unref(&client->pool); + + master_service_client_connection_destroyed(master_service); + submission_refresh_proctitle(); +} + +static void +client_connection_trans_start(void *context, + struct smtp_server_transaction *trans) +{ + struct client *client = context; + + client->state.pool = + pool_alloconly_create("submission client state", 1024); + + client->v.trans_start(client, trans); +} + +static void +client_default_trans_start(struct client *client, + struct smtp_server_transaction *trans) +{ + submission_backends_trans_start(client, trans); +} + +static void +client_connection_trans_free(void *context, + struct smtp_server_transaction *trans) +{ + struct client *client = context; + + client->v.trans_free(client, trans); +} + +static void +client_default_trans_free(struct client *client, + struct smtp_server_transaction *trans) +{ + array_clear(&client->rcpt_to); + + submission_backends_trans_free(client, trans); + client_state_reset(client); +} + +static void +client_connection_state_changed(void *context ATTR_UNUSED, + enum smtp_server_state new_state, + const char *new_args) +{ + struct client *client = context; + + i_free(client->state.args); + + client->state.state = new_state; + client->state.args = i_strdup(new_args); + + if (submission_client_count == 1) + submission_refresh_proctitle(); +} + +static const char *client_stats(struct client *client) +{ + const struct smtp_server_stats *stats = + smtp_server_connection_get_stats(client->conn); + const char *trans_id = + smtp_server_connection_get_transaction_id(client->conn); + const struct var_expand_table logout_tab[] = { + { 'i', dec2str(stats->input), "input" }, + { 'o', dec2str(stats->output), "output" }, + { '\0', dec2str(stats->command_count), "command_count" }, + { '\0', dec2str(stats->reply_count), "reply_count" }, + { '\0', trans_id, "transaction_id" }, + { '\0', NULL, NULL } + }; + const struct var_expand_table *user_tab = + mail_user_var_expand_table(client->user); + const struct var_expand_table *tab = + t_var_expand_merge_tables(logout_tab, user_tab); + string_t *str; + const char *error; + + str = t_str_new(128); + if (var_expand_with_funcs(str, client->set->submission_logout_format, + tab, mail_user_var_expand_func_table, + client->user, &error) < 0) { + i_error("Failed to expand submission_logout_format=%s: %s", + client->set->submission_logout_format, error); + } + return str_c(str); +} + +static void client_connection_disconnect(void *context, const char *reason) +{ + struct client *client = context; + const char *log_reason; + + if (client->disconnected) + return; + client->disconnected = TRUE; + + timeout_remove(&client->to_quit); + submission_backends_destroy_all(client); + + if (array_is_created(&client->rcpt_to)) + array_clear(&client->rcpt_to); + + if (reason == NULL) + log_reason = "Connection closed"; + else + log_reason = t_str_oneline(reason); + i_info("Disconnected: %s %s", log_reason, client_stats(client)); +} + +static void client_connection_free(void *context) +{ + struct client *client = context; + + client->v.destroy(client); +} + +uoff_t client_get_max_mail_size(struct client *client) +{ + struct submission_backend *backend; + uoff_t max_size, limit; + + /* Account for backend SIZE limits and calculate our own relative to + those. */ + max_size = client->set->submission_max_mail_size; + if (max_size == 0) + max_size = UOFF_T_MAX; + for (backend = client->backends; backend != NULL; + backend = backend->next) { + limit = submission_backend_get_max_mail_size(backend); + + if (limit <= SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE) + continue; + limit -= SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE; + if (limit < max_size) + max_size = limit; + } + + return max_size; +} + +void client_add_extra_capability(struct client *client, const char *capability, + const char *params) +{ + struct client_extra_capability cap; + + /* Don't add capabilties handled by lib-smtp here */ + i_assert(smtp_capability_find_by_name(capability) + == SMTP_CAPABILITY_NONE); + + /* Avoid committing protocol errors */ + i_assert(smtp_ehlo_keyword_is_valid(capability)); + i_assert(params == NULL || smtp_ehlo_params_str_is_valid(params)); + + i_zero(&cap); + cap.capability = p_strdup(client->pool, capability); + cap.params = p_strdup(client->pool, params); + + if (!array_is_created(&client->extra_capabilities)) + p_array_init(&client->extra_capabilities, client->pool, 5); + + array_push_back(&client->extra_capabilities, &cap); +} + +void clients_destroy_all(void) +{ + while (submission_clients != NULL) { + struct client *client = submission_clients; + + mail_storage_service_io_activate_user(client->service_user); + client_destroy(&client, "4.3.2", "Shutting down"); + } +} + +static const struct smtp_server_callbacks smtp_callbacks = { + .conn_cmd_helo = cmd_helo, + + .conn_cmd_mail = cmd_mail, + .conn_cmd_rcpt = cmd_rcpt, + .conn_cmd_rset = cmd_rset, + + .conn_cmd_data_begin = cmd_data_begin, + .conn_cmd_data_continue = cmd_data_continue, + + .conn_cmd_vrfy = cmd_vrfy, + + .conn_cmd_noop = cmd_noop, + .conn_cmd_quit = cmd_quit, + + .conn_cmd_input_pre = client_input_pre, + .conn_cmd_input_post = client_input_post, + + .conn_trans_start = client_connection_trans_start, + .conn_trans_free = client_connection_trans_free, + + .conn_state_changed = client_connection_state_changed, + + .conn_disconnect = client_connection_disconnect, + .conn_free = client_connection_free, +}; + +static const struct submission_client_vfuncs submission_client_vfuncs = { + client_default_destroy, + + .trans_start = client_default_trans_start, + .trans_free = client_default_trans_free, + + .cmd_helo = client_default_cmd_helo, + + .cmd_mail = client_default_cmd_mail, + .cmd_rcpt = client_default_cmd_rcpt, + .cmd_rset = client_default_cmd_rset, + .cmd_data = client_default_cmd_data, + + .cmd_vrfy = client_default_cmd_vrfy, + + .cmd_noop = client_default_cmd_noop, + .cmd_quit = client_default_cmd_quit, +}; |