summaryrefslogtreecommitdiffstats
path: root/src/lib-imap-client/imapc-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-imap-client/imapc-client.c')
-rw-r--r--src/lib-imap-client/imapc-client.c584
1 files changed, 584 insertions, 0 deletions
diff --git a/src/lib-imap-client/imapc-client.c b/src/lib-imap-client/imapc-client.c
new file mode 100644
index 0000000..5806a80
--- /dev/null
+++ b/src/lib-imap-client/imapc-client.c
@@ -0,0 +1,584 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "safe-mkstemp.h"
+#include "iostream-ssl.h"
+#include "imapc-msgmap.h"
+#include "imapc-connection.h"
+#include "imapc-client-private.h"
+
+#include <unistd.h>
+
+const char *imapc_command_state_names[] = {
+ "OK", "NO", "BAD", "(auth failed)", "(disconnected)"
+
+};
+
+const struct imapc_capability_name imapc_capability_names[] = {
+ { "SASL-IR", IMAPC_CAPABILITY_SASL_IR },
+ { "LITERAL+", IMAPC_CAPABILITY_LITERALPLUS },
+ { "QRESYNC", IMAPC_CAPABILITY_QRESYNC },
+ { "IDLE", IMAPC_CAPABILITY_IDLE },
+ { "UIDPLUS", IMAPC_CAPABILITY_UIDPLUS },
+ { "AUTH=PLAIN", IMAPC_CAPABILITY_AUTH_PLAIN },
+ { "STARTTLS", IMAPC_CAPABILITY_STARTTLS },
+ { "X-GM-EXT-1", IMAPC_CAPABILITY_X_GM_EXT_1 },
+ { "CONDSTORE", IMAPC_CAPABILITY_CONDSTORE },
+ { "NAMESPACE", IMAPC_CAPABILITY_NAMESPACE },
+ { "UNSELECT", IMAPC_CAPABILITY_UNSELECT },
+ { "ESEARCH", IMAPC_CAPABILITY_ESEARCH },
+ { "WITHIN", IMAPC_CAPABILITY_WITHIN },
+ { "QUOTA", IMAPC_CAPABILITY_QUOTA },
+ { "ID", IMAPC_CAPABILITY_ID },
+ { "SAVEDATE", IMAPC_CAPABILITY_SAVEDATE },
+
+ { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 },
+ { NULL, 0 }
+};
+
+unsigned int imapc_client_cmd_tag_counter = 0;
+
+static void
+default_untagged_callback(const struct imapc_untagged_reply *reply ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+}
+
+struct imapc_client *
+imapc_client_init(const struct imapc_client_settings *set,
+ struct event *event_parent)
+{
+ struct imapc_client *client;
+ const char *error;
+ pool_t pool;
+
+ i_assert(set->connect_retry_count == 0 ||
+ set->connect_retry_interval_msecs > 0);
+
+ pool = pool_alloconly_create("imapc client", 1024);
+ client = p_new(pool, struct imapc_client, 1);
+ client->pool = pool;
+ client->refcount = 1;
+ client->event = event_create(event_parent);
+
+ client->set.debug = set->debug;
+ client->set.host = p_strdup(pool, set->host);
+ client->set.port = set->port;
+ client->set.master_user = p_strdup_empty(pool, set->master_user);
+ client->set.username = p_strdup(pool, set->username);
+ client->set.password = p_strdup(pool, set->password);
+ client->set.sasl_mechanisms = p_strdup(pool, set->sasl_mechanisms);
+ client->set.session_id_prefix = p_strdup(pool, set->session_id_prefix);
+ client->set.use_proxyauth = set->use_proxyauth;
+ client->set.dns_client_socket_path =
+ p_strdup(pool, set->dns_client_socket_path);
+ client->set.temp_path_prefix =
+ p_strdup(pool, set->temp_path_prefix);
+ client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);
+ client->set.max_idle_time = set->max_idle_time;
+ client->set.connect_timeout_msecs = set->connect_timeout_msecs != 0 ?
+ set->connect_timeout_msecs :
+ IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS;
+ client->set.connect_retry_count = set->connect_retry_count;
+ client->set.connect_retry_interval_msecs = set->connect_retry_interval_msecs;
+ client->set.cmd_timeout_msecs = set->cmd_timeout_msecs != 0 ?
+ set->cmd_timeout_msecs : IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS;
+ client->set.max_line_length = set->max_line_length != 0 ?
+ set->max_line_length : IMAPC_DEFAULT_MAX_LINE_LENGTH;
+ client->set.throttle_set = set->throttle_set;
+
+ if (client->set.throttle_set.init_msecs == 0)
+ client->set.throttle_set.init_msecs = IMAPC_THROTTLE_DEFAULT_INIT_MSECS;
+ if (client->set.throttle_set.max_msecs == 0)
+ client->set.throttle_set.max_msecs = IMAPC_THROTTLE_DEFAULT_MAX_MSECS;
+ if (client->set.throttle_set.shrink_min_msecs == 0)
+ client->set.throttle_set.shrink_min_msecs = IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS;
+
+ if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) {
+ client->set.ssl_mode = set->ssl_mode;
+ ssl_iostream_settings_init_from(pool, &client->set.ssl_set, &set->ssl_set);
+ client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert;
+ if (ssl_iostream_client_context_cache_get(&client->set.ssl_set,
+ &client->ssl_ctx,
+ &error) < 0) {
+ i_error("imapc(%s:%u): Couldn't initialize SSL context: %s",
+ set->host, set->port, error);
+ }
+ }
+ client->untagged_callback = default_untagged_callback;
+
+ p_array_init(&client->conns, pool, 8);
+ return client;
+}
+
+void imapc_client_ref(struct imapc_client *client)
+{
+ i_assert(client->refcount > 0);
+
+ client->refcount++;
+}
+
+void imapc_client_unref(struct imapc_client **_client)
+{
+ struct imapc_client *client = *_client;
+
+ *_client = NULL;
+
+ i_assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return;
+
+ if (client->ssl_ctx != NULL)
+ ssl_iostream_context_unref(&client->ssl_ctx);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+}
+
+void imapc_client_disconnect(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *conns, *conn;
+ unsigned int i, count;
+
+ conns = array_get(&client->conns, &count);
+ for (i = count; i > 0; i--) {
+ conn = conns[i-1];
+ array_delete(&client->conns, i-1, 1);
+
+ i_assert(imapc_connection_get_mailbox(conn->conn) == NULL);
+ imapc_connection_deinit(&conn->conn);
+ i_free(conn);
+ }
+}
+
+void imapc_client_deinit(struct imapc_client **_client)
+{
+ struct imapc_client *client = *_client;
+
+ imapc_client_disconnect(client);
+ imapc_client_unref(_client);
+}
+
+void imapc_client_register_untagged(struct imapc_client *client,
+ imapc_untagged_callback_t *callback,
+ void *context)
+{
+ client->untagged_callback = callback;
+ client->untagged_context = context;
+}
+
+static void imapc_client_run_pre(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ struct ioloop *prev_ioloop = current_ioloop;
+
+ i_assert(client->ioloop == NULL);
+
+ client->ioloop = io_loop_create();
+ io_loop_set_running(client->ioloop);
+
+ array_foreach_elem(&client->conns, conn) {
+ imapc_connection_ioloop_changed(conn->conn);
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED)
+ imapc_connection_connect(conn->conn);
+ }
+
+ if (io_loop_is_running(client->ioloop))
+ io_loop_run(client->ioloop);
+ io_loop_set_current(prev_ioloop);
+}
+
+static void imapc_client_run_post(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ struct ioloop *ioloop = client->ioloop;
+
+ client->ioloop = NULL;
+ array_foreach_elem(&client->conns, conn)
+ imapc_connection_ioloop_changed(conn->conn);
+
+ io_loop_set_current(ioloop);
+ io_loop_destroy(&ioloop);
+}
+
+void imapc_client_run(struct imapc_client *client)
+{
+ imapc_client_run_pre(client);
+ imapc_client_run_post(client);
+}
+
+void imapc_client_stop(struct imapc_client *client)
+{
+ if (client->ioloop != NULL)
+ io_loop_stop(client->ioloop);
+}
+
+void imapc_client_try_stop(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ array_foreach_elem(&client->conns, conn)
+ if (imapc_connection_get_state(conn->conn) != IMAPC_CONNECTION_STATE_DISCONNECTED)
+ return;
+ imapc_client_stop(client);
+}
+
+bool imapc_client_is_running(struct imapc_client *client)
+{
+ return client->ioloop != NULL;
+}
+
+static void imapc_client_login_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_client_connection *conn = context;
+ struct imapc_client *client = conn->client;
+ struct imapc_client_mailbox *box = conn->box;
+
+ if (box != NULL && box->reconnecting) {
+ box->reconnecting = FALSE;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ /* reopen the mailbox */
+ box->reopen_callback(box->reopen_context);
+ } else {
+ imapc_connection_abort_commands(box->conn, NULL, FALSE);
+ }
+ }
+
+ /* call the login callback only once */
+ if (client->login_callback != NULL) {
+ imapc_command_callback_t *callback = client->login_callback;
+ void *context = client->login_context;
+
+ client->login_callback = NULL;
+ client->login_context = NULL;
+ callback(reply, context);
+ }
+}
+
+static struct imapc_client_connection *
+imapc_client_add_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+
+ conn = i_new(struct imapc_client_connection, 1);
+ conn->client = client;
+ conn->conn = imapc_connection_init(client, imapc_client_login_callback,
+ conn);
+ array_push_back(&client->conns, &conn);
+ return conn;
+}
+
+static struct imapc_connection *
+imapc_client_find_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *connp;
+
+ /* FIXME: stupid algorithm */
+ if (array_count(&client->conns) == 0)
+ return imapc_client_add_connection(client)->conn;
+ connp = array_front(&client->conns);
+ return (*connp)->conn;
+}
+
+struct imapc_command *
+imapc_client_cmd(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_connection *conn;
+
+ conn = imapc_client_find_connection(client);
+ return imapc_connection_cmd(conn, callback, context);
+}
+
+static struct imapc_client_connection *
+imapc_client_get_unboxed_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *conns;
+ unsigned int i, count;
+
+ conns = array_get(&client->conns, &count);
+ for (i = 0; i < count; i++) {
+ if (conns[i]->box == NULL)
+ return conns[i];
+ }
+ return imapc_client_add_connection(client);
+}
+
+
+void imapc_client_login(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+
+ i_assert(client->login_callback != NULL);
+ i_assert(array_count(&client->conns) == 0);
+
+ conn = imapc_client_add_connection(client);
+ imapc_connection_connect(conn->conn);
+}
+
+struct imapc_logout_ctx {
+ struct imapc_client *client;
+ unsigned int logout_count;
+};
+
+static void
+imapc_client_logout_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_logout_ctx *ctx = context;
+
+ i_assert(ctx->logout_count > 0);
+
+ if (--ctx->logout_count == 0)
+ imapc_client_stop(ctx->client);
+}
+
+void imapc_client_logout(struct imapc_client *client)
+{
+ struct imapc_logout_ctx ctx = { .client = client };
+ struct imapc_client_connection *conn;
+ struct imapc_command *cmd;
+
+ client->logging_out = TRUE;
+
+ /* send LOGOUT to all connections */
+ array_foreach_elem(&client->conns, conn) {
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED)
+ continue;
+ imapc_connection_set_no_reconnect(conn->conn);
+ ctx.logout_count++;
+ cmd = imapc_connection_cmd(conn->conn,
+ imapc_client_logout_callback, &ctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN |
+ IMAPC_COMMAND_FLAG_LOGOUT);
+ imapc_command_send(cmd, "LOGOUT");
+ }
+
+ /* wait for LOGOUT to finish */
+ while (ctx.logout_count > 0)
+ imapc_client_run(client);
+
+ /* we should have disconnected all clients already, but if there were
+ any timeouts there may be some clients left. */
+ imapc_client_disconnect(client);
+}
+
+struct imapc_client_mailbox *
+imapc_client_mailbox_open(struct imapc_client *client,
+ void *untagged_box_context)
+{
+ struct imapc_client_mailbox *box;
+ struct imapc_client_connection *conn;
+
+ box = i_new(struct imapc_client_mailbox, 1);
+ box->client = client;
+ box->untagged_box_context = untagged_box_context;
+ conn = imapc_client_get_unboxed_connection(client);
+ conn->box = box;
+ box->conn = conn->conn;
+ box->msgmap = imapc_msgmap_init();
+ /* if we get disconnected before the SELECT is finished, allow
+ one reconnect retry. */
+ box->reconnect_ok = TRUE;
+ return box;
+}
+
+void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box,
+ void (*callback)(void *context),
+ void *context)
+{
+ box->reopen_callback = callback;
+ box->reopen_context = context;
+}
+
+bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box)
+{
+ /* the reconnect_ok flag attempts to avoid infinite reconnection loops
+ to a server that keeps disconnecting us (e.g. some of the commands
+ we send keeps crashing it always) */
+ return box->reopen_callback != NULL && box->reconnect_ok;
+}
+
+void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box,
+ const char *errmsg)
+{
+ imapc_connection_try_reconnect(box->conn, errmsg, 0, FALSE);
+}
+
+void imapc_client_mailbox_close(struct imapc_client_mailbox **_box)
+{
+ struct imapc_client_mailbox *box = *_box;
+ struct imapc_client_connection *conn;
+
+ box->closing = TRUE;
+
+ /* cancel any pending commands */
+ imapc_connection_unselect(box, FALSE);
+
+ if (box->reconnecting) {
+ /* need to abort the reconnection so it won't try to access
+ the box */
+ imapc_connection_disconnect(box->conn);
+ }
+
+ /* set this only after unselect, which may cancel some commands that
+ reference this box */
+ *_box = NULL;
+
+ array_foreach_elem(&box->client->conns, conn) {
+ if (conn->box == box) {
+ conn->box = NULL;
+ break;
+ }
+ }
+
+ imapc_msgmap_deinit(&box->msgmap);
+ timeout_remove(&box->to_send_idle);
+ i_free(box);
+}
+
+struct imapc_command *
+imapc_client_mailbox_cmd(struct imapc_client_mailbox *box,
+ imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_command *cmd;
+
+ i_assert(!box->closing);
+
+ cmd = imapc_connection_cmd(box->conn, callback, context);
+ imapc_command_set_mailbox(cmd, box);
+ return cmd;
+}
+
+struct imapc_msgmap *
+imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box)
+{
+ return box->msgmap;
+}
+
+static void imapc_client_mailbox_idle_send(struct imapc_client_mailbox *box)
+{
+ timeout_remove(&box->to_send_idle);
+ if (imapc_client_mailbox_is_opened(box))
+ imapc_connection_idle(box->conn);
+}
+
+void imapc_client_mailbox_idle(struct imapc_client_mailbox *box)
+{
+ /* send the IDLE with a delay to avoid unnecessary IDLEs that are
+ immediately aborted */
+ if (box->to_send_idle == NULL && imapc_client_mailbox_is_opened(box)) {
+ box->to_send_idle =
+ timeout_add_short(IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS,
+ imapc_client_mailbox_idle_send, box);
+ }
+ /* we're done with all work at this point. */
+ box->reconnect_ok = TRUE;
+}
+
+bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box)
+{
+ struct imapc_client_mailbox *selected_box;
+
+ if (box->closing ||
+ imapc_connection_get_state(box->conn) != IMAPC_CONNECTION_STATE_DONE)
+ return FALSE;
+
+ selected_box = imapc_connection_get_mailbox(box->conn);
+ if (selected_box != box) {
+ if (selected_box != NULL)
+ i_error("imapc: Selected mailbox changed unexpectedly");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+imapc_client_get_any_capabilities(struct imapc_client *client,
+ enum imapc_capability *capabilities_r)
+{
+ struct imapc_client_connection *conn;
+
+ array_foreach_elem(&client->conns, conn) {
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DONE) {
+ *capabilities_r = imapc_connection_get_capabilities(conn->conn);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+int imapc_client_get_capabilities(struct imapc_client *client,
+ enum imapc_capability *capabilities_r)
+{
+ /* try to find a connection that is already logged in */
+ if (imapc_client_get_any_capabilities(client, capabilities_r))
+ return 0;
+
+ /* if there are no connections yet, create one */
+ if (array_count(&client->conns) == 0)
+ (void)imapc_client_add_connection(client);
+
+ /* wait for any of the connections to login */
+ client->stop_on_state_finish = TRUE;
+ imapc_client_run(client);
+ client->stop_on_state_finish = FALSE;
+ if (imapc_client_get_any_capabilities(client, capabilities_r))
+ return 0;
+
+ /* failed */
+ return -1;
+}
+
+int imapc_client_create_temp_fd(struct imapc_client *client,
+ const char **path_r)
+{
+ string_t *path;
+ int fd;
+
+ if (client->set.temp_path_prefix == NULL) {
+ i_error("imapc: temp_path_prefix not set, "
+ "can't create temp file");
+ return -1;
+ }
+
+ path = t_str_new(128);
+ str_append(path, client->set.temp_path_prefix);
+ fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+
+ /* we just want the fd, unlink it */
+ if (i_unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ i_close_fd(&fd);
+ return -1;
+ }
+ *path_r = str_c(path);
+ return fd;
+}
+
+void imapc_client_register_state_change_callback(struct imapc_client *client,
+ imapc_state_change_callback_t *cb,
+ void *context)
+{
+ i_assert(client->state_change_callback == NULL);
+ i_assert(client->state_change_context == NULL);
+
+ client->state_change_callback = cb;
+ client->state_change_context = context;
+}
+
+void
+imapc_client_set_login_callback(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context)
+{
+ client->login_callback = callback;
+ client->login_context = context;
+}
+