summaryrefslogtreecommitdiffstats
path: root/src/imap/imap-master-client.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/imap/imap-master-client.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/imap/imap-master-client.c b/src/imap/imap-master-client.c
new file mode 100644
index 0000000..66f7f3d
--- /dev/null
+++ b/src/imap/imap-master-client.c
@@ -0,0 +1,415 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "connection.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "ostream.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "str-sanitize.h"
+#include "time-util.h"
+#include "master-service.h"
+#include "mail-storage-service.h"
+#include "imap-client.h"
+#include "imap-state.h"
+#include "imap-master-client.h"
+
+struct imap_master_client {
+ struct connection conn;
+ bool imap_client_created;
+};
+
+struct imap_master_input {
+ /* input we've already read from the IMAP client. */
+ buffer_t *client_input;
+ /* output that imap-hibernate was supposed to send to IMAP client,
+ but couldn't send it yet. */
+ buffer_t *client_output;
+ /* IMAP connection state */
+ buffer_t *state;
+ /* command tag */
+ const char *tag;
+ /* Timestamp when hibernation started */
+ struct timeval hibernation_start_time;
+
+ dev_t peer_dev;
+ ino_t peer_ino;
+
+ bool state_import_bad_idle_done;
+ bool state_import_idle_continue;
+};
+
+static struct connection_list *master_clients = NULL;
+
+static void imap_master_client_destroy(struct connection *conn)
+{
+ struct imap_master_client *client = (struct imap_master_client *)conn;
+
+ if (!client->imap_client_created)
+ master_service_client_connection_destroyed(master_service);
+ connection_deinit(conn);
+ i_free(conn);
+}
+
+static int
+imap_master_client_parse_input(const char *const *args, pool_t pool,
+ struct mail_storage_service_input *input_r,
+ struct imap_master_input *master_input_r,
+ const char **error_r)
+{
+ const char *key, *value;
+ unsigned int peer_dev_major = 0, peer_dev_minor = 0;
+
+ i_zero(input_r);
+ i_zero(master_input_r);
+ master_input_r->client_input = buffer_create_dynamic(pool, 64);
+ master_input_r->client_output = buffer_create_dynamic(pool, 16);
+ master_input_r->state = buffer_create_dynamic(pool, 512);
+
+ input_r->module = input_r->service = "imap";
+ /* we never want to do userdb lookup again when restoring the client.
+ we have the userdb_fields cached already. */
+ input_r->flags_override_remove = MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+
+ if (args[0] == NULL) {
+ *error_r = "Missing username in input";
+ return -1;
+ }
+ input_r->username = args[0];
+
+ for (args++; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value != NULL)
+ key = t_strdup_until(*args, value++);
+ else {
+ key = *args;
+ value = "";
+ }
+ if (strcmp(key, "lip") == 0) {
+ if (net_addr2ip(value, &input_r->local_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "lport") == 0) {
+ if (net_str2port(value, &input_r->local_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rip") == 0) {
+ if (net_addr2ip(value, &input_r->remote_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rport") == 0) {
+ if (net_str2port(value, &input_r->remote_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_major") == 0) {
+ if (str_to_uint(value, &peer_dev_major) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_major value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_minor") == 0) {
+ if (str_to_uint(value, &peer_dev_minor) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_minor value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_ino") == 0) {
+ if (str_to_ino(value, &master_input_r->peer_ino) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ino value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "session") == 0) {
+ input_r->session_id = value;
+ } else if (strcmp(key, "session_created") == 0) {
+ if (str_to_time(value, &input_r->session_create_time) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid session_created value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "hibernation_started") == 0) {
+ if (str_to_timeval(value, &master_input_r->hibernation_start_time) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid hibernation_started value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "userdb_fields") == 0) {
+ input_r->userdb_fields =
+ t_strsplit_tabescaped(value);
+ } else if (strcmp(key, "client_input") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->client_input) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid client_input base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "client_output") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->client_output) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid client_output base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "state") == 0) {
+ if (base64_decode(value, strlen(value), NULL,
+ master_input_r->state) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid state base64 value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "tag") == 0) {
+ master_input_r->tag = 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) {
+ master_input_r->state_import_idle_continue = TRUE;
+ }
+ }
+ if (peer_dev_major != 0 || peer_dev_minor != 0) {
+ master_input_r->peer_dev =
+ makedev(peer_dev_major, peer_dev_minor);
+ }
+ return 0;
+}
+
+static int imap_master_client_verify(const struct imap_master_input *master_input,
+ int fd_client, const char **error_r)
+{
+ struct stat peer_st;
+
+ if (master_input->peer_ino == 0)
+ return 0;
+
+ /* make sure we have the right fd */
+ if (fstat(fd_client, &peer_st) < 0) {
+ *error_r = t_strdup_printf("fstat(peer) failed: %m");
+ return -1;
+ }
+ if (peer_st.st_ino != master_input->peer_ino ||
+ !CMP_DEV_T(peer_st.st_dev, master_input->peer_dev)) {
+ *error_r = t_strdup_printf(
+ "BUG: Expected peer device=%lu,%lu inode=%s doesn't match "
+ "client fd's actual device=%lu,%lu inode=%s",
+ (unsigned long)major(peer_st.st_dev),
+ (unsigned long)minor(peer_st.st_dev), dec2str(peer_st.st_ino),
+ (unsigned long)major(master_input->peer_dev),
+ (unsigned long)minor(master_input->peer_dev),
+ dec2str(master_input->peer_ino));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imap_master_client_input_args(struct connection *conn, const char *const *args,
+ int fd_client, pool_t pool)
+{
+ struct imap_master_client *client = (struct imap_master_client *)conn;
+ struct client *imap_client;
+ struct mail_storage_service_input input;
+ struct imap_master_input master_input;
+ const char *error = NULL, *reason;
+ int ret;
+
+ if (imap_master_client_parse_input(args, pool, &input, &master_input,
+ &error) < 0) {
+ e_error(conn->event, "imap-master: Failed to parse client input: %s", error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to parse client input: %s\n", error));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ if (imap_master_client_verify(&master_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));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+
+ /* NOTE: before client_create_from_input() on failures we need to close
+ fd_client, but afterward it gets closed by client_destroy() */
+ ret = client_create_from_input(&input, fd_client, fd_client,
+ &imap_client, &error);
+ if (ret < 0) {
+ e_error(conn->event,
+ "imap-master(%s): Failed to create client: %s",
+ input.username, error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to create client: %s\n", error));
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ client->imap_client_created = TRUE;
+
+ long long hibernation_usecs =
+ timeval_diff_usecs(&ioloop_timeval,
+ &master_input.hibernation_start_time);
+ struct event *event = event_create(imap_client->event);
+ event_set_name(event, "imap_client_unhibernated");
+ event_add_int(event, "hibernation_usecs", hibernation_usecs);
+ imap_client->state_import_bad_idle_done =
+ master_input.state_import_bad_idle_done;
+ imap_client->state_import_idle_continue =
+ master_input.state_import_idle_continue;
+ if (imap_client->state_import_bad_idle_done) {
+ reason = "IDLE was stopped with BAD command";
+ event_add_str(event, "reason", "idle_bad_reply");
+ } else if (imap_client->state_import_idle_continue) {
+ reason = "mailbox changes need to be sent";
+ event_add_str(event, "reason", "mailbox_changes");
+ } else {
+ reason = "IDLE was stopped with DONE";
+ event_add_str(event, "reason", "idle_done");
+ }
+
+ /* Send a success notification before we start anything that lasts
+ potentially a long time. imap-hibernate process is waiting for us
+ to answer. Even if we fail later, we log the error anyway. From now
+ on it's our responsibility to also log the imap_client_unhibernated
+ event. */
+ o_stream_nsend_str(conn->output, "+\n");
+ (void)o_stream_flush(conn->output);
+
+ if (master_input.client_input->used > 0) {
+ client_add_istream_prefix(imap_client,
+ master_input.client_input->data,
+ master_input.client_input->used);
+ }
+
+ client_create_finish_io(imap_client);
+ if (client_create_finish(imap_client, &error) < 0) {
+ event_add_str(event, "error", error);
+ e_error(event, "imap-master: %s", error);
+ event_unref(&event);
+ client_destroy(imap_client, error);
+ return -1;
+ }
+ /* log prefix is set at this point, so we don't need to add the
+ username anymore to the log messages */
+
+ o_stream_nsend(imap_client->output,
+ master_input.client_output->data,
+ master_input.client_output->used);
+
+ struct event_reason *event_reason =
+ event_reason_begin("imap:unhibernate");
+ ret = imap_state_import_internal(imap_client, master_input.state->data,
+ master_input.state->used, &error);
+ event_reason_end(&event_reason);
+
+ if (ret <= 0) {
+ error = t_strdup_printf("Failed to import client state: %s", error);
+ event_add_str(event, "error", error);
+ e_error(event, "imap-master: %s", error);
+ event_unref(&event);
+ client_destroy(imap_client, "Client state initialization failed");
+ return -1;
+ }
+ if (imap_client->mailbox != NULL) {
+ /* Would be nice to set this earlier, but the previous errors
+ happen rarely enough that it shouldn't really matter. */
+ event_add_str(event, "mailbox",
+ mailbox_get_vname(imap_client->mailbox));
+ }
+
+ if (master_input.tag != NULL)
+ imap_state_import_idle_cmd_tag(imap_client, master_input.tag);
+
+ e_debug(event, "imap-master: Unhibernated because %s "
+ "(hibernated for %llu.%06llu secs)", reason,
+ hibernation_usecs/1000000, hibernation_usecs%1000000);
+ event_unref(&event);
+
+ /* make sure all pending input gets handled */
+ if (master_input.client_input->used > 0) {
+ e_debug(imap_client->event,
+ "imap-master: Pending client input: '%s'",
+ str_sanitize(str_c(master_input.client_input), 128));
+ io_set_pending(imap_client->io);
+ }
+
+ imap_refresh_proctitle();
+ /* we'll always disconnect the client afterwards */
+ return -1;
+}
+
+static int
+imap_master_client_input_line(struct connection *conn, const char *line)
+{
+ char *const *args;
+ pool_t pool;
+ int fd_client, ret;
+
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(conn, t_strsplit_tabescaped(line)) < 0)
+ return -1;
+ conn->version_received = TRUE;
+ return 1;
+ }
+
+ fd_client = i_stream_unix_get_read_fd(conn->input);
+ if (fd_client == -1) {
+ e_error(conn->event, "imap-master: IMAP client fd not received");
+ return -1;
+ }
+
+ if (imap_debug)
+ e_debug(conn->event, "imap-master: Client input: %s", line);
+
+ pool = pool_alloconly_create("imap master client cmd", 1024);
+ args = p_strsplit_tabescaped(pool, line);
+ ret = imap_master_client_input_args(conn, (const void *)args,
+ fd_client, pool);
+ pool_unref(&pool);
+ return ret;
+}
+
+void imap_master_client_create(int fd)
+{
+ struct imap_master_client *client;
+
+ client = i_new(struct imap_master_client, 1);
+ client->conn.unix_socket = TRUE;
+ connection_init_server(master_clients, &client->conn,
+ "imap-master", fd, fd);
+
+ /* read the first file descriptor that we can */
+ i_stream_unix_set_read_fd(client->conn.input);
+}
+
+static struct connection_settings client_set = {
+ .service_name_in = "imap-master",
+ .service_name_out = "imap-master",
+ .major_version = 1,
+ .minor_version = 0,
+
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_master_client_destroy,
+ .input_line = imap_master_client_input_line
+};
+
+void imap_master_clients_init(void)
+{
+ master_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_master_clients_deinit(void)
+{
+ connection_list_deinit(&master_clients);
+}