summaryrefslogtreecommitdiffstats
path: root/src/doveadm/doveadm-mail-server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/doveadm/doveadm-mail-server.c')
-rw-r--r--src/doveadm/doveadm-mail-server.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-mail-server.c b/src/doveadm/doveadm-mail-server.c
new file mode 100644
index 0000000..a829a24
--- /dev/null
+++ b/src/doveadm/doveadm-mail-server.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "ioloop.h"
+#include "master-service.h"
+#include "iostream-ssl.h"
+#include "auth-master.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "server-connection.h"
+#include "doveadm-settings.h"
+#include "doveadm-print.h"
+#include "doveadm-server.h"
+#include "doveadm-mail.h"
+
+#define DOVEADM_SERVER_CONNECTIONS_MAX 4
+#define DOVEADM_SERVER_QUEUE_MAX 16
+
+#define DOVEADM_MAIL_SERVER_FAILED() \
+ (internal_failure || master_service_is_killed(master_service))
+
+struct doveadm_mail_server_cmd {
+ struct server_connection *conn;
+ char *username;
+};
+
+static HASH_TABLE(char *, struct doveadm_server *) servers;
+static pool_t server_pool;
+static struct doveadm_mail_cmd_context *cmd_ctx;
+static bool internal_failure = FALSE;
+
+static void doveadm_mail_server_handle(struct server_connection *conn,
+ const char *username);
+
+static struct doveadm_server *
+doveadm_server_get(struct doveadm_mail_cmd_context *ctx, const char *name)
+{
+ struct doveadm_server *server;
+ const char *p;
+ char *dup_name;
+
+ if (!hash_table_is_created(servers)) {
+ server_pool = pool_alloconly_create("doveadm servers", 1024*16);
+ hash_table_create(&servers, server_pool, 0, str_hash, strcmp);
+ }
+ server = hash_table_lookup(servers, name);
+ if (server == NULL) {
+ server = p_new(server_pool, struct doveadm_server, 1);
+ server->name = dup_name = p_strdup(server_pool, name);
+ p = strrchr(server->name, ':');
+ server->hostname = p == NULL ? server->name :
+ p_strdup_until(server_pool, server->name, p);
+
+ p_array_init(&server->connections, server_pool,
+ ctx->set->doveadm_worker_count);
+ p_array_init(&server->queue, server_pool,
+ DOVEADM_SERVER_QUEUE_MAX);
+ hash_table_insert(servers, dup_name, server);
+ }
+ return server;
+}
+
+static struct server_connection *
+doveadm_server_find_unused_conn(struct doveadm_server *server)
+{
+ struct server_connection *conn;
+
+ array_foreach_elem(&server->connections, conn) {
+ if (server_connection_is_idle(conn))
+ return conn;
+ }
+ return NULL;
+}
+
+static bool doveadm_server_have_used_connections(struct doveadm_server *server)
+{
+ struct server_connection *conn;
+
+ array_foreach_elem(&server->connections, conn) {
+ if (!server_connection_is_idle(conn))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void doveadm_cmd_callback(int exit_code, const char *error,
+ void *context)
+{
+ struct doveadm_mail_server_cmd *servercmd = context;
+ struct doveadm_server *server =
+ server_connection_get_server(servercmd->conn);
+ const char *username = t_strdup(servercmd->username);
+
+ i_free(servercmd->username);
+ i_free(servercmd);
+
+ switch (exit_code) {
+ case 0:
+ break;
+ case SERVER_EXIT_CODE_DISCONNECTED:
+ i_error("%s: Command %s failed for %s: %s",
+ server->name, cmd_ctx->cmd->name, username, error);
+ internal_failure = TRUE;
+ io_loop_stop(current_ioloop);
+ return;
+ case EX_NOUSER:
+ i_error("%s: No such user: %s", server->name, username);
+ if (cmd_ctx->exit_code == 0)
+ cmd_ctx->exit_code = EX_NOUSER;
+ break;
+ default:
+ if (cmd_ctx->exit_code == 0 || exit_code == EX_TEMPFAIL)
+ cmd_ctx->exit_code = exit_code;
+ break;
+ }
+
+ if (array_count(&server->queue) > 0) {
+ struct server_connection *conn;
+ char *const *usernamep = array_front(&server->queue);
+ char *username = *usernamep;
+
+ conn = doveadm_server_find_unused_conn(server);
+ if (conn != NULL) {
+ array_pop_front(&server->queue);
+ doveadm_mail_server_handle(conn, username);
+ i_free(username);
+ }
+ }
+
+ io_loop_stop(current_ioloop);
+}
+
+static void doveadm_mail_server_handle(struct server_connection *conn,
+ const char *username)
+{
+ struct doveadm_mail_server_cmd *servercmd;
+ string_t *cmd;
+ unsigned int i;
+
+ /* <flags> <username> <command> [<args>] */
+ cmd = t_str_new(256);
+ if (doveadm_debug)
+ str_append_c(cmd, 'D');
+ else if (doveadm_verbose)
+ str_append_c(cmd, 'v');
+ str_append_c(cmd, '\t');
+
+ str_append_tabescaped(cmd, username);
+ str_append_c(cmd, '\t');
+ str_append_tabescaped(cmd, cmd_ctx->cmd->name);
+ for (i = 0; cmd_ctx->full_args[i] != NULL; i++) {
+ str_append_c(cmd, '\t');
+ str_append_tabescaped(cmd, cmd_ctx->full_args[i]);
+ }
+ str_append_c(cmd, '\n');
+
+ servercmd = i_new(struct doveadm_mail_server_cmd, 1);
+ servercmd->conn = conn;
+ servercmd->username = i_strdup(username);
+ server_connection_cmd(conn, str_c(cmd), cmd_ctx->cmd_input,
+ doveadm_cmd_callback, servercmd);
+}
+
+static void doveadm_server_flush_one(struct doveadm_server *server)
+{
+ unsigned int count = array_count(&server->queue);
+
+ do {
+ io_loop_run(current_ioloop);
+ } while (array_count(&server->queue) == count &&
+ doveadm_server_have_used_connections(server) &&
+ !DOVEADM_MAIL_SERVER_FAILED());
+}
+
+static int
+doveadm_mail_server_user_get_host(struct doveadm_mail_cmd_context *ctx,
+ const struct mail_storage_service_input *input,
+ const char **user_r, const char **host_r,
+ struct ip_addr *hostip_r, in_port_t *port_r,
+ enum doveadm_proxy_ssl_flags *ssl_flags_r,
+ const char **error_r)
+{
+ struct auth_master_connection *auth_conn;
+ struct auth_user_info info;
+ pool_t pool;
+ const char *auth_socket_path, *proxy_host, *proxy_hostip, *const *fields;
+ unsigned int i;
+ in_port_t proxy_port;
+ bool proxying;
+ int ret;
+
+ *user_r = input->username;
+ *host_r = ctx->set->doveadm_socket_path;
+ *port_r = ctx->set->doveadm_port;
+
+ if (ctx->set->doveadm_port == 0)
+ return 0;
+
+ if (strcmp(ctx->set->doveadm_ssl, "ssl") == 0)
+ *ssl_flags_r |= PROXY_SSL_FLAG_YES;
+ else if (strcmp(ctx->set->doveadm_ssl, "starttls") == 0)
+ *ssl_flags_r |= PROXY_SSL_FLAG_YES | PROXY_SSL_FLAG_STARTTLS;
+
+ /* make sure we have an auth connection */
+ mail_storage_service_init_settings(ctx->storage_service, input);
+
+ i_zero(&info);
+ info.service = master_service_get_name(master_service);
+ info.local_ip = input->local_ip;
+ info.remote_ip = input->remote_ip;
+ info.local_port = input->local_port;
+ info.remote_port = input->remote_port;
+
+ pool = pool_alloconly_create("auth lookup", 1024);
+ auth_conn = mail_storage_service_get_auth_conn(ctx->storage_service);
+ auth_socket_path = auth_master_get_socket_path(auth_conn);
+ ret = auth_master_pass_lookup(auth_conn, input->username, &info,
+ pool, &fields);
+ if (ret < 0) {
+ *error_r = fields[0] != NULL ?
+ t_strdup(fields[0]) : "passdb lookup failed";
+ *error_r = t_strdup_printf("%s: %s (to see if user is proxied, "
+ "because doveadm_port is set)",
+ auth_socket_path, *error_r);
+ } else if (ret == 0) {
+ /* user not found from passdb. it could be in userdb though,
+ so just continue with the default host */
+ } else {
+ proxy_host = NULL; proxy_hostip = NULL; proxying = FALSE;
+ proxy_port = ctx->set->doveadm_port;
+ for (i = 0; fields[i] != NULL; i++) {
+ if (str_begins(fields[i], "proxy") &&
+ (fields[i][5] == '\0' || fields[i][5] == '='))
+ proxying = TRUE;
+ else if (str_begins(fields[i], "host="))
+ proxy_host = fields[i]+5;
+ else if (str_begins(fields[i], "hostip="))
+ proxy_hostip = fields[i]+7;
+ else if (str_begins(fields[i], "user="))
+ *user_r = t_strdup(fields[i]+5);
+ else if (str_begins(fields[i], "destuser="))
+ *user_r = t_strdup(fields[i]+9);
+ else if (str_begins(fields[i], "port=")) {
+ if (net_str2port(fields[i]+5, &proxy_port) < 0)
+ proxy_port = 0;
+ } else if (str_begins(fields[i], "ssl=")) {
+ *ssl_flags_r |= PROXY_SSL_FLAG_YES;
+ if (strcmp(fields[i]+4, "any-cert") == 0)
+ *ssl_flags_r |= PROXY_SSL_FLAG_ANY_CERT;
+ } else if (str_begins(fields[i], "starttls=")) {
+ *ssl_flags_r |= PROXY_SSL_FLAG_YES |
+ PROXY_SSL_FLAG_STARTTLS;
+ if (strcmp(fields[i]+9, "any-cert") == 0)
+ *ssl_flags_r |= PROXY_SSL_FLAG_ANY_CERT;
+ }
+ }
+ if (proxy_hostip != NULL &&
+ net_addr2ip(proxy_hostip, hostip_r) < 0) {
+ *error_r = t_strdup_printf("%s Invalid hostip value '%s'",
+ auth_socket_path, proxy_hostip);
+ ret = -1;
+ }
+ if (!proxying)
+ ret = 0;
+ else if (proxy_host == NULL) {
+ *error_r = t_strdup_printf("%s: Proxy is missing destination host",
+ auth_socket_path);
+ if (strstr(auth_socket_path, "/auth-userdb") != NULL) {
+ *error_r = t_strdup_printf(
+ "%s (maybe set auth_socket_path=director-userdb)",
+ *error_r);
+ }
+ ret = -1;
+ } else {
+ *port_r = proxy_port;
+ *host_r = t_strdup_printf("%s:%u", proxy_host, proxy_port);
+ }
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+int doveadm_mail_server_user(struct doveadm_mail_cmd_context *ctx,
+ const struct mail_storage_service_input *input,
+ const char **error_r)
+{
+ struct doveadm_server *server;
+ struct server_connection *conn;
+ const char *user, *host;
+ struct ip_addr hostip;
+ enum doveadm_proxy_ssl_flags ssl_flags = 0;
+ char *username_dup;
+ int ret;
+ in_port_t port;
+
+ i_assert(cmd_ctx == ctx || cmd_ctx == NULL);
+ cmd_ctx = ctx;
+
+ i_zero(&hostip);
+ ret = doveadm_mail_server_user_get_host(ctx, input, &user, &host, &hostip,
+ &port, &ssl_flags, error_r);
+ if (ret < 0)
+ return ret;
+ if (ret == 0 &&
+ (ctx->set->doveadm_worker_count == 0 || doveadm_server)) {
+ /* run it ourself */
+ return 0;
+ }
+
+ /* server sends the sticky headers for each row as well,
+ so undo any sticks we might have added already */
+ doveadm_print_unstick_headers();
+
+ server = doveadm_server_get(ctx, host);
+ server->ip = hostip;
+ server->ssl_flags = ssl_flags;
+ server->port = port;
+ conn = doveadm_server_find_unused_conn(server);
+ if (conn != NULL)
+ doveadm_mail_server_handle(conn, user);
+ else if (array_count(&server->connections) <
+ I_MAX(ctx->set->doveadm_worker_count, 1)) {
+ if (server_connection_create(server, &conn, error_r) < 0) {
+ internal_failure = TRUE;
+ return -1;
+ } else {
+ doveadm_mail_server_handle(conn, user);
+ }
+ } else {
+ if (array_count(&server->queue) >= DOVEADM_SERVER_QUEUE_MAX)
+ doveadm_server_flush_one(server);
+
+ username_dup = i_strdup(user);
+ array_push_back(&server->queue, &username_dup);
+ }
+ *error_r = "doveadm server failure";
+ return DOVEADM_MAIL_SERVER_FAILED() ? -1 : 1;
+}
+
+static struct doveadm_server *doveadm_server_find_used(void)
+{
+ struct hash_iterate_context *iter;
+ struct doveadm_server *ret = NULL;
+ char *key;
+ struct doveadm_server *server;
+
+ iter = hash_table_iterate_init(servers);
+ while (hash_table_iterate(iter, servers, &key, &server)) {
+ if (doveadm_server_have_used_connections(server)) {
+ ret = server;
+ break;
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+ return ret;
+}
+
+static void doveadm_servers_destroy_all_connections(void)
+{
+ struct hash_iterate_context *iter;
+ char *key;
+ struct doveadm_server *server;
+
+ iter = hash_table_iterate_init(servers);
+ while (hash_table_iterate(iter, servers, &key, &server)) {
+ while (array_count(&server->connections) > 0) {
+ struct server_connection *const *connp, *conn;
+
+ connp = array_front(&server->connections);
+ conn = *connp;
+ server_connection_destroy(&conn);
+ }
+ ssl_iostream_context_unref(&server->ssl_ctx);
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+void doveadm_mail_server_flush(void)
+{
+ struct doveadm_server *server;
+
+ if (!hash_table_is_created(servers)) {
+ cmd_ctx = NULL;
+ return;
+ }
+
+ while ((server = doveadm_server_find_used()) != NULL &&
+ !DOVEADM_MAIL_SERVER_FAILED())
+ doveadm_server_flush_one(server);
+
+ doveadm_servers_destroy_all_connections();
+ if (master_service_is_killed(master_service))
+ i_error("Aborted");
+ if (DOVEADM_MAIL_SERVER_FAILED())
+ doveadm_mail_failed_error(cmd_ctx, MAIL_ERROR_TEMP);
+
+ hash_table_destroy(&servers);
+ pool_unref(&server_pool);
+ cmd_ctx = NULL;
+}