]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap, imap-hibernate: Use DOVECOT-TOKEN authentication for unhibernation
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 9 Mar 2026 22:44:46 +0000 (00:44 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Thu, 12 Mar 2026 14:59:51 +0000 (14:59 +0000)
The hibernated sessions provide an authentication token to imap-hibernate
process, which sends the token to imap-master socket when unhibernating.
If the token doesn't match, the unhibernation will fail. This allows
giving imap-master socket wider permissions, since it can no longer be
used to log in as any user.

src/imap-hibernate/imap-client.c
src/imap-hibernate/imap-client.h
src/imap-hibernate/imap-hibernate-client.c
src/imap/Makefile.am
src/imap/imap-client-hibernate.c
src/imap/imap-master-client.c
src/imap/test-imap-client-hibernate.c

index 4c2fb4bc0aa05ce48e09ba003897cf702f7beca0..216513c318fb4ed475121903bc2fcfafbb958a10 100644 (file)
@@ -199,6 +199,14 @@ imap_client_move_back_send_callback(void *context, struct ostream *output)
                str_append(str, "\tstate=");
                base64_encode(state->state, state->state_size, str);
        }
+       if (state->auth_token != NULL) {
+               str_append(str, "\tauth_token=");
+               str_append_tabescaped(str, state->auth_token);
+       }
+       if (state->session_pid != NULL) {
+               str_append(str, "\tsession_pid=");
+               str_append_tabescaped(str, state->session_pid);
+       }
        input_data = i_stream_get_data(client->input, &input_size);
        if (input_size > 0) {
                str_append(str, "\tclient_input=");
@@ -644,6 +652,8 @@ imap_client_create(int fd, const struct imap_client_state *state)
        client->state.session_id = p_strdup(pool, state->session_id);
        client->state.userdb_fields = p_strdup(pool, state->userdb_fields);
        client->state.stats = p_strdup(pool, state->stats);
+       client->state.auth_token = p_strdup(pool, state->auth_token);
+       client->state.session_pid = p_strdup(pool, state->session_pid);
        client->state.tag = i_strdup(state->tag);
 
        client->event = event_create(NULL);
index f22be018e8a121d471d6766d70a23796cb0467f1..ffd1566c37d551b6f06b3cef7241ae693bcf1427 100644 (file)
@@ -9,6 +9,7 @@ struct imap_client_state {
        const char *username, *mail_log_prefix;
        /* optional: */
        const char *session_id, *mailbox_vname, *userdb_fields, *stats;
+       const char *auth_token, *session_pid;
        struct ip_addr local_ip, remote_ip;
        in_port_t local_port, remote_port;
        time_t session_created;
index 493f0cdf062863713ba7b1dbbbfc830fc424209a..3506520f76ed691bb4d0696ce7fac865a641b1f6 100644 (file)
@@ -129,6 +129,10 @@ imap_hibernate_client_parse_input(const char *const *args, pool_t pool,
                        }
                } else if (strcmp(key, "stats") == 0) {
                        state_r->stats = value;
+               } else if (strcmp(key, "auth_token") == 0) {
+                       state_r->auth_token = value;
+               } else if (strcmp(key, "session_pid") == 0) {
+                       state_r->session_pid = value;
                } else if (strcmp(key, "idle-cmd") == 0) {
                        state_r->idle_cmd = TRUE;
                } else if (strcmp(key, "session") == 0) {
index 5eac988dcbca841120a1e42c9d066ab72a668586..984d0bd916e0a4dbac66c1363caaf89f5c5f30bb 100644 (file)
@@ -20,6 +20,8 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-storage \
        -I$(top_srcdir)/src/lib-compression \
        -I$(top_srcdir)/src/lib-var-expand \
+       -I$(top_srcdir)/src/lib-auth-client \
+       -I$(top_srcdir)/src/lib-sasl \
        $(BINARY_CFLAGS)
 
 imap_LDFLAGS = -export-dynamic \
index 067b984cc04e9da40fe67168bc16b3c76ff32028..1fe8e5982fb7577544a177fb268383997c93146d 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
 
 #include "imap-common.h"
+#include "hostpid.h"
 #include "fdpass.h"
 #include "net.h"
 #include "istream.h"
@@ -108,6 +109,11 @@ static void imap_hibernate_write_cmd(struct client *client, string_t *cmd,
                str_append(cmd, "\ttag=");
                str_append_tabescaped(cmd, tag);
        }
+       if (user->auth_token != NULL) {
+               str_append(cmd, "\tauth_token=");
+               str_append_tabescaped(cmd, user->auth_token);
+       }
+       str_printfa(cmd, "\tsession_pid=%s", my_pid);
        str_append(cmd, "\tstats=");
        str_append_tabescaped(cmd, client_stats(client));
        if (client->command_queue != NULL &&
index 60024b7b7d2c3f8b2575ec0b47bbcad13d26f5c7..1da280135eb42b6c6ad30ec19a8df6cbce480e47 100644 (file)
@@ -13,7 +13,9 @@
 #include "time-util.h"
 #include "process-title.h"
 #include "master-service.h"
+#include "master-service-settings.h"
 #include "mail-storage-service.h"
+#include "auth-client.h"
 #include "imap-client.h"
 #include "imap-state.h"
 #include "imap-master-client.h"
@@ -40,6 +42,9 @@ struct imap_master_input {
        /* Timestamp when hibernation started */
        struct timeval hibernation_start_time;
 
+       const char *auth_token;
+       const char *session_pid;
+
        dev_t peer_dev;
        ino_t peer_ino;
 
@@ -180,6 +185,10 @@ imap_master_client_parse_input(const char *const *args, pool_t pool,
                        }
                } else if (strcmp(key, "tag") == 0) {
                        master_input_r->tag = t_strdup(value);
+               } else if (strcmp(key, "auth_token") == 0) {
+                       master_input_r->auth_token = t_strdup(value);
+               } else if (strcmp(key, "session_pid") == 0) {
+                       master_input_r->session_pid = t_strdup(value);
                } else if (strcmp(key, "bad-done") == 0) {
                        master_input_r->state_import_bad_idle_done = TRUE;
                } else if (strcmp(key, "idle-continue") == 0) {
@@ -259,11 +268,135 @@ imap_master_client_parse_input(const char *const *args, pool_t pool,
        return 0;
 }
 
+struct imap_master_auth_result {
+       struct ioloop *ioloop;
+       char *error;
+};
+
+static void imap_master_auth_connected(struct auth_client *client ATTR_UNUSED,
+                                      const char *error, void *context)
+{
+       struct imap_master_auth_result *result = context;
+
+       result->error = i_strdup(error);
+       io_loop_stop(result->ioloop);
+}
+
+static void
+imap_master_auth_callback(struct auth_client_request *request ATTR_UNUSED,
+                         enum auth_request_status status,
+                         const char *log_error,
+                         const char *data_base64 ATTR_UNUSED,
+                         const char *const *args ATTR_UNUSED,
+                         void *context)
+{
+       struct imap_master_auth_result *result = context;
+
+       i_assert(status != AUTH_REQUEST_STATUS_CONTINUE);
+
+       if (status != AUTH_REQUEST_STATUS_OK)
+               result->error = i_strdup(log_error);
+       io_loop_stop(result->ioloop);
+}
+
+static int
+imap_master_client_authenticate(const char *username, const char *session_id,
+                               const char *auth_token, const char *session_pid,
+                               const char **error_r)
+{
+       if (auth_token == NULL) {
+               *error_r = "Missing auth_token";
+               return -1;
+       }
+       if (session_pid == NULL) {
+               *error_r = "Missing session_pid";
+               return -1;
+       }
+       if (session_id == NULL) {
+               *error_r = "Missing session_id";
+               return -1;
+       }
+
+       const struct master_service_settings *master_set =
+               master_service_get_service_settings(master_service);
+       const char *auth_socket_path =
+               t_strconcat(master_set->base_dir, "/auth-token", NULL);
+
+       struct ioloop *ioloop = io_loop_create();
+       struct auth_client *auth_client =
+               auth_client_init(auth_socket_path, getpid(), imap_debug);
+
+       struct imap_master_auth_result result = {
+               .ioloop = ioloop,
+       };
+       auth_client_set_connect_notify(auth_client, imap_master_auth_connected,
+                                      &result);
+       auth_client_connect(auth_client);
+       if (!auth_client_is_disconnected(auth_client))
+               io_loop_run(ioloop);
+
+       if (result.error != NULL) {
+               auth_client_deinit(&auth_client);
+               io_loop_destroy(&ioloop);
+               *error_r = t_strdup_printf("Failed to connect to %s: %s",
+                                          auth_socket_path, result.error);
+               i_free(result.error);
+               return -1;
+       }
+       auth_client_set_connect_notify(auth_client, NULL, NULL);
+
+       /* payload: service \0 pid \0 username \0 session_id \0 auth_token */
+       string_t *token_payload = t_str_new(128);
+       str_append(token_payload, master_service_get_name(master_service));
+       str_append_c(token_payload, '\0');
+       str_append(token_payload, session_pid);
+       str_append_c(token_payload, '\0');
+       str_append(token_payload, username);
+       str_append_c(token_payload, '\0');
+       str_append(token_payload, session_id);
+       str_append_c(token_payload, '\0');
+       str_append(token_payload, auth_token);
+
+       string_t *initial_resp_b64 = t_str_new(256);
+       base64_encode(str_data(token_payload), str_len(token_payload),
+                     initial_resp_b64);
+
+       struct auth_request_info info = {
+               .mech = "DOVECOT-TOKEN",
+               .protocol = "imap",
+               .session_id = session_id,
+               .flags = AUTH_REQUEST_FLAG_CONN_SECURED,
+               .initial_resp_base64 = str_c(initial_resp_b64),
+       };
+
+       (void)auth_client_request_new(auth_client, &info,
+                                     imap_master_auth_callback, &result);
+       io_loop_set_running(ioloop);
+       io_loop_run(ioloop);
+
+       auth_client_deinit(&auth_client);
+       io_loop_destroy(&ioloop);
+
+       if (result.error != NULL) {
+               *error_r = t_strdup_printf(
+                       "DOVECOT-TOKEN authentication failed: %s", result.error);
+               i_free(result.error);
+               return -1;
+       }
+       return 0;
+}
+
 static int imap_master_client_verify(const struct imap_master_input *master_input,
+                                    const struct mail_storage_service_input *input,
                                     int fd_client, const char **error_r)
 {
        struct stat peer_st;
 
+       if (imap_master_client_authenticate(input->username, input->session_id,
+                                           master_input->auth_token,
+                                           master_input->session_pid, error_r) < 0)
+               return -1;
+
        if (master_input->peer_ino == 0)
                return 0;
 
@@ -308,7 +441,7 @@ imap_master_client_input_args(struct connection *conn, const char *const *args,
                i_close_fd(&fd_client);
                return -1;
        }
-       if (imap_master_client_verify(&master_input, fd_client, &error) < 0) {
+       if (imap_master_client_verify(&master_input, &input, fd_client, &error) < 0) {
                e_error(conn->event, "imap-master: Failed to verify client input: %s", error);
                o_stream_nsend_str(conn->output, t_strdup_printf(
                        "-Failed to verify client input: %s\n", error));
index 29b687c496a81df11261300ced721d8522a0d6d9..44c4dd5b3fb9f7a0c4056cbbca1688be5e35a727 100644 (file)
@@ -115,7 +115,8 @@ static int imap_hibernate_server(struct test_imap_client_hibernate *ctx)
        if (ctx->has_mailbox)
                test_assert_strcmp(args[i++], "mailbox="EVILSTR"mailbox");
        test_assert_strcmp(args[i++], "tag="EVILSTR"tag");
-       test_assert(str_begins_with(args[i++], "stats="));
+       test_assert(str_begins_with(args[i++], "session_pid="));
+       test_assert(args[i] != NULL && str_begins_with(args[i++], "stats="));
        test_assert_strcmp(args[i++], "idle-cmd");
        if (ctx->has_mailbox)
                test_assert_strcmp(args[i++], "notify_fd");