src/dns/dns-client
src/doveadm/doveadm
src/doveadm/doveadm-server
+src/imap-hibernate/imap-hibernate
src/imap-login/imap-login
src/imap-urlauth/imap-urlauth
src/imap-urlauth/imap-urlauth-login
src/indexer/Makefile
src/ipc/Makefile
src/imap/Makefile
+src/imap-hibernate/Makefile
src/imap-login/Makefile
src/imap-urlauth/Makefile
src/login-common/Makefile
ipc \
master \
login-common \
+ imap-hibernate \
imap-login \
imap \
imap-urlauth \
--- /dev/null
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = imap-hibernate
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-imap
+
+imap_hibernate_LDADD = $(LIBDOVECOT)
+imap_hibernate_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+imap_hibernate_SOURCES = \
+ imap-client.c \
+ imap-hibernate-client.c \
+ imap-hibernate-settings.c \
+ imap-master-connection.c \
+ main.c
+
+noinst_HEADERS = \
+ imap-client.h \
+ imap-hibernate-client.h \
+ imap-master-connection.h
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "fd-set-nonblock.h"
+#include "fdpass.h"
+#include "hostpid.h"
+#include "connection.h"
+#include "iostream.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "var-expand.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "imap-keepalive.h"
+#include "imap-master-connection.h"
+#include "imap-client.h"
+
+#include <unistd.h>
+
+#define IMAP_MASTER_SOCKET_NAME "imap-master"
+
+/* we only need enough for "DONE\r\nIDLE\r\n" */
+#define IMAP_MAX_INBUF 12
+
+enum imap_client_input_state {
+ IMAP_CLIENT_INPUT_STATE_UNKNOWN,
+ IMAP_CLIENT_INPUT_STATE_BAD,
+ IMAP_CLIENT_INPUT_STATE_DONE_LF,
+ IMAP_CLIENT_INPUT_STATE_DONE_CRLF,
+ IMAP_CLIENT_INPUT_STATE_DONEIDLE
+};
+
+struct imap_client_notify {
+ int fd;
+ struct io *io;
+};
+
+struct imap_client {
+ struct imap_client *prev, *next;
+ pool_t pool;
+ struct imap_client_state state;
+ ARRAY(struct imap_client_notify) notifys;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct timeout *to_keepalive;
+ struct imap_master_connection *master_conn;
+ struct ioloop_context *ioloop_ctx;
+ const char *log_prefix;
+ unsigned int imap_still_here_text_pos;
+ unsigned int next_read_threshold;
+ bool bad_done, idle_done;
+};
+
+static struct imap_client *imap_clients;
+static const char imap_still_here_text[] = "* OK Still here\r\n";
+
+static void imap_client_stop(struct imap_client *client);
+void imap_client_destroy(struct imap_client **_client, const char *reason);
+static void imap_client_add_idle_keepalive_timeout(struct imap_client *client);
+
+static void imap_client_disconnected(struct imap_client **_client)
+{
+ struct imap_client *client = *_client;
+ const char *reason;
+
+ reason = io_stream_get_disconnect_reason(client->input, NULL);
+ imap_client_destroy(_client, reason);
+}
+
+static void
+imap_client_parse_userdb_fields(struct imap_client *client,
+ const char **auth_user_r)
+{
+ const char *const *field;
+ unsigned int i;
+
+ *auth_user_r = NULL;
+
+ if (client->state.userdb_fields == NULL)
+ return;
+
+ field = t_strsplit_tabescaped(client->state.userdb_fields);
+ for (i = 0; field[i] != NULL; i++) {
+ if (strncmp(field[i], "auth_user=", 10) == 0)
+ *auth_user_r = field[i] + 10;
+ }
+}
+
+static void
+imap_client_move_back_send_callback(void *context, struct ostream *output)
+{
+ struct imap_client *client = context;
+ const struct imap_client_state *state = &client->state;
+ string_t *str = t_str_new(256);
+ const unsigned char *input_data;
+ size_t input_size;
+ ssize_t ret;
+
+ str_append_tabescaped(str, state->username);
+ if (state->session_id != NULL) {
+ str_append(str, "\tsession=");
+ str_append_tabescaped(str, state->session_id);
+ }
+ if (state->local_ip.family != 0)
+ str_printfa(str, "\tlip=%s", net_ip2addr(&state->local_ip));
+ if (state->remote_ip.family != 0)
+ str_printfa(str, "\trip=%s", net_ip2addr(&state->remote_ip));
+ if (state->userdb_fields != NULL) {
+ str_append(str, "\tuserdb_fields=");
+ str_append_tabescaped(str, state->userdb_fields);
+ }
+ if (state->peer_ip.family != 0)
+ str_printfa(str, "\tpeer_ip=%s", net_ip2addr(&state->peer_ip));
+ if (state->peer_port != 0)
+ str_printfa(str, "\tpeer_port=%u", state->peer_port);
+ if (state->state_size > 0) {
+ str_append(str, "\tstate=");
+ base64_encode(state->state, state->state_size, str);
+ }
+ input_data = i_stream_get_data(client->input, &input_size);
+ if (input_size > 0) {
+ str_append(str, "\tclient_input=");
+ base64_encode(input_data, input_size, str);
+ }
+ if (client->imap_still_here_text_pos != 0) {
+ str_append(str, "\tclient_output=");
+ base64_encode(imap_still_here_text + client->imap_still_here_text_pos,
+ sizeof(imap_still_here_text)-1 - client->imap_still_here_text_pos,
+ str);
+ }
+ if (client->idle_done) {
+ if (client->bad_done)
+ str_append(str, "\tbad-done");
+ } else if (client->state.idle_cmd) {
+ /* IDLE continues after sending changes */
+ str_append(str, "\tidle-continue");
+ }
+ str_append_c(str, '\n');
+
+ /* send the fd first */
+ ret = fd_send(o_stream_get_fd(output), client->fd, str_data(str), 1);
+ if (ret < 0) {
+ i_error("fd_send(%s) failed: %m",
+ o_stream_get_name(output));
+ imap_client_destroy(&client, "Failed to recreate imap process");
+ return;
+ }
+ i_assert(ret > 0);
+ o_stream_nsend(output, str_data(str) + 1, str_len(str) - 1);
+}
+
+static void
+imap_client_move_back_read_callback(void *context, const char *line)
+{
+ struct imap_client *client = context;
+
+ if (line[0] != '+') {
+ /* failed - FIXME: retry later? */
+ imap_client_destroy(&client, t_strdup_printf(
+ "Failed to recreate imap process: %s", line+1));
+ } else {
+ imap_client_destroy(&client, NULL);
+ }
+}
+
+static void imap_client_move_back(struct imap_client *client)
+{
+ const struct master_service_settings *master_set;
+ const char *path;
+
+ imap_client_stop(client);
+
+ master_set = master_service_settings_get(master_service);
+ path = t_strconcat(master_set->base_dir,
+ "/"IMAP_MASTER_SOCKET_NAME, NULL);
+ if (imap_master_connection_init(path,
+ imap_client_move_back_send_callback,
+ imap_client_move_back_read_callback,
+ client, &client->master_conn) < 0) {
+ /* failed to connect to the imap-master socket */
+ imap_client_destroy(&client, "Failed to connect to master socket");
+ }
+}
+
+static enum imap_client_input_state
+imap_client_input_parse(const unsigned char *data, size_t size)
+{
+ enum imap_client_input_state state = IMAP_CLIENT_INPUT_STATE_DONE_LF;
+
+ /* skip over DONE[\r]\n */
+ if (i_memcasecmp(data, "DONE", I_MIN(size, 4)) != 0)
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+ if (size <= 4)
+ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+ data += 4; size -= 4;
+
+ if (data[0] == '\r') {
+ state = IMAP_CLIENT_INPUT_STATE_DONE_CRLF;
+ data++; size--;
+ }
+ if (size == 0)
+ return IMAP_CLIENT_INPUT_STATE_UNKNOWN;
+ if (data[0] != '\n')
+ return IMAP_CLIENT_INPUT_STATE_BAD;
+ data++; size--;
+
+ /* skip over IDLE[\r]\n - checking this assumes that the DONE and IDLE
+ are sent in the same IP packet, otherwise we'll unnecessarily
+ recreate the imap process and immediately resume IDLE there. if this
+ becomes an issue we could add a small delay to the imap process
+ creation and wait for the IDLE command during it. */
+ if (size <= 4 || i_memcasecmp(data, "IDLE", 4) != 0)
+ return state;
+ data += 4; size -= 4;
+
+ if (data[0] == '\r') {
+ data++; size--;
+ }
+ return size == 1 && data[0] == '\n' ?
+ IMAP_CLIENT_INPUT_STATE_DONEIDLE : state;
+}
+
+static void imap_client_input_idle_cmd(struct imap_client *client)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ /* we should read either DONE or disconnection. also handle if client
+ sends DONE\nIDLE simply to recreate the IDLE. */
+ ret = i_stream_read_data(client->input, &data, &size,
+ client->next_read_threshold);
+ if (size == 0) {
+ if (ret < 0)
+ imap_client_disconnected(&client);
+ return;
+ }
+ client->next_read_threshold = 0;
+ switch (imap_client_input_parse(data, size)) {
+ case IMAP_CLIENT_INPUT_STATE_UNKNOWN:
+ /* we haven't received a full DONE[\r]\n yet - wait */
+ client->next_read_threshold = size;
+ return;
+ case IMAP_CLIENT_INPUT_STATE_BAD:
+ /* invalid input - return this to the imap process */
+ client->bad_done = TRUE;
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONE_LF:
+ i_stream_skip(client->input, 4+1);
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONE_CRLF:
+ i_stream_skip(client->input, 4+2);
+ break;
+ case IMAP_CLIENT_INPUT_STATE_DONEIDLE:
+ /* we received DONE+IDLE, so the client simply wanted to notify
+ us that it's still there. continue hibernation. */
+ i_stream_skip(client->input, size);
+ return;
+ }
+ client->idle_done = TRUE;
+ imap_client_move_back(client);
+}
+
+static void imap_client_input_nonidle(struct imap_client *client)
+{
+ if (i_stream_read(client->input) < 0)
+ imap_client_disconnected(&client);
+ else
+ imap_client_move_back(client);
+}
+
+static void imap_client_input_notify(struct imap_client *client)
+{
+ imap_client_move_back(client);
+}
+
+static void keepalive_timeout(struct imap_client *client)
+{
+ unsigned int text_left = sizeof(imap_still_here_text)-1 -
+ client->imap_still_here_text_pos;
+ ssize_t ret;
+
+ ret = write(client->fd,
+ imap_still_here_text + client->imap_still_here_text_pos,
+ text_left);
+ if (ret < 0) {
+ /* disconnect */
+ const char *reason = errno == EPIPE ? "Connection closed" :
+ t_strdup_printf("Connection closed: %m");
+ imap_client_destroy(&client, reason);
+ return;
+ }
+ client->imap_still_here_text_pos += ret;
+ if (client->imap_still_here_text_pos >= sizeof(imap_still_here_text)-1)
+ client->imap_still_here_text_pos = 0;
+ else {
+ /* we didn't write the entire line, but that's ok. we could
+ io_add(IO_WRITE) here, but that's probably not necessary.
+ just continue writing the "still here" line after the next
+ timeout. its main purpose is to see if the connection is
+ still alive, and we're successfully doing that already.
+ besides, it's highly unlikely we'll ever even get here.. */
+ }
+ imap_client_add_idle_keepalive_timeout(client);
+}
+
+static void imap_client_add_idle_keepalive_timeout(struct imap_client *client)
+{
+ unsigned int interval = client->state.imap_idle_notify_interval;
+
+ if (interval == 0)
+ return;
+
+ interval = imap_keepalive_interval_msecs(client->state.username,
+ &client->state.remote_ip,
+ interval);
+
+ if (client->to_keepalive!= NULL)
+ timeout_remove(&client->to_keepalive);
+ client->to_keepalive = timeout_add(interval, keepalive_timeout, client);
+}
+
+static const struct var_expand_table *
+imap_client_get_var_expand_table(struct imap_client *client)
+{
+ static struct var_expand_table static_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" },
+ { 'i', NULL, "uid" },
+ { '\0', NULL, "gid" },
+ { '\0', NULL, "session" },
+ { '\0', NULL, "auth_user" },
+ { '\0', NULL, "auth_username" },
+ { '\0', NULL, "auth_domain" },
+ /* NOTE: keep this synced with lib-storage's
+ mail_user_var_expand_table() */
+ { '\0', NULL, NULL }
+ };
+ struct var_expand_table *tab;
+ const char *auth_user;
+
+ tab = t_malloc(sizeof(static_tab));
+ memcpy(tab, static_tab, sizeof(static_tab));
+
+ tab[0].value = client->state.username;
+ tab[1].value = t_strcut(client->state.username, '@');
+ tab[2].value = strchr(client->state.username, '@');
+ if (tab[2].value != NULL) tab[2].value++;
+ tab[3].value = "imap-hibernate";
+ tab[4].value = NULL; /* we shouldn't need this */
+ tab[5].value = client->state.local_ip.family == 0 ? NULL :
+ net_ip2addr(&client->state.local_ip);
+ tab[6].value = client->state.remote_ip.family == 0 ? NULL :
+ net_ip2addr(&client->state.remote_ip);
+ tab[7].value = my_pid;
+ tab[8].value = dec2str(client->state.uid);
+ tab[9].value = dec2str(client->state.gid);
+ tab[10].value = client->state.session_id;
+
+ imap_client_parse_userdb_fields(client, &auth_user);
+ if (auth_user == NULL) {
+ tab[11].value = tab[0].value;
+ tab[12].value = tab[1].value;
+ tab[13].value = tab[2].value;
+ } else {
+ tab[11].value = auth_user;
+ tab[12].value = t_strcut(auth_user, '@');
+ tab[13].value = strchr(auth_user, '@');
+ }
+ return tab;
+}
+
+static void imap_client_io_activate_user(struct imap_client *client)
+{
+ i_set_failure_prefix("%s", client->log_prefix);
+}
+
+static void imap_client_io_deactivate_user(struct imap_client *client ATTR_UNUSED)
+{
+ i_set_failure_prefix("imap-hibernate: ");
+}
+
+struct imap_client *
+imap_client_create(int fd, const struct imap_client_state *state)
+{
+ struct imap_client *client;
+ pool_t pool = pool_alloconly_create("imap client", 256);
+ void *statebuf;
+
+ i_assert(state->username != NULL);
+ i_assert(state->mail_log_prefix != NULL);
+
+ fd_set_nonblock(fd, TRUE); /* it should already be, but be sure */
+
+ client = p_new(pool, struct imap_client, 1);
+ client->pool = pool;
+ client->fd = fd;
+ client->input = i_stream_create_fd(fd, IMAP_MAX_INBUF, FALSE);
+
+ client->state.username = p_strdup(pool, state->username);
+ 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.local_ip = state->local_ip;
+ client->state.remote_ip = state->remote_ip;
+ client->state.imap_idle_notify_interval =
+ state->imap_idle_notify_interval;
+ client->state.idle_cmd = state->idle_cmd;
+
+ if (state->state_size > 0) {
+ client->state.state = statebuf = p_malloc(pool, state->state_size);
+ memcpy(statebuf, state->state, state->state_size);
+ client->state.state_size = state->state_size;
+ }
+ T_BEGIN {
+ string_t *str;
+
+ str = t_str_new(256);
+ var_expand(str, state->mail_log_prefix,
+ imap_client_get_var_expand_table(client));
+ client->log_prefix = p_strdup(pool, str_c(str));
+ } T_END;
+
+ p_array_init(&client->notifys, pool, 2);
+ DLLIST_PREPEND(&imap_clients, client);
+ return client;
+}
+
+static void imap_client_stop(struct imap_client *client)
+{
+ struct imap_client_notify *notify;
+
+ if (client->io != NULL)
+ io_remove(&client->io);
+ if (client->to_keepalive != NULL)
+ timeout_remove(&client->to_keepalive);
+
+ array_foreach_modifiable(&client->notifys, notify) {
+ if (notify->io != NULL)
+ io_remove(¬ify->io);
+ if (notify->fd != -1)
+ i_close_fd(¬ify->fd);
+ }
+}
+
+void imap_client_destroy(struct imap_client **_client, const char *reason)
+{
+ struct imap_client *client = *_client;
+
+ *_client = NULL;
+
+ if (reason != NULL) {
+ /* the client input/output bytes don't count the DONE+IDLE by
+ imap-hibernate, but that shouldn't matter much. */
+ i_info("%s %s", reason, client->state.stats);
+ }
+
+ if (client->ioloop_ctx != NULL) {
+ io_loop_context_remove_callbacks(client->ioloop_ctx,
+ imap_client_io_activate_user,
+ imap_client_io_deactivate_user, client);
+ imap_client_io_deactivate_user(client);
+ io_loop_context_unref(&client->ioloop_ctx);
+ }
+
+ DLLIST_REMOVE(&imap_clients, client);
+ imap_client_stop(client);
+ i_stream_destroy(&client->input);
+ i_close_fd(&client->fd);
+ pool_unref(&client->pool);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+void imap_client_add_notify_fd(struct imap_client *client, int fd)
+{
+ struct imap_client_notify *notify;
+
+ notify = array_append_space(&client->notifys);
+ notify->fd = fd;
+}
+
+void imap_client_create_finish(struct imap_client *client)
+{
+ struct imap_client_notify *notify;
+
+ client->ioloop_ctx = io_loop_context_new(current_ioloop);
+ io_loop_context_add_callbacks(client->ioloop_ctx,
+ imap_client_io_activate_user,
+ imap_client_io_deactivate_user, client);
+ imap_client_io_activate_user(client);
+
+ if (client->state.idle_cmd) {
+ client->io = io_add(client->fd, IO_READ,
+ imap_client_input_idle_cmd, client);
+ } else {
+ client->io = io_add(client->fd, IO_READ,
+ imap_client_input_nonidle, client);
+ }
+ imap_client_add_idle_keepalive_timeout(client);
+
+ array_foreach_modifiable(&client->notifys, notify) {
+ notify->io = io_add(notify->fd, IO_READ,
+ imap_client_input_notify, client);
+ }
+}
+
+void imap_clients_deinit(void)
+{
+ while (imap_clients != NULL) {
+ struct imap_client *client = imap_clients;
+
+ imap_client_io_activate_user(client);
+ imap_client_destroy(&client, "Shutting down");
+ }
+}
--- /dev/null
+#ifndef IMAP_CLIENT_H
+#define IMAP_CLIENT_H
+
+#include "net.h"
+
+struct imap_client_state {
+ /* required: */
+ const char *username, *mail_log_prefix;
+ /* optional: */
+ const char *session_id, *userdb_fields, *stats;
+ struct ip_addr local_ip, remote_ip;
+
+ uid_t uid;
+ gid_t gid;
+
+ struct ip_addr peer_ip;
+ unsigned int peer_port;
+
+ const unsigned char *state;
+ size_t state_size;
+
+ unsigned int imap_idle_notify_interval;
+ bool idle_cmd;
+ bool have_notify_fd;
+};
+
+struct imap_client *
+imap_client_create(int fd, const struct imap_client_state *state);
+void imap_client_add_notify_fd(struct imap_client *client, int fd);
+void imap_client_create_finish(struct imap_client *client);
+void imap_client_destroy(struct imap_client **_client, const char *reason);
+
+void imap_clients_deinit(void);
+
+#endif
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "connection.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "ostream.h"
+#include "buffer.h"
+#include "base64.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "imap-client.h"
+#include "imap-hibernate-client.h"
+
+struct imap_hibernate_client {
+ struct connection conn;
+ struct imap_client *imap_client;
+ bool imap_client_created;
+ bool debug;
+};
+
+struct imap_hibernate_input {
+ /* input we've already read from the IMAP client. */
+ buffer_t *client_input;
+ /* IMAP connection state */
+ buffer_t *state;
+};
+
+static struct connection_list *hibernate_clients = NULL;
+
+static void imap_hibernate_client_destroy(struct connection *conn)
+{
+ struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn;
+
+ if (!client->imap_client_created)
+ master_service_client_connection_destroyed(master_service);
+ connection_deinit(conn);
+ i_free(conn);
+}
+
+static int
+imap_hibernate_client_parse_input(const char *const *args, pool_t pool,
+ struct imap_client_state *state_r,
+ const char **error_r)
+{
+ const char *key, *value;
+
+ memset(state_r, 0, sizeof(*state_r));
+ if (args[0] == NULL) {
+ *error_r = "Missing username in input";
+ return -1;
+ }
+ state_r->username = args[0]; args++;
+ if (args[0] == NULL) {
+ *error_r = "Missing mail_log_prefix in input";
+ return -1;
+ }
+ state_r->mail_log_prefix = args[0]; args++;
+
+ for (; *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, &state_r->local_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rip") == 0) {
+ if (net_addr2ip(value, &state_r->remote_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_ip") == 0) {
+ if (net_addr2ip(value, &state_r->peer_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_port") == 0) {
+ if (str_to_uint(value, &state_r->peer_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "uid") == 0) {
+ if (str_to_uid(value, &state_r->uid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid uid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "gid") == 0) {
+ if (str_to_gid(value, &state_r->gid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid gid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "stats") == 0) {
+ state_r->stats = value;
+ } else if (strcmp(key, "idle-cmd") == 0) {
+ state_r->idle_cmd = TRUE;
+ } else if (strcmp(key, "session") == 0) {
+ state_r->session_id = value;
+ } else if (strcmp(key, "userdb_fields") == 0) {
+ state_r->userdb_fields = value;
+ } else if (strcmp(key, "notify_fd") == 0) {
+ state_r->have_notify_fd = TRUE;
+ } else if (strcmp(key, "idle_notify_interval") == 0) {
+ if (str_to_uint(value, &state_r->imap_idle_notify_interval) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid idle_notify_interval value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "state") == 0) {
+ buffer_t *state_buf;
+
+ state_buf = buffer_create_dynamic(pool, 1024);
+ if (base64_decode(value, strlen(value), NULL,
+ state_buf) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid state base64 value: %s", value);
+ return -1;
+ }
+ state_r->state = state_buf->data;
+ state_r->state_size = state_buf->used;
+ }
+ }
+ return 0;
+}
+
+static int
+imap_hibernate_client_input_args(struct connection *conn,
+ const char *const *args, int fd, pool_t pool)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ struct imap_client_state state;
+ const char *error;
+
+ if (imap_hibernate_client_parse_input(args, pool, &state, &error) < 0) {
+ i_error("Failed to parse client input: %s", error);
+ o_stream_send_str(conn->output, t_strdup_printf(
+ "-Failed to parse client input: %s\n", error));
+ return -1;
+ }
+ client->imap_client = imap_client_create(fd, &state);
+ /* the transferred imap client fd is now counted as the client. */
+ client->imap_client_created = TRUE;
+ return state.have_notify_fd ? 0 : 1;
+}
+
+static int
+imap_hibernate_client_input_line(struct connection *conn, const char *line)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ int fd = -1, ret;
+
+ if (!conn->version_received) {
+ if (connection_verify_version(conn, t_strsplit_tabescaped(line)) < 0)
+ return -1;
+ conn->version_received = TRUE;
+ return 1;
+ }
+
+ if (client->imap_client == NULL) {
+ char *const *args;
+ pool_t pool;
+
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ i_error("IMAP client fd not received");
+ return -1;
+ }
+
+ pool = pool_alloconly_create("client cmd", 1024);
+ args = p_strsplit_tabescaped(pool, line);
+ ret = imap_hibernate_client_input_args(conn, (void *)args, fd, pool);
+ if (ret >= 0 && client->debug)
+ i_debug("Create client with input: %s", line);
+ pool_unref(&pool);
+ } else {
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ i_error("IMAP notify fd not received (input: %s)", line);
+ ret = -1;
+ } else if (line[0] != '\0') {
+ i_error("Expected empty notify fd line from client, but got: %s", line);
+ o_stream_send_str(conn->output,
+ "Expected empty notify fd line");
+ ret = -1;
+ } else {
+ imap_client_add_notify_fd(client->imap_client, fd);
+ ret = 1;
+ }
+ }
+
+ if (ret < 0) {
+ if (client->imap_client != NULL)
+ imap_client_destroy(&client->imap_client, NULL);
+ if (fd != -1)
+ i_close_fd(&fd);
+ return -1;
+ } else if (ret == 0) {
+ /* still need to read another fd */
+ i_stream_unix_set_read_fd(conn->input);
+ o_stream_send_str(conn->output, "+\n");
+ return 1;
+ } else {
+ /* finished - always disconnect the hibernate client
+ afterwards */
+ o_stream_send_str(conn->output, "+\n");
+ imap_client_create_finish(client->imap_client);
+ return -1;
+ }
+}
+
+void imap_hibernate_client_create(int fd, bool debug)
+{
+ struct imap_hibernate_client *client;
+
+ client = i_new(struct imap_hibernate_client, 1);
+ client->debug = debug;
+ connection_init_server(hibernate_clients, &client->conn,
+ "imap-hibernate", fd, fd);
+
+ i_assert(client->conn.input == NULL);
+ client->conn.input = i_stream_create_unix(fd, (size_t)-1);
+ i_stream_unix_set_read_fd(client->conn.input);
+}
+
+static struct connection_settings client_set = {
+ .service_name_in = "imap-hibernate",
+ .service_name_out = "imap-hibernate",
+ .major_version = 1,
+ .minor_version = 0,
+
+ .input_max_size = 0, /* don't auto-create istream */
+ .output_max_size = (size_t)-1,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_hibernate_client_destroy,
+ .input_line = imap_hibernate_client_input_line
+};
+
+void imap_hibernate_clients_init(void)
+{
+ hibernate_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_hibernate_clients_deinit(void)
+{
+ connection_list_deinit(&hibernate_clients);
+}
--- /dev/null
+#ifndef IMAP_HIBERNATE_CLIENT_H
+#define IMAP_HIBERNATE_CLIENT_H
+
+void imap_hibernate_client_create(int fd, bool debug);
+
+void imap_hibernate_clients_init(void);
+void imap_hibernate_clients_deinit(void);
+
+#endif
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* <settings checks> */
+static struct file_listener_settings imap_hibernate_unix_listeners_array[] = {
+ { "imap-hibernate", 0600, "", "" }
+};
+static struct file_listener_settings *imap_hibernate_unix_listeners[] = {
+ &imap_hibernate_unix_listeners_array[0]
+};
+static buffer_t imap_hibernate_unix_listeners_buf = {
+ imap_hibernate_unix_listeners, sizeof(imap_hibernate_unix_listeners), { NULL, }
+};
+/* </settings checks> */
+
+struct service_settings imap_hibernate_service_settings = {
+ .name = "imap-hibernate",
+ .protocol = "imap",
+ .type = "",
+ .executable = "imap-hibernate",
+ .user = "$default_internal_user",
+ .group = "",
+ .privileged_group = "",
+ .extra_groups = "",
+ .chroot = "",
+
+ .drop_priv_before_exec = FALSE,
+
+ .process_min_avail = 0,
+ .process_limit = 0,
+ .client_limit = 0,
+ .service_count = 0,
+ .idle_kill = 0,
+ .vsz_limit = (uoff_t)-1,
+
+ .unix_listeners = { { &imap_hibernate_unix_listeners_buf,
+ sizeof(imap_hibernate_unix_listeners[0]) } },
+ .fifo_listeners = ARRAY_INIT,
+ .inet_listeners = ARRAY_INIT
+};
--- /dev/null
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "imap-master-connection.h"
+
+#define IMAP_MASTER_CONNECTION_TIMEOUT_MSECS 5000
+
+struct imap_master_connection {
+ struct connection conn;
+ struct timeout *to;
+
+ imap_master_connection_send_callback_t *send_callback;
+ imap_master_connection_read_callback_t *read_callback;
+ void *context;
+};
+
+static struct connection_list *master_clients;
+
+static void imap_master_connection_timeout(struct imap_master_connection *conn)
+{
+ i_error("Timeout communicating with %s", conn->conn.name);
+ imap_master_connection_deinit(&conn);
+}
+
+int imap_master_connection_init(const char *path,
+ imap_master_connection_send_callback_t *send_callback,
+ imap_master_connection_read_callback_t *read_callback,
+ void *context,
+ struct imap_master_connection **conn_r)
+{
+ struct imap_master_connection *conn;
+
+ conn = i_new(struct imap_master_connection, 1);
+ conn->send_callback = send_callback;
+ conn->read_callback = read_callback;
+ conn->context = context;
+ connection_init_client_unix(master_clients, &conn->conn, path);
+ if (connection_client_connect(&conn->conn) < 0) {
+ i_error("net_connect_unix(%s) failed: %m", path);
+ connection_deinit(&conn->conn);
+ i_free(conn);
+ return -1;
+ }
+ conn->to = timeout_add(IMAP_MASTER_CONNECTION_TIMEOUT_MSECS,
+ imap_master_connection_timeout, conn);
+ *conn_r = conn;
+ return 0;
+}
+
+void imap_master_connection_deinit(struct imap_master_connection **_conn)
+{
+ struct imap_master_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (conn->read_callback != NULL)
+ conn->read_callback(conn->context, "-");
+
+ timeout_remove(&conn->to);
+ connection_deinit(&conn->conn);
+ i_free(conn);
+}
+
+static void imap_master_client_destroy(struct connection *_conn)
+{
+ struct imap_master_connection *conn =
+ (struct imap_master_connection *)_conn;
+
+ imap_master_connection_deinit(&conn);
+}
+
+static int
+imap_master_client_input_line(struct connection *_conn, const char *line)
+{
+ struct imap_master_connection *conn =
+ (struct imap_master_connection *)_conn;
+
+ if (!_conn->version_received) {
+ if (connection_input_line_default(_conn, line) < 0)
+ return -1;
+
+ conn->send_callback(conn->context, _conn->output);
+ return 1;
+ } else {
+ imap_master_connection_read_callback_t *read_callback =
+ conn->read_callback;
+
+ conn->read_callback = NULL;
+ read_callback(conn->context, line);
+ /* we're finished now with this connection - disconnect it */
+ return -1;
+ }
+}
+
+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_t)-1,
+ .output_max_size = (size_t)-1,
+ .client = TRUE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_master_client_destroy,
+ .input_line = imap_master_client_input_line
+};
+
+void imap_master_connections_init(void)
+{
+ master_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_master_connections_deinit(void)
+{
+ connection_list_deinit(&master_clients);
+}
--- /dev/null
+#ifndef IMAP_MASTER_CONNECTION_H
+#define IMAP_MASTER_CONNECTION_H
+
+struct imap_master_connection;
+
+typedef void
+imap_master_connection_send_callback_t(void *context, struct ostream *output);
+typedef void
+imap_master_connection_read_callback_t(void *context, const char *reply);
+
+int imap_master_connection_init(const char *path,
+ imap_master_connection_send_callback_t *send_callback,
+ imap_master_connection_read_callback_t *read_callback,
+ void *context,
+ struct imap_master_connection **conn_r);
+void imap_master_connection_deinit(struct imap_master_connection **conn);
+
+void imap_master_connections_init(void);
+void imap_master_connections_deinit(void);
+
+#endif
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "restrict-access.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "imap-client.h"
+#include "imap-hibernate-client.h"
+#include "imap-master-connection.h"
+
+static bool debug = FALSE;
+
+static void client_connected(struct master_service_connection *conn)
+{
+ master_service_client_connection_accept(conn);
+ imap_hibernate_client_create(conn->fd, debug);
+}
+
+int main(int argc, char *argv[])
+{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_UPDATE_PROCTITLE;
+ const char *error;
+ int c;
+
+ master_service = master_service_init("imap-hibernate", service_flags,
+ &argc, &argv, "D");
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+
+ if (master_service_settings_read_simple(master_service, NULL, &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
+
+ master_service_init_log(master_service, "imap-hibernate: ");
+ restrict_access_by_env(NULL, FALSE);
+ restrict_access_allow_coredumps(TRUE);
+
+ imap_master_connections_init();
+ imap_hibernate_clients_init();
+ master_service_init_finish(master_service);
+
+ master_service_run(master_service, client_connected);
+
+ imap_master_connections_deinit();
+ imap_hibernate_clients_deinit();
+ imap_clients_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}
cmd-unselect.c \
cmd-unsubscribe.c \
cmd-urlfetch.c \
- cmd-x-cancel.c
+ cmd-x-cancel.c \
+ cmd-x-state.c
imap_SOURCES = \
$(cmds) \
imap-client.c \
+ imap-client-hibernate.c \
imap-commands.c \
imap-commands-util.c \
imap-expunge.c \
imap-fetch.c \
imap-fetch-body.c \
imap-list.c \
+ imap-master-client.c \
imap-notify.c \
imap-search.c \
imap-search-args.c \
imap-settings.c \
imap-status.c \
+ imap-state.c \
imap-sync.c \
mail-storage-callbacks.c \
main.c
imap-expunge.h \
imap-fetch.h \
imap-list.h \
+ imap-master-client.h \
imap-notify.h \
imap-search.h \
imap-search-args.h \
imap-settings.h \
imap-status.h \
+ imap-state.h \
imap-sync.h
pkginc_libdir=$(pkgincludedir)
/* Copyright (c) 2002-2015 Dovecot authors, see the included COPYING file */
#include "imap-common.h"
-#include "net.h"
-#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "crc32.h"
#include "imap-keepalive.h"
#include "imap-sync.h"
-#include <stdlib.h>
-
struct cmd_idle_context {
struct client *client;
struct client_command_context *cmd;
struct imap_sync_context *sync_ctx;
- struct timeout *keepalive_to;
+ struct timeout *keepalive_to, *to_hibernate;
unsigned int manual_cork:1;
unsigned int sync_pending:1;
if (ctx->keepalive_to != NULL)
timeout_remove(&ctx->keepalive_to);
+ if (ctx->to_hibernate != NULL)
+ timeout_remove(&ctx->to_hibernate);
if (ctx->sync_ctx != NULL) {
/* we're here only in connection failure cases */
ctx->keepalive_to = timeout_add(interval, keepalive_timeout, ctx);
}
+static void idle_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ i_assert(ctx->sync_ctx == NULL);
+ i_assert(!ctx->sync_pending);
+
+ if (imap_client_hibernate(&client)) {
+ /* client may be destroyed now */
+ } else {
+ /* failed - don't bother retrying */
+ timeout_remove(&ctx->to_hibernate);
+ }
+}
+
+static void idle_add_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ unsigned int secs = ctx->client->set->imap_hibernate_timeout;
+
+ i_assert(ctx->to_hibernate == NULL);
+
+ if (secs == 0)
+ return;
+
+ ctx->to_hibernate =
+ timeout_add(secs * 1000, idle_hibernate_timeout, ctx);
+}
+
static bool cmd_idle_continue(struct client_command_context *cmd)
{
struct client *client = cmd->client;
return TRUE;
}
+ if (ctx->to_hibernate != NULL)
+ timeout_reset(ctx->to_hibernate);
+
if (ctx->manual_cork) {
/* we're coming from idle_callback instead of a normal
I/O handler, so we'll have to do corking manually */
so we return here instead of doing everything twice. */
return FALSE;
}
+ if (ctx->to_hibernate == NULL)
+ idle_add_hibernate_timeout(ctx);
cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
if (ctx->manual_cork) {
ctx->cmd = cmd;
ctx->client = client;
idle_add_keepalive_timeout(ctx);
+ idle_add_hibernate_timeout(ctx);
if (client->mailbox != NULL)
mailbox_notify_changes(client->mailbox, idle_callback, ctx);
- client_send_line(client, "+ idling");
+ if (!client->state_import_idle_continue)
+ client_send_line(client, "+ idling");
+ else {
+ /* continuing an IDLE after hibernation */
+ client->state_import_idle_continue = FALSE;
+ }
io_remove(&client->io);
client->io = io_add_istream(client->input, idle_client_input, ctx);
--- /dev/null
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "base64.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "imap-state.h"
+
+bool cmd_x_state(struct client_command_context *cmd)
+{
+ /* FIXME: state importing can cause unnecessarily large memory usage
+ by specifying an old modseq, because the EXPUNGE/FETCH replies
+ aren't currently sent asynchronously. so this command is disabled
+ for now. */
+#if 0
+ const struct imap_arg *args;
+ const char *str, *error;
+ buffer_t *state, *state_encoded;
+ int ret;
+
+ if (!client_read_args(cmd, 0, 0, &args))
+ return FALSE;
+
+ state = buffer_create_dynamic(cmd->pool, 256);
+ if (imap_arg_get_astring(&args[0], &str)) {
+ if (cmd->client->mailbox != NULL) {
+ client_send_tagline(cmd,
+ "BAD Can't be used in SELECTED state");
+ return TRUE;
+ }
+ if (base64_decode(str, strlen(str), NULL, state) < 0)
+ ret = 0;
+ else {
+ ret = imap_state_import_external(cmd->client,
+ state->data, state->used, &error);
+ }
+ if (ret < 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO Failed to restore state: %s", error));
+ } else if (ret == 0) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "BAD Broken state: %s", error));
+ } else {
+ client_send_tagline(cmd, "OK State imported.");
+ }
+ return TRUE;
+ } else if (args[0].type == IMAP_ARG_EOL) {
+ if (!imap_state_export_external(cmd->client, state, &error)) {
+ client_send_tagline(cmd, t_strdup_printf(
+ "NO Can't save state: %s", error));
+ return TRUE;
+ }
+ state_encoded = buffer_create_dynamic(cmd->pool,
+ MAX_BASE64_ENCODED_SIZE(state->used)+10);
+ str_append(state_encoded, "* STATE ");
+ base64_encode(state->data, state->used, state_encoded);
+ client_send_line(cmd->client, str_c(state_encoded));
+ client_send_tagline(cmd, "OK State exported.");
+ return TRUE;
+ } else {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return TRUE;
+ }
+#else
+ client_send_command_error(cmd, "Command is disabled for now.");
+ return TRUE;
+#endif
+}
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "fdpass.h"
+#include "net.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "base64.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "mailbox-watch.h"
+#include "imap-state.h"
+#include "imap-client.h"
+
+#define IMAP_HIBERNATE_SOCKET_NAME "imap-hibernate"
+#define IMAP_HIBERNATE_SEND_TIMEOUT_SECS 10
+#define IMAP_HIBERNATE_HANDSHAKE "VERSION\timap-hibernate\t1\t0\n"
+
+static int imap_hibernate_handshake(int fd, const char *path)
+{
+ char buf[1024];
+ ssize_t ret;
+
+ if (write_full(fd, IMAP_HIBERNATE_HANDSHAKE,
+ strlen(IMAP_HIBERNATE_HANDSHAKE)) < 0) {
+ i_error("write(%s) failed: %m", path);
+ return -1;
+ } else if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
+ i_error("read(%s) failed: %m", path);
+ return -1;
+ } else if (ret > 0 && buf[ret-1] == '\n') {
+ buf[ret-1] = '\0';
+ if (version_string_verify(buf, "imap-hibernate", 1))
+ return 0;
+ }
+ i_error("%s sent invalid VERSION handshake: %s", path, buf);
+ return -1;
+}
+
+static void imap_hibernate_write_cmd(struct client *client, string_t *cmd,
+ const buffer_t *state, int fd_notify)
+{
+ struct ip_addr peer_ip;
+ unsigned int peer_port;
+
+ str_append_tabescaped(cmd, client->user->username);
+ str_append_c(cmd, '\t');
+ str_append_tabescaped(cmd, client->user->set->mail_log_prefix);
+ str_printfa(cmd, "\tidle_notify_interval=%u",
+ client->set->imap_idle_notify_interval);
+ if (net_getpeername(client->fd_in, &peer_ip, &peer_port) == 0) {
+ str_printfa(cmd, "\tpeer_ip=%s\tpeer_port=%u",
+ net_ip2addr(&peer_ip), peer_port);
+ }
+
+ if (client->session_id != NULL) {
+ str_append(cmd, "\tsession=");
+ str_append_tabescaped(cmd, client->session_id);
+ }
+ if (client->user->local_ip != NULL)
+ str_printfa(cmd, "\tlip=%s", net_ip2addr(client->user->local_ip));
+ if (client->user->remote_ip != NULL)
+ str_printfa(cmd, "\trip=%s", net_ip2addr(client->user->remote_ip));
+ if (client->userdb_fields != NULL) {
+ string_t *userdb_fields = t_str_new(256);
+ unsigned int i;
+
+ for (i = 0; client->userdb_fields[i] != NULL; i++) {
+ if (i > 0)
+ str_append_c(userdb_fields, '\t');
+ str_append_tabescaped(userdb_fields, client->userdb_fields[i]);
+ }
+ str_append(cmd, "\tuserdb_fields=");
+ str_append_tabescaped(cmd, str_c(userdb_fields));
+ }
+ if (client->user->uid != (uid_t)-1)
+ str_printfa(cmd, "\tuid=%s", dec2str(client->user->uid));
+ if (client->user->gid != (gid_t)-1)
+ str_printfa(cmd, "\tgid=%s", dec2str(client->user->gid));
+ str_append(cmd, "\tstats=");
+ str_append_tabescaped(cmd, client_stats(client));
+ if (client->command_queue != NULL &&
+ strcasecmp(client->command_queue->name, "IDLE") == 0)
+ str_append(cmd, "\tidle-cmd");
+ if (fd_notify != -1)
+ str_append(cmd, "\tnotify_fd");
+ str_append(cmd, "\tstate=");
+ base64_encode(state->data, state->used, cmd);
+ str_append_c(cmd, '\n');
+}
+
+static int
+imap_hibernate_process_send_cmd(int fd_socket, const char *path,
+ const string_t *cmd, int fd_client)
+{
+ ssize_t ret;
+
+ i_assert(fd_socket != -1);
+ i_assert(str_len(cmd) > 1);
+
+ if (imap_hibernate_handshake(fd_socket, path) < 0)
+ return -1;
+ if ((ret = fd_send(fd_socket, fd_client, str_data(cmd), 1)) < 0) {
+ i_error("fd_send(%s) failed: %m", path);
+ return -1;
+ }
+ if ((ret = write_full(fd_socket, str_data(cmd)+1, str_len(cmd)-1)) < 0) {
+ i_error("write(%s) failed: %m", path);
+ return -1;
+ }
+ return 0;
+}
+
+static int imap_hibernate_process_read(int fd, const char *path)
+{
+ char buf[1024];
+ ssize_t ret;
+
+ if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
+ i_error("read(%s) failed: %m", path);
+ return -1;
+ } else if (ret == 0) {
+ i_error("%s disconnected", path);
+ return -1;
+ } else if (buf[0] != '+') {
+ buf[ret] = '\0';
+ i_error("%s returned failure: %s", path,
+ ret > 0 && buf[0] == '-' ? buf+1 : buf);
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static int
+imap_hibernate_process_send(struct client *client,
+ const buffer_t *state, int fd_notify)
+{
+ string_t *cmd = t_str_new(512);
+ const char *path;
+ ssize_t ret = 0;
+ int fd;
+
+ i_assert(state->used > 0);
+
+ path = t_strconcat(client->user->set->base_dir,
+ "/"IMAP_HIBERNATE_SOCKET_NAME, NULL);
+ fd = net_connect_unix_with_retries(path, 1000);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m", path);
+ return -1;
+ }
+ net_set_nonblock(fd, FALSE);
+
+ imap_hibernate_write_cmd(client, cmd, state, fd_notify);
+
+ alarm(IMAP_HIBERNATE_SEND_TIMEOUT_SECS);
+ if (imap_hibernate_process_send_cmd(fd, path, cmd, client->fd_in) < 0 ||
+ imap_hibernate_process_read(fd, path) < 0)
+ ret = -1;
+ else if (fd_notify != -1) {
+ if ((ret = fd_send(fd, fd_notify, "\n", 1)) < 0)
+ i_error("fd_send(%s) failed: %m", path);
+ else
+ ret = imap_hibernate_process_read(fd, path);
+ }
+ alarm(0);
+ net_disconnect(fd);
+ return ret < 0 ? -1 : 0;
+}
+
+bool imap_client_hibernate(struct client **_client)
+{
+ struct client *client = *_client;
+ buffer_t *state;
+ const char *error;
+ int ret, fd_notify = -1;
+
+ if (client->fd_in != client->fd_out) {
+ /* we won't try to hibernate stdio clients */
+ return FALSE;
+ }
+ if (o_stream_get_buffer_used_size(client->output) > 0) {
+ /* wait until we've sent the pending output to client */
+ return FALSE;
+ }
+
+ state = buffer_create_dynamic(default_pool, 1024);
+ ret = imap_state_export_internal(client, state, &error);
+ if (ret < 0) {
+ i_error("Couldn't hibernate imap client: "
+ "Couldn't export state: %s", error);
+ } else if (ret == 0 && client->user->mail_debug) {
+ i_debug("Couldn't hibernate imap client: "
+ "Couldn't export state: %s", error);
+ }
+ if (ret > 0 && client->mailbox != NULL) {
+ fd_notify = mailbox_watch_extract_notify_fd(client->mailbox,
+ &error);
+ if (fd_notify == -1) {
+ if (client->user->mail_debug) {
+ i_debug("Couldn't hibernate imap client: "
+ "Couldn't extract notifications fd: %s",
+ error);
+ }
+ ret = -1;
+ }
+ }
+ if (ret > 0) {
+ if (imap_hibernate_process_send(client, state, fd_notify) < 0)
+ ret = -1;
+ }
+ if (fd_notify != -1)
+ i_close_fd(&fd_notify);
+ if (ret > 0) {
+ /* hide the disconnect log message, because the client didn't
+ actually log out */
+ client->disconnected = TRUE;
+ client_destroy(client, NULL);
+ *_client = NULL;
+ }
+ buffer_free(&state);
+ return ret > 0;
+}
#include "mail-error.h"
#include "mail-namespace.h"
#include "mail-storage-service.h"
+#include "imap-state.h"
#include "imap-search.h"
#include "imap-notify.h"
#include "imap-commands.h"
}
}
-static const char *client_stats(struct client *client)
+const char *client_stats(struct client *client)
{
static struct var_expand_table static_tab[] = {
{ 'i', NULL, "input" },
return cmd;
}
-static void client_add_missing_io(struct client *client)
+void client_add_missing_io(struct client *client)
{
if (client->io == NULL && !client->disconnected)
client->io = io_add_istream(client->input, client_input, client);
}
struct imap_client_vfuncs imap_client_vfuncs = {
+ imap_state_export_base,
+ imap_state_import_base,
client_default_destroy
};
};
struct imap_client_vfuncs {
+ /* Export client state into buffer. Returns 1 if ok, 0 if some state
+ couldn't be preserved, -1 if temporary internal error occurred. */
+ int (*state_export)(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
+ /* Import a single block of client state from the given data. Returns
+ number of bytes successfully imported from the block, or 0 if state
+ is corrupted or contains unknown data (e.g. some plugin is no longer
+ loaded), -1 if temporary internal error occurred. */
+ ssize_t (*state_import)(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r);
void (*destroy)(struct client *client, const char *reason);
};
struct imap_client_vfuncs v;
const char *session_id;
+ const char *const *userdb_fields; /* for internal session saving/restoring */
int fd_in, fd_out;
struct io *io;
unsigned int notify_flag_changes:1;
unsigned int imap_metadata_enabled:1;
unsigned int nonpermanent_modseqs:1;
+ unsigned int state_import_bad_idle_done:1;
+ unsigned int state_import_idle_continue:1;
};
struct imap_module_register {
bool client_handle_search_save_ambiguity(struct client_command_context *cmd);
int client_enable(struct client *client, enum mailbox_feature features);
+/* Send client processing to imap-idle process. If successful, returns TRUE
+ and destroys the client. */
+bool imap_client_hibernate(struct client **client);
struct imap_search_update *
client_search_update_lookup(struct client *client, const char *tag,
bool client_handle_unfinished_cmd(struct client_command_context *cmd);
void client_continue_pending_input(struct client *client);
+void client_add_missing_io(struct client *client);
+const char *client_stats(struct client *client);
void client_input(struct client *client);
bool client_handle_input(struct client *client);
{ "UID THREAD", cmd_thread, COMMAND_FLAG_BREAKS_SEQS },
{ "UNSELECT", cmd_unselect, COMMAND_FLAG_BREAKS_MAILBOX },
{ "X-CANCEL", cmd_x_cancel, 0 },
+ { "X-STATE", cmd_x_state, COMMAND_FLAG_REQUIRES_SYNC },
{ "XLIST", cmd_list, 0 },
/* IMAP URLAUTH (RFC4467): */
{ "GENURLAUTH", cmd_genurlauth, 0 },
bool cmd_move(struct client_command_context *cmd);
bool cmd_unselect(struct client_command_context *cmd);
bool cmd_x_cancel(struct client_command_context *cmd);
+bool cmd_x_state(struct client_command_context *cmd);
/* IMAP URLAUTH (RFC4467): */
bool cmd_genurlauth(struct client_command_context *cmd);
#include "imap-client.h"
#include "imap-settings.h"
+struct mail_storage_service_input;
+
typedef void imap_client_created_func_t(struct client **client);
extern imap_client_created_func_t *hook_client_created;
+extern bool imap_debug;
/* Sets the hook_client_created and returns the previous hook,
which the new_hook should call if it's non-NULL. */
void imap_refresh_proctitle(void);
+int client_create_from_input(const struct mail_storage_service_input *input,
+ int fd_in, int fd_out, struct client **client_r,
+ const char **error_r);
+
#endif
--- /dev/null
+/* Copyright (c) 2014-2015 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 "strescape.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;
+
+ struct ip_addr peer_ip;
+ unsigned int peer_port;
+
+ 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;
+
+ memset(input_r, 0, sizeof(*input_r));
+ memset(master_input_r, 0, sizeof(*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, "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, "peer_ip") == 0) {
+ if (net_addr2ip(value, &master_input_r->peer_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_port") == 0) {
+ if (str_to_uint32(value, &master_input_r->peer_port) < 0 ||
+ master_input_r->peer_port == 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_port value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "session") == 0) {
+ input_r->session_id = value;
+ } 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, "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;
+ }
+ }
+ return 0;
+}
+
+static int imap_master_client_verify(const struct imap_master_input *master_input,
+ int fd_client, const char **error_r)
+{
+ struct ip_addr peer_ip;
+ unsigned int peer_port;
+
+ if (master_input->peer_port == 0)
+ return 0;
+
+ /* make sure we have the right fd */
+ if (net_getpeername(fd_client, &peer_ip, &peer_port) < 0) {
+ *error_r = t_strdup_printf("net_getpeername() failed: %m");
+ return -1;
+ }
+ if (!net_ip_compare(&peer_ip, &master_input->peer_ip) ||
+ peer_port != master_input->peer_port) {
+ *error_r = t_strdup_printf(
+ "BUG: Expected peer_ip=%s peer_port=%u doesn't match "
+ "client fd's actual ip=%s port=%u",
+ net_ip2addr(&master_input->peer_ip),
+ master_input->peer_port,
+ net_ip2addr(&peer_ip), peer_port);
+ 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;
+ int ret;
+
+ if (imap_master_client_parse_input(args, pool, &input, &master_input,
+ &error) < 0) {
+ i_error("imap-master: Failed to parse client input: %s", error);
+ o_stream_send_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) {
+ i_error("imap-master: Failed to verify client input: %s", error);
+ o_stream_send_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) {
+ i_error("imap-master(%s): Failed to create client: %s",
+ input.username, error);
+ o_stream_send_str(conn->output, t_strdup_printf(
+ "-Failed to create client: %s\n", error));
+ master_service_client_connection_destroyed(master_service);
+ i_close_fd(&fd_client);
+ return -1;
+ }
+ /* log prefix is set at this point, so we don't need to add the
+ username anymore to the log messages */
+ client->imap_client_created = TRUE;
+
+ o_stream_nsend(imap_client->output,
+ master_input.client_output->data,
+ master_input.client_output->used);
+ if (master_input.client_input->used > 0 &&
+ !i_stream_add_data(imap_client->input,
+ master_input.client_input->data,
+ master_input.client_input->used)) {
+ i_error("imap-master: Couldn't add %"PRIuSIZE_T
+ " bytes to client's input stream",
+ master_input.client_input->used);
+ o_stream_send_str(conn->output,
+ "-Couldn't add client input\n");
+ client_destroy(imap_client, "Client initialization failed");
+ return -1;
+ }
+ 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;
+ ret = imap_state_import_internal(imap_client, master_input.state->data,
+ master_input.state->used, &error);
+ if (ret <= 0) {
+ i_error("imap-master: Failed to import client state: %s", error);
+ client_destroy(imap_client, "Client state initialization failed");
+ return -1;
+ }
+
+ /* make sure all pending input gets handled */
+ i_assert(imap_client->to_delayed_input == NULL);
+ if (master_input.client_input->used > 0) {
+ imap_client->to_delayed_input =
+ timeout_add(0, client_input, imap_client);
+ }
+
+ o_stream_send_str(conn->output, "+\n");
+ 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_verify_version(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) {
+ i_error("imap-master: IMAP client fd not received");
+ return -1;
+ }
+
+ if (imap_debug)
+ i_debug("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, (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);
+ connection_init_server(master_clients, &client->conn,
+ "imap-master", fd, fd);
+
+ i_assert(client->conn.input == NULL);
+ client->conn.input = i_stream_create_unix(fd, (size_t)-1);
+ /* 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 = 0, /* don't auto-create istream */
+ .output_max_size = (size_t)-1,
+ .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);
+}
--- /dev/null
+#ifndef IMAP_MASTER_CLIENT_H
+#define IMAP_MASTER_CLIENT_H
+
+void imap_master_client_create(int fd);
+
+void imap_master_clients_init(void);
+void imap_master_clients_deinit(void);
+
+#endif
/* <settings checks> */
static struct file_listener_settings imap_unix_listeners_array[] = {
- { "login/imap", 0666, "", "" }
+ { "login/imap", 0666, "", "" },
+ { "imap-master", 0600, "", "" }
};
static struct file_listener_settings *imap_unix_listeners[] = {
- &imap_unix_listeners_array[0]
+ &imap_unix_listeners_array[0],
+ &imap_unix_listeners_array[1]
};
static buffer_t imap_unix_listeners_buf = {
imap_unix_listeners, sizeof(imap_unix_listeners), { NULL, }
DEF(SET_STR, imap_id_send),
DEF(SET_STR, imap_id_log),
DEF(SET_BOOL, imap_metadata),
+ DEF(SET_TIME, imap_hibernate_timeout),
DEF(SET_STR, imap_urlauth_host),
DEF(SET_UINT, imap_urlauth_port),
.imap_id_send = "name *",
.imap_id_log = "",
.imap_metadata = FALSE,
+ .imap_hibernate_timeout = 0,
.imap_urlauth_host = "",
.imap_urlauth_port = 143
const char *imap_id_send;
const char *imap_id_log;
bool imap_metadata;
+ unsigned int imap_hibernate_timeout;
/* imap urlauth: */
const char *imap_urlauth_host;
--- /dev/null
+/* Copyright (c) 2014-2015 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "crc32.h"
+#include "numpack.h"
+#include "net.h"
+#include "ostream.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "imap-util.h"
+#include "mail-search-build.h"
+#include "mail-storage.h"
+#include "mailbox-recent-flags.h"
+#include "imap-client.h"
+#include "imap-fetch.h"
+#include "imap-search-args.h"
+#include "imap-state.h"
+
+enum imap_state_type_public {
+ IMAP_STATE_TYPE_MAILBOX = 'B',
+ IMAP_STATE_TYPE_ENABLED_FEATURES = 'F',
+ IMAP_STATE_TYPE_SEARCHRES = '1',
+};
+
+enum imap_state_type_internal {
+ IMAP_STATE_TYPE_ID_LOGGED = 'I',
+ IMAP_STATE_TYPE_TLS_COMPRESSION = 'C',
+ IMAP_STATE_TYPE_IDLE_CMD_TAG = 'T'
+};
+
+enum imap_state_feature {
+ IMAP_STATE_FEATURE_CONDSTORE = 'C',
+ IMAP_STATE_FEATURE_QRESYNC = 'Q'
+};
+
+struct mailbox_import_state {
+ const char *vname;
+ guid_128_t mailbox_guid;
+ bool examined;
+ uint32_t keywords_count, keywords_crc32, uids_crc32;
+ uint32_t uidvalidity, uidnext, messages;
+ uint64_t highest_modseq;
+ ARRAY_TYPE(seq_range) recent_uids;
+};
+
+static void
+export_seq_range(buffer_t *dest, const ARRAY_TYPE(seq_range) *range)
+{
+ const struct seq_range *uids;
+ unsigned int i, count;
+ uint32_t next_uid;
+
+ uids = array_get(range, &count);
+ numpack_encode(dest, count);
+ next_uid = 1;
+ for (i = 0; i < count; i++) {
+ i_assert(uids[i].seq1 >= next_uid);
+ if (uids[i].seq1 == uids[i].seq2) {
+ numpack_encode(dest, (uids[i].seq1 - next_uid) << 1);
+ } else {
+ numpack_encode(dest, 1 | ((uids[i].seq1 - next_uid) << 1));
+ numpack_encode(dest, uids[i].seq2 - uids[i].seq1 - 1);
+ }
+ next_uid = uids[i].seq2 + 1;
+ }
+}
+
+static int
+import_seq_range(const unsigned char **data, const unsigned char *end,
+ ARRAY_TYPE(seq_range) *range)
+{
+ uint32_t i, count, next_uid, num, uid1, uid2;
+
+ if (numpack_decode32(data, end, &count) < 0)
+ return -1;
+ next_uid = 1;
+
+ for (i = 0; i < count; i++) {
+ if (numpack_decode32(data, end, &num) < 0)
+ return -1;
+ uid1 = next_uid + (num >> 1);
+ if ((num & 1) == 0) {
+ uid2 = uid1;
+ seq_range_array_add(range, uid1);
+ } else {
+ if (numpack_decode32(data, end, &num) < 0)
+ return -1;
+ uid2 = uid1 + num + 1;
+ seq_range_array_add_range(range, uid1, uid2);
+ }
+ next_uid = uid2 + 1;
+ }
+ return 0;
+}
+
+int imap_state_export_internal(struct client *client, buffer_t *dest,
+ const char **error_r)
+{
+ /* the only IMAP command we allow running is IDLE or X-STATE */
+ if (client->command_queue_size > 1) {
+ *error_r = "Multiple commands in progress";
+ return 0;
+ }
+ if (client->command_queue == NULL ||
+ strcasecmp(client->command_queue->name, "IDLE") != 0) {
+ /* this would require saving the seq <-> uid mapping
+ and restore it on import. quite a lot of trouble if
+ messages have been expunged in the mean time. */
+ *error_r = "Non-IDLE connections not supported currently";
+ return 0;
+ }
+ return client->v.state_export(client, TRUE, dest, error_r);
+}
+
+int imap_state_export_external(struct client *client, buffer_t *dest,
+ const char **error_r)
+{
+ if (client->command_queue_size > 1) {
+ *error_r = "Multiple commands in progress";
+ return 0;
+ }
+
+ i_assert(client->command_queue_size == 1);
+ i_assert(strcmp(client->command_queue->name, "X-STATE") == 0);
+ return client->v.state_export(client, FALSE, dest, error_r);
+}
+
+static int
+imap_state_import(struct client *client, bool internal,
+ const unsigned char *data, size_t size, const char **error_r)
+{
+ ssize_t ret;
+
+ while (size > 0) {
+ ret = client->v.state_import(client, internal,
+ data, size, error_r);
+ if (ret <= 0) {
+ i_assert(*error_r != NULL);
+ return ret < 0 ? -1 : 0;
+ }
+ i_assert((size_t)ret <= size);
+ data += ret;
+ size -= ret;
+ }
+ return 1;
+}
+
+int imap_state_import_internal(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ return imap_state_import(client, TRUE, data, size, error_r);
+}
+
+int imap_state_import_external(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ return imap_state_import(client, FALSE, data, size, error_r);
+}
+
+static int
+imap_state_export_mailbox_mails(buffer_t *dest, struct mailbox *box,
+ const char **error_r)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ ARRAY_TYPE(seq_range) recent_uids;
+ uint32_t crc = 0;
+ int ret = 1;
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(box, 0);
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ t_array_init(&recent_uids, 8);
+ while (mailbox_search_next(search_ctx, &mail)) {
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ if ((mail_get_flags(mail) & MAIL_RECENT) != 0)
+ seq_range_array_add(&recent_uids, mail->uid);
+ }
+ if (mailbox_search_deinit(&search_ctx) < 0) {
+ *error_r = mailbox_get_last_error(box, NULL);
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&trans);
+
+ numpack_encode(dest, crc);
+ export_seq_range(dest, &recent_uids);
+ return ret;
+}
+
+static uint32_t
+mailbox_status_keywords_crc32(const struct mailbox_status *status)
+{
+ const char *const *strp;
+ uint32_t crc = 0;
+
+ array_foreach(status->keywords, strp)
+ crc = crc32_str(*strp);
+ return crc;
+}
+
+static int
+imap_state_export_mailbox(buffer_t *dest, struct client *client,
+ struct mailbox *box, const char **error_r)
+{
+ struct mailbox_status status;
+ struct mailbox_metadata metadata;
+ const char *vname = mailbox_get_vname(box);
+
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT |
+ STATUS_MESSAGES | STATUS_HIGHESTMODSEQ |
+ STATUS_KEYWORDS,
+ &status);
+ if (status.nonpermanent_modseqs) {
+ *error_r = "Nonpermanent modseqs";
+ return 0;
+ }
+
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+ *error_r = mailbox_get_last_error(box, NULL);
+ return -1;
+ }
+
+ buffer_append_c(dest, IMAP_STATE_TYPE_MAILBOX);
+ buffer_append(dest, vname, strlen(vname)+1);
+ buffer_append(dest, metadata.guid, sizeof(metadata.guid));
+
+ buffer_append_c(dest, client->mailbox_examined ? 1 : 0);
+ numpack_encode(dest, status.uidvalidity);
+ numpack_encode(dest, status.uidnext);
+ numpack_encode(dest, status.messages);
+ if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0 &&
+ !client->nonpermanent_modseqs)
+ numpack_encode(dest, client->sync_last_full_modseq);
+ else
+ numpack_encode(dest, status.highest_modseq);
+
+ /* keywords count + CRC32 should be enough to figure out if it
+ needs to be resent */
+ numpack_encode(dest, array_count(status.keywords));
+ numpack_encode(dest, mailbox_status_keywords_crc32(&status));
+
+ /* we're now basically done, but just in case there's a bug add a
+ checksum of the currently existing UIDs and verify it when
+ importing. this also writes the list of recent UIDs. */
+ return imap_state_export_mailbox_mails(dest, box, error_r);
+}
+
+int imap_state_export_base(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r)
+{
+ int ret;
+
+ str_append(dest, "base\n");
+ if (array_is_created(&client->search_updates) &&
+ array_count(&client->search_updates) > 0) {
+ /* these could be tricky */
+ *error_r = "SEARCH=CONTEXT updates not supported currently";
+ return 0;
+ }
+ if (client->notify_ctx != NULL) {
+ /* FIXME: this really should be supported. also IDLE wouldn't
+ be needed if NOTIFY allows sending EXPUNGEs to selected
+ mailbox. */
+ *error_r = "NOTIFY not supported currently";
+ return 0;
+ }
+
+ if (client->mailbox != NULL) {
+ ret = imap_state_export_mailbox(dest, client,
+ client->mailbox, error_r);
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* IMAP features */
+ if (client->enabled_features != 0) {
+ i_assert((client->enabled_features & ~(MAILBOX_FEATURE_CONDSTORE |
+ MAILBOX_FEATURE_QRESYNC)) == 0);
+ buffer_append_c(dest, IMAP_STATE_TYPE_ENABLED_FEATURES);
+ if ((client->enabled_features & MAILBOX_FEATURE_CONDSTORE) != 0)
+ buffer_append_c(dest, IMAP_STATE_FEATURE_CONDSTORE);
+ if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0)
+ buffer_append_c(dest, IMAP_STATE_FEATURE_QRESYNC);
+ buffer_append_c(dest, '\0');
+ }
+ if (internal) {
+ if (client->id_logged)
+ buffer_append_c(dest, IMAP_STATE_TYPE_ID_LOGGED);
+ if (client->tls_compression)
+ buffer_append_c(dest, IMAP_STATE_TYPE_TLS_COMPRESSION);
+ if (client->command_queue != NULL) {
+ i_assert(strcasecmp(client->command_queue->name, "IDLE") == 0);
+ buffer_append_c(dest, IMAP_STATE_TYPE_IDLE_CMD_TAG);
+ buffer_append(dest, client->command_queue->tag,
+ strlen(client->command_queue->tag) + 1);
+ }
+ }
+
+ /* IMAP SEARCHRES extension */
+ if (array_is_created(&client->search_saved_uidset) &&
+ array_count(&client->search_saved_uidset) > 0) {
+ buffer_append_c(dest, IMAP_STATE_TYPE_SEARCHRES);
+ export_seq_range(dest, &client->search_saved_uidset);
+ }
+ return 1;
+}
+
+static int
+import_string(const unsigned char **data, const unsigned char *end,
+ const char **str_r)
+{
+ const unsigned char *p;
+
+ p = memchr(*data, '\0', end - *data);
+ if (p == NULL)
+ return -1;
+ *str_r = (const void *)*data;
+ *data = p + 1;
+ return 0;
+}
+
+static int
+import_send_expunges(struct client *client,
+ const struct mailbox_import_state *state,
+ unsigned int *expunge_count_r,
+ const char **error_r)
+{
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ uint32_t crc = 0, seq, expunged_uid;
+ ARRAY_TYPE(seq_range) uids_filter, expunged_uids;
+ ARRAY_TYPE(uint32_t) expunged_seqs;
+ struct seq_range_iter iter;
+ const uint32_t *seqs;
+ unsigned int i, expunge_count, n = 0;
+ string_t *str;
+ int ret = 0;
+
+ *expunge_count_r = 0;
+
+ if (state->messages == 0) {
+ /* the mailbox was empty originally - there couldn't be any
+ pending expunges. */
+ return 0;
+ }
+ if (state->uidnext <= 1) {
+ *error_r = "Invalid UIDNEXT";
+ return -1;
+ }
+
+ /* get all the message UIDs expunged since the last known modseq */
+ t_array_init(&uids_filter, 1);
+ t_array_init(&expunged_uids, 128);
+ seq_range_array_add_range(&uids_filter, 1, state->uidnext-1);
+ if (!mailbox_get_expunged_uids(client->mailbox, state->highest_modseq,
+ &uids_filter, &expunged_uids)) {
+ *error_r = t_strdup_printf(
+ "Couldn't get recently expunged UIDs "
+ "(uidnext=%u highest_modseq=%llu)", state->uidnext,
+ (unsigned long long)state->highest_modseq);
+ return -1;
+ }
+ seq_range_array_iter_init(&iter, &expunged_uids);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(client->mailbox, 0);
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ /* find sequence numbers for the expunged UIDs */
+ t_array_init(&expunged_seqs, array_count(&expunged_uids)+1); seq = 0;
+ while (mailbox_search_next(search_ctx, &mail)) {
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid) &&
+ expunged_uid < mail->uid) {
+ seq++; n++;
+ array_append(&expunged_seqs, &seq, 1);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ }
+ if (seq == state->messages)
+ break;
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ if (++seq == state->messages)
+ break;
+ }
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid)) {
+ seq++; n++;
+ array_append(&expunged_seqs, &seq, 1);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ }
+
+ if (mailbox_search_deinit(&search_ctx) < 0) {
+ *error_r = mailbox_get_last_error(client->mailbox, NULL);
+ ret = -1;
+ } else if (seq != state->messages) {
+ *error_r = "Message count mismatch after handling expunges";
+ ret = -1;
+ }
+ (void)mailbox_transaction_commit(&trans);
+ if (ret < 0)
+ return -1;
+
+ seqs = array_get(&expunged_seqs, &expunge_count);
+ if (client->messages_count + expunge_count < state->messages) {
+ *error_r = "Message count too low after handling expunges";
+ return -1;
+ }
+ if (crc != state->uids_crc32) {
+ *error_r = "Message UIDs CRC32 mismatch";
+ return -1;
+ }
+
+ if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) == 0) {
+ str = t_str_new(32);
+ for (i = expunge_count; i > 0; i--) {
+ str_truncate(str, 0);
+ str_printfa(str, "* %u EXPUNGE", seqs[i-1]);
+ client_send_line(client, str_c(str));
+ }
+ } else {
+ str = str_new(default_pool, 128);
+ str_append(str, "* VANISHED ");
+ imap_write_seq_range(str, &expunged_uids);
+ str_append(str, "\r\n");
+ o_stream_nsend(client->output, str_data(str), str_len(str));
+ str_free(&str);
+ }
+ *expunge_count_r = expunge_count;
+ return 0;
+}
+
+static int
+import_send_flag_changes(struct client *client,
+ const struct mailbox_import_state *state)
+{
+ struct imap_fetch_context *fetch_ctx;
+ struct mail_search_args *search_args;
+ ARRAY_TYPE(seq_range) old_uids;
+ pool_t pool;
+ int ret;
+
+ if (state->messages == 0)
+ return 0;
+
+ t_array_init(&old_uids, 1);
+ seq_range_array_add_range(&old_uids, 1, state->uidnext-1);
+
+ search_args = mail_search_build_init();
+ search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
+ search_args->args->type = SEARCH_UIDSET;
+ search_args->args->value.seqset = old_uids;
+ imap_search_add_changed_since(search_args, state->highest_modseq);
+
+ pool = pool_alloconly_create("imap state flag changes", 1024);
+ fetch_ctx = imap_fetch_alloc(client, pool);
+ pool_unref(&pool);
+
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
+ if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) {
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
+ imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_modseq_init);
+ }
+
+ imap_fetch_begin(fetch_ctx, client->mailbox, search_args);
+ mail_search_args_unref(&search_args);
+
+ /* FIXME: ideally do this asynchronously.. */
+ while (imap_fetch_more_no_lock_update(fetch_ctx) == 0) ;
+
+ ret = imap_fetch_end(fetch_ctx);
+ imap_fetch_free(&fetch_ctx);
+ return ret;
+}
+
+static ssize_t
+import_state_mailbox_struct(const unsigned char *data, size_t size,
+ struct mailbox_import_state *state_r,
+ const char **error_r)
+{
+ const unsigned char *p = data, *end = data + size;
+
+ memset(state_r, 0, sizeof(*state_r));
+ t_array_init(&state_r->recent_uids, 8);
+
+ /* vname */
+ if (import_string(&p, end, &state_r->vname) < 0) {
+ *error_r = "Mailbox state truncated at name";
+ return 0;
+ }
+
+ /* GUID */
+ if (end-p < (int)sizeof(state_r->mailbox_guid)) {
+ *error_r = "Mailbox state truncated at GUID";
+ return 0;
+ }
+ memcpy(state_r->mailbox_guid, p, sizeof(state_r->mailbox_guid));
+ p += sizeof(state_r->mailbox_guid);
+
+ if (guid_128_is_empty(state_r->mailbox_guid)) {
+ *error_r = "Empty GUID";
+ return 0;
+ }
+
+ /* EXAMINEd vs SELECTed */
+ if (p == end) {
+ *error_r = "Mailbox state truncated at examined-flag";
+ return 0;
+ }
+ state_r->examined = p[0] != 0;
+ p++;
+
+ /* mailbox state */
+ if (numpack_decode32(&p, end, &state_r->uidvalidity) < 0 ||
+ numpack_decode32(&p, end, &state_r->uidnext) < 0 ||
+ numpack_decode32(&p, end, &state_r->messages) < 0 ||
+ numpack_decode(&p, end, &state_r->highest_modseq) < 0 ||
+ numpack_decode32(&p, end, &state_r->keywords_count) < 0 ||
+ numpack_decode32(&p, end, &state_r->keywords_crc32) < 0 ||
+ numpack_decode32(&p, end, &state_r->uids_crc32) < 0 ||
+ import_seq_range(&p, end, &state_r->recent_uids) < 0) {
+ *error_r = "Mailbox state truncated";
+ return 0;
+ }
+ if (state_r->uidvalidity == 0) {
+ *error_r = "Empty UIDVALIDITY";
+ return 0;
+ }
+ if (state_r->uidnext == 0) {
+ *error_r = "Empty UIDNEXT";
+ return 0;
+ }
+ return p - data;
+}
+
+static int
+import_state_mailbox_open(struct client *client,
+ const struct mailbox_import_state *state,
+ const char **error_r)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ const struct seq_range *range;
+ enum mailbox_flags flags = 0;
+ unsigned int expunge_count;
+ uint32_t uid;
+ int ret = 0;
+
+ ns = mail_namespace_find(client->user->namespaces, state->vname);
+ if (ns == NULL) {
+ *error_r = "Namespace not found for mailbox";
+ return -1;
+ }
+
+ if (state->examined)
+ flags |= MAILBOX_FLAG_READONLY;
+ else
+ flags |= MAILBOX_FLAG_DROP_RECENT;
+ box = mailbox_alloc(ns->list, state->vname, flags);
+ if (mailbox_open(box) < 0) {
+ *error_r = t_strdup_printf("Couldn't open mailbox: %s",
+ mailbox_get_last_error(box, NULL));
+ mailbox_free(&box);
+ return -1;
+ }
+
+ if (client->enabled_features != 0)
+ ret = mailbox_enable(box, client->enabled_features);
+ if (ret < 0 || mailbox_sync(box, 0) < 0) {
+ *error_r = t_strdup_printf("Couldn't sync mailbox: %s",
+ mailbox_get_last_error(box, NULL));
+ mailbox_free(&box);
+ return -1;
+ }
+ /* verify that this still looks like the same mailbox */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+ *error_r = mailbox_get_last_error(box, NULL);
+ return -1;
+ }
+ if (!guid_128_equals(metadata.guid, state->mailbox_guid)) {
+ *error_r = "Mailbox GUID has changed";
+ return -1;
+ }
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT |
+ STATUS_HIGHESTMODSEQ | STATUS_RECENT |
+ STATUS_KEYWORDS, &status);
+ if (status.uidvalidity != state->uidvalidity) {
+ *error_r = "Mailbox UIDVALIDITY has changed";
+ return -1;
+ }
+ if (status.uidnext < state->uidnext) {
+ *error_r = "Mailbox UIDNEXT shrank";
+ return -1;
+ }
+ if (status.highest_modseq < state->highest_modseq) {
+ *error_r = "Mailbox HIGHESTMODSEQ shrank";
+ return -1;
+ }
+
+ client->mailbox = box;
+ client->mailbox_examined = state->examined;
+ client->messages_count = status.messages;
+ client->recent_count = status.recent;
+ client->uidvalidity = status.uidvalidity;
+ client->notify_uidnext = status.uidnext;
+
+ if (import_send_expunges(client, state, &expunge_count, error_r) < 0)
+ return -1;
+ i_assert(expunge_count <= state->messages);
+ if (state->messages - expunge_count > client->messages_count) {
+ *error_r = "Mailbox message count shrank";
+ return -1;
+ }
+ if (state->messages - expunge_count < client->messages_count) {
+ /* new messages arrived */
+ client_send_line(client,
+ t_strdup_printf("* %u EXISTS", client->messages_count));
+ client_send_line(client,
+ t_strdup_printf("* %u RECENT", client->recent_count));
+ }
+
+ client_update_mailbox_flags(client, status.keywords);
+ array_foreach(&state->recent_uids, range) {
+ for (uid = range->seq1; uid <= range->seq2; uid++)
+ mailbox_recent_flags_set_uid_forced(box, uid);
+ }
+ if (array_count(status.keywords) == state->keywords_count &&
+ mailbox_status_keywords_crc32(&status) == state->keywords_crc32) {
+ /* no changes to keywords */
+ client->keywords.announce_count = state->keywords_count;
+ } else {
+ client_send_mailbox_flags(client, TRUE);
+ }
+ if (import_send_flag_changes(client, state) < 0) {
+ *error_r = "Couldn't send flag changes";
+ return -1;
+ }
+ if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0 &&
+ !client->nonpermanent_modseqs &&
+ status.highest_modseq != state->highest_modseq) {
+ client_send_line(client, t_strdup_printf(
+ "* OK [HIGHESTMODSEQ %llu] Highest",
+ (unsigned long long)status.highest_modseq));
+ client->sync_last_full_modseq = status.highest_modseq;
+ }
+ return 0;
+}
+
+static ssize_t
+import_state_mailbox(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ struct mailbox_import_state state;
+ ssize_t ret;
+
+ if (client->mailbox != NULL) {
+ *error_r = "Duplicate mailbox state";
+ return 0;
+ }
+
+ ret = import_state_mailbox_struct(data, size, &state, error_r);
+ if (ret <= 0) {
+ i_assert(*error_r != NULL);
+ return ret;
+ }
+ if (import_state_mailbox_open(client, &state, error_r) < 0)
+ return -1;
+ return ret;
+}
+
+static ssize_t
+import_state_enabled_features(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ enum imap_state_feature feature;
+ size_t i = 0;
+
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\0')
+ return i+1;
+ feature = data[i];
+ switch (feature) {
+ case IMAP_STATE_FEATURE_CONDSTORE:
+ client->enabled_features |= MAILBOX_FEATURE_CONDSTORE;
+ break;
+ case IMAP_STATE_FEATURE_QRESYNC:
+ client->enabled_features |= MAILBOX_FEATURE_QRESYNC;
+ break;
+ default:
+ *error_r = t_strdup_printf(
+ "Unknown feature '%c'", feature);
+ return 0;
+ }
+
+ }
+ *error_r = "Non-terminated features list";
+ return 0;
+}
+
+static ssize_t
+import_state_searchres(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ const unsigned char *p = data;
+
+ i_array_init(&client->search_saved_uidset, 128);
+ if (import_seq_range(&p, data+size, &client->search_saved_uidset) < 0) {
+ *error_r = "Invalid SEARCHRES seq-range";
+ return 0;
+ }
+ return p - data;
+}
+
+static ssize_t
+import_state_id_logged(struct client *client,
+ const unsigned char *data ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ client->id_logged = TRUE;
+ return 0;
+}
+
+static ssize_t
+import_state_tls_compression(struct client *client,
+ const unsigned char *data ATTR_UNUSED,
+ size_t size ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ client->tls_compression = TRUE;
+ return 0;
+}
+
+static ssize_t
+import_state_idle_cmd_tag(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ const unsigned char *p = data, *end = data + size;
+ const char *tag;
+
+ if (import_string(&p, end, &tag) < 0) {
+ *error_r = "Missing idle tag";
+ return 0;
+ }
+
+ if (client->state_import_idle_continue) {
+ /* IDLE command continues */
+ struct client_command_context *cmd;
+ struct command *command;
+
+ cmd = client_command_alloc(client);
+ cmd->tag = p_strdup(cmd->pool, tag);
+ cmd->name = "IDLE";
+
+ command = command_find("IDLE");
+ i_assert(command != NULL);
+ cmd->func = command->func;
+ cmd->cmd_flags = command->flags;
+ if (command_exec(cmd)) {
+ /* IDLE terminated because of an external change, but
+ DONE was already buffered */
+ client_command_free(&cmd);
+ client_add_missing_io(client);
+ } else {
+ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
+ }
+ } else {
+ /* we're finishing IDLE command */
+ client_send_line(client, t_strdup_printf(
+ "%s %s Idle completed.", tag,
+ client->state_import_bad_idle_done ? "BAD" : "OK"));
+ }
+ return p - data;
+}
+
+static struct {
+ enum imap_state_type_public type;
+ ssize_t (*import)(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r);
+} imap_states_public[] = {
+ { IMAP_STATE_TYPE_MAILBOX, import_state_mailbox },
+ { IMAP_STATE_TYPE_ENABLED_FEATURES, import_state_enabled_features },
+ { IMAP_STATE_TYPE_SEARCHRES, import_state_searchres }
+};
+
+static struct {
+ enum imap_state_type_public type;
+ ssize_t (*import)(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r);
+} imap_states_internal[] = {
+ { IMAP_STATE_TYPE_ID_LOGGED, import_state_id_logged },
+ { IMAP_STATE_TYPE_TLS_COMPRESSION, import_state_tls_compression },
+ { IMAP_STATE_TYPE_IDLE_CMD_TAG, import_state_idle_cmd_tag },
+};
+
+static ssize_t
+imap_state_try_import_public(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ unsigned int i;
+ ssize_t ret;
+
+ i_assert(size > 0);
+
+ for (i = 0; i < N_ELEMENTS(imap_states_public); i++) {
+ if (imap_states_public[i].type == data[0]) {
+ ret = imap_states_public[i].
+ import(client, data+1, size-1, error_r);
+ return ret < 0 ? -1 : ret+1;
+ }
+ }
+ return -2;
+}
+
+static ssize_t
+imap_state_try_import_internal(struct client *client, const unsigned char *data,
+ size_t size, const char **error_r)
+{
+ unsigned int i;
+ ssize_t ret;
+
+ i_assert(size > 0);
+
+ for (i = 0; i < N_ELEMENTS(imap_states_internal); i++) {
+ if (imap_states_internal[i].type == data[0]) {
+ ret = imap_states_internal[i].
+ import(client, data+1, size-1, error_r);
+ return ret < 0 ? -1 : ret+1;
+ }
+ }
+ return -2;
+}
+
+ssize_t imap_state_import_base(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r)
+{
+ const unsigned char *p;
+ ssize_t ret;
+ size_t pos;
+
+ i_assert(client->mailbox == NULL);
+
+ *error_r = NULL;
+
+ if (size < 5 || memcmp(data, "base\n", 5) != 0) {
+ p = memchr(data, '\n', size);
+ if (p == NULL)
+ p = data + I_MIN(size, 20);
+ *error_r = t_strdup_printf("Unknown state block '%s'",
+ str_sanitize(t_strdup_until(data, p), 20));
+ return 0;
+ }
+
+ pos = 5;
+ while (pos < size) {
+ ret = imap_state_try_import_public(client, data+pos,
+ size-pos, error_r);
+ if (ret == -2 && internal) {
+ ret = imap_state_try_import_internal(client, data+pos,
+ size-pos, error_r);
+ }
+ if (ret < 0 || *error_r != NULL) {
+ if (ret == -2) {
+ *error_r = t_strdup_printf("Unknown type '%c'",
+ data[pos]);
+ }
+ i_assert(*error_r != NULL);
+ return ret < 0 ? -1 : 0;
+ }
+ i_assert(size - pos >= (size_t)ret);
+ pos += ret;
+ }
+ return pos;
+}
--- /dev/null
+#ifndef IMAP_STATE_H
+#define IMAP_STATE_H
+
+/* Export the IMAP client state to the given buffer. Returns 1 if ok,
+ 0 if state couldn't be exported, -1 if temporary internal error error. */
+int imap_state_export_internal(struct client *client, buffer_t *dest,
+ const char **error_r);
+int imap_state_export_external(struct client *client, buffer_t *dest,
+ const char **error_r);
+
+/* Returns 1 if ok, 0 if state was corrupted, -1 if other error. Internal state
+ comes from another Dovecot component, which can override IP addresses,
+ session IDs, etc. */
+int imap_state_import_internal(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+int imap_state_import_external(struct client *client,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+
+/* INTERNAL API: Note that the "internal" flag specifies whether we're doing
+ the import/export from/to another Dovecot component or an untrusted
+ IMAP client. */
+int imap_state_export_base(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
+ssize_t imap_state_import_base(struct client *client, bool internal,
+ const unsigned char *data, size_t size,
+ const char **error_r);
+
+#endif
#include "master-login.h"
#include "mail-user.h"
#include "mail-storage-service.h"
+#include "imap-master-client.h"
#include "imap-resp-code.h"
#include "imap-commands.h"
#include "imap-fetch.h"
static struct master_login *master_login = NULL;
imap_client_created_func_t *hook_client_created = NULL;
+bool imap_debug = FALSE;
imap_client_created_func_t *
imap_client_created_hook_set(imap_client_created_func_t *new_hook)
o_stream_unref(&output);
}
-static int
-client_create_from_input(const struct mail_storage_service_input *input,
- int fd_in, int fd_out,
- struct client **client_r, const char **error_r)
+int client_create_from_input(const struct mail_storage_service_input *input,
+ int fd_in, int fd_out,
+ struct client **client_r, const char **error_r)
{
struct mail_storage_service_user *user;
struct mail_user *mail_user;
client = client_create(fd_in, fd_out, input->session_id,
mail_user, user, set);
+ client->userdb_fields = input->userdb_fields == NULL ? NULL :
+ p_strarray_dup(client->pool, input->userdb_fields);
*client_r = client;
return 0;
}
i_assert(master_login != NULL);
master_service_client_connection_accept(conn);
- master_login_add(master_login, conn->fd);
+ if (strcmp(conn->name, "imap-master") == 0) {
+ /* restoring existing IMAP connection (e.g. from imap-idle) */
+ imap_master_client_create(conn->fd);
+ } else {
+ master_login_add(master_login, conn->fd);
+ }
}
int main(int argc, char *argv[])
}
master_service = master_service_init("imap", service_flags,
- &argc, &argv, "t:u:");
+ &argc, &argv, "Dt:u:");
while ((c = master_getopt(master_service)) > 0) {
switch (c) {
case 't':
MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
username = optarg;
break;
+ case 'D':
+ imap_debug = TRUE;
+ break;
default:
return FATAL_DEFAULT;
}
/* plugins may want to add commands, so this needs to be called early */
commands_init();
imap_fetch_handlers_init();
+ imap_master_clients_init();
random_init();
storage_service =
imap_fetch_handlers_deinit();
commands_deinit();
+ imap_master_clients_deinit();
random_deinit();
master_service_deinit(&master_service);
{ '\0', NULL, "auth_user" },
{ '\0', NULL, "auth_username" },
{ '\0', NULL, "auth_domain" },
+ /* NOTE: keep this synced with imap-hibernate's
+ imap_client_var_expand_table() */
{ '\0', NULL, NULL }
};
struct var_expand_table *tab;
struct zlib_client {
union imap_module_context module_ctx;
+ int (*next_state_export)(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r);
const struct compression_handler *handler;
};
return TRUE;
}
+static int
+imap_zlib_state_export(struct client *client, bool internal,
+ buffer_t *dest, const char **error_r)
+{
+ struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
+
+ if (zclient->handler != NULL && internal) {
+ *error_r = "COMPRESS enabled";
+ return 0;
+ }
+ return zclient->next_state_export(client, internal, dest, error_r);
+}
+
static void imap_zlib_client_created(struct client **clientp)
{
struct client *client = *clientp;
zclient = p_new(client->pool, struct zlib_client, 1);
MODULE_CONTEXT_SET(client, imap_zlib_imap_module, zclient);
+ zclient->next_state_export = (*clientp)->v.state_export;
+ (*clientp)->v.state_export = imap_zlib_state_export;
+
str_append(client->capability_string, " COMPRESS=DEFLATE");
}