summaryrefslogtreecommitdiffstats
path: root/src/login-common/client-common.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/login-common/client-common.c
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login-common/client-common.c')
-rw-r--r--src/login-common/client-common.c1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/login-common/client-common.c b/src/login-common/client-common.c
new file mode 100644
index 0000000..fc44d2b
--- /dev/null
+++ b/src/login-common/client-common.c
@@ -0,0 +1,1212 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "array.h"
+#include "hostpid.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream.h"
+#include "iostream-ssl.h"
+#include "iostream-proxy.h"
+#include "iostream-rawlog.h"
+#include "process-title.h"
+#include "hook-build.h"
+#include "buffer.h"
+#include "str.h"
+#include "strescape.h"
+#include "base64.h"
+#include "str-sanitize.h"
+#include "safe-memset.h"
+#include "time-util.h"
+#include "var-expand.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-service-ssl-settings.h"
+#include "master-auth.h"
+#include "anvil-client.h"
+#include "auth-client.h"
+#include "dsasl-client.h"
+#include "login-proxy.h"
+#include "client-common.h"
+
+struct client *clients = NULL;
+struct client *destroyed_clients = NULL;
+static struct client *last_client = NULL;
+static unsigned int clients_count = 0;
+
+static struct client *client_fd_proxies = NULL;
+static unsigned int client_fd_proxies_count = 0;
+
+struct login_client_module_hooks {
+ struct module *module;
+ const struct login_client_hooks *hooks;
+};
+
+static ARRAY(struct login_client_module_hooks) module_hooks = ARRAY_INIT;
+
+static const char *client_get_log_str(struct client *client, const char *msg);
+
+void login_client_hooks_add(struct module *module,
+ const struct login_client_hooks *hooks)
+{
+ struct login_client_module_hooks *hook;
+
+ hook = array_append_space(&module_hooks);
+ hook->module = module;
+ hook->hooks = hooks;
+}
+
+void login_client_hooks_remove(const struct login_client_hooks *hooks)
+{
+ const struct login_client_module_hooks *module_hook;
+ unsigned int idx = UINT_MAX;
+
+ array_foreach(&module_hooks, module_hook) {
+ if (module_hook->hooks == hooks) {
+ idx = array_foreach_idx(&module_hooks, module_hook);
+ break;
+ }
+ }
+ i_assert(idx != UINT_MAX);
+
+ array_delete(&module_hooks, idx, 1);
+}
+
+static void hook_login_client_allocated(struct client *client)
+{
+ const struct login_client_module_hooks *module_hook;
+ struct hook_build_context *ctx;
+
+ ctx = hook_build_init((void *)&client->v, sizeof(client->v));
+ client->vlast = &client->v;
+ array_foreach(&module_hooks, module_hook) {
+ if (module_hook->hooks->client_allocated != NULL) T_BEGIN {
+ module_hook->hooks->client_allocated(client);
+ hook_build_update(ctx, client->vlast);
+ } T_END;
+ }
+ client->vlast = NULL;
+ hook_build_deinit(&ctx);
+}
+
+static void client_idle_disconnect_timeout(struct client *client)
+{
+ const char *user_reason, *destroy_reason;
+ unsigned int secs;
+
+ if (client->master_tag != 0) {
+ secs = ioloop_time - client->auth_finished;
+ user_reason = "Timeout while finishing login.";
+ destroy_reason = t_strdup_printf(
+ "Timeout while finishing login (waited %u secs)", secs);
+ e_error(client->event, "%s", destroy_reason);
+ } else if (client->auth_request != NULL) {
+ user_reason =
+ "Disconnected for inactivity during authentication.";
+ destroy_reason = "Inactivity during authentication";
+ } else if (client->login_proxy != NULL) {
+ secs = ioloop_time - client->created.tv_sec;
+ user_reason = "Timeout while finishing login.";
+ destroy_reason = t_strdup_printf(
+ "Logging in timed out "
+ "(state=%s, duration=%us)",
+ client_proxy_get_state(client), secs);
+ e_error(login_proxy_get_event(client->login_proxy),
+ "%s", destroy_reason);
+ } else {
+ user_reason = "Disconnected for inactivity.";
+ destroy_reason = "Inactivity";
+ }
+ client_notify_disconnect(client, CLIENT_DISCONNECT_TIMEOUT, user_reason);
+ client_destroy(client, destroy_reason);
+}
+
+static void client_open_streams(struct client *client)
+{
+ client->input = i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE);
+ client->output = o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE);
+ o_stream_set_no_error_handling(client->output, TRUE);
+
+ if (login_rawlog_dir != NULL) {
+ if (iostream_rawlog_create(login_rawlog_dir, &client->input,
+ &client->output) < 0)
+ login_rawlog_dir = NULL;
+ }
+}
+
+static const char *
+client_log_msg_callback(struct client *client,
+ enum log_type log_type ATTR_UNUSED,
+ const char *message)
+{
+ return client_get_log_str(client, message);
+}
+
+static bool client_is_trusted(struct client *client)
+{
+ const char *const *net;
+ struct ip_addr net_ip;
+ unsigned int bits;
+
+ if (client->set->login_trusted_networks == NULL)
+ return FALSE;
+
+ net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
+ for (; *net != NULL; net++) {
+ if (net_parse_range(*net, &net_ip, &bits) < 0) {
+ e_error(client->event, "login_trusted_networks: "
+ "Invalid network '%s'", *net);
+ break;
+ }
+
+ if (net_is_in_network(&client->ip, &net_ip, bits))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+struct client *
+client_alloc(int fd, pool_t pool,
+ const struct master_service_connection *conn,
+ const struct login_settings *set,
+ const struct master_service_ssl_settings *ssl_set,
+ const struct master_service_ssl_server_settings *ssl_server_set)
+{
+ struct client *client;
+
+ i_assert(fd != -1);
+
+ client = login_binary->client_vfuncs->alloc(pool);
+ client->v = *login_binary->client_vfuncs;
+ if (client->v.auth_send_challenge == NULL)
+ client->v.auth_send_challenge = client_auth_send_challenge;
+ if (client->v.auth_parse_response == NULL)
+ client->v.auth_parse_response = client_auth_parse_response;
+
+ client->created = ioloop_timeval;
+ client->refcount = 1;
+
+ client->pool = pool;
+ client->preproxy_pool = pool_alloconly_create(MEMPOOL_GROWING"preproxy pool", 256);
+ client->set = set;
+ client->ssl_set = ssl_set;
+ client->ssl_server_set = ssl_server_set;
+ p_array_init(&client->module_contexts, client->pool, 5);
+
+ client->fd = fd;
+ client->local_ip = conn->local_ip;
+ client->local_port = conn->local_port;
+ client->ip = conn->remote_ip;
+ client->remote_port = conn->remote_port;
+ client->real_local_ip = conn->real_local_ip;
+ client->real_local_port = conn->real_local_port;
+ client->real_remote_ip = conn->real_remote_ip;
+ client->real_remote_port = conn->real_remote_port;
+ client->listener_name = p_strdup(client->pool, conn->name);
+ client->trusted = client_is_trusted(client);
+
+ if (conn->proxied) {
+ client->proxied_ssl = conn->proxy.ssl;
+ client->secured = conn->proxy.ssl || client->trusted;
+ client->ssl_secured = conn->proxy.ssl;
+ client->local_name = conn->proxy.hostname;
+ client->client_cert_common_name = conn->proxy.cert_common_name;
+ } else {
+ client->secured = client->trusted ||
+ net_ip_compare(&conn->real_remote_ip, &conn->real_local_ip);
+ }
+ client->proxy_ttl = LOGIN_PROXY_TTL;
+
+ client->event = event_create(NULL);
+ event_add_category(client->event, &login_binary->event_category);
+ event_add_str(client->event, "local_ip", net_ip2addr(&conn->local_ip));
+ event_add_int(client->event, "local_port", conn->local_port);
+ event_add_str(client->event, "remote_ip", net_ip2addr(&conn->remote_ip));
+ event_add_int(client->event, "remote_port", conn->remote_port);
+ event_add_str(client->event, "service", login_binary->protocol);
+ event_set_log_message_callback(client->event, client_log_msg_callback,
+ client);
+
+ client_open_streams(client);
+ return client;
+}
+
+void client_init(struct client *client, void **other_sets)
+{
+ if (last_client == NULL)
+ last_client = client;
+ client->list_type = CLIENT_LIST_TYPE_ACTIVE;
+ DLLIST_PREPEND(&clients, client);
+ clients_count++;
+
+ client->to_disconnect =
+ timeout_add(CLIENT_LOGIN_TIMEOUT_MSECS,
+ client_idle_disconnect_timeout, client);
+
+ hook_login_client_allocated(client);
+ client->v.create(client, other_sets);
+ client->create_finished = TRUE;
+
+ if (auth_client_is_connected(auth_client))
+ client_notify_auth_ready(client);
+ else
+ client_set_auth_waiting(client);
+
+ login_refresh_proctitle();
+}
+
+void client_disconnect(struct client *client, const char *reason,
+ bool add_disconnected_prefix)
+{
+ if (client->disconnected)
+ return;
+ client->disconnected = TRUE;
+
+ if (!client->login_success &&
+ !client->no_extra_disconnect_reason && reason != NULL) {
+ const char *extra_reason =
+ client_get_extra_disconnect_reason(client);
+ if (extra_reason[0] != '\0')
+ reason = t_strconcat(reason, " ", extra_reason, NULL);
+ }
+ if (reason != NULL) {
+ struct event *event = client->login_proxy == NULL ?
+ client->event :
+ login_proxy_get_event(client->login_proxy);
+ if (add_disconnected_prefix)
+ e_info(event, "Disconnected: %s", reason);
+ else
+ e_info(event, "%s", reason);
+ }
+
+ if (client->output != NULL)
+ o_stream_uncork(client->output);
+ if (!client->login_success) {
+ bool unref = FALSE;
+
+ io_remove(&client->io);
+ ssl_iostream_destroy(&client->ssl_iostream);
+ if (client->iostream_fd_proxy != NULL) {
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ unref = TRUE;
+ }
+ i_stream_close(client->input);
+ o_stream_close(client->output);
+ i_close_fd(&client->fd);
+ if (unref) {
+ i_assert(client->refcount > 1);
+ client_unref(&client);
+ }
+ } else {
+ /* Login was successful. We may now be proxying the connection,
+ so don't disconnect the client until client_unref(). */
+ if (client->iostream_fd_proxy != NULL) {
+ i_assert(!client->fd_proxying);
+ client->fd_proxying = TRUE;
+ i_assert(client->list_type == CLIENT_LIST_TYPE_DESTROYED);
+ DLLIST_REMOVE(&destroyed_clients, client);
+ client->list_type = CLIENT_LIST_TYPE_FD_PROXY;
+ DLLIST_PREPEND(&client_fd_proxies, client);
+ client_fd_proxies_count++;
+ }
+ }
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+ i_assert(client->create_finished);
+
+ if (client->destroyed)
+ return;
+ client->destroyed = TRUE;
+
+ if (last_client == client)
+ last_client = client->prev;
+ /* move to destroyed_clients linked list before it's potentially
+ added to client_fd_proxies. */
+ i_assert(!client->fd_proxying);
+ i_assert(client->list_type == CLIENT_LIST_TYPE_ACTIVE);
+ DLLIST_REMOVE(&clients, client);
+ client->list_type = CLIENT_LIST_TYPE_DESTROYED;
+ DLLIST_PREPEND(&destroyed_clients, client);
+
+ client_disconnect(client, reason, !client->login_success);
+
+ pool_unref(&client->preproxy_pool);
+ client->forward_fields = NULL;
+ client->client_id = NULL;
+
+ if (client->master_tag != 0) {
+ i_assert(client->auth_request == NULL);
+ i_assert(client->authenticating);
+ i_assert(client->refcount > 1);
+ client->authenticating = FALSE;
+ master_auth_request_abort(master_auth, client->master_tag);
+ client->refcount--;
+ } else if (client->auth_request != NULL ||
+ client->anvil_query != NULL ||
+ client->final_response) {
+ i_assert(client->authenticating);
+ sasl_server_auth_abort(client);
+ }
+ i_assert(!client->authenticating);
+ i_assert(client->auth_request == NULL);
+ i_assert(client->anvil_query == NULL);
+
+ timeout_remove(&client->to_disconnect);
+ timeout_remove(&client->to_auth_waiting);
+ str_free(&client->auth_response);
+
+ if (client->proxy_password != NULL) {
+ safe_memset(client->proxy_password, 0,
+ strlen(client->proxy_password));
+ i_free_and_null(client->proxy_password);
+ }
+
+ dsasl_client_free(&client->proxy_sasl_client);
+ if (client->login_proxy != NULL)
+ login_proxy_free(&client->login_proxy);
+ if (client->v.destroy != NULL)
+ client->v.destroy(client);
+ if (client_unref(&client) && initial_service_count == 1) {
+ /* as soon as this connection is done with proxying
+ (or whatever), the process will die. there's no need for
+ authentication anymore, so close the connection.
+ do this only with initial service_count=1, in case there
+ are other clients with pending authentications */
+ auth_client_disconnect(auth_client, "unnecessary connection");
+ }
+ login_client_destroyed();
+ login_refresh_proctitle();
+}
+
+void client_destroy_iostream_error(struct client *client)
+{
+ const char *reason =
+ io_stream_get_disconnect_reason(client->input, client->output);
+ client_destroy(client, reason);
+}
+
+void client_destroy_success(struct client *client, const char *reason)
+{
+ client->login_success = TRUE;
+ client_destroy(client, reason);
+}
+
+void client_ref(struct client *client)
+{
+ client->refcount++;
+}
+
+bool client_unref(struct client **_client)
+{
+ struct client *client = *_client;
+
+ *_client = NULL;
+
+ i_assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return TRUE;
+
+ if (!client->create_finished) {
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+ pool_unref(&client->preproxy_pool);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+ return FALSE;
+ }
+
+ i_assert(client->destroyed);
+ i_assert(client->login_proxy == NULL);
+
+ if (client->v.free != NULL)
+ client->v.free(client);
+
+ ssl_iostream_destroy(&client->ssl_iostream);
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ if (client->fd_proxying) {
+ i_assert(client->list_type == CLIENT_LIST_TYPE_FD_PROXY);
+ DLLIST_REMOVE(&client_fd_proxies, client);
+ i_assert(client_fd_proxies_count > 0);
+ client_fd_proxies_count--;
+ } else {
+ i_assert(client->list_type == CLIENT_LIST_TYPE_DESTROYED);
+ DLLIST_REMOVE(&destroyed_clients, client);
+ }
+ client->list_type = CLIENT_LIST_TYPE_NONE;
+ i_stream_unref(&client->input);
+ o_stream_unref(&client->output);
+ i_close_fd(&client->fd);
+ event_unref(&client->event);
+
+ i_free(client->proxy_user);
+ i_free(client->proxy_master_user);
+ i_free(client->virtual_user);
+ i_free(client->virtual_user_orig);
+ i_free(client->virtual_auth_user);
+ i_free(client->auth_mech_name);
+ i_free(client->master_data_prefix);
+ pool_unref(&client->pool);
+
+ i_assert(clients_count > 0);
+ clients_count--;
+
+ master_service_client_connection_destroyed(master_service);
+ login_refresh_proctitle();
+ return FALSE;
+}
+
+void client_common_default_free(struct client *client ATTR_UNUSED)
+{
+}
+
+bool client_destroy_oldest(bool kill, struct timeval *created_r)
+{
+ struct client *client;
+
+ if (last_client == NULL) {
+ /* we have no clients */
+ return FALSE;
+ }
+
+ /* destroy the last client that hasn't successfully authenticated yet.
+ this is usually the last client, but don't kill it if it's just
+ waiting for master to finish its job. Also prefer to kill clients
+ that can immediately be killed (i.e. refcount=1) */
+ for (client = last_client; client != NULL; client = client->prev) {
+ if (client->master_tag == 0 && client->refcount == 1)
+ break;
+ }
+ if (client == NULL)
+ client = last_client;
+
+ *created_r = client->created;
+ if (!kill)
+ return TRUE;
+
+ client_notify_disconnect(client, CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+ "Connection queue full");
+ client_ref(client);
+ client_destroy(client, "Connection queue full");
+ /* return TRUE only if the client was actually freed */
+ i_assert(client->create_finished);
+ return !client_unref(&client);
+}
+
+void clients_destroy_all_reason(const char *reason)
+{
+ struct client *client, *next;
+
+ for (client = clients; client != NULL; client = next) {
+ next = client->next;
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_SYSTEM_SHUTDOWN, reason);
+ client_destroy(client, reason);
+ }
+}
+
+void clients_destroy_all(void)
+{
+ clients_destroy_all_reason("Shutting down");
+}
+
+static int client_sni_callback(const char *name, const char **error_r,
+ void *context)
+{
+ struct client *client = context;
+ struct ssl_iostream_context *ssl_ctx;
+ struct ssl_iostream_settings ssl_set;
+ void **other_sets;
+ const char *error;
+
+ if (client->ssl_servername_settings_read)
+ return 0;
+ client->ssl_servername_settings_read = TRUE;
+
+ client->local_name = p_strdup(client->pool, name);
+ client->set = login_settings_read(client->pool, &client->local_ip,
+ &client->ip, name,
+ &client->ssl_set,
+ &client->ssl_server_set, &other_sets);
+
+ master_service_ssl_server_settings_to_iostream_set(client->ssl_set,
+ client->ssl_server_set, pool_datastack_create(), &ssl_set);
+ if (ssl_iostream_server_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to initialize SSL server context: %s", error);
+ return -1;
+ }
+ ssl_iostream_change_context(client->ssl_iostream, ssl_ctx);
+ ssl_iostream_context_unref(&ssl_ctx);
+ return 0;
+}
+
+int client_init_ssl(struct client *client)
+{
+ struct ssl_iostream_context *ssl_ctx;
+ struct ssl_iostream_settings ssl_set;
+ const char *error;
+
+ i_assert(client->fd != -1);
+
+ if (strcmp(client->ssl_set->ssl, "no") == 0) {
+ e_info(client->event, "SSL is disabled (ssl=no)");
+ return -1;
+ }
+
+ master_service_ssl_server_settings_to_iostream_set(client->ssl_set,
+ client->ssl_server_set, pool_datastack_create(), &ssl_set);
+ /* If the client cert is invalid, we'll reply NO to the login
+ command. */
+ ssl_set.allow_invalid_cert = TRUE;
+ if (ssl_iostream_server_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) {
+ e_error(client->event,
+ "Failed to initialize SSL server context: %s", error);
+ return -1;
+ }
+ if (io_stream_create_ssl_server(ssl_ctx, &ssl_set,
+ &client->input, &client->output,
+ &client->ssl_iostream, &error) < 0) {
+ e_error(client->event,
+ "Failed to initialize SSL connection: %s", error);
+ ssl_iostream_context_unref(&ssl_ctx);
+ return -1;
+ }
+ ssl_iostream_context_unref(&ssl_ctx);
+ ssl_iostream_set_sni_callback(client->ssl_iostream,
+ client_sni_callback, client);
+
+ client->tls = TRUE;
+ client->secured = TRUE;
+ client->ssl_secured = TRUE;
+
+ if (client->starttls) {
+ io_remove(&client->io);
+ if (!client_does_custom_io(client)) {
+ client->io = io_add_istream(client->input,
+ client_input, client);
+ }
+ }
+ return 0;
+}
+
+static void client_start_tls(struct client *client)
+{
+ client->starttls = TRUE;
+ if (client_init_ssl(client) < 0) {
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_INTERNAL_ERROR,
+ "TLS initialization failed.");
+ client_destroy(client, "TLS initialization failed.");
+ return;
+ }
+ login_refresh_proctitle();
+
+ client->v.starttls(client);
+}
+
+static int client_output_starttls(struct client *client)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(client->output)) < 0) {
+ client_destroy_iostream_error(client);
+ return 1;
+ }
+
+ if (ret > 0) {
+ o_stream_unset_flush_callback(client->output);
+ client_start_tls(client);
+ }
+ return 1;
+}
+
+void client_cmd_starttls(struct client *client)
+{
+ if (client->tls) {
+ client->v.notify_starttls(client, FALSE, "TLS is already active.");
+ return;
+ }
+
+ if (!client_is_tls_enabled(client)) {
+ client->v.notify_starttls(client, FALSE, "TLS support isn't enabled.");
+ return;
+ }
+
+ /* remove input handler, SSL proxy gives us a new fd. we also have to
+ remove it in case we have to wait for buffer to be flushed */
+ io_remove(&client->io);
+
+ client->v.notify_starttls(client, TRUE, "Begin TLS negotiation now.");
+
+ /* uncork the old fd */
+ o_stream_uncork(client->output);
+
+ if (o_stream_flush(client->output) <= 0) {
+ /* the buffer has to be flushed */
+ o_stream_set_flush_pending(client->output, TRUE);
+ o_stream_set_flush_callback(client->output,
+ client_output_starttls, client);
+ } else {
+ client_start_tls(client);
+ }
+}
+
+static void
+iostream_fd_proxy_finished(enum iostream_proxy_side side ATTR_UNUSED,
+ enum iostream_proxy_status status ATTR_UNUSED,
+ struct client *client)
+{
+ /* Destroy the proxy now. The other side of the proxy is still
+ unfinished and we don't want to get back here and unreference
+ the client twice. */
+ iostream_proxy_unref(&client->iostream_fd_proxy);
+ client_unref(&client);
+}
+
+int client_get_plaintext_fd(struct client *client, int *fd_r, bool *close_fd_r)
+{
+ int fds[2];
+
+ if (!client->tls) {
+ /* Plaintext connection - We can send the fd directly to
+ the post-login process without any proxying. */
+ *fd_r = client->fd;
+ *close_fd_r = FALSE;
+ return 0;
+ }
+
+ /* We'll have to start proxying from now on until either side
+ disconnects. Create a socketpair where login process is proxying on
+ one side and the other side is sent to the post-login process. */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ e_error(client->event, "socketpair() failed: %m");
+ return -1;
+ }
+ fd_set_nonblock(fds[0], TRUE);
+ fd_set_nonblock(fds[1], TRUE);
+
+ struct ostream *output = o_stream_create_fd(fds[0], IO_BLOCK_SIZE);
+ struct istream *input =
+ i_stream_create_fd_autoclose(&fds[0], IO_BLOCK_SIZE);
+ o_stream_set_no_error_handling(output, TRUE);
+
+ i_assert(client->io == NULL);
+
+ client_ref(client);
+ client->iostream_fd_proxy =
+ iostream_proxy_create(input, output,
+ client->input, client->output);
+ i_stream_unref(&input);
+ o_stream_unref(&output);
+
+ iostream_proxy_set_completion_callback(client->iostream_fd_proxy,
+ iostream_fd_proxy_finished,
+ client);
+ iostream_proxy_start(client->iostream_fd_proxy);
+
+ *fd_r = fds[1];
+ *close_fd_r = TRUE;
+ return 0;
+}
+
+unsigned int clients_get_count(void)
+{
+ return clients_count;
+}
+
+unsigned int clients_get_fd_proxies_count(void)
+{
+ return client_fd_proxies_count;
+}
+
+struct client *clients_get_first_fd_proxy(void)
+{
+ return client_fd_proxies;
+}
+
+void client_add_forward_field(struct client *client, const char *key,
+ const char *value)
+{
+ if (client->forward_fields == NULL)
+ client->forward_fields = str_new(client->preproxy_pool, 32);
+ else
+ str_append_c(client->forward_fields, '\t');
+ /* prefixing is done by auth process */
+ str_append_tabescaped(client->forward_fields, key);
+ str_append_c(client->forward_fields, '=');
+ str_append_tabescaped(client->forward_fields, value);
+}
+
+const char *client_get_session_id(struct client *client)
+{
+ buffer_t *buf, *base64_buf;
+ struct timeval tv;
+ uint64_t timestamp;
+ unsigned int i;
+
+ if (client->session_id != NULL)
+ return client->session_id;
+
+ buf = t_buffer_create(24);
+ base64_buf = t_buffer_create(24*2);
+
+ i_gettimeofday(&tv);
+ timestamp = tv.tv_usec + (long long)tv.tv_sec * 1000ULL*1000ULL;
+
+ /* add lowest 48 bits of the timestamp. this gives us a bit less than
+ 9 years until it wraps */
+ for (i = 0; i < 48; i += 8)
+ buffer_append_c(buf, (timestamp >> i) & 0xff);
+
+ buffer_append_c(buf, client->remote_port & 0xff);
+ buffer_append_c(buf, (client->remote_port >> 8) & 0xff);
+ if (IPADDR_IS_V6(&client->ip))
+ buffer_append(buf, &client->ip.u.ip6, sizeof(client->ip.u.ip6));
+ else
+ buffer_append(buf, &client->ip.u.ip4, sizeof(client->ip.u.ip4));
+ base64_encode(buf->data, buf->used, base64_buf);
+ client->session_id = p_strdup(client->pool, str_c(base64_buf));
+ return client->session_id;
+}
+
+/* increment index if new proper login variables are added
+ * make sure the aliases stay in the current order */
+#define VAR_EXPAND_ALIAS_INDEX_START 27
+
+static struct var_expand_table login_var_expand_empty_tab[] = {
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+
+ { 's', NULL, "service" },
+ { 'h', NULL, "home" },
+ { 'l', NULL, "lip" },
+ { 'r', NULL, "rip" },
+ { 'p', NULL, "pid" },
+ { 'm', NULL, "mech" },
+ { 'a', NULL, "lport" },
+ { 'b', NULL, "rport" },
+ { 'c', NULL, "secured" },
+ { 'k', NULL, "ssl_security" },
+ { 'e', NULL, "mail_pid" },
+ { '\0', NULL, "session" },
+ { '\0', NULL, "real_lip" },
+ { '\0', NULL, "real_rip" },
+ { '\0', NULL, "real_lport" },
+ { '\0', NULL, "real_rport" },
+ { '\0', NULL, "orig_user" },
+ { '\0', NULL, "orig_username" },
+ { '\0', NULL, "orig_domain" },
+ { '\0', NULL, "auth_user" },
+ { '\0', NULL, "auth_username" },
+ { '\0', NULL, "auth_domain" },
+ { '\0', NULL, "listener" },
+ { '\0', NULL, "local_name" },
+
+ /* aliases: */
+ { '\0', NULL, "local_ip" },
+ { '\0', NULL, "remote_ip" },
+ { '\0', NULL, "local_port" },
+ { '\0', NULL, "remote_port" },
+ { '\0', NULL, "real_local_ip" },
+ { '\0', NULL, "real_remote_ip" },
+ { '\0', NULL, "real_local_port" },
+ { '\0', NULL, "real_remote_port" },
+ { '\0', NULL, "mechanism" },
+ { '\0', NULL, "original_user" },
+ { '\0', NULL, "original_username" },
+ { '\0', NULL, "original_domain" },
+
+ { '\0', NULL, NULL }
+};
+
+static void
+get_var_expand_users(struct var_expand_table *tab, const char *user)
+{
+ unsigned int i;
+
+ tab[0].value = user;
+ tab[1].value = t_strcut(user, '@');
+ tab[2].value = i_strchr_to_next(user, '@');
+
+ for (i = 0; i < 3; i++)
+ tab[i].value = str_sanitize(tab[i].value, 80);
+}
+
+static const struct var_expand_table *
+get_var_expand_table(struct client *client)
+{
+ struct var_expand_table *tab;
+
+ tab = t_malloc_no0(sizeof(login_var_expand_empty_tab));
+ memcpy(tab, login_var_expand_empty_tab,
+ sizeof(login_var_expand_empty_tab));
+
+ if (client->virtual_user != NULL)
+ get_var_expand_users(tab, client->virtual_user);
+ tab[3].value = login_binary->protocol;
+ tab[4].value = getenv("HOME");
+ tab[VAR_EXPAND_ALIAS_INDEX_START].value = tab[5].value =
+ net_ip2addr(&client->local_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 1].value = tab[6].value =
+ net_ip2addr(&client->ip);
+ tab[7].value = my_pid;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 8].value = tab[8].value =
+ client->auth_mech_name == NULL ? NULL :
+ str_sanitize(client->auth_mech_name, MAX_MECH_NAME);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 2].value = tab[9].value =
+ dec2str(client->local_port);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 3].value = tab[10].value =
+ dec2str(client->remote_port);
+ if (!client->tls) {
+ tab[11].value = client->secured ? "secured" : NULL;
+ tab[12].value = "";
+ } else if (client->proxied_ssl) {
+ tab[11].value = "TLS";
+ tab[12].value = "(proxied)";
+ } else if (client->ssl_iostream != NULL) {
+ const char *ssl_state =
+ ssl_iostream_is_handshaked(client->ssl_iostream) ?
+ "TLS" : "TLS handshaking";
+ const char *ssl_error =
+ ssl_iostream_get_last_error(client->ssl_iostream);
+
+ tab[11].value = ssl_error == NULL ? ssl_state :
+ t_strdup_printf("%s: %s", ssl_state, ssl_error);
+ tab[12].value =
+ ssl_iostream_get_security_string(client->ssl_iostream);
+ } else {
+ tab[11].value = "TLS";
+ tab[12].value = "";
+ }
+ tab[13].value = client->mail_pid == 0 ? "" :
+ dec2str(client->mail_pid);
+ tab[14].value = client_get_session_id(client);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 4].value = tab[15].value =
+ net_ip2addr(&client->real_local_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 5].value = tab[16].value =
+ net_ip2addr(&client->real_remote_ip);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 6].value = tab[17].value =
+ dec2str(client->real_local_port);
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 7].value = tab[18].value =
+ dec2str(client->real_remote_port);
+ if (client->virtual_user_orig != NULL)
+ get_var_expand_users(tab+19, client->virtual_user_orig);
+ else {
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 9].value = tab[19].value = tab[0].value;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 10].value = tab[20].value = tab[1].value;
+ tab[VAR_EXPAND_ALIAS_INDEX_START + 11].value = tab[21].value = tab[2].value;
+ }
+ if (client->virtual_auth_user != NULL)
+ get_var_expand_users(tab+22, client->virtual_auth_user);
+ else {
+ tab[22].value = tab[19].value;
+ tab[23].value = tab[20].value;
+ tab[24].value = tab[21].value;
+ }
+ tab[25].value = client->listener_name;
+ tab[26].value = str_sanitize(client->local_name, 256);
+ return tab;
+}
+
+static bool have_username_key(const char *str)
+{
+ char key;
+
+ for (; *str != '\0'; str++) {
+ if (str[0] == '%' && str[1] != '\0') {
+ str++;
+ key = var_get_key(str);
+ if (key == 'u' || key == 'n')
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static int
+client_var_expand_func_passdb(const char *data, void *context,
+ const char **value_r,
+ const char **error_r ATTR_UNUSED)
+{
+ struct client *client = context;
+ const char *field_name = data;
+ unsigned int i;
+ size_t field_name_len;
+
+ *value_r = NULL;
+
+ if (client->auth_passdb_args == NULL)
+ return 1;
+
+ field_name_len = strlen(field_name);
+ for (i = 0; client->auth_passdb_args[i] != NULL; i++) {
+ if (strncmp(client->auth_passdb_args[i], field_name,
+ field_name_len) == 0 &&
+ client->auth_passdb_args[i][field_name_len] == '=') {
+ *value_r = client->auth_passdb_args[i] + field_name_len+1;
+ return 1;
+ }
+ }
+ return 1;
+}
+
+static const char *
+client_get_log_str(struct client *client, const char *msg)
+{
+ static const struct var_expand_func_table func_table[] = {
+ { "passdb", client_var_expand_func_passdb },
+ { NULL, NULL }
+ };
+ static bool expand_error_logged = FALSE;
+ const struct var_expand_table *var_expand_table;
+ char *const *e;
+ const char *error;
+ string_t *str, *str2;
+ unsigned int pos;
+
+ var_expand_table = get_var_expand_table(client);
+
+ str = t_str_new(256);
+ str2 = t_str_new(128);
+ for (e = client->set->log_format_elements_split; *e != NULL; e++) {
+ pos = str_len(str);
+ if (var_expand_with_funcs(str, *e, var_expand_table,
+ func_table, client, &error) <= 0 &&
+ !expand_error_logged) {
+ /* NOTE: Don't log via client->event - it would cause
+ recursion */
+ i_error("Failed to expand log_format_elements=%s: %s",
+ *e, error);
+ expand_error_logged = TRUE;
+ }
+ if (have_username_key(*e)) {
+ /* username is added even if it's empty */
+ } else {
+ str_truncate(str2, 0);
+ if (var_expand(str2, *e, login_var_expand_empty_tab,
+ &error) <= 0) {
+ /* we just logged this error above. no need
+ to do it again. */
+ }
+ if (strcmp(str_c(str)+pos, str_c(str2)) == 0) {
+ /* empty %variables, don't add */
+ str_truncate(str, pos);
+ continue;
+ }
+ }
+
+ if (str_len(str) > 0)
+ str_append(str, ", ");
+ }
+
+ if (str_len(str) > 0)
+ str_truncate(str, str_len(str)-2);
+
+ const struct var_expand_table tab[3] = {
+ { 's', t_strdup(str_c(str)), NULL },
+ { '$', msg, NULL },
+ { '\0', NULL, NULL }
+ };
+
+ str_truncate(str, 0);
+ if (var_expand(str, client->set->login_log_format, tab, &error) <= 0) {
+ /* NOTE: Don't log via client->event - it would cause
+ recursion */
+ i_error("Failed to expand login_log_format=%s: %s",
+ client->set->login_log_format, error);
+ expand_error_logged = TRUE;
+ }
+ return str_c(str);
+}
+
+bool client_is_tls_enabled(struct client *client)
+{
+ return login_ssl_initialized && strcmp(client->ssl_set->ssl, "no") != 0;
+}
+
+const char *client_get_extra_disconnect_reason(struct client *client)
+{
+ unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
+ ioloop_time - client->auth_first_started;
+
+ if (client->set->auth_ssl_require_client_cert &&
+ client->ssl_iostream != NULL) {
+ if (ssl_iostream_has_broken_client_cert(client->ssl_iostream))
+ return "(client sent an invalid cert)";
+ if (!ssl_iostream_has_valid_client_cert(client->ssl_iostream))
+ return "(client didn't send a cert)";
+ }
+
+ if (!client->notified_auth_ready)
+ return t_strdup_printf(
+ "(disconnected before auth was ready, waited %u secs)",
+ (unsigned int)(ioloop_time - client->created.tv_sec));
+
+ if (client->auth_attempts == 0) {
+ if (!client->banner_sent) {
+ /* disconnected by a plugin */
+ return "";
+ }
+ return t_strdup_printf("(no auth attempts in %u secs)",
+ (unsigned int)(ioloop_time - client->created.tv_sec));
+ }
+
+ /* some auth attempts without SSL/TLS */
+ if (client->set->auth_ssl_require_client_cert &&
+ client->ssl_iostream == NULL)
+ return "(cert required, client didn't start TLS)";
+
+ if (client->auth_waiting && client->auth_attempts == 1) {
+ return t_strdup_printf("(client didn't finish SASL auth, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->auth_request != NULL && client->auth_attempts == 1) {
+ return t_strdup_printf("(disconnected while authenticating, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->authenticating && client->auth_attempts == 1) {
+ return t_strdup_printf("(disconnected while finishing login, "
+ "waited %u secs)", auth_secs);
+ }
+ if (client->auth_try_aborted && client->auth_attempts == 1)
+ return "(aborted authentication)";
+ if (client->auth_process_comm_fail)
+ return "(auth process communication failure)";
+
+ if (client->proxy_auth_failed)
+ return "(proxy dest auth failed)";
+ if (client->auth_successes > 0) {
+ return t_strdup_printf("(internal failure, %u successful auths)",
+ client->auth_successes);
+ }
+
+ switch (client->last_auth_fail) {
+ case CLIENT_AUTH_FAIL_CODE_AUTHZFAILED:
+ return t_strdup_printf(
+ "(authorization failed, %u attempts in %u secs)",
+ client->auth_attempts, auth_secs);
+ case CLIENT_AUTH_FAIL_CODE_TEMPFAIL:
+ return "(auth service reported temporary failure)";
+ case CLIENT_AUTH_FAIL_CODE_USER_DISABLED:
+ return "(user disabled)";
+ case CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED:
+ return "(password expired)";
+ case CLIENT_AUTH_FAIL_CODE_INVALID_BASE64:
+ return "(sent invalid base64 in response)";
+ case CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED:
+ return "(login disabled)";
+ case CLIENT_AUTH_FAIL_CODE_MECH_INVALID:
+ return "(tried to use unsupported auth mechanism)";
+ case CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED:
+ return "(tried to use disallowed plaintext auth)";
+ default:
+ break;
+ }
+
+ return t_strdup_printf("(auth failed, %u attempts in %u secs)",
+ client->auth_attempts, auth_secs);
+}
+
+void client_notify_disconnect(struct client *client,
+ enum client_disconnect_reason reason,
+ const char *text)
+{
+ if (!client->notified_disconnect) {
+ if (client->v.notify_disconnect != NULL)
+ client->v.notify_disconnect(client, reason, text);
+ client->notified_disconnect = TRUE;
+ }
+}
+
+void client_notify_auth_ready(struct client *client)
+{
+ if (!client->notified_auth_ready) {
+ if (client->v.notify_auth_ready != NULL)
+ client->v.notify_auth_ready(client);
+ client->notified_auth_ready = TRUE;
+ }
+}
+
+void client_notify_status(struct client *client, bool bad, const char *text)
+{
+ if (client->v.notify_status != NULL)
+ client->v.notify_status(client, bad, text);
+}
+
+void client_common_send_raw_data(struct client *client,
+ const void *data, size_t size)
+{
+ ssize_t ret;
+
+ ret = o_stream_send(client->output, data, size);
+ if (ret < 0 || (size_t)ret != size) {
+ /* either disconnection or buffer full. in either case we want
+ this connection destroyed. however destroying it here might
+ break things if client is still tried to be accessed without
+ being referenced.. */
+ i_stream_close(client->input);
+ }
+}
+
+void client_send_raw_data(struct client *client, const void *data, size_t size)
+{
+ client->v.send_raw_data(client, data, size);
+}
+
+void client_send_raw(struct client *client, const char *data)
+{
+ client_send_raw_data(client, data, strlen(data));
+}
+
+bool client_read(struct client *client)
+{
+ switch (i_stream_read(client->input)) {
+ case -2:
+ /* buffer full */
+ client_notify_disconnect(client,
+ CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
+ "Input buffer full, aborting");
+ client_destroy(client, "Input buffer full");
+ return FALSE;
+ case -1:
+ /* disconnected */
+ client_destroy_iostream_error(client);
+ return FALSE;
+ case 0:
+ /* nothing new read */
+ return i_stream_get_data_size(client->input) > 0;
+ default:
+ /* something was read */
+ return TRUE;
+ }
+}
+
+void client_input(struct client *client)
+{
+ i_assert(client->v.input != NULL);
+ client->v.input(client);
+}
+
+void client_common_init(void)
+{
+ i_array_init(&module_hooks, 32);
+}
+
+void client_destroy_fd_proxies(void)
+{
+ while (client_fd_proxies != NULL) {
+ struct client *client = client_fd_proxies;
+ client_unref(&client);
+ }
+ i_assert(client_fd_proxies_count == 0);
+}
+
+void client_common_deinit(void)
+{
+ i_assert(destroyed_clients == NULL);
+ array_free(&module_hooks);
+}