Mercurial > dovecot > core-2.2
changeset 13554:743ebecc1224
Moved imapc-client into its own lib-imap-client library.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 26 Sep 2011 15:34:58 +0300 |
parents | af13ef62d083 |
children | fe89e95867a4 |
files | configure.in src/Makefile.am src/lib-imap-client/Makefile.am src/lib-imap-client/imapc-client-private.h src/lib-imap-client/imapc-client.c src/lib-imap-client/imapc-client.h src/lib-imap-client/imapc-connection.c src/lib-imap-client/imapc-connection.h src/lib-imap-client/imapc-msgmap.c src/lib-imap-client/imapc-msgmap.h src/lib-storage/index/imapc/Makefile.am src/lib-storage/index/imapc/imapc-client-private.h src/lib-storage/index/imapc/imapc-client.c src/lib-storage/index/imapc/imapc-client.h src/lib-storage/index/imapc/imapc-connection.c src/lib-storage/index/imapc/imapc-connection.h src/lib-storage/index/imapc/imapc-msgmap.c src/lib-storage/index/imapc/imapc-msgmap.h |
diffstat | 18 files changed, 2584 insertions(+), 2461 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Sun Sep 25 22:32:04 2011 +0000 +++ b/configure.in Mon Sep 26 15:34:58 2011 +0300 @@ -2435,7 +2435,7 @@ sdbox_libs='$(top_builddir)/src/lib-storage/index/dbox-single/libstorage_dbox_single.la' mdbox_libs='$(top_builddir)/src/lib-storage/index/dbox-multi/libstorage_dbox_multi.la' cydir_libs='$(top_builddir)/src/lib-storage/index/cydir/libstorage_cydir.la' -imapc_libs='$(top_builddir)/src/lib-storage/index/imapc/libstorage_imapc.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la' +imapc_libs='$(top_builddir)/src/lib-storage/index/imapc/libstorage_imapc.la $(top_builddir)/src/lib-imap-client/libimap_client.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la' raw_libs='$(top_builddir)/src/lib-storage/index/raw/libstorage_raw.la' shared_libs='$(top_builddir)/src/lib-storage/index/shared/libstorage_shared.la' @@ -2719,6 +2719,7 @@ src/lib-dns/Makefile src/lib-fs/Makefile src/lib-imap/Makefile +src/lib-imap-client/Makefile src/lib-index/Makefile src/lib-lda/Makefile src/lib-mail/Makefile
--- a/src/Makefile.am Sun Sep 25 22:32:04 2011 +0000 +++ b/src/Makefile.am Mon Sep 26 15:34:58 2011 +0300 @@ -14,6 +14,7 @@ lib-test \ $(LIBDOVECOT_SUBDIRS) \ lib-ssl-iostream \ + lib-imap-client \ lib-dovecot \ lib-index \ lib-storage \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/Makefile.am Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,22 @@ +noinst_LTLIBRARIES = libimap_client.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap + +libimap_client_la_SOURCES = \ + imapc-client.c \ + imapc-connection.c \ + imapc-msgmap.c + +headers = \ + imapc-client.h \ + imapc-client-private.h \ + imapc-connection.h \ + imapc-msgmap.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-client-private.h Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,40 @@ +#ifndef IMAPC_CLIENT_PRIVATE_H +#define IMAPC_CLIENT_PRIVATE_H + +#include "imapc-client.h" + +struct imapc_client_connection { + struct imapc_connection *conn; + struct imapc_client_mailbox *box; +}; + +struct imapc_client { + pool_t pool; + int refcount; + + struct imapc_client_settings set; + struct ssl_iostream_context *ssl_ctx; + + imapc_untagged_callback_t *untagged_callback; + void *untagged_context; + + ARRAY_DEFINE(conns, struct imapc_client_connection *); + + struct ioloop *ioloop; + + unsigned int stop_now:1; +}; + +struct imapc_client_mailbox { + struct imapc_client *client; + struct imapc_connection *conn; + struct imapc_msgmap *msgmap; + + void *untagged_box_context; + unsigned int pending_box_command_count; +}; + +void imapc_client_ref(struct imapc_client *client); +void imapc_client_unref(struct imapc_client **client); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-client.c Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,444 @@ +/* Copyright (c) 2011 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> + +struct imapc_client_command_context { + struct imapc_client_mailbox *box; + + imapc_command_callback_t *callback; + void *context; +}; + +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 }, + + { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 }, + { NULL, 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 imapc_client *client; + struct ssl_iostream_settings ssl_set; + const char *source; + pool_t pool; + + pool = pool_alloconly_create("imapc client", 1024); + client = p_new(pool, struct imapc_client, 1); + client->pool = pool; + client->refcount = 1; + + client->set.debug = set->debug; + client->set.host = p_strdup(pool, set->host); + client->set.port = set->port; + client->set.master_user = p_strdup(pool, set->master_user); + client->set.username = p_strdup(pool, set->username); + client->set.password = p_strdup(pool, set->password); + 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); + + if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) { + client->set.ssl_mode = set->ssl_mode; + client->set.ssl_ca_dir = p_strdup(pool, set->ssl_ca_dir); + + memset(&ssl_set, 0, sizeof(ssl_set)); + ssl_set.ca_dir = set->ssl_ca_dir; + ssl_set.verify_remote_cert = TRUE; + + source = t_strdup_printf("%s:%u", set->host, set->port); + if (ssl_iostream_context_init_client(source, &ssl_set, + &client->ssl_ctx) < 0) { + i_error("imapc(%s): Couldn't initialize SSL context", + source); + } + } + 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; + + i_assert(client->refcount > 0); + if (--client->refcount > 0) + return; + + if (client->ssl_ctx != NULL) + ssl_iostream_context_deinit(&client->ssl_ctx); + pool_unref(&client->pool); +} + +void imapc_client_deinit(struct imapc_client **_client) +{ + struct imapc_client *client = *_client; + struct imapc_client_connection **connp; + + array_foreach_modifiable(&client->conns, connp) { + imapc_connection_deinit(&(*connp)->conn); + i_free(*connp); + } + array_clear(&client->conns); + 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; +} + +void imapc_client_run_pre(struct imapc_client *client) +{ + struct imapc_client_connection *const *connp; + struct ioloop *prev_ioloop = current_ioloop; + bool handle_pending = client->stop_now; + + i_assert(client->ioloop == NULL); + + client->stop_now = FALSE; + + client->ioloop = io_loop_create(); + io_loop_set_running(client->ioloop); + + array_foreach(&client->conns, connp) { + imapc_connection_ioloop_changed((*connp)->conn); + imapc_connection_connect((*connp)->conn, NULL, NULL); + if (handle_pending) + imapc_connection_input_pending((*connp)->conn); + } + + if (io_loop_is_running(client->ioloop)) + io_loop_run(client->ioloop); + current_ioloop = prev_ioloop; +} + +void imapc_client_run_post(struct imapc_client *client) +{ + struct imapc_client_connection *const *connp; + struct ioloop *ioloop = client->ioloop; + + client->ioloop = NULL; + array_foreach(&client->conns, connp) + imapc_connection_ioloop_changed((*connp)->conn); + + current_ioloop = ioloop; + io_loop_destroy(&ioloop); +} + +void imapc_client_stop(struct imapc_client *client) +{ + if (client->ioloop != NULL) + io_loop_stop(client->ioloop); +} + +bool imapc_client_is_running(struct imapc_client *client) +{ + return client->ioloop != NULL; +} + +void imapc_client_stop_now(struct imapc_client *client) +{ + client->stop_now = TRUE; + imapc_client_stop(client); +} + +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->conn = imapc_connection_init(client); + array_append(&client->conns, &conn, 1); + 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_idx(&client->conns, 0); + return (*connp)->conn; +} + +void imapc_client_cmdf(struct imapc_client *client, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, ...) +{ + struct imapc_connection *conn; + va_list args; + + conn = imapc_client_find_connection(client); + + va_start(args, cmd_fmt); + imapc_connection_cmdvf(conn, FALSE, callback, context, cmd_fmt, args); + va_end(args); +} + +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, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_client_connection *conn; + + i_assert(array_count(&client->conns) == 0); + + conn = imapc_client_add_connection(client); + imapc_connection_connect(conn->conn, callback, context); +} + +struct imapc_client_mailbox * +imapc_client_mailbox_open(struct imapc_client *client, + const char *name, bool examine, + imapc_command_callback_t *callback, void *context, + 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(); + + imapc_connection_select(box, name, examine, callback, context); + return box; +} + +void imapc_client_mailbox_disconnect(struct imapc_client_mailbox *box) +{ + if (box->conn != NULL) + imapc_connection_disconnect(box->conn); +} + +void imapc_client_mailbox_close(struct imapc_client_mailbox **_box) +{ + struct imapc_client_mailbox *box = *_box; + struct imapc_client_connection *const *connp; + + array_foreach(&box->client->conns, connp) { + if ((*connp)->box == box) { + (*connp)->box = NULL; + break; + } + } + + if (box->conn != NULL) + imapc_connection_unselect(box); + imapc_msgmap_deinit(&box->msgmap); + i_free(box); + + /* set this only after unselect, which may cancel some commands that + reference this box */ + *_box = NULL; +} + +static void imapc_client_mailbox_cmd_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_client_command_context *ctx = context; + + ctx->box->pending_box_command_count--; + + ctx->callback(reply, ctx->context); + i_free(ctx); +} + +static struct imapc_client_command_context * +imapc_client_mailbox_cmd_common(struct imapc_client_mailbox *box, + imapc_command_callback_t *callback, + void *context) +{ + struct imapc_client_command_context *ctx; + + ctx = i_new(struct imapc_client_command_context, 1); + ctx->box = box; + ctx->callback = callback; + ctx->context = context; + + box->pending_box_command_count++; + return ctx; +} + +static bool +imapc_client_mailbox_is_selected(struct imapc_client_mailbox *box, + struct imapc_command_reply *reply_r) +{ + struct imapc_client_mailbox *selected_box; + + selected_box = box->conn == NULL ? NULL : + imapc_connection_get_mailbox(box->conn); + if (selected_box == box) + return TRUE; + + memset(reply_r, 0, sizeof(*reply_r)); + reply_r->state = IMAPC_COMMAND_STATE_DISCONNECTED; + if (selected_box == NULL) { + reply_r->text_full = "Disconnected from server"; + } else { + i_error("imapc: Selected mailbox changed unexpectedly"); + reply_r->text_full = "Internal error"; + } + reply_r->text_without_resp = reply_r->text_full; + + box->conn = NULL; + return FALSE; +} + +void imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, + const char *cmd, + imapc_command_callback_t *callback, + void *context) +{ + struct imapc_client_command_context *ctx; + struct imapc_command_reply reply; + + if (!imapc_client_mailbox_is_selected(box, &reply)) { + callback(&reply, context); + return; + } + + ctx = imapc_client_mailbox_cmd_common(box, callback, context); + imapc_connection_cmd(box->conn, TRUE, cmd, + imapc_client_mailbox_cmd_cb, ctx); +} + +void imapc_client_mailbox_cmdf(struct imapc_client_mailbox *box, + imapc_command_callback_t *callback, + void *context, const char *cmd_fmt, ...) +{ + struct imapc_client_command_context *ctx; + va_list args; + struct imapc_command_reply reply; + + if (!imapc_client_mailbox_is_selected(box, &reply)) { + callback(&reply, context); + return; + } + + ctx = imapc_client_mailbox_cmd_common(box, callback, context); + va_start(args, cmd_fmt); + imapc_connection_cmdvf(box->conn, TRUE, imapc_client_mailbox_cmd_cb, + ctx, cmd_fmt, args); + va_end(args); +} + +struct imapc_msgmap * +imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box) +{ + return box->msgmap; +} + +void imapc_client_mailbox_idle(struct imapc_client_mailbox *box) +{ + struct imapc_command_reply reply; + + if (imapc_client_mailbox_is_selected(box, &reply)) + imapc_connection_idle(box->conn); +} + +bool imapc_client_mailbox_is_connected(struct imapc_client_mailbox *box) +{ + struct imapc_command_reply reply; + + return imapc_client_mailbox_is_selected(box, &reply); +} + +enum imapc_capability +imapc_client_get_capabilities(struct imapc_client *client) +{ + struct imapc_client_connection *const *connp; + + connp = array_idx(&client->conns, 0); + return imapc_connection_get_capabilities((*connp)->conn); +} + +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 (unlink(str_c(path)) < 0) { + /* shouldn't happen.. */ + i_error("unlink(%s) failed: %m", str_c(path)); + (void)close(fd); + return -1; + } + *path_r = str_c(path); + return fd; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-client.h Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,155 @@ +#ifndef IMAPC_CLIENT_H +#define IMAPC_CLIENT_H + +enum imapc_command_state { + IMAPC_COMMAND_STATE_OK, + IMAPC_COMMAND_STATE_NO, + IMAPC_COMMAND_STATE_BAD, + IMAPC_COMMAND_STATE_DISCONNECTED +}; + +enum imapc_capability { + IMAPC_CAPABILITY_SASL_IR = 0x01, + IMAPC_CAPABILITY_LITERALPLUS = 0x02, + IMAPC_CAPABILITY_QRESYNC = 0x04, + IMAPC_CAPABILITY_IDLE = 0x08, + IMAPC_CAPABILITY_UIDPLUS = 0x10, + IMAPC_CAPABILITY_AUTH_PLAIN = 0x20, + IMAPC_CAPABILITY_STARTTLS = 0x40, + + IMAPC_CAPABILITY_IMAP4REV1 = 0x400000000 +}; +struct imapc_capability_name { + const char *name; + enum imapc_capability capability; +}; +extern const struct imapc_capability_name imapc_capability_names[]; + +enum imapc_client_ssl_mode { + IMAPC_CLIENT_SSL_MODE_NONE, + IMAPC_CLIENT_SSL_MODE_IMMEDIATE, + IMAPC_CLIENT_SSL_MODE_STARTTLS +}; + +struct imapc_client_settings { + const char *host; + unsigned int port; + + const char *master_user; + const char *username; + const char *password; + + const char *dns_client_socket_path; + const char *temp_path_prefix; + + enum imapc_client_ssl_mode ssl_mode; + const char *ssl_ca_dir; + + const char *rawlog_dir; + bool debug; +}; + +struct imapc_command_reply { + enum imapc_command_state state; + /* "[RESP TEXT]" produces key=RESP, value=TEXT. + "[RESP]" produces key=RESP, value=NULL + otherwise both are NULL */ + const char *resp_text_key, *resp_text_value; + /* The full tagged reply, including [RESP TEXT]. */ + const char *text_full; + /* Tagged reply text without [RESP TEXT] */ + const char *text_without_resp; +}; + +struct imapc_arg_file { + /* file descriptor containing the value */ + int fd; + + /* parent_arg.list[list_idx] points to the IMAP_ARG_LITERAL_SIZE + argument */ + const struct imap_arg *parent_arg; + unsigned int list_idx; +}; + +struct imapc_untagged_reply { + /* name of the untagged reply, e.g. EXISTS */ + const char *name; + /* number at the beginning of the reply, or 0 if there wasn't any. + Set for EXISTS, EXPUNGE, etc. */ + uint32_t num; + /* the rest of the reply can be read from these args. */ + const struct imap_arg *args; + /* arguments whose contents are stored into files. only + "FETCH (BODY[" arguments can be here. */ + const struct imapc_arg_file *file_args; + unsigned int file_args_count; + + /* "* OK [RESP TEXT]" produces key=RESP, value=TEXT. + "* OK [RESP]" produces key=RESP, value=NULL + otherwise both are NULL */ + const char *resp_text_key, *resp_text_value; + + /* If this reply occurred while a mailbox was selected, this contains + the mailbox's untagged_context. */ + void *untagged_box_context; +}; + +/* Called when tagged reply is received for command. */ +typedef void imapc_command_callback_t(const struct imapc_command_reply *reply, + void *context); +/* Called each time untagged input is received. */ +typedef void imapc_untagged_callback_t(const struct imapc_untagged_reply *reply, + void *context); + +struct imapc_client * +imapc_client_init(const struct imapc_client_settings *set); +void imapc_client_deinit(struct imapc_client **client); + +/* Explicitly login to server (also done automatically). */ +void imapc_client_login(struct imapc_client *client, + imapc_command_callback_t *callback, void *context); + +void imapc_client_cmdf(struct imapc_client *client, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, ...) ATTR_FORMAT(4, 5); + +void imapc_client_register_untagged(struct imapc_client *client, + imapc_untagged_callback_t *callback, + void *context); + +void imapc_client_run_pre(struct imapc_client *client); +void imapc_client_run_post(struct imapc_client *client); +void imapc_client_stop(struct imapc_client *client); +/* Stop immediately, don't finish even any already read pending replies. + They'll be finished when imapc_client_run() is again called. */ +void imapc_client_stop_now(struct imapc_client *client); +bool imapc_client_is_running(struct imapc_client *client); + +struct imapc_client_mailbox * +imapc_client_mailbox_open(struct imapc_client *client, + const char *name, bool examine, + imapc_command_callback_t *callback, void *context, + void *untagged_box_context); +void imapc_client_mailbox_close(struct imapc_client_mailbox **box); +void imapc_client_mailbox_disconnect(struct imapc_client_mailbox *box); +void imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, + const char *cmd, + imapc_command_callback_t *callback, + void *context); +void imapc_client_mailbox_cmdf(struct imapc_client_mailbox *box, + imapc_command_callback_t *callback, + void *context, const char *cmd_fmt, ...) + ATTR_FORMAT(4, 5); +struct imapc_msgmap * +imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box); + +void imapc_client_mailbox_idle(struct imapc_client_mailbox *box); +bool imapc_client_mailbox_is_connected(struct imapc_client_mailbox *box); + +enum imapc_capability +imapc_client_get_capabilities(struct imapc_client *client); + +int imapc_client_create_temp_fd(struct imapc_client *client, + const char **path_r); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-connection.c Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,1758 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "network.h" +#include "istream.h" +#include "ostream.h" +#include "base64.h" +#include "write-full.h" +#include "str.h" +#include "dns-lookup.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" +#include "imap-quote.h" +#include "imap-util.h" +#include "imap-parser.h" +#include "imapc-client-private.h" +#include "imapc-connection.h" + +#include <unistd.h> +#include <ctype.h> + +#define IMAPC_DNS_LOOKUP_TIMEOUT_MSECS (1000*30) +#define IMAPC_CONNECT_TIMEOUT_MSECS (1000*30) +#define IMAPC_COMMAND_TIMEOUT_MSECS (1000*60*5) +#define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32) +/* IMAP protocol requires activity at least every 30 minutes */ +#define IMAPC_MAX_IDLE_MSECS (1000*60*29) + +enum imapc_input_state { + IMAPC_INPUT_STATE_NONE = 0, + IMAPC_INPUT_STATE_PLUS, + IMAPC_INPUT_STATE_UNTAGGED, + IMAPC_INPUT_STATE_UNTAGGED_NUM, + IMAPC_INPUT_STATE_TAGGED +}; + +struct imapc_command_stream { + unsigned int pos; + uoff_t size; + struct istream *input; +}; + +struct imapc_command { + pool_t pool; + buffer_t *data; + unsigned int send_pos; + unsigned int tag; + + ARRAY_DEFINE(streams, struct imapc_command_stream); + + imapc_command_callback_t *callback; + void *context; + + unsigned int idle:1; + unsigned int mailboxcmd:1; + unsigned int wait_for_literal:1; +}; + +struct imapc_connection_literal { + char *temp_path; + int fd; + uoff_t bytes_left; + + const struct imap_arg *parent_arg; + unsigned int list_idx; +}; + +struct imapc_connection { + struct imapc_client *client; + char *name; + int refcount; + + int fd; + struct io *io; + struct istream *input, *raw_input; + struct ostream *output, *raw_output; + struct imap_parser *parser; + struct timeout *to; + struct timeout *to_output; + + struct ssl_iostream *ssl_iostream; + + int (*input_callback)(struct imapc_connection *conn); + enum imapc_input_state input_state; + unsigned int cur_tag; + uint32_t cur_num; + + struct imapc_client_mailbox *selecting_box, *selected_box; + enum imapc_connection_state state; + + enum imapc_capability capabilities; + char **capabilities_list; + + imapc_command_callback_t *login_callback; + void *login_context; + + /* commands pending in queue to be sent */ + ARRAY_DEFINE(cmd_send_queue, struct imapc_command *); + /* commands that have been sent, waiting for their tagged reply */ + ARRAY_DEFINE(cmd_wait_list, struct imapc_command *); + + unsigned int ips_count, prev_connect_idx; + struct ip_addr *ips; + + struct imapc_connection_literal literal; + ARRAY_DEFINE(literal_files, struct imapc_arg_file); + + unsigned int idling:1; + unsigned int idle_stopping:1; + unsigned int idle_plus_waiting:1; + unsigned int handshake_failed:1; +}; + +static int imapc_connection_output(struct imapc_connection *conn); +static int imapc_connection_ssl_init(struct imapc_connection *conn); +static void imapc_command_free(struct imapc_command *cmd); +static void imapc_command_send_more(struct imapc_connection *conn, + struct imapc_command *cmd); + +struct imapc_connection * +imapc_connection_init(struct imapc_client *client) +{ + struct imapc_connection *conn; + + conn = i_new(struct imapc_connection, 1); + conn->refcount = 1; + conn->client = client; + conn->fd = -1; + conn->name = i_strdup_printf("%s:%u", client->set.host, + client->set.port); + conn->literal.fd = -1; + i_array_init(&conn->cmd_send_queue, 8); + i_array_init(&conn->cmd_wait_list, 32); + i_array_init(&conn->literal_files, 4); + + imapc_client_ref(client); + return conn; +} + +static void imapc_connection_ref(struct imapc_connection *conn) +{ + i_assert(conn->refcount > 0); + + conn->refcount++; +} + +static void imapc_connection_unref(struct imapc_connection **_conn) +{ + struct imapc_connection *conn = *_conn; + + i_assert(conn->refcount > 0); + + *_conn = NULL; + if (--conn->refcount > 0) + return; + + if (conn->capabilities_list != NULL) + p_strsplit_free(default_pool, conn->capabilities_list); + array_free(&conn->cmd_send_queue); + array_free(&conn->cmd_wait_list); + array_free(&conn->literal_files); + imapc_client_unref(&conn->client); + i_free(conn->ips); + i_free(conn->name); + i_free(conn); +} + +void imapc_connection_deinit(struct imapc_connection **_conn) +{ + imapc_connection_disconnect(*_conn); + imapc_connection_unref(_conn); +} + +void imapc_connection_ioloop_changed(struct imapc_connection *conn) +{ + if (conn->io != NULL) + conn->io = io_loop_move_io(&conn->io); + if (conn->to != NULL) + conn->to = io_loop_move_timeout(&conn->to); + if (conn->output != NULL) + o_stream_switch_ioloop(conn->output); + + if (conn->client->ioloop == NULL && conn->to_output != NULL) { + /* we're only once moving the to_output to the main ioloop, + since timeout moves currently also reset the timeout. + (the rest of the times this is a no-op) */ + conn->to_output = io_loop_move_timeout(&conn->to_output); + } +} + +static const char *imapc_command_get_readable(struct imapc_command *cmd) +{ + string_t *str = t_str_new(256); + const unsigned char *data = cmd->data->data; + unsigned int i; + + for (i = 0; i < cmd->data->used; i++) { + if (data[i] != '\r' && data[i] != '\n') + str_append_c(str, data[i]); + } + return str_c(str); +} + +static void +imapc_connection_abort_pending_commands(struct imapc_connection *conn, + const struct imapc_command_reply *reply) +{ + struct imapc_command *const *cmdp, *cmd; + + while (array_count(&conn->cmd_wait_list) > 0) { + cmdp = array_idx(&conn->cmd_wait_list, 0); + cmd = *cmdp; + array_delete(&conn->cmd_wait_list, 0, 1); + + if (cmd->callback != NULL) + cmd->callback(reply, cmd->context); + imapc_command_free(cmd); + } + while (array_count(&conn->cmd_send_queue) > 0) { + cmdp = array_idx(&conn->cmd_send_queue, 0); + cmd = *cmdp; + array_delete(&conn->cmd_send_queue, 0, 1); + + if (cmd->callback != NULL) + cmd->callback(reply, cmd->context); + imapc_command_free(cmd); + } +} + +static void +imapc_login_callback(struct imapc_connection *conn, + const struct imapc_command_reply *reply) +{ + imapc_command_callback_t *login_callback = conn->login_callback; + void *login_context = conn->login_context; + + if (login_callback == NULL) + return; + + conn->login_callback = NULL; + conn->login_context = NULL; + login_callback(reply, login_context); +} + +static void imapc_connection_set_state(struct imapc_connection *conn, + enum imapc_connection_state state) +{ + if (state == IMAPC_CONNECTION_STATE_DISCONNECTED) { + struct imapc_command_reply reply; + + memset(&reply, 0, sizeof(reply)); + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + reply.text_without_resp = reply.text_full = + "Disconnected from server"; + imapc_connection_abort_pending_commands(conn, &reply); + imapc_login_callback(conn, &reply); + + conn->idling = FALSE; + conn->idle_plus_waiting = FALSE; + conn->idle_stopping = FALSE; + + conn->selecting_box = NULL; + conn->selected_box = NULL; + } + if (state == IMAPC_CONNECTION_STATE_DONE) { + if (array_count(&conn->cmd_send_queue) > 0) { + struct imapc_command *const *cmd_p = + array_idx(&conn->cmd_send_queue, 0); + imapc_command_send_more(conn, *cmd_p); + } + } + conn->state = state; +} + +static void imapc_connection_lfiles_free(struct imapc_connection *conn) +{ + struct imapc_arg_file *lfile; + + array_foreach_modifiable(&conn->literal_files, lfile) { + if (close(lfile->fd) < 0) + i_error("imapc: close(literal file) failed: %m"); + } + array_clear(&conn->literal_files); +} + +static void +imapc_connection_literal_reset(struct imapc_connection_literal *literal) +{ + if (literal->fd != -1) { + if (close(literal->fd) < 0) + i_error("close(%s) failed: %m", literal->temp_path); + } + i_free_and_null(literal->temp_path); + + memset(literal, 0, sizeof(*literal)); + literal->fd = -1; +} + +void imapc_connection_disconnect(struct imapc_connection *conn) +{ + if (conn->fd == -1) + return; + + if (conn->client->set.debug) + i_debug("imapc(%s): Disconnected", conn->name); + + imapc_connection_lfiles_free(conn); + imapc_connection_literal_reset(&conn->literal); + if (conn->to != NULL) + timeout_remove(&conn->to); + if (conn->to_output != NULL) + timeout_remove(&conn->to_output); + imap_parser_destroy(&conn->parser); + io_remove(&conn->io); + if (conn->ssl_iostream != NULL) + ssl_iostream_unref(&conn->ssl_iostream); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + net_disconnect(conn->fd); + conn->fd = -1; + + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); +} + +static void ATTR_FORMAT(2, 3) +imapc_connection_input_error(struct imapc_connection *conn, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + i_error("imapc(%s): Server sent invalid input: %s", + conn->name, t_strdup_vprintf(fmt, va)); + sleep(3600); + imapc_connection_disconnect(conn); + va_end(va); +} + +static bool last_arg_is_fetch_body(const struct imap_arg *args, + const struct imap_arg **parent_arg_r, + unsigned int *idx_r) +{ + const struct imap_arg *list; + const char *name; + unsigned int count; + + if (args[0].type == IMAP_ARG_ATOM && + imap_arg_atom_equals(&args[1], "FETCH") && + imap_arg_get_list_full(&args[2], &list, &count) && count >= 2 && + list[count].type == IMAP_ARG_LITERAL_SIZE && + imap_arg_get_atom(&list[count-1], &name) && + strncasecmp(name, "BODY[", 5) == 0) { + *parent_arg_r = &args[2]; + *idx_r = count; + return TRUE; + } + return FALSE; +} + +static int +imapc_connection_read_literal_init(struct imapc_connection *conn, uoff_t size, + const struct imap_arg *args) +{ + const char *path; + const struct imap_arg *parent_arg; + unsigned int idx; + + i_assert(size > 0); + i_assert(conn->literal.fd == -1); + + if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE || + !last_arg_is_fetch_body(args, &parent_arg, &idx)) { + /* read the literal directly into parser */ + return 0; + } + + conn->literal.fd = imapc_client_create_temp_fd(conn->client, &path); + if (conn->literal.fd == -1) + return -1; + conn->literal.temp_path = i_strdup(path); + conn->literal.bytes_left = size; + conn->literal.parent_arg = parent_arg; + conn->literal.list_idx = idx; + return 1; +} + +static int imapc_connection_read_literal(struct imapc_connection *conn) +{ + struct imapc_arg_file *lfile; + const unsigned char *data; + size_t size; + + if (conn->literal.bytes_left == 0) + return 1; + + data = i_stream_get_data(conn->input, &size); + if (size > conn->literal.bytes_left) + size = conn->literal.bytes_left; + if (size > 0) { + if (write_full(conn->literal.fd, data, size) < 0) { + i_error("imapc(%s): write(%s) failed: %m", + conn->name, conn->literal.temp_path); + imapc_connection_disconnect(conn); + return -1; + } + i_stream_skip(conn->input, size); + conn->literal.bytes_left -= size; + } + if (conn->literal.bytes_left > 0) + return 0; + + /* finished */ + lfile = array_append_space(&conn->literal_files); + lfile->fd = conn->literal.fd; + lfile->parent_arg = conn->literal.parent_arg; + lfile->list_idx = conn->literal.list_idx; + + conn->literal.fd = -1; + imapc_connection_literal_reset(&conn->literal); + return 1; +} + +static int +imapc_connection_read_line_more(struct imapc_connection *conn, + const struct imap_arg **imap_args_r) +{ + uoff_t literal_size; + bool fatal; + int ret; + + if ((ret = imapc_connection_read_literal(conn)) <= 0) + return ret; + + ret = imap_parser_read_args(conn->parser, 0, + IMAP_PARSE_FLAG_LITERAL_SIZE | + IMAP_PARSE_FLAG_ATOM_ALLCHARS, imap_args_r); + if (ret == -2) { + /* need more data */ + return 0; + } + if (ret < 0) { + imapc_connection_input_error(conn, "Error parsing input: %s", + imap_parser_get_error(conn->parser, &fatal)); + return -1; + } + + if (imap_parser_get_literal_size(conn->parser, &literal_size)) { + if (imapc_connection_read_literal_init(conn, literal_size, + *imap_args_r) <= 0) { + imap_parser_read_last_literal(conn->parser); + return 2; + } + return imapc_connection_read_line_more(conn, imap_args_r); + } + return 1; +} + +static int +imapc_connection_read_line(struct imapc_connection *conn, + const struct imap_arg **imap_args_r) +{ + const unsigned char *data; + size_t size; + int ret; + + while ((ret = imapc_connection_read_line_more(conn, imap_args_r)) == 2) + ; + + if (ret > 0) { + data = i_stream_get_data(conn->input, &size); + if (size >= 2 && data[0] == '\r' && data[1] == '\n') + i_stream_skip(conn->input, 2); + else if (size >= 1 && data[0] == '\n') + i_stream_skip(conn->input, 1); + else + i_panic("imapc: Missing LF from input line"); + } + return ret; +} + +static int +imapc_connection_parse_capability(struct imapc_connection *conn, + const char *value) +{ + const char *const *tmp; + unsigned int i; + + if (conn->client->set.debug) { + i_debug("imapc(%s): Server capabilities: %s", + conn->name, value); + } + + conn->capabilities = 0; + if (conn->capabilities_list != NULL) + p_strsplit_free(default_pool, conn->capabilities_list); + conn->capabilities_list = p_strsplit(default_pool, value, " "); + + for (tmp = t_strsplit(value, " "); *tmp != NULL; tmp++) { + for (i = 0; imapc_capability_names[i].name != NULL; i++) { + const struct imapc_capability_name *cap = + &imapc_capability_names[i]; + + if (strcasecmp(*tmp, cap->name) == 0) { + conn->capabilities |= cap->capability; + break; + } + } + } + + if ((conn->capabilities & IMAPC_CAPABILITY_IMAP4REV1) == 0) { + imapc_connection_input_error(conn, + "CAPABILITY list is missing IMAP4REV1"); + return -1; + } + return 0; +} + +static int +imapc_connection_handle_resp_text_code(struct imapc_connection *conn, + const char *key, const char *value) +{ + if (strcasecmp(key, "CAPABILITY") == 0) { + if (imapc_connection_parse_capability(conn, value) < 0) + return -1; + } + if (strcasecmp(key, "CLOSED") == 0) { + /* QRESYNC: SELECTing another mailbox */ + if (conn->selecting_box != NULL) { + conn->selected_box = conn->selecting_box; + conn->selecting_box = NULL; + } + } + return 0; +} + +static int +imapc_connection_handle_resp_text(struct imapc_connection *conn, + const char *text, + const char **key_r, const char **value_r) +{ + const char *p, *value; + + i_assert(text[0] == '['); + + p = strchr(text, ']'); + if (p == NULL) { + imapc_connection_input_error(conn, "Missing ']' in resp-text"); + return -1; + } + text = t_strdup_until(text + 1, p); + value = strchr(text, ' '); + if (value != NULL) { + *key_r = t_strdup_until(text, value); + *value_r = value + 1; + } else { + *key_r = text; + *value_r = NULL; + } + return 0; +} + +static int +imapc_connection_handle_imap_resp_text(struct imapc_connection *conn, + const struct imap_arg *args, + const char **key_r, const char **value_r) +{ + const char *text; + + if (args->type != IMAP_ARG_ATOM) + return 0; + + text = imap_args_to_str(args); + if (*text != '[') { + if (*text == '\0') { + imapc_connection_input_error(conn, + "Missing text in resp-text"); + return -1; + } + return 0; + } + if (imapc_connection_handle_resp_text(conn, text, key_r, value_r) < 0) + return -1; + + return imapc_connection_handle_resp_text_code(conn, *key_r, *value_r); +} + +static bool need_literal(const char *str) +{ + unsigned int i; + + for (i = 0; str[i] != '\0'; i++) { + unsigned char c = str[i]; + + if ((c & 0x80) != 0 || c == '\r' || c == '\n') + return TRUE; + } + return FALSE; +} + +static void imapc_connection_input_reset(struct imapc_connection *conn) +{ + conn->input_state = IMAPC_INPUT_STATE_NONE; + conn->cur_tag = 0; + conn->cur_num = 0; + if (conn->parser != NULL) + imap_parser_reset(conn->parser); + imapc_connection_lfiles_free(conn); +} + +static void imapc_connection_login_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + + if (reply->state != IMAPC_COMMAND_STATE_OK) { + if (conn->login_callback != NULL) + imapc_login_callback(conn, reply); + else { + i_error("imapc(%s): Authentication failed: %s", + conn->name, reply->text_full); + } + imapc_connection_disconnect(conn); + return; + } + + if (conn->client->set.debug) + i_debug("imapc(%s): Authenticated successfully", conn->name); + + timeout_remove(&conn->to); + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE); + imapc_login_callback(conn, reply); +} + +static const char * +imapc_connection_get_sasl_plain_request(struct imapc_connection *conn) +{ + const struct imapc_client_settings *set = &conn->client->set; + string_t *in, *out; + + in = t_str_new(128); + if (set->master_user != NULL) { + str_append(in, set->username); + str_append_c(in, '\0'); + str_append(in, set->master_user); + } else { + str_append_c(in, '\0'); + str_append(in, set->username); + } + str_append_c(in, '\0'); + str_append(in, set->password); + + out = t_str_new(128); + base64_encode(in->data, in->used, out); + return str_c(out); +} + +static void imapc_connection_authenticate(struct imapc_connection *conn) +{ + const struct imapc_client_settings *set = &conn->client->set; + const char *cmd; + + if (conn->client->set.debug) { + if (set->master_user == NULL) { + i_debug("imapc(%s): Authenticating as %s", + conn->name, set->username); + } else { + i_debug("imapc(%s): Authenticating as %s for user %s", + conn->name, set->master_user, set->username); + } + } + + if ((set->master_user == NULL && + need_literal(set->username) && need_literal(set->password)) || + (conn->capabilities & IMAPC_CAPABILITY_AUTH_PLAIN) == 0) { + /* We can use LOGIN command */ + imapc_connection_cmdf(conn, FALSE, imapc_connection_login_cb, + conn, "LOGIN %s %s", + set->username, set->password); + } else if ((conn->capabilities & IMAPC_CAPABILITY_SASL_IR) != 0) { + cmd = t_strdup_printf("AUTHENTICATE PLAIN %s", + imapc_connection_get_sasl_plain_request(conn)); + imapc_connection_cmd(conn, FALSE, cmd, + imapc_connection_login_cb, conn); + } else { + cmd = t_strdup_printf("AUTHENTICATE PLAIN\r\n%s", + imapc_connection_get_sasl_plain_request(conn)); + imapc_connection_cmd(conn, FALSE, cmd, + imapc_connection_login_cb, conn); + } +} + +static void +imapc_connection_starttls_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + + if (reply->state != IMAPC_COMMAND_STATE_OK) { + imapc_connection_input_error(conn, "STARTTLS failed: %s", + reply->text_full); + return; + } + + if (imapc_connection_ssl_init(conn) < 0) + imapc_connection_disconnect(conn); + else + imapc_connection_authenticate(conn); +} + +static void imapc_connection_starttls(struct imapc_connection *conn) +{ + if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_STARTTLS && + conn->ssl_iostream == NULL) { + if ((conn->capabilities & IMAPC_CAPABILITY_STARTTLS) == 0) { + i_error("imapc(%s): Requested STARTTLS, " + "but server doesn't support it", + conn->name); + imapc_connection_disconnect(conn); + return; + } + imapc_connection_cmd(conn, FALSE, "STARTTLS", + imapc_connection_starttls_cb, conn); + return; + } + imapc_connection_authenticate(conn); +} + +static void +imapc_connection_capability_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + + if (reply->state != IMAPC_COMMAND_STATE_OK) { + imapc_connection_input_error(conn, + "Failed to get capabilities: %s", reply->text_full); + } else if (conn->capabilities == 0) { + imapc_connection_input_error(conn, + "Capabilities not returned by server"); + } else { + imapc_connection_starttls(conn); + } +} + +static int imapc_connection_input_banner(struct imapc_connection *conn) +{ + const struct imap_arg *imap_args; + const char *key, *value; + int ret; + + if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) + return ret; + + if (imapc_connection_handle_imap_resp_text(conn, imap_args, + &key, &value) < 0) + return -1; + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING); + + if (conn->capabilities == 0) { + /* capabilities weren't sent in the banner. ask for them. */ + imapc_connection_cmd(conn, FALSE, "CAPABILITY", + imapc_connection_capability_cb, conn); + } else { + imapc_connection_starttls(conn); + } + conn->input_callback = NULL; + imapc_connection_input_reset(conn); + return 1; +} + +static int imapc_connection_input_untagged(struct imapc_connection *conn) +{ + const struct imap_arg *imap_args; + const char *name, *value; + struct imapc_untagged_reply reply; + int ret; + + if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) { + /* input banner */ + name = imap_parser_read_word(conn->parser); + if (name == NULL) + return 0; + + if (strcasecmp(name, "OK") != 0) { + imapc_connection_input_error(conn, + "Banner doesn't begin with OK: %s", name); + return -1; + } + conn->input_callback = imapc_connection_input_banner; + return 1; + } + + if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) + return ret; + if (!imap_arg_get_atom(&imap_args[0], &name)) { + imapc_connection_input_error(conn, "Invalid untagged reply"); + return -1; + } + imap_args++; + + if (conn->input_state == IMAPC_INPUT_STATE_UNTAGGED && + str_to_uint32(name, &conn->cur_num) == 0) { + /* <seq> <event> */ + conn->input_state = IMAPC_INPUT_STATE_UNTAGGED_NUM; + if (!imap_arg_get_atom(&imap_args[0], &name)) { + imapc_connection_input_error(conn, + "Invalid untagged reply"); + return -1; + } + imap_args++; + } + memset(&reply, 0, sizeof(reply)); + + if (strcasecmp(name, "OK") == 0) { + if (imapc_connection_handle_imap_resp_text(conn, imap_args, + &reply.resp_text_key, + &reply.resp_text_value) < 0) + return -1; + } else if (strcasecmp(name, "CAPABILITY") == 0) { + value = imap_args_to_str(imap_args); + if (imapc_connection_parse_capability(conn, value) < 0) + return -1; + } + + reply.name = name; + reply.num = conn->cur_num; + reply.args = imap_args; + reply.file_args = array_get(&conn->literal_files, + &reply.file_args_count); + + if (conn->selected_box != NULL) { + reply.untagged_box_context = + conn->selected_box->untagged_box_context; + } + conn->client->untagged_callback(&reply, conn->client->untagged_context); + imapc_connection_input_reset(conn); + return 1; +} + +static int imapc_connection_input_plus(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int cmds_count; + const char *line; + + if ((line = i_stream_next_line(conn->input)) == NULL) + return 0; + + cmds = array_get(&conn->cmd_send_queue, &cmds_count); + if (conn->idle_plus_waiting) { + /* "+ idling" reply for IDLE command */ + conn->idle_plus_waiting = FALSE; + conn->idling = TRUE; + } else if (cmds_count > 0 && cmds[0]->wait_for_literal) { + /* reply for literal */ + cmds[0]->wait_for_literal = FALSE; + imapc_command_send_more(conn, cmds[0]); + } else { + imapc_connection_input_error(conn, "Unexpected '+': %s", line); + return -1; + } + + imapc_connection_input_reset(conn); + return 1; +} + +static int imapc_connection_input_tagged(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds, *cmd = NULL; + unsigned int i, count; + char *line, *linep; + const char *p; + struct imapc_command_reply reply; + + line = i_stream_next_line(conn->input); + if (line == NULL) + return 0; + + memset(&reply, 0, sizeof(reply)); + + linep = strchr(line, ' '); + if (linep == NULL) + reply.text_full = ""; + else { + *linep = '\0'; + reply.text_full = linep + 1; + } + + if (strcasecmp(line, "ok") == 0) + reply.state = IMAPC_COMMAND_STATE_OK; + else if (strcasecmp(line, "no") == 0) + reply.state = IMAPC_COMMAND_STATE_NO; + else if (strcasecmp(line, "bad") == 0) + reply.state = IMAPC_COMMAND_STATE_BAD; + else { + imapc_connection_input_error(conn, + "Invalid state in tagged reply: %u %s %s", + conn->cur_tag, line, reply.text_full); + return -1; + } + + if (reply.text_full[0] == '[') { + /* get resp-text */ + if (imapc_connection_handle_resp_text(conn, reply.text_full, + &reply.resp_text_key, + &reply.resp_text_value) < 0) + return -1; + + p = strchr(reply.text_full, ']'); + i_assert(p != NULL); + reply.text_without_resp = p + 1; + if (reply.text_without_resp[0] == ' ') + reply.text_without_resp++; + } else { + reply.text_without_resp = reply.text_full; + } + + /* find the command. it's either the first command in send queue + (literal failed) or somewhere in wait list. */ + cmds = array_get(&conn->cmd_send_queue, &count); + if (count > 0 && cmds[0]->tag == conn->cur_tag) { + cmd = cmds[0]; + array_delete(&conn->cmd_send_queue, 0, 1); + } else { + cmds = array_get(&conn->cmd_wait_list, &count); + for (i = 0; i < count; i++) { + if (cmds[i]->tag == conn->cur_tag) { + cmd = cmds[i]; + array_delete(&conn->cmd_wait_list, i, 1); + break; + } + } + } + if (array_count(&conn->cmd_wait_list) == 0 && + array_count(&conn->cmd_send_queue) == 0 && + conn->state == IMAPC_CONNECTION_STATE_DONE && conn->to != NULL) + timeout_remove(&conn->to); + + if (cmd == NULL) { + imapc_connection_input_error(conn, + "Unknown tag in a reply: %u %s %s", + conn->cur_tag, line, reply.text_full); + return -1; + } + + if (reply.state == IMAPC_COMMAND_STATE_BAD) { + i_error("imapc(%s): Command '%s' failed with BAD: %u %s", + conn->name, imapc_command_get_readable(cmd), + conn->cur_tag, reply.text_full); + imapc_connection_disconnect(conn); + } + + imapc_connection_input_reset(conn); + if (cmd->callback != NULL) + cmd->callback(&reply, cmd->context); + imapc_command_free(cmd); + return 1; +} + +static int imapc_connection_input_one(struct imapc_connection *conn) +{ + const char *tag; + int ret = -1; + + if (conn->input_callback != NULL) + return conn->input_callback(conn); + + switch (conn->input_state) { + case IMAPC_INPUT_STATE_NONE: + tag = imap_parser_read_word(conn->parser); + if (tag == NULL) + return 0; + + if (strcmp(tag, "*") == 0) { + conn->input_state = IMAPC_INPUT_STATE_UNTAGGED; + conn->cur_num = 0; + ret = imapc_connection_input_untagged(conn); + } else if (strcmp(tag, "+") == 0) { + conn->input_state = IMAPC_INPUT_STATE_PLUS; + ret = imapc_connection_input_plus(conn); + } else { + conn->input_state = IMAPC_INPUT_STATE_TAGGED; + if (str_to_uint(tag, &conn->cur_tag) < 0 || + conn->cur_tag == 0) { + imapc_connection_input_error(conn, + "Invalid command tag: %s", tag); + ret = -1; + } else { + ret = imapc_connection_input_tagged(conn); + } + } + break; + case IMAPC_INPUT_STATE_PLUS: + ret = imapc_connection_input_plus(conn); + break; + case IMAPC_INPUT_STATE_UNTAGGED: + case IMAPC_INPUT_STATE_UNTAGGED_NUM: + ret = imapc_connection_input_untagged(conn); + break; + case IMAPC_INPUT_STATE_TAGGED: + ret = imapc_connection_input_tagged(conn); + break; + } + return ret; +} + +static void imapc_connection_input(struct imapc_connection *conn) +{ + const char *errstr; + ssize_t ret = 0; + + /* we need to read as much as we can with SSL streams to avoid + hanging */ + imapc_connection_ref(conn); + while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0) + imapc_connection_input_pending(conn); + + if (ret < 0) { + /* disconnected */ + if (conn->ssl_iostream == NULL) { + i_error("imapc(%s): Server disconnected unexpectedly", + conn->name); + } else if (!conn->handshake_failed) { + errstr = ssl_iostream_get_last_error(conn->ssl_iostream); + i_error("imapc(%s): Server disconnected: %s", + conn->name, errstr != NULL ? errstr : ""); + } + imapc_connection_disconnect(conn); + } + imapc_connection_unref(&conn); +} + +static int imapc_connection_ssl_handshaked(void *context) +{ + struct imapc_connection *conn = context; + + if (!ssl_iostream_has_valid_client_cert(conn->ssl_iostream)) { + if (!ssl_iostream_has_broken_client_cert(conn->ssl_iostream)) { + i_error("imapc(%s): SSL certificate not received", + conn->name); + } else { + i_error("imapc(%s): Received invalid SSL certificate", + conn->name); + } + } else if (ssl_iostream_cert_match_name(conn->ssl_iostream, + conn->client->set.host) < 0) { + i_error("imapc(%s): SSL certificate doesn't match host name", + conn->name); + } else { + if (conn->client->set.debug) { + i_debug("imapc(%s): SSL handshake successful", + conn->name); + } + return 0; + } + conn->handshake_failed = TRUE; + i_stream_close(conn->input); + return -1; +} + +static int imapc_connection_ssl_init(struct imapc_connection *conn) +{ + struct ssl_iostream_settings ssl_set; + const char *source; + + if (conn->client->ssl_ctx == NULL) { + i_error("imapc(%s): No SSL context", conn->name); + return -1; + } + + memset(&ssl_set, 0, sizeof(ssl_set)); + ssl_set.verbose_invalid_cert = TRUE; + ssl_set.verify_remote_cert = TRUE; + ssl_set.require_valid_cert = TRUE; + + if (conn->client->set.debug) + i_debug("imapc(%s): Starting SSL handshake", conn->name); + + if (conn->raw_input != conn->input) { + /* recreate rawlog after STARTTLS */ + i_stream_ref(conn->raw_input); + o_stream_ref(conn->raw_output); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + conn->input = conn->raw_input; + conn->output = conn->raw_output; + } + + source = t_strdup_printf("imapc(%s): ", conn->name); + if (io_stream_create_ssl(conn->client->ssl_ctx, source, &ssl_set, + &conn->input, &conn->output, + &conn->ssl_iostream) < 0) { + i_error("imapc(%s): Couldn't initialize SSL client", + conn->name); + return -1; + } + ssl_iostream_set_handshake_callback(conn->ssl_iostream, + imapc_connection_ssl_handshaked, + conn); + if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { + i_error("imapc(%s): SSL handshake failed: %s", conn->name, + ssl_iostream_get_last_error(conn->ssl_iostream)); + return -1; + } + + if (*conn->client->set.rawlog_dir != '\0') { + (void)iostream_rawlog_create(conn->client->set.rawlog_dir, + &conn->input, &conn->output); + } + + imap_parser_set_streams(conn->parser, conn->input, NULL); + return 0; +} + +static void imapc_connection_connected(struct imapc_connection *conn) +{ + const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; + int err; + + err = net_geterror(conn->fd); + if (err != 0) { + i_error("imapc(%s): connect(%s, %u) failed: %s", + conn->name, net_ip2addr(ip), conn->client->set.port, + strerror(err)); + imapc_connection_disconnect(conn); + return; + } + io_remove(&conn->io); + conn->io = io_add(conn->fd, IO_READ, imapc_connection_input, conn); + + if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { + if (imapc_connection_ssl_init(conn) < 0) + imapc_connection_disconnect(conn); + } +} + +static void imapc_connection_timeout(struct imapc_connection *conn) +{ + const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; + + switch (conn->state) { + case IMAPC_CONNECTION_STATE_CONNECTING: + i_error("imapc(%s): connect(%s, %u) timed out after %u seconds", + conn->name, net_ip2addr(ip), conn->client->set.port, + IMAPC_CONNECT_TIMEOUT_MSECS/1000); + break; + case IMAPC_CONNECTION_STATE_AUTHENTICATING: + i_error("imapc(%s): Authentication timed out after %u seconds", + conn->name, IMAPC_CONNECT_TIMEOUT_MSECS/1000); + break; + default: + i_unreached(); + } + imapc_connection_disconnect(conn); +} + +static void +imapc_reidle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context) +{ + struct imapc_connection *conn = context; + + imapc_connection_idle(conn); +} + +static void imapc_connection_reset_idle(struct imapc_connection *conn) +{ + if (!conn->idling) + imapc_connection_cmd(conn, FALSE, "NOOP", NULL, NULL); + else { + imapc_connection_cmd(conn, FALSE, "NOOP", + imapc_reidle_callback, conn); + } +} + +static void imapc_connection_connect_next_ip(struct imapc_connection *conn) +{ + const struct ip_addr *ip; + int fd; + + conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count; + ip = &conn->ips[conn->prev_connect_idx]; + fd = net_connect_ip(ip, conn->client->set.port, NULL); + if (fd == -1) { + imapc_connection_set_state(conn, + IMAPC_CONNECTION_STATE_DISCONNECTED); + return; + } + conn->fd = fd; + conn->input = conn->raw_input = i_stream_create_fd(fd, (size_t)-1, FALSE); + conn->output = conn->raw_output = o_stream_create_fd(fd, (size_t)-1, FALSE); + + if (*conn->client->set.rawlog_dir != '\0' && + conn->client->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { + (void)iostream_rawlog_create(conn->client->set.rawlog_dir, + &conn->input, &conn->output); + } + + o_stream_set_flush_callback(conn->output, imapc_connection_output, + conn); + conn->io = io_add(fd, IO_WRITE, imapc_connection_connected, conn); + conn->parser = imap_parser_create(conn->input, NULL, (size_t)-1); + conn->to = timeout_add(IMAPC_CONNECT_TIMEOUT_MSECS, + imapc_connection_timeout, conn); + conn->to_output = timeout_add(IMAPC_MAX_IDLE_MSECS, + imapc_connection_reset_idle, conn); + if (conn->client->set.debug) { + i_debug("imapc(%s): Connecting to %s:%u", conn->name, + net_ip2addr(ip), conn->client->set.port); + } +} + +static void +imapc_connection_dns_callback(const struct dns_lookup_result *result, + void *context) +{ + struct imapc_connection *conn = context; + + if (result->ret != 0) { + i_error("imapc(%s): dns_lookup(%s) failed: %s", + conn->name, conn->client->set.host, result->error); + imapc_connection_set_state(conn, + IMAPC_CONNECTION_STATE_DISCONNECTED); + return; + } + + i_assert(result->ips_count > 0); + conn->ips_count = result->ips_count; + conn->ips = i_new(struct ip_addr, conn->ips_count); + memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count); + conn->prev_connect_idx = conn->ips_count - 1; + + imapc_connection_connect_next_ip(conn); +} + +void imapc_connection_connect(struct imapc_connection *conn, + imapc_command_callback_t *callback, void *context) +{ + struct dns_lookup_settings dns_set; + + i_assert(conn->login_callback == NULL); + if (conn->fd != -1) { + i_assert(callback == NULL); + return; + } + conn->login_callback = callback; + conn->login_context = context; + + imapc_connection_input_reset(conn); + + if (conn->client->set.debug) + i_debug("imapc(%s): Looking up IP address", conn->name); + + memset(&dns_set, 0, sizeof(dns_set)); + dns_set.dns_client_socket_path = + conn->client->set.dns_client_socket_path; + dns_set.timeout_msecs = IMAPC_DNS_LOOKUP_TIMEOUT_MSECS; + + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_CONNECTING); + if (conn->ips_count == 0) { + (void)dns_lookup(conn->client->set.host, &dns_set, + imapc_connection_dns_callback, conn); + } else { + imapc_connection_connect_next_ip(conn); + } +} + +void imapc_connection_input_pending(struct imapc_connection *conn) +{ + int ret = 1; + + if (conn->input == NULL) + return; + + if (conn->to != NULL) + timeout_reset(conn->to); + + o_stream_cork(conn->output); + while (ret > 0 && !conn->client->stop_now && conn->input != NULL) { + T_BEGIN { + ret = imapc_connection_input_one(conn); + } T_END; + } + + if (conn->output != NULL) + o_stream_uncork(conn->output); +} + +static struct imapc_command * +imapc_command_begin(imapc_command_callback_t *callback, void *context) +{ + static unsigned int cmd_tag_counter = 0; + struct imapc_command *cmd; + pool_t pool; + + pool = pool_alloconly_create("imapc command", 2048); + cmd = p_new(pool, struct imapc_command, 1); + cmd->pool = pool; + cmd->callback = callback; + cmd->context = context; + + if (++cmd_tag_counter == 0) + cmd_tag_counter++; + cmd->tag = cmd_tag_counter; + return cmd; +} + +static void imapc_command_free(struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + + if (array_is_created(&cmd->streams)) { + array_foreach_modifiable(&cmd->streams, stream) + i_stream_unref(&stream->input); + } + pool_unref(&cmd->pool); +} + +static bool +parse_sync_literal(const unsigned char *data, unsigned int pos, + unsigned int *value_r) +{ + unsigned int value = 0, mul = 1; + + /* data should contain "{size}\r\n" and pos points after \n */ + if (pos <= 4 || data[pos-1] != '\n' || data[pos-2] != '\r' || + data[pos-3] != '}' || !i_isdigit(data[pos-4])) + return FALSE; + pos -= 4; + + do { + value += (data[pos] - '0') * mul; + mul = mul*10; + pos--; + } while (pos > 0 && i_isdigit(data[pos])); + + if (pos == 0 || data[pos] != '{') + return FALSE; + + *value_r = value; + return TRUE; +} + +static void imapc_command_send_done(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + if (cmd->idle) + conn->idle_plus_waiting = TRUE; + + /* everything sent. move command to wait list. */ + i_assert(*array_idx(&conn->cmd_send_queue, 0) == cmd); + array_delete(&conn->cmd_send_queue, 0, 1); + array_append(&conn->cmd_wait_list, &cmd, 1); + + if (array_count(&conn->cmd_send_queue) > 0 && + conn->state == IMAPC_CONNECTION_STATE_DONE) { + /* send the next command in queue */ + struct imapc_command *const *cmd2_p = + array_idx(&conn->cmd_send_queue, 0); + imapc_command_send_more(conn, *cmd2_p); + } +} + +static struct imapc_command_stream * +imapc_command_get_sending_stream(struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + + if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0) + return NULL; + + stream = array_idx_modifiable(&cmd->streams, 0); + if (stream->pos != cmd->send_pos) + return NULL; + return stream; +} + +static int imapc_command_try_send_stream(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + + stream = imapc_command_get_sending_stream(cmd); + if (stream == NULL) + return -1; + + /* we're sending the stream now */ + o_stream_set_max_buffer_size(conn->output, 0); + (void)o_stream_send_istream(conn->output, stream->input); + o_stream_set_max_buffer_size(conn->output, (size_t)-1); + + if (!i_stream_is_eof(stream->input)) { + o_stream_set_flush_pending(conn->output, TRUE); + i_assert(stream->input->v_offset < stream->size); + return 0; + } + i_assert(stream->input->v_offset == stream->size); + + /* finished with the stream */ + i_stream_unref(&stream->input); + array_delete(&cmd->streams, 0, 1); + + i_assert(cmd->send_pos != cmd->data->used); + return 1; +} + +static void imapc_command_send_more(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + const unsigned char *p, *data; + unsigned int seek_pos, start_pos, end_pos, size; + int ret; + + i_assert(!cmd->wait_for_literal); + i_assert(cmd->send_pos < cmd->data->used); + + timeout_reset(conn->to_output); + if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0) + return; + + seek_pos = cmd->send_pos; + if (seek_pos != 0 && ret < 0) { + /* skip over the literal. we can also get here from + AUTHENTICATE command, which doesn't use a literal */ + if (parse_sync_literal(cmd->data->data, seek_pos, &size)) { + seek_pos += size; + i_assert(seek_pos <= cmd->data->used); + } + } + + do { + start_pos = seek_pos; + p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n', + cmd->data->used - seek_pos); + i_assert(p != NULL); + + seek_pos = p - (const unsigned char *)cmd->data->data + 1; + /* keep going for LITERAL+ command */ + } while (start_pos + 3 < seek_pos && + p[-1] == '\r' && p[-2] == '}' && p[-3] == '+'); + end_pos = seek_pos; + + data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos); + size = end_pos - cmd->send_pos; + o_stream_send(conn->output, data, size); + cmd->send_pos = end_pos; + + if (cmd->send_pos == cmd->data->used) { + i_assert(!array_is_created(&cmd->streams) || + array_count(&cmd->streams) == 0); + imapc_command_send_done(conn, cmd); + } else { + cmd->wait_for_literal = TRUE; + } +} + +static void imapc_command_timeout(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int count; + + cmds = array_get(&conn->cmd_wait_list, &count); + i_assert(count > 0); + + i_error("imapc(%s): Command '%s' timed out, disconnecting", + conn->name, imapc_command_get_readable(cmds[0])); + imapc_connection_disconnect(conn); +} + +static void imapc_connection_send_idle_done(struct imapc_connection *conn) +{ + if ((conn->idling || conn->idle_plus_waiting) && !conn->idle_stopping) { + conn->idle_stopping = TRUE; + o_stream_send_str(conn->output, "DONE\r\n"); + } +} + +static void imapc_command_send(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + imapc_connection_send_idle_done(conn); + switch (conn->state) { + case IMAPC_CONNECTION_STATE_AUTHENTICATING: + array_insert(&conn->cmd_send_queue, 0, &cmd, 1); + imapc_command_send_more(conn, cmd); + break; + case IMAPC_CONNECTION_STATE_DONE: + if (cmd->idle) { + if (conn->to != NULL) + timeout_remove(&conn->to); + } else if (conn->to == NULL) { + conn->to = timeout_add(IMAPC_COMMAND_TIMEOUT_MSECS, + imapc_command_timeout, conn); + } + + array_append(&conn->cmd_send_queue, &cmd, 1); + if (array_count(&conn->cmd_send_queue) == 1) + imapc_command_send_more(conn, cmd); + break; + default: + array_append(&conn->cmd_send_queue, &cmd, 1); + break; + } +} + +static int imapc_connection_output(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int count; + int ret; + + if (conn->to != NULL) + timeout_reset(conn->to); + + o_stream_cork(conn->output); + if ((ret = o_stream_flush(conn->output)) < 0) + return 1; + + imapc_connection_ref(conn); + cmds = array_get(&conn->cmd_send_queue, &count); + if (count > 0) { + if (imapc_command_get_sending_stream(cmds[0]) != NULL && + !cmds[0]->wait_for_literal) { + /* we're sending a stream. send more. */ + imapc_command_send_more(conn, cmds[0]); + } + } + o_stream_uncork(conn->output); + imapc_connection_unref(&conn); + return ret; +} + +static struct imapc_command * +imapc_connection_cmd_build(const char *cmdline, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_command *cmd; + unsigned int len = strlen(cmdline); + + cmd = imapc_command_begin(callback, context); + cmd->data = str_new(cmd->pool, 6 + len + 2); + str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmdline); + return cmd; +} + +void imapc_connection_cmd(struct imapc_connection *conn, bool mailboxcmd, + const char *cmdline, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_command *cmd; + + cmd = imapc_connection_cmd_build(cmdline, callback, context); + cmd->mailboxcmd = mailboxcmd; + imapc_command_send(conn, cmd); +} + +void imapc_connection_cmdf(struct imapc_connection *conn, bool mailboxcmd, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, ...) +{ + va_list args; + + va_start(args, cmd_fmt); + imapc_connection_cmdvf(conn, mailboxcmd, callback, context, + cmd_fmt, args); + va_end(args); +} + +void imapc_connection_cmdvf(struct imapc_connection *conn, bool mailboxcmd, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, va_list args) +{ + struct imapc_command *cmd; + unsigned int i; + + cmd = imapc_command_begin(callback, context); + cmd->mailboxcmd = mailboxcmd; + cmd->data = str_new(cmd->pool, 128); + str_printfa(cmd->data, "%u ", cmd->tag); + + for (i = 0; cmd_fmt[i] != '\0'; i++) { + if (cmd_fmt[i] != '%') { + str_append_c(cmd->data, cmd_fmt[i]); + continue; + } + + switch (cmd_fmt[++i]) { + case '\0': + i_unreached(); + case 'u': { + unsigned int arg = va_arg(args, unsigned int); + + str_printfa(cmd->data, "%u", arg); + break; + } + case 'p': { + struct istream *input = va_arg(args, struct istream *); + struct imapc_command_stream *s; + uoff_t size; + + if (!array_is_created(&cmd->streams)) + p_array_init(&cmd->streams, cmd->pool, 2); + if (i_stream_get_size(input, TRUE, &size) < 0) + size = 0; + str_printfa(cmd->data, "{%"PRIuUOFF_T"}\r\n", size); + s = array_append_space(&cmd->streams); + s->pos = str_len(cmd->data); + s->size = size; + s->input = input; + i_stream_ref(input); + break; + } + case 's': { + const char *arg = va_arg(args, const char *); + + if (!need_literal(arg)) + imap_dquote_append(cmd->data, arg); + else if ((conn->capabilities & + IMAPC_CAPABILITY_LITERALPLUS) != 0) { + str_printfa(cmd->data, "{%"PRIuSIZE_T"+}\r\n%s", + strlen(arg), arg); + } else { + str_printfa(cmd->data, "{%"PRIuSIZE_T"}\r\n%s", + strlen(arg), arg); + } + break; + } + case '1': { + /* %1s - no quoting */ + const char *arg = va_arg(args, const char *); + + i_assert(cmd_fmt[++i] == 's'); + str_append(cmd->data, arg); + break; + } + } + } + str_append(cmd->data, "\r\n"); + + imapc_command_send(conn, cmd); +} + +enum imapc_connection_state +imapc_connection_get_state(struct imapc_connection *conn) +{ + return conn->state; +} + +enum imapc_capability +imapc_connection_get_capabilities(struct imapc_connection *conn) +{ + return conn->capabilities; +} + +void imapc_connection_select(struct imapc_client_mailbox *box, + const char *name, bool examine, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_connection *conn = box->conn; + + i_assert(conn->selecting_box == NULL); + + if (conn->selected_box != NULL && + (conn->capabilities & IMAPC_CAPABILITY_QRESYNC) != 0) { + /* server will send a [CLOSED] once selected mailbox is + closed */ + conn->selecting_box = box; + } else { + /* we'll have to assume that all the future untagged messages + are for the mailbox we're selecting */ + conn->selected_box = box; + } + + imapc_connection_cmdf(conn, FALSE, callback, context, + examine ? "EXAMINE %s" : "SELECT %s", name); +} + +void imapc_connection_unselect(struct imapc_client_mailbox *box) +{ + struct imapc_connection *conn = box->conn; + struct imapc_command *const *cmdp, *cmd; + struct imapc_command_reply reply; + unsigned int i; + + /* mailbox is being closed. if there are any pending commands, we must + finish them immediately so callbacks don't access any freed + contexts */ + memset(&reply, 0, sizeof(reply)); + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + reply.text_without_resp = reply.text_full = "Closing mailbox"; + + imapc_connection_send_idle_done(conn); + + array_foreach(&conn->cmd_wait_list, cmdp) { + if ((*cmdp)->callback != NULL && (*cmdp)->mailboxcmd) { + (*cmdp)->callback(&reply, (*cmdp)->context); + (*cmdp)->callback = NULL; + } + } + for (i = 0; i < array_count(&conn->cmd_send_queue); ) { + cmdp = array_idx(&conn->cmd_send_queue, i); + cmd = *cmdp; + if (!cmd->mailboxcmd) + i++; + else { + array_delete(&conn->cmd_send_queue, i, 1); + if (cmd->callback != NULL) + cmd->callback(&reply, cmd->context); + imapc_command_free(cmd); + } + } + + if (conn->selected_box == NULL && conn->selecting_box == NULL) { + i_assert(conn->state == IMAPC_CONNECTION_STATE_DISCONNECTED); + } else { + i_assert(conn->selected_box == box || + conn->selecting_box == box); + + conn->selected_box = NULL; + conn->selecting_box = NULL; + } +} + +struct imapc_client_mailbox * +imapc_connection_get_mailbox(struct imapc_connection *conn) +{ + if (conn->selecting_box != NULL) + return conn->selecting_box; + return conn->selected_box; +} + +static void +imapc_connection_idle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context) +{ + struct imapc_connection *conn = context; + + conn->idling = FALSE; + conn->idle_plus_waiting = FALSE; + conn->idle_stopping = FALSE; +} + +void imapc_connection_idle(struct imapc_connection *conn) +{ + struct imapc_command *cmd; + + if (array_count(&conn->cmd_send_queue) != 0 || + array_count(&conn->cmd_wait_list) != 0 || + conn->idling || conn->idle_plus_waiting || + (conn->capabilities & IMAPC_CAPABILITY_IDLE) == 0) + return; + + cmd = imapc_connection_cmd_build("IDLE", imapc_connection_idle_callback, + conn); + cmd->idle = TRUE; + imapc_command_send(conn, cmd); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-connection.h Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,56 @@ +#ifndef IMAPC_CONNECTION_H +#define IMAPC_CONNECTION_H + +#include "imapc-client.h" + +struct imapc_client; +struct imapc_connection; + +enum imapc_connection_state { + /* No connection */ + IMAPC_CONNECTION_STATE_DISCONNECTED = 0, + /* Trying to connect */ + IMAPC_CONNECTION_STATE_CONNECTING, + /* Connected, trying to authenticate */ + IMAPC_CONNECTION_STATE_AUTHENTICATING, + /* Authenticated, ready to accept commands */ + IMAPC_CONNECTION_STATE_DONE +}; + +struct imapc_connection * +imapc_connection_init(struct imapc_client *client); +void imapc_connection_deinit(struct imapc_connection **conn); + +void imapc_connection_connect(struct imapc_connection *conn, + imapc_command_callback_t *callback, + void *context); +void imapc_connection_disconnect(struct imapc_connection *conn); +void imapc_connection_ioloop_changed(struct imapc_connection *conn); +void imapc_connection_input_pending(struct imapc_connection *conn); + +void imapc_connection_cmd(struct imapc_connection *conn, bool mailboxcmd, + const char *cmdline, + imapc_command_callback_t *callback, void *context); +void imapc_connection_cmdf(struct imapc_connection *conn, bool mailboxcmd, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, ...) ATTR_FORMAT(5, 6); +void imapc_connection_cmdvf(struct imapc_connection *conn, bool mailboxcmd, + imapc_command_callback_t *callback, void *context, + const char *cmd_fmt, va_list args) + ATTR_FORMAT(5, 0); +void imapc_connection_select(struct imapc_client_mailbox *box, + const char *name, bool examine, + imapc_command_callback_t *callback, void *context); +void imapc_connection_unselect(struct imapc_client_mailbox *box); + +enum imapc_connection_state +imapc_connection_get_state(struct imapc_connection *conn); +enum imapc_capability +imapc_connection_get_capabilities(struct imapc_connection *conn); + +struct imapc_client_mailbox * +imapc_connection_get_mailbox(struct imapc_connection *conn); + +void imapc_connection_idle(struct imapc_connection *conn); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-msgmap.c Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,88 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "imapc-msgmap.h" + +struct imapc_msgmap { + ARRAY_TYPE(uint32_t) uids; + uint32_t uid_next; +}; + +struct imapc_msgmap *imapc_msgmap_init(void) +{ + struct imapc_msgmap *msgmap; + + msgmap = i_new(struct imapc_msgmap, 1); + i_array_init(&msgmap->uids, 128); + msgmap->uid_next = 1; + return msgmap; +} + +void imapc_msgmap_deinit(struct imapc_msgmap **_msgmap) +{ + struct imapc_msgmap *msgmap = *_msgmap; + + *_msgmap = NULL; + + array_free(&msgmap->uids); + i_free(msgmap); +} + +uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap) +{ + return array_count(&msgmap->uids); +} + +uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap) +{ + return msgmap->uid_next; +} + +uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq) +{ + const uint32_t *uidp; + + uidp = array_idx(&msgmap->uids, rseq-1); + return *uidp; +} + +static int uint32_cmp(const uint32_t *p1, const uint32_t *p2) +{ + return *p1 < *p2 ? -1 : + (*p1 > *p2 ? 1 : 0); +} + +bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, + uint32_t uid, uint32_t *rseq_r) +{ + const uint32_t *p, *first; + + p = array_bsearch(&msgmap->uids, &uid, uint32_cmp); + if (p == NULL) { + *rseq_r = 0; + return FALSE; + } + + first = array_idx(&msgmap->uids, 0); + *rseq_r = (p - first) + 1; + return TRUE; +} + +void imapc_msgmap_append(struct imapc_msgmap *msgmap, + uint32_t rseq, uint32_t uid) +{ + i_assert(rseq == imapc_msgmap_count(msgmap) + 1); + i_assert(uid >= msgmap->uid_next); + + msgmap->uid_next = uid + 1; + array_append(&msgmap->uids, &uid, 1); +} + +void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq) +{ + i_assert(rseq > 0); + i_assert(rseq <= imapc_msgmap_count(msgmap)); + + array_delete(&msgmap->uids, rseq-1, 1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap-client/imapc-msgmap.h Mon Sep 26 15:34:58 2011 +0300 @@ -0,0 +1,17 @@ +#ifndef IMAPC_MSGMAP_H +#define IMAPC_MSGMAP_H + +struct imapc_msgmap *imapc_msgmap_init(void); +void imapc_msgmap_deinit(struct imapc_msgmap **msgmap); + +uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap); +uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap); +uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq); +bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, + uint32_t uid, uint32_t *rseq_r); + +void imapc_msgmap_append(struct imapc_msgmap *msgmap, + uint32_t rseq, uint32_t uid); +void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq); + +#endif
--- a/src/lib-storage/index/imapc/Makefile.am Sun Sep 25 22:32:04 2011 +0000 +++ b/src/lib-storage/index/imapc/Makefile.am Mon Sep 26 15:34:58 2011 +0300 @@ -4,35 +4,27 @@ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ - -I$(top_srcdir)/src/lib-dns \ - -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-imap-client \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/list \ -I$(top_srcdir)/src/lib-storage/index libstorage_imapc_la_SOURCES = \ - imapc-client.c \ - imapc-connection.c \ imapc-list.c \ imapc-mail.c \ imapc-mail-fetch.c \ imapc-mailbox.c \ - imapc-msgmap.c \ imapc-save.c \ imapc-settings.c \ imapc-sync.c \ imapc-storage.c headers = \ - imapc-client.h \ - imapc-client-private.h \ - imapc-connection.h \ imapc-list.h \ imapc-mail.h \ - imapc-msgmap.h \ imapc-settings.h \ imapc-storage.h \ imapc-sync.h
--- a/src/lib-storage/index/imapc/imapc-client-private.h Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -#ifndef IMAPC_CLIENT_PRIVATE_H -#define IMAPC_CLIENT_PRIVATE_H - -#include "imapc-client.h" - -struct imapc_client_connection { - struct imapc_connection *conn; - struct imapc_client_mailbox *box; -}; - -struct imapc_client { - pool_t pool; - struct imapc_client_settings set; - struct ssl_iostream_context *ssl_ctx; - - imapc_untagged_callback_t *untagged_callback; - void *untagged_context; - - ARRAY_DEFINE(conns, struct imapc_client_connection *); - - struct ioloop *ioloop; - - unsigned int stop_now:1; -}; - -struct imapc_client_mailbox { - struct imapc_client *client; - struct imapc_connection *conn; - struct imapc_msgmap *msgmap; - - void *untagged_box_context; - unsigned int pending_box_command_count; -}; - -#endif
--- a/src/lib-storage/index/imapc/imapc-client.c Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,402 +0,0 @@ -/* Copyright (c) 2011 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> - -struct imapc_client_command_context { - struct imapc_client_mailbox *box; - - imapc_command_callback_t *callback; - void *context; -}; - -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 }, - - { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 }, - { NULL, 0 } -}; - -struct imapc_client * -imapc_client_init(const struct imapc_client_settings *set) -{ - struct imapc_client *client; - struct ssl_iostream_settings ssl_set; - const char *source; - pool_t pool; - - pool = pool_alloconly_create("imapc client", 1024); - client = p_new(pool, struct imapc_client, 1); - client->pool = pool; - - client->set.debug = set->debug; - client->set.host = p_strdup(pool, set->host); - client->set.port = set->port; - client->set.master_user = p_strdup(pool, set->master_user); - client->set.username = p_strdup(pool, set->username); - client->set.password = p_strdup(pool, set->password); - 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); - - if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) { - client->set.ssl_mode = set->ssl_mode; - client->set.ssl_ca_dir = p_strdup(pool, set->ssl_ca_dir); - - memset(&ssl_set, 0, sizeof(ssl_set)); - ssl_set.ca_dir = set->ssl_ca_dir; - ssl_set.verify_remote_cert = TRUE; - - source = t_strdup_printf("%s:%u", set->host, set->port); - if (ssl_iostream_context_init_client(source, &ssl_set, - &client->ssl_ctx) < 0) { - i_error("imapc(%s): Couldn't initialize SSL context", - source); - } - } - - p_array_init(&client->conns, pool, 8); - return client; -} - -void imapc_client_deinit(struct imapc_client **_client) -{ - struct imapc_client *client = *_client; - struct imapc_client_connection **connp; - - *_client = NULL; - - if (client->ssl_ctx != NULL) - ssl_iostream_context_deinit(&client->ssl_ctx); - array_foreach_modifiable(&client->conns, connp) { - imapc_connection_deinit(&(*connp)->conn); - i_free(*connp); - } - pool_unref(&client->pool); -} - -void imapc_client_register_untagged(struct imapc_client *client, - imapc_untagged_callback_t *callback, - void *context) -{ - client->untagged_callback = callback; - client->untagged_context = context; -} - -void imapc_client_run_pre(struct imapc_client *client) -{ - struct imapc_client_connection *const *connp; - struct ioloop *prev_ioloop = current_ioloop; - bool handle_pending = client->stop_now; - - i_assert(client->ioloop == NULL); - - client->stop_now = FALSE; - - client->ioloop = io_loop_create(); - io_loop_set_running(client->ioloop); - - array_foreach(&client->conns, connp) { - imapc_connection_ioloop_changed((*connp)->conn); - imapc_connection_connect((*connp)->conn); - if (handle_pending) - imapc_connection_input_pending((*connp)->conn); - } - - if (io_loop_is_running(client->ioloop)) - io_loop_run(client->ioloop); - current_ioloop = prev_ioloop; -} - -void imapc_client_run_post(struct imapc_client *client) -{ - struct imapc_client_connection *const *connp; - struct ioloop *ioloop = client->ioloop; - - client->ioloop = NULL; - array_foreach(&client->conns, connp) - imapc_connection_ioloop_changed((*connp)->conn); - - current_ioloop = ioloop; - io_loop_destroy(&ioloop); -} - -void imapc_client_stop(struct imapc_client *client) -{ - if (client->ioloop != NULL) - io_loop_stop(client->ioloop); -} - -bool imapc_client_is_running(struct imapc_client *client) -{ - return client->ioloop != NULL; -} - -void imapc_client_stop_now(struct imapc_client *client) -{ - client->stop_now = TRUE; - imapc_client_stop(client); -} - -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->conn = imapc_connection_init(client); - array_append(&client->conns, &conn, 1); - 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_idx(&client->conns, 0); - return (*connp)->conn; -} - -void imapc_client_cmdf(struct imapc_client *client, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, ...) -{ - struct imapc_connection *conn; - va_list args; - - conn = imapc_client_find_connection(client); - - va_start(args, cmd_fmt); - imapc_connection_cmdvf(conn, FALSE, callback, context, cmd_fmt, args); - va_end(args); -} - -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); -} - - -struct imapc_client_mailbox * -imapc_client_mailbox_open(struct imapc_client *client, - const char *name, bool examine, - imapc_command_callback_t *callback, void *context, - 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(); - - imapc_connection_select(box, name, examine, callback, context); - return box; -} - -void imapc_client_mailbox_disconnect(struct imapc_client_mailbox *box) -{ - if (box->conn != NULL) - imapc_connection_disconnect(box->conn); -} - -void imapc_client_mailbox_close(struct imapc_client_mailbox **_box) -{ - struct imapc_client_mailbox *box = *_box; - struct imapc_client_connection *const *connp; - - array_foreach(&box->client->conns, connp) { - if ((*connp)->box == box) { - (*connp)->box = NULL; - break; - } - } - - if (box->conn != NULL) - imapc_connection_unselect(box); - imapc_msgmap_deinit(&box->msgmap); - i_free(box); - - /* set this only after unselect, which may cancel some commands that - reference this box */ - *_box = NULL; -} - -static void imapc_client_mailbox_cmd_cb(const struct imapc_command_reply *reply, - void *context) -{ - struct imapc_client_command_context *ctx = context; - - ctx->box->pending_box_command_count--; - - ctx->callback(reply, ctx->context); - i_free(ctx); -} - -static struct imapc_client_command_context * -imapc_client_mailbox_cmd_common(struct imapc_client_mailbox *box, - imapc_command_callback_t *callback, - void *context) -{ - struct imapc_client_command_context *ctx; - - ctx = i_new(struct imapc_client_command_context, 1); - ctx->box = box; - ctx->callback = callback; - ctx->context = context; - - box->pending_box_command_count++; - return ctx; -} - -static bool -imapc_client_mailbox_is_selected(struct imapc_client_mailbox *box, - struct imapc_command_reply *reply_r) -{ - struct imapc_client_mailbox *selected_box; - - selected_box = box->conn == NULL ? NULL : - imapc_connection_get_mailbox(box->conn); - if (selected_box == box) - return TRUE; - - memset(reply_r, 0, sizeof(*reply_r)); - reply_r->state = IMAPC_COMMAND_STATE_DISCONNECTED; - if (selected_box == NULL) { - reply_r->text_full = "Disconnected from server"; - } else { - i_error("imapc: Selected mailbox changed unexpectedly"); - reply_r->text_full = "Internal error"; - } - reply_r->text_without_resp = reply_r->text_full; - - box->conn = NULL; - return FALSE; -} - -void imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, - const char *cmd, - imapc_command_callback_t *callback, - void *context) -{ - struct imapc_client_command_context *ctx; - struct imapc_command_reply reply; - - if (!imapc_client_mailbox_is_selected(box, &reply)) { - callback(&reply, context); - return; - } - - ctx = imapc_client_mailbox_cmd_common(box, callback, context); - imapc_connection_cmd(box->conn, TRUE, cmd, - imapc_client_mailbox_cmd_cb, ctx); -} - -void imapc_client_mailbox_cmdf(struct imapc_client_mailbox *box, - imapc_command_callback_t *callback, - void *context, const char *cmd_fmt, ...) -{ - struct imapc_client_command_context *ctx; - va_list args; - struct imapc_command_reply reply; - - if (!imapc_client_mailbox_is_selected(box, &reply)) { - callback(&reply, context); - return; - } - - ctx = imapc_client_mailbox_cmd_common(box, callback, context); - va_start(args, cmd_fmt); - imapc_connection_cmdvf(box->conn, TRUE, imapc_client_mailbox_cmd_cb, - ctx, cmd_fmt, args); - va_end(args); -} - -struct imapc_msgmap * -imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box) -{ - return box->msgmap; -} - -void imapc_client_mailbox_idle(struct imapc_client_mailbox *box) -{ - struct imapc_command_reply reply; - - if (imapc_client_mailbox_is_selected(box, &reply)) - imapc_connection_idle(box->conn); -} - -bool imapc_client_mailbox_is_connected(struct imapc_client_mailbox *box) -{ - struct imapc_command_reply reply; - - return imapc_client_mailbox_is_selected(box, &reply); -} - -enum imapc_capability -imapc_client_get_capabilities(struct imapc_client *client) -{ - struct imapc_client_connection *const *connp; - - connp = array_idx(&client->conns, 0); - return imapc_connection_get_capabilities((*connp)->conn); -} - -int imapc_client_create_temp_fd(struct imapc_client *client, - const char **path_r) -{ - string_t *path; - int fd; - - 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 (unlink(str_c(path)) < 0) { - /* shouldn't happen.. */ - i_error("unlink(%s) failed: %m", str_c(path)); - (void)close(fd); - return -1; - } - *path_r = str_c(path); - return fd; -}
--- a/src/lib-storage/index/imapc/imapc-client.h Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ -#ifndef IMAPC_CLIENT_H -#define IMAPC_CLIENT_H - -enum imapc_command_state { - IMAPC_COMMAND_STATE_OK, - IMAPC_COMMAND_STATE_NO, - IMAPC_COMMAND_STATE_BAD, - IMAPC_COMMAND_STATE_DISCONNECTED -}; - -enum imapc_capability { - IMAPC_CAPABILITY_SASL_IR = 0x01, - IMAPC_CAPABILITY_LITERALPLUS = 0x02, - IMAPC_CAPABILITY_QRESYNC = 0x04, - IMAPC_CAPABILITY_IDLE = 0x08, - IMAPC_CAPABILITY_UIDPLUS = 0x10, - IMAPC_CAPABILITY_AUTH_PLAIN = 0x20, - IMAPC_CAPABILITY_STARTTLS = 0x40, - - IMAPC_CAPABILITY_IMAP4REV1 = 0x400000000 -}; -struct imapc_capability_name { - const char *name; - enum imapc_capability capability; -}; -extern const struct imapc_capability_name imapc_capability_names[]; - -enum imapc_client_ssl_mode { - IMAPC_CLIENT_SSL_MODE_NONE, - IMAPC_CLIENT_SSL_MODE_IMMEDIATE, - IMAPC_CLIENT_SSL_MODE_STARTTLS -}; - -struct imapc_client_settings { - const char *host; - unsigned int port; - - const char *master_user; - const char *username; - const char *password; - - const char *dns_client_socket_path; - const char *temp_path_prefix; - - enum imapc_client_ssl_mode ssl_mode; - const char *ssl_ca_dir; - - const char *rawlog_dir; - bool debug; -}; - -struct imapc_command_reply { - enum imapc_command_state state; - /* "[RESP TEXT]" produces key=RESP, value=TEXT. - "[RESP]" produces key=RESP, value=NULL - otherwise both are NULL */ - const char *resp_text_key, *resp_text_value; - /* The full tagged reply, including [RESP TEXT]. */ - const char *text_full; - /* Tagged reply text without [RESP TEXT] */ - const char *text_without_resp; -}; - -struct imapc_arg_file { - /* file descriptor containing the value */ - int fd; - - /* parent_arg.list[list_idx] points to the IMAP_ARG_LITERAL_SIZE - argument */ - const struct imap_arg *parent_arg; - unsigned int list_idx; -}; - -struct imapc_untagged_reply { - /* name of the untagged reply, e.g. EXISTS */ - const char *name; - /* number at the beginning of the reply, or 0 if there wasn't any. - Set for EXISTS, EXPUNGE, etc. */ - uint32_t num; - /* the rest of the reply can be read from these args. */ - const struct imap_arg *args; - /* arguments whose contents are stored into files. only - "FETCH (BODY[" arguments can be here. */ - const struct imapc_arg_file *file_args; - unsigned int file_args_count; - - /* "* OK [RESP TEXT]" produces key=RESP, value=TEXT. - "* OK [RESP]" produces key=RESP, value=NULL - otherwise both are NULL */ - const char *resp_text_key, *resp_text_value; - - /* If this reply occurred while a mailbox was selected, this contains - the mailbox's untagged_context. */ - void *untagged_box_context; -}; - -/* Called when tagged reply is received for command. */ -typedef void imapc_command_callback_t(const struct imapc_command_reply *reply, - void *context); -/* Called each time untagged input is received. */ -typedef void imapc_untagged_callback_t(const struct imapc_untagged_reply *reply, - void *context); - -struct imapc_client * -imapc_client_init(const struct imapc_client_settings *set); -void imapc_client_deinit(struct imapc_client **client); - -void imapc_client_cmdf(struct imapc_client *client, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, ...) ATTR_FORMAT(4, 5); - -void imapc_client_register_untagged(struct imapc_client *client, - imapc_untagged_callback_t *callback, - void *context); - -void imapc_client_run_pre(struct imapc_client *client); -void imapc_client_run_post(struct imapc_client *client); -void imapc_client_stop(struct imapc_client *client); -/* Stop immediately, don't finish even any already read pending replies. - They'll be finished when imapc_client_run() is again called. */ -void imapc_client_stop_now(struct imapc_client *client); -bool imapc_client_is_running(struct imapc_client *client); - -struct imapc_client_mailbox * -imapc_client_mailbox_open(struct imapc_client *client, - const char *name, bool examine, - imapc_command_callback_t *callback, void *context, - void *untagged_box_context); -void imapc_client_mailbox_close(struct imapc_client_mailbox **box); -void imapc_client_mailbox_disconnect(struct imapc_client_mailbox *box); -void imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, - const char *cmd, - imapc_command_callback_t *callback, - void *context); -void imapc_client_mailbox_cmdf(struct imapc_client_mailbox *box, - imapc_command_callback_t *callback, - void *context, const char *cmd_fmt, ...) - ATTR_FORMAT(4, 5); -struct imapc_msgmap * -imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box); - -void imapc_client_mailbox_idle(struct imapc_client_mailbox *box); -bool imapc_client_mailbox_is_connected(struct imapc_client_mailbox *box); - -enum imapc_capability -imapc_client_get_capabilities(struct imapc_client *client); - -int imapc_client_create_temp_fd(struct imapc_client *client, - const char **path_r); - -#endif
--- a/src/lib-storage/index/imapc/imapc-connection.c Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1704 +0,0 @@ -/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ - -#include "lib.h" -#include "ioloop.h" -#include "network.h" -#include "istream.h" -#include "ostream.h" -#include "base64.h" -#include "write-full.h" -#include "str.h" -#include "dns-lookup.h" -#include "iostream-rawlog.h" -#include "iostream-ssl.h" -#include "imap-quote.h" -#include "imap-util.h" -#include "imap-parser.h" -#include "imapc-client-private.h" -#include "imapc-connection.h" - -#include <unistd.h> -#include <ctype.h> - -#define IMAPC_DNS_LOOKUP_TIMEOUT_MSECS (1000*30) -#define IMAPC_CONNECT_TIMEOUT_MSECS (1000*30) -#define IMAPC_COMMAND_TIMEOUT_MSECS (1000*60*5) -#define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32) -/* IMAP protocol requires activity at least every 30 minutes */ -#define IMAPC_MAX_IDLE_MSECS (1000*60*29) - -enum imapc_input_state { - IMAPC_INPUT_STATE_NONE = 0, - IMAPC_INPUT_STATE_PLUS, - IMAPC_INPUT_STATE_UNTAGGED, - IMAPC_INPUT_STATE_UNTAGGED_NUM, - IMAPC_INPUT_STATE_TAGGED -}; - -struct imapc_command_stream { - unsigned int pos; - uoff_t size; - struct istream *input; -}; - -struct imapc_command { - pool_t pool; - buffer_t *data; - unsigned int send_pos; - unsigned int tag; - - ARRAY_DEFINE(streams, struct imapc_command_stream); - - imapc_command_callback_t *callback; - void *context; - - unsigned int idle:1; - unsigned int mailboxcmd:1; - unsigned int wait_for_literal:1; -}; - -struct imapc_connection_literal { - char *temp_path; - int fd; - uoff_t bytes_left; - - const struct imap_arg *parent_arg; - unsigned int list_idx; -}; - -struct imapc_connection { - struct imapc_client *client; - char *name; - - int fd; - struct io *io; - struct istream *input, *raw_input; - struct ostream *output, *raw_output; - struct imap_parser *parser; - struct timeout *to; - struct timeout *to_output; - - struct ssl_iostream *ssl_iostream; - - int (*input_callback)(struct imapc_connection *conn); - enum imapc_input_state input_state; - unsigned int cur_tag; - uint32_t cur_num; - - struct imapc_client_mailbox *selecting_box, *selected_box; - enum imapc_connection_state state; - - enum imapc_capability capabilities; - char **capabilities_list; - - /* commands pending in queue to be sent */ - ARRAY_DEFINE(cmd_send_queue, struct imapc_command *); - /* commands that have been sent, waiting for their tagged reply */ - ARRAY_DEFINE(cmd_wait_list, struct imapc_command *); - - unsigned int ips_count, prev_connect_idx; - struct ip_addr *ips; - - struct imapc_connection_literal literal; - ARRAY_DEFINE(literal_files, struct imapc_arg_file); - - unsigned int idling:1; - unsigned int idle_stopping:1; - unsigned int idle_plus_waiting:1; - unsigned int handshake_failed:1; -}; - -static int imapc_connection_output(struct imapc_connection *conn); -static int imapc_connection_ssl_init(struct imapc_connection *conn); -static void imapc_command_free(struct imapc_command *cmd); -static void imapc_command_send_more(struct imapc_connection *conn, - struct imapc_command *cmd); - -struct imapc_connection * -imapc_connection_init(struct imapc_client *client) -{ - struct imapc_connection *conn; - - conn = i_new(struct imapc_connection, 1); - conn->client = client; - conn->fd = -1; - conn->name = i_strdup_printf("%s:%u", client->set.host, - client->set.port); - conn->literal.fd = -1; - i_array_init(&conn->cmd_send_queue, 8); - i_array_init(&conn->cmd_wait_list, 32); - i_array_init(&conn->literal_files, 4); - return conn; -} - -void imapc_connection_deinit(struct imapc_connection **_conn) -{ - struct imapc_connection *conn = *_conn; - - *_conn = NULL; - - imapc_connection_disconnect(conn); - if (conn->capabilities_list != NULL) - p_strsplit_free(default_pool, conn->capabilities_list); - array_free(&conn->cmd_send_queue); - array_free(&conn->cmd_wait_list); - array_free(&conn->literal_files); - i_free(conn->ips); - i_free(conn->name); - i_free(conn); -} - -void imapc_connection_ioloop_changed(struct imapc_connection *conn) -{ - if (conn->io != NULL) - conn->io = io_loop_move_io(&conn->io); - if (conn->to != NULL) - conn->to = io_loop_move_timeout(&conn->to); - if (conn->output != NULL) - o_stream_switch_ioloop(conn->output); - - if (conn->client->ioloop == NULL && conn->to_output != NULL) { - /* we're only once moving the to_output to the main ioloop, - since timeout moves currently also reset the timeout. - (the rest of the times this is a no-op) */ - conn->to_output = io_loop_move_timeout(&conn->to_output); - } -} - -static const char *imapc_command_get_readable(struct imapc_command *cmd) -{ - string_t *str = t_str_new(256); - const unsigned char *data = cmd->data->data; - unsigned int i; - - for (i = 0; i < cmd->data->used; i++) { - if (data[i] != '\r' && data[i] != '\n') - str_append_c(str, data[i]); - } - return str_c(str); -} - -static void -imapc_connection_abort_pending_commands(struct imapc_connection *conn, - const struct imapc_command_reply *reply) -{ - struct imapc_command *const *cmdp, *cmd; - - while (array_count(&conn->cmd_wait_list) > 0) { - cmdp = array_idx(&conn->cmd_wait_list, 0); - cmd = *cmdp; - array_delete(&conn->cmd_wait_list, 0, 1); - - if (cmd->callback != NULL) - cmd->callback(reply, cmd->context); - imapc_command_free(cmd); - } - while (array_count(&conn->cmd_send_queue) > 0) { - cmdp = array_idx(&conn->cmd_send_queue, 0); - cmd = *cmdp; - array_delete(&conn->cmd_send_queue, 0, 1); - - if (cmd->callback != NULL) - cmd->callback(reply, cmd->context); - imapc_command_free(cmd); - } -} - -static void imapc_connection_set_state(struct imapc_connection *conn, - enum imapc_connection_state state) -{ - if (state == IMAPC_CONNECTION_STATE_DISCONNECTED) { - struct imapc_command_reply reply; - - memset(&reply, 0, sizeof(reply)); - reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; - reply.text_without_resp = reply.text_full = - "Disconnected from server"; - imapc_connection_abort_pending_commands(conn, &reply); - - conn->idling = FALSE; - conn->idle_plus_waiting = FALSE; - conn->idle_stopping = FALSE; - - conn->selecting_box = NULL; - conn->selected_box = NULL; - } - if (state == IMAPC_CONNECTION_STATE_DONE) { - if (array_count(&conn->cmd_send_queue) > 0) { - struct imapc_command *const *cmd_p = - array_idx(&conn->cmd_send_queue, 0); - imapc_command_send_more(conn, *cmd_p); - } - } - conn->state = state; -} - -static void imapc_connection_lfiles_free(struct imapc_connection *conn) -{ - struct imapc_arg_file *lfile; - - array_foreach_modifiable(&conn->literal_files, lfile) { - if (close(lfile->fd) < 0) - i_error("imapc: close(literal file) failed: %m"); - } - array_clear(&conn->literal_files); -} - -static void -imapc_connection_literal_reset(struct imapc_connection_literal *literal) -{ - if (literal->fd != -1) { - if (close(literal->fd) < 0) - i_error("close(%s) failed: %m", literal->temp_path); - } - i_free_and_null(literal->temp_path); - - memset(literal, 0, sizeof(*literal)); - literal->fd = -1; -} - -void imapc_connection_disconnect(struct imapc_connection *conn) -{ - if (conn->fd == -1) - return; - - if (conn->client->set.debug) - i_debug("imapc(%s): Disconnected", conn->name); - - imapc_connection_lfiles_free(conn); - imapc_connection_literal_reset(&conn->literal); - if (conn->to != NULL) - timeout_remove(&conn->to); - if (conn->to_output != NULL) - timeout_remove(&conn->to_output); - imap_parser_destroy(&conn->parser); - io_remove(&conn->io); - if (conn->ssl_iostream != NULL) - ssl_iostream_unref(&conn->ssl_iostream); - i_stream_destroy(&conn->input); - o_stream_destroy(&conn->output); - net_disconnect(conn->fd); - conn->fd = -1; - - imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); -} - -static void ATTR_FORMAT(2, 3) -imapc_connection_input_error(struct imapc_connection *conn, - const char *fmt, ...) -{ - va_list va; - - va_start(va, fmt); - i_error("imapc(%s): Server sent invalid input: %s", - conn->name, t_strdup_vprintf(fmt, va)); - sleep(3600); - imapc_connection_disconnect(conn); - va_end(va); -} - -static bool last_arg_is_fetch_body(const struct imap_arg *args, - const struct imap_arg **parent_arg_r, - unsigned int *idx_r) -{ - const struct imap_arg *list; - const char *name; - unsigned int count; - - if (args[0].type == IMAP_ARG_ATOM && - imap_arg_atom_equals(&args[1], "FETCH") && - imap_arg_get_list_full(&args[2], &list, &count) && count >= 2 && - list[count].type == IMAP_ARG_LITERAL_SIZE && - imap_arg_get_atom(&list[count-1], &name) && - strncasecmp(name, "BODY[", 5) == 0) { - *parent_arg_r = &args[2]; - *idx_r = count; - return TRUE; - } - return FALSE; -} - -static int -imapc_connection_read_literal_init(struct imapc_connection *conn, uoff_t size, - const struct imap_arg *args) -{ - const char *path; - const struct imap_arg *parent_arg; - unsigned int idx; - - i_assert(size > 0); - i_assert(conn->literal.fd == -1); - - if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE || - !last_arg_is_fetch_body(args, &parent_arg, &idx)) { - /* read the literal directly into parser */ - return 0; - } - - conn->literal.fd = imapc_client_create_temp_fd(conn->client, &path); - if (conn->literal.fd == -1) - return -1; - conn->literal.temp_path = i_strdup(path); - conn->literal.bytes_left = size; - conn->literal.parent_arg = parent_arg; - conn->literal.list_idx = idx; - return 1; -} - -static int imapc_connection_read_literal(struct imapc_connection *conn) -{ - struct imapc_arg_file *lfile; - const unsigned char *data; - size_t size; - - if (conn->literal.bytes_left == 0) - return 1; - - data = i_stream_get_data(conn->input, &size); - if (size > conn->literal.bytes_left) - size = conn->literal.bytes_left; - if (size > 0) { - if (write_full(conn->literal.fd, data, size) < 0) { - i_error("imapc(%s): write(%s) failed: %m", - conn->name, conn->literal.temp_path); - imapc_connection_disconnect(conn); - return -1; - } - i_stream_skip(conn->input, size); - conn->literal.bytes_left -= size; - } - if (conn->literal.bytes_left > 0) - return 0; - - /* finished */ - lfile = array_append_space(&conn->literal_files); - lfile->fd = conn->literal.fd; - lfile->parent_arg = conn->literal.parent_arg; - lfile->list_idx = conn->literal.list_idx; - - conn->literal.fd = -1; - imapc_connection_literal_reset(&conn->literal); - return 1; -} - -static int -imapc_connection_read_line_more(struct imapc_connection *conn, - const struct imap_arg **imap_args_r) -{ - uoff_t literal_size; - bool fatal; - int ret; - - if ((ret = imapc_connection_read_literal(conn)) <= 0) - return ret; - - ret = imap_parser_read_args(conn->parser, 0, - IMAP_PARSE_FLAG_LITERAL_SIZE | - IMAP_PARSE_FLAG_ATOM_ALLCHARS, imap_args_r); - if (ret == -2) { - /* need more data */ - return 0; - } - if (ret < 0) { - imapc_connection_input_error(conn, "Error parsing input: %s", - imap_parser_get_error(conn->parser, &fatal)); - return -1; - } - - if (imap_parser_get_literal_size(conn->parser, &literal_size)) { - if (imapc_connection_read_literal_init(conn, literal_size, - *imap_args_r) <= 0) { - imap_parser_read_last_literal(conn->parser); - return 2; - } - return imapc_connection_read_line_more(conn, imap_args_r); - } - return 1; -} - -static int -imapc_connection_read_line(struct imapc_connection *conn, - const struct imap_arg **imap_args_r) -{ - const unsigned char *data; - size_t size; - int ret; - - while ((ret = imapc_connection_read_line_more(conn, imap_args_r)) == 2) - ; - - if (ret > 0) { - data = i_stream_get_data(conn->input, &size); - if (size >= 2 && data[0] == '\r' && data[1] == '\n') - i_stream_skip(conn->input, 2); - else if (size >= 1 && data[0] == '\n') - i_stream_skip(conn->input, 1); - else - i_panic("imapc: Missing LF from input line"); - } - return ret; -} - -static int -imapc_connection_parse_capability(struct imapc_connection *conn, - const char *value) -{ - const char *const *tmp; - unsigned int i; - - if (conn->client->set.debug) { - i_debug("imapc(%s): Server capabilities: %s", - conn->name, value); - } - - conn->capabilities = 0; - if (conn->capabilities_list != NULL) - p_strsplit_free(default_pool, conn->capabilities_list); - conn->capabilities_list = p_strsplit(default_pool, value, " "); - - for (tmp = t_strsplit(value, " "); *tmp != NULL; tmp++) { - for (i = 0; imapc_capability_names[i].name != NULL; i++) { - const struct imapc_capability_name *cap = - &imapc_capability_names[i]; - - if (strcasecmp(*tmp, cap->name) == 0) { - conn->capabilities |= cap->capability; - break; - } - } - } - - if ((conn->capabilities & IMAPC_CAPABILITY_IMAP4REV1) == 0) { - imapc_connection_input_error(conn, - "CAPABILITY list is missing IMAP4REV1"); - return -1; - } - return 0; -} - -static int -imapc_connection_handle_resp_text_code(struct imapc_connection *conn, - const char *key, const char *value) -{ - if (strcasecmp(key, "CAPABILITY") == 0) { - if (imapc_connection_parse_capability(conn, value) < 0) - return -1; - } - if (strcasecmp(key, "CLOSED") == 0) { - /* QRESYNC: SELECTing another mailbox */ - if (conn->selecting_box != NULL) { - conn->selected_box = conn->selecting_box; - conn->selecting_box = NULL; - } - } - return 0; -} - -static int -imapc_connection_handle_resp_text(struct imapc_connection *conn, - const char *text, - const char **key_r, const char **value_r) -{ - const char *p, *value; - - i_assert(text[0] == '['); - - p = strchr(text, ']'); - if (p == NULL) { - imapc_connection_input_error(conn, "Missing ']' in resp-text"); - return -1; - } - text = t_strdup_until(text + 1, p); - value = strchr(text, ' '); - if (value != NULL) { - *key_r = t_strdup_until(text, value); - *value_r = value + 1; - } else { - *key_r = text; - *value_r = NULL; - } - return 0; -} - -static int -imapc_connection_handle_imap_resp_text(struct imapc_connection *conn, - const struct imap_arg *args, - const char **key_r, const char **value_r) -{ - const char *text; - - if (args->type != IMAP_ARG_ATOM) - return 0; - - text = imap_args_to_str(args); - if (*text != '[') { - if (*text == '\0') { - imapc_connection_input_error(conn, - "Missing text in resp-text"); - return -1; - } - return 0; - } - if (imapc_connection_handle_resp_text(conn, text, key_r, value_r) < 0) - return -1; - - return imapc_connection_handle_resp_text_code(conn, *key_r, *value_r); -} - -static bool need_literal(const char *str) -{ - unsigned int i; - - for (i = 0; str[i] != '\0'; i++) { - unsigned char c = str[i]; - - if ((c & 0x80) != 0 || c == '\r' || c == '\n') - return TRUE; - } - return FALSE; -} - -static void imapc_connection_input_reset(struct imapc_connection *conn) -{ - conn->input_state = IMAPC_INPUT_STATE_NONE; - conn->cur_tag = 0; - conn->cur_num = 0; - if (conn->parser != NULL) - imap_parser_reset(conn->parser); - imapc_connection_lfiles_free(conn); -} - -static void imapc_connection_login_cb(const struct imapc_command_reply *reply, - void *context) -{ - struct imapc_connection *conn = context; - - if (reply->state != IMAPC_COMMAND_STATE_OK) { - i_error("imapc(%s): Authentication failed: %s", - conn->name, reply->text_full); - imapc_connection_disconnect(conn); - return; - } - - if (conn->client->set.debug) - i_debug("imapc(%s): Authenticated successfully", conn->name); - - timeout_remove(&conn->to); - imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE); -} - -static const char * -imapc_connection_get_sasl_plain_request(struct imapc_connection *conn) -{ - const struct imapc_client_settings *set = &conn->client->set; - string_t *in, *out; - - in = t_str_new(128); - if (set->master_user != NULL) { - str_append(in, set->username); - str_append_c(in, '\0'); - str_append(in, set->master_user); - } else { - str_append_c(in, '\0'); - str_append(in, set->username); - } - str_append_c(in, '\0'); - str_append(in, set->password); - - out = t_str_new(128); - base64_encode(in->data, in->used, out); - return str_c(out); -} - -static void imapc_connection_authenticate(struct imapc_connection *conn) -{ - const struct imapc_client_settings *set = &conn->client->set; - const char *cmd; - - if (conn->client->set.debug) { - if (set->master_user == NULL) { - i_debug("imapc(%s): Authenticating as %s", - conn->name, set->username); - } else { - i_debug("imapc(%s): Authenticating as %s for user %s", - conn->name, set->master_user, set->username); - } - } - - if ((set->master_user == NULL && - need_literal(set->username) && need_literal(set->password)) || - (conn->capabilities & IMAPC_CAPABILITY_AUTH_PLAIN) == 0) { - /* We can use LOGIN command */ - imapc_connection_cmdf(conn, FALSE, imapc_connection_login_cb, - conn, "LOGIN %s %s", - set->username, set->password); - } else if ((conn->capabilities & IMAPC_CAPABILITY_SASL_IR) != 0) { - cmd = t_strdup_printf("AUTHENTICATE PLAIN %s", - imapc_connection_get_sasl_plain_request(conn)); - imapc_connection_cmd(conn, FALSE, cmd, - imapc_connection_login_cb, conn); - } else { - cmd = t_strdup_printf("AUTHENTICATE PLAIN\r\n%s", - imapc_connection_get_sasl_plain_request(conn)); - imapc_connection_cmd(conn, FALSE, cmd, - imapc_connection_login_cb, conn); - } -} - -static void -imapc_connection_starttls_cb(const struct imapc_command_reply *reply, - void *context) -{ - struct imapc_connection *conn = context; - - if (reply->state != IMAPC_COMMAND_STATE_OK) { - imapc_connection_input_error(conn, "STARTTLS failed: %s", - reply->text_full); - return; - } - - if (imapc_connection_ssl_init(conn) < 0) - imapc_connection_disconnect(conn); - else - imapc_connection_authenticate(conn); -} - -static void imapc_connection_starttls(struct imapc_connection *conn) -{ - if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_STARTTLS && - conn->ssl_iostream == NULL) { - if ((conn->capabilities & IMAPC_CAPABILITY_STARTTLS) == 0) { - i_error("imapc(%s): Requested STARTTLS, " - "but server doesn't support it", - conn->name); - imapc_connection_disconnect(conn); - return; - } - imapc_connection_cmd(conn, FALSE, "STARTTLS", - imapc_connection_starttls_cb, conn); - return; - } - imapc_connection_authenticate(conn); -} - -static void -imapc_connection_capability_cb(const struct imapc_command_reply *reply, - void *context) -{ - struct imapc_connection *conn = context; - - if (reply->state != IMAPC_COMMAND_STATE_OK) { - imapc_connection_input_error(conn, - "Failed to get capabilities: %s", reply->text_full); - } else if (conn->capabilities == 0) { - imapc_connection_input_error(conn, - "Capabilities not returned by server"); - } else { - imapc_connection_starttls(conn); - } -} - -static int imapc_connection_input_banner(struct imapc_connection *conn) -{ - const struct imap_arg *imap_args; - const char *key, *value; - int ret; - - if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) - return ret; - - if (imapc_connection_handle_imap_resp_text(conn, imap_args, - &key, &value) < 0) - return -1; - imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING); - - if (conn->capabilities == 0) { - /* capabilities weren't sent in the banner. ask for them. */ - imapc_connection_cmd(conn, FALSE, "CAPABILITY", - imapc_connection_capability_cb, conn); - } else { - imapc_connection_starttls(conn); - } - conn->input_callback = NULL; - imapc_connection_input_reset(conn); - return 1; -} - -static int imapc_connection_input_untagged(struct imapc_connection *conn) -{ - const struct imap_arg *imap_args; - const char *name, *value; - struct imapc_untagged_reply reply; - int ret; - - if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) { - /* input banner */ - name = imap_parser_read_word(conn->parser); - if (name == NULL) - return 0; - - if (strcasecmp(name, "OK") != 0) { - imapc_connection_input_error(conn, - "Banner doesn't begin with OK: %s", name); - return -1; - } - conn->input_callback = imapc_connection_input_banner; - return 1; - } - - if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) - return ret; - if (!imap_arg_get_atom(&imap_args[0], &name)) { - imapc_connection_input_error(conn, "Invalid untagged reply"); - return -1; - } - imap_args++; - - if (conn->input_state == IMAPC_INPUT_STATE_UNTAGGED && - str_to_uint32(name, &conn->cur_num) == 0) { - /* <seq> <event> */ - conn->input_state = IMAPC_INPUT_STATE_UNTAGGED_NUM; - if (!imap_arg_get_atom(&imap_args[0], &name)) { - imapc_connection_input_error(conn, - "Invalid untagged reply"); - return -1; - } - imap_args++; - } - memset(&reply, 0, sizeof(reply)); - - if (strcasecmp(name, "OK") == 0) { - if (imapc_connection_handle_imap_resp_text(conn, imap_args, - &reply.resp_text_key, - &reply.resp_text_value) < 0) - return -1; - } else if (strcasecmp(name, "CAPABILITY") == 0) { - value = imap_args_to_str(imap_args); - if (imapc_connection_parse_capability(conn, value) < 0) - return -1; - } - - reply.name = name; - reply.num = conn->cur_num; - reply.args = imap_args; - reply.file_args = array_get(&conn->literal_files, - &reply.file_args_count); - - if (conn->selected_box != NULL) { - reply.untagged_box_context = - conn->selected_box->untagged_box_context; - } - conn->client->untagged_callback(&reply, conn->client->untagged_context); - imapc_connection_input_reset(conn); - return 1; -} - -static int imapc_connection_input_plus(struct imapc_connection *conn) -{ - struct imapc_command *const *cmds; - unsigned int cmds_count; - const char *line; - - if ((line = i_stream_next_line(conn->input)) == NULL) - return 0; - - cmds = array_get(&conn->cmd_send_queue, &cmds_count); - if (conn->idle_plus_waiting) { - /* "+ idling" reply for IDLE command */ - conn->idle_plus_waiting = FALSE; - conn->idling = TRUE; - } else if (cmds_count > 0 && cmds[0]->wait_for_literal) { - /* reply for literal */ - cmds[0]->wait_for_literal = FALSE; - imapc_command_send_more(conn, cmds[0]); - } else { - imapc_connection_input_error(conn, "Unexpected '+': %s", line); - return -1; - } - - imapc_connection_input_reset(conn); - return 1; -} - -static int imapc_connection_input_tagged(struct imapc_connection *conn) -{ - struct imapc_command *const *cmds, *cmd = NULL; - unsigned int i, count; - char *line, *linep; - const char *p; - struct imapc_command_reply reply; - - line = i_stream_next_line(conn->input); - if (line == NULL) - return 0; - - memset(&reply, 0, sizeof(reply)); - - linep = strchr(line, ' '); - if (linep == NULL) - reply.text_full = ""; - else { - *linep = '\0'; - reply.text_full = linep + 1; - } - - if (strcasecmp(line, "ok") == 0) - reply.state = IMAPC_COMMAND_STATE_OK; - else if (strcasecmp(line, "no") == 0) - reply.state = IMAPC_COMMAND_STATE_NO; - else if (strcasecmp(line, "bad") == 0) - reply.state = IMAPC_COMMAND_STATE_BAD; - else { - imapc_connection_input_error(conn, - "Invalid state in tagged reply: %u %s %s", - conn->cur_tag, line, reply.text_full); - return -1; - } - - if (reply.text_full[0] == '[') { - /* get resp-text */ - if (imapc_connection_handle_resp_text(conn, reply.text_full, - &reply.resp_text_key, - &reply.resp_text_value) < 0) - return -1; - - p = strchr(reply.text_full, ']'); - i_assert(p != NULL); - reply.text_without_resp = p + 1; - if (reply.text_without_resp[0] == ' ') - reply.text_without_resp++; - } else { - reply.text_without_resp = reply.text_full; - } - - /* find the command. it's either the first command in send queue - (literal failed) or somewhere in wait list. */ - cmds = array_get(&conn->cmd_send_queue, &count); - if (count > 0 && cmds[0]->tag == conn->cur_tag) { - cmd = cmds[0]; - array_delete(&conn->cmd_send_queue, 0, 1); - } else { - cmds = array_get(&conn->cmd_wait_list, &count); - for (i = 0; i < count; i++) { - if (cmds[i]->tag == conn->cur_tag) { - cmd = cmds[i]; - array_delete(&conn->cmd_wait_list, i, 1); - break; - } - } - } - if (array_count(&conn->cmd_wait_list) == 0 && - array_count(&conn->cmd_send_queue) == 0 && - conn->state == IMAPC_CONNECTION_STATE_DONE && conn->to != NULL) - timeout_remove(&conn->to); - - if (cmd == NULL) { - imapc_connection_input_error(conn, - "Unknown tag in a reply: %u %s %s", - conn->cur_tag, line, reply.text_full); - return -1; - } - - if (reply.state == IMAPC_COMMAND_STATE_BAD) { - i_error("imapc(%s): Command '%s' failed with BAD: %u %s", - conn->name, imapc_command_get_readable(cmd), - conn->cur_tag, reply.text_full); - imapc_connection_disconnect(conn); - } - - imapc_connection_input_reset(conn); - if (cmd->callback != NULL) - cmd->callback(&reply, cmd->context); - imapc_command_free(cmd); - return 1; -} - -static int imapc_connection_input_one(struct imapc_connection *conn) -{ - const char *tag; - int ret = -1; - - if (conn->input_callback != NULL) - return conn->input_callback(conn); - - switch (conn->input_state) { - case IMAPC_INPUT_STATE_NONE: - tag = imap_parser_read_word(conn->parser); - if (tag == NULL) - return 0; - - if (strcmp(tag, "*") == 0) { - conn->input_state = IMAPC_INPUT_STATE_UNTAGGED; - conn->cur_num = 0; - ret = imapc_connection_input_untagged(conn); - } else if (strcmp(tag, "+") == 0) { - conn->input_state = IMAPC_INPUT_STATE_PLUS; - ret = imapc_connection_input_plus(conn); - } else { - conn->input_state = IMAPC_INPUT_STATE_TAGGED; - if (str_to_uint(tag, &conn->cur_tag) < 0 || - conn->cur_tag == 0) { - imapc_connection_input_error(conn, - "Invalid command tag: %s", tag); - ret = -1; - } else { - ret = imapc_connection_input_tagged(conn); - } - } - break; - case IMAPC_INPUT_STATE_PLUS: - ret = imapc_connection_input_plus(conn); - break; - case IMAPC_INPUT_STATE_UNTAGGED: - case IMAPC_INPUT_STATE_UNTAGGED_NUM: - ret = imapc_connection_input_untagged(conn); - break; - case IMAPC_INPUT_STATE_TAGGED: - ret = imapc_connection_input_tagged(conn); - break; - } - return ret; -} - -static void imapc_connection_input(struct imapc_connection *conn) -{ - const char *errstr; - ssize_t ret = 0; - - /* we need to read as much as we can with SSL streams to avoid - hanging */ - while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0) - imapc_connection_input_pending(conn); - - if (ret < 0) { - /* disconnected */ - if (conn->ssl_iostream == NULL) { - i_error("imapc(%s): Server disconnected unexpectedly", - conn->name); - } else if (!conn->handshake_failed) { - errstr = ssl_iostream_get_last_error(conn->ssl_iostream); - i_error("imapc(%s): Server disconnected: %s", - conn->name, errstr != NULL ? errstr : ""); - } - imapc_connection_disconnect(conn); - return; - } -} - -static int imapc_connection_ssl_handshaked(void *context) -{ - struct imapc_connection *conn = context; - - if (!ssl_iostream_has_valid_client_cert(conn->ssl_iostream)) { - if (!ssl_iostream_has_broken_client_cert(conn->ssl_iostream)) { - i_error("imapc(%s): SSL certificate not received", - conn->name); - } else { - i_error("imapc(%s): Received invalid SSL certificate", - conn->name); - } - } else if (ssl_iostream_cert_match_name(conn->ssl_iostream, - conn->client->set.host) < 0) { - i_error("imapc(%s): SSL certificate doesn't match host name", - conn->name); - } else { - if (conn->client->set.debug) { - i_debug("imapc(%s): SSL handshake successful", - conn->name); - } - return 0; - } - conn->handshake_failed = TRUE; - i_stream_close(conn->input); - return -1; -} - -static int imapc_connection_ssl_init(struct imapc_connection *conn) -{ - struct ssl_iostream_settings ssl_set; - const char *source; - - if (conn->client->ssl_ctx == NULL) { - i_error("imapc(%s): No SSL context", conn->name); - return -1; - } - - memset(&ssl_set, 0, sizeof(ssl_set)); - ssl_set.verbose_invalid_cert = TRUE; - ssl_set.verify_remote_cert = TRUE; - ssl_set.require_valid_cert = TRUE; - - if (conn->client->set.debug) - i_debug("imapc(%s): Starting SSL handshake", conn->name); - - if (conn->raw_input != conn->input) { - /* recreate rawlog after STARTTLS */ - i_stream_ref(conn->raw_input); - o_stream_ref(conn->raw_output); - i_stream_destroy(&conn->input); - o_stream_destroy(&conn->output); - conn->input = conn->raw_input; - conn->output = conn->raw_output; - } - - source = t_strdup_printf("imapc(%s): ", conn->name); - if (io_stream_create_ssl(conn->client->ssl_ctx, source, &ssl_set, - &conn->input, &conn->output, - &conn->ssl_iostream) < 0) { - i_error("imapc(%s): Couldn't initialize SSL client", - conn->name); - return -1; - } - ssl_iostream_set_handshake_callback(conn->ssl_iostream, - imapc_connection_ssl_handshaked, - conn); - if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { - i_error("imapc(%s): SSL handshake failed: %s", conn->name, - ssl_iostream_get_last_error(conn->ssl_iostream)); - return -1; - } - - if (*conn->client->set.rawlog_dir != '\0') { - (void)iostream_rawlog_create(conn->client->set.rawlog_dir, - &conn->input, &conn->output); - } - - imap_parser_set_streams(conn->parser, conn->input, NULL); - return 0; -} - -static void imapc_connection_connected(struct imapc_connection *conn) -{ - const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; - int err; - - err = net_geterror(conn->fd); - if (err != 0) { - i_error("imapc(%s): connect(%s, %u) failed: %s", - conn->name, net_ip2addr(ip), conn->client->set.port, - strerror(err)); - imapc_connection_disconnect(conn); - return; - } - io_remove(&conn->io); - conn->io = io_add(conn->fd, IO_READ, imapc_connection_input, conn); - - if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { - if (imapc_connection_ssl_init(conn) < 0) - imapc_connection_disconnect(conn); - } -} - -static void imapc_connection_timeout(struct imapc_connection *conn) -{ - const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; - - switch (conn->state) { - case IMAPC_CONNECTION_STATE_CONNECTING: - i_error("imapc(%s): connect(%s, %u) timed out after %u seconds", - conn->name, net_ip2addr(ip), conn->client->set.port, - IMAPC_CONNECT_TIMEOUT_MSECS/1000); - break; - case IMAPC_CONNECTION_STATE_AUTHENTICATING: - i_error("imapc(%s): Authentication timed out after %u seconds", - conn->name, IMAPC_CONNECT_TIMEOUT_MSECS/1000); - break; - default: - i_unreached(); - } - imapc_connection_disconnect(conn); -} - -static void -imapc_reidle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, - void *context) -{ - struct imapc_connection *conn = context; - - imapc_connection_idle(conn); -} - -static void imapc_connection_reset_idle(struct imapc_connection *conn) -{ - if (!conn->idling) - imapc_connection_cmd(conn, FALSE, "NOOP", NULL, NULL); - else { - imapc_connection_cmd(conn, FALSE, "NOOP", - imapc_reidle_callback, conn); - } -} - -static void imapc_connection_connect_next_ip(struct imapc_connection *conn) -{ - const struct ip_addr *ip; - int fd; - - conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count; - ip = &conn->ips[conn->prev_connect_idx]; - fd = net_connect_ip(ip, conn->client->set.port, NULL); - if (fd == -1) { - imapc_connection_set_state(conn, - IMAPC_CONNECTION_STATE_DISCONNECTED); - return; - } - conn->fd = fd; - conn->input = conn->raw_input = i_stream_create_fd(fd, (size_t)-1, FALSE); - conn->output = conn->raw_output = o_stream_create_fd(fd, (size_t)-1, FALSE); - - if (*conn->client->set.rawlog_dir != '\0' && - conn->client->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { - (void)iostream_rawlog_create(conn->client->set.rawlog_dir, - &conn->input, &conn->output); - } - - o_stream_set_flush_callback(conn->output, imapc_connection_output, - conn); - conn->io = io_add(fd, IO_WRITE, imapc_connection_connected, conn); - conn->parser = imap_parser_create(conn->input, NULL, (size_t)-1); - conn->to = timeout_add(IMAPC_CONNECT_TIMEOUT_MSECS, - imapc_connection_timeout, conn); - conn->to_output = timeout_add(IMAPC_MAX_IDLE_MSECS, - imapc_connection_reset_idle, conn); - if (conn->client->set.debug) { - i_debug("imapc(%s): Connecting to %s:%u", conn->name, - net_ip2addr(ip), conn->client->set.port); - } -} - -static void -imapc_connection_dns_callback(const struct dns_lookup_result *result, - void *context) -{ - struct imapc_connection *conn = context; - - if (result->ret != 0) { - i_error("imapc(%s): dns_lookup(%s) failed: %s", - conn->name, conn->client->set.host, result->error); - imapc_connection_set_state(conn, - IMAPC_CONNECTION_STATE_DISCONNECTED); - return; - } - - i_assert(result->ips_count > 0); - conn->ips_count = result->ips_count; - conn->ips = i_new(struct ip_addr, conn->ips_count); - memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count); - conn->prev_connect_idx = conn->ips_count - 1; - - imapc_connection_connect_next_ip(conn); -} - -void imapc_connection_connect(struct imapc_connection *conn) -{ - struct dns_lookup_settings dns_set; - - if (conn->fd != -1) - return; - - imapc_connection_input_reset(conn); - - if (conn->client->set.debug) - i_debug("imapc(%s): Looking up IP address", conn->name); - - memset(&dns_set, 0, sizeof(dns_set)); - dns_set.dns_client_socket_path = - conn->client->set.dns_client_socket_path; - dns_set.timeout_msecs = IMAPC_DNS_LOOKUP_TIMEOUT_MSECS; - - imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_CONNECTING); - if (conn->ips_count == 0) { - (void)dns_lookup(conn->client->set.host, &dns_set, - imapc_connection_dns_callback, conn); - } else { - imapc_connection_connect_next_ip(conn); - } -} - -void imapc_connection_input_pending(struct imapc_connection *conn) -{ - int ret = 1; - - if (conn->input == NULL) - return; - - if (conn->to != NULL) - timeout_reset(conn->to); - - o_stream_cork(conn->output); - while (ret > 0 && !conn->client->stop_now && conn->input != NULL) { - T_BEGIN { - ret = imapc_connection_input_one(conn); - } T_END; - } - - if (conn->output != NULL) - o_stream_uncork(conn->output); -} - -static struct imapc_command * -imapc_command_begin(imapc_command_callback_t *callback, void *context) -{ - static unsigned int cmd_tag_counter = 0; - struct imapc_command *cmd; - pool_t pool; - - pool = pool_alloconly_create("imapc command", 2048); - cmd = p_new(pool, struct imapc_command, 1); - cmd->pool = pool; - cmd->callback = callback; - cmd->context = context; - - if (++cmd_tag_counter == 0) - cmd_tag_counter++; - cmd->tag = cmd_tag_counter; - return cmd; -} - -static void imapc_command_free(struct imapc_command *cmd) -{ - struct imapc_command_stream *stream; - - if (array_is_created(&cmd->streams)) { - array_foreach_modifiable(&cmd->streams, stream) - i_stream_unref(&stream->input); - } - pool_unref(&cmd->pool); -} - -static bool -parse_sync_literal(const unsigned char *data, unsigned int pos, - unsigned int *value_r) -{ - unsigned int value = 0, mul = 1; - - /* data should contain "{size}\r\n" and pos points after \n */ - if (pos <= 4 || data[pos-1] != '\n' || data[pos-2] != '\r' || - data[pos-3] != '}' || !i_isdigit(data[pos-4])) - return FALSE; - pos -= 4; - - do { - value += (data[pos] - '0') * mul; - mul = mul*10; - pos--; - } while (pos > 0 && i_isdigit(data[pos])); - - if (pos == 0 || data[pos] != '{') - return FALSE; - - *value_r = value; - return TRUE; -} - -static void imapc_command_send_done(struct imapc_connection *conn, - struct imapc_command *cmd) -{ - if (cmd->idle) - conn->idle_plus_waiting = TRUE; - - /* everything sent. move command to wait list. */ - i_assert(*array_idx(&conn->cmd_send_queue, 0) == cmd); - array_delete(&conn->cmd_send_queue, 0, 1); - array_append(&conn->cmd_wait_list, &cmd, 1); - - if (array_count(&conn->cmd_send_queue) > 0 && - conn->state == IMAPC_CONNECTION_STATE_DONE) { - /* send the next command in queue */ - struct imapc_command *const *cmd2_p = - array_idx(&conn->cmd_send_queue, 0); - imapc_command_send_more(conn, *cmd2_p); - } -} - -static struct imapc_command_stream * -imapc_command_get_sending_stream(struct imapc_command *cmd) -{ - struct imapc_command_stream *stream; - - if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0) - return NULL; - - stream = array_idx_modifiable(&cmd->streams, 0); - if (stream->pos != cmd->send_pos) - return NULL; - return stream; -} - -static int imapc_command_try_send_stream(struct imapc_connection *conn, - struct imapc_command *cmd) -{ - struct imapc_command_stream *stream; - - stream = imapc_command_get_sending_stream(cmd); - if (stream == NULL) - return -1; - - /* we're sending the stream now */ - o_stream_set_max_buffer_size(conn->output, 0); - (void)o_stream_send_istream(conn->output, stream->input); - o_stream_set_max_buffer_size(conn->output, (size_t)-1); - - if (!i_stream_is_eof(stream->input)) { - o_stream_set_flush_pending(conn->output, TRUE); - i_assert(stream->input->v_offset < stream->size); - return 0; - } - i_assert(stream->input->v_offset == stream->size); - - /* finished with the stream */ - i_stream_unref(&stream->input); - array_delete(&cmd->streams, 0, 1); - - i_assert(cmd->send_pos != cmd->data->used); - return 1; -} - -static void imapc_command_send_more(struct imapc_connection *conn, - struct imapc_command *cmd) -{ - const unsigned char *p, *data; - unsigned int seek_pos, start_pos, end_pos, size; - int ret; - - i_assert(!cmd->wait_for_literal); - i_assert(cmd->send_pos < cmd->data->used); - - timeout_reset(conn->to_output); - if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0) - return; - - seek_pos = cmd->send_pos; - if (seek_pos != 0 && ret < 0) { - /* skip over the literal. we can also get here from - AUTHENTICATE command, which doesn't use a literal */ - if (parse_sync_literal(cmd->data->data, seek_pos, &size)) { - seek_pos += size; - i_assert(seek_pos <= cmd->data->used); - } - } - - do { - start_pos = seek_pos; - p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n', - cmd->data->used - seek_pos); - i_assert(p != NULL); - - seek_pos = p - (const unsigned char *)cmd->data->data + 1; - /* keep going for LITERAL+ command */ - } while (start_pos + 3 < seek_pos && - p[-1] == '\r' && p[-2] == '}' && p[-3] == '+'); - end_pos = seek_pos; - - data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos); - size = end_pos - cmd->send_pos; - o_stream_send(conn->output, data, size); - cmd->send_pos = end_pos; - - if (cmd->send_pos == cmd->data->used) { - i_assert(!array_is_created(&cmd->streams) || - array_count(&cmd->streams) == 0); - imapc_command_send_done(conn, cmd); - } else { - cmd->wait_for_literal = TRUE; - } -} - -static void imapc_command_timeout(struct imapc_connection *conn) -{ - struct imapc_command *const *cmds; - unsigned int count; - - cmds = array_get(&conn->cmd_wait_list, &count); - i_assert(count > 0); - - i_error("imapc(%s): Command '%s' timed out, disconnecting", - conn->name, imapc_command_get_readable(cmds[0])); - imapc_connection_disconnect(conn); -} - -static void imapc_connection_send_idle_done(struct imapc_connection *conn) -{ - if ((conn->idling || conn->idle_plus_waiting) && !conn->idle_stopping) { - conn->idle_stopping = TRUE; - o_stream_send_str(conn->output, "DONE\r\n"); - } -} - -static void imapc_command_send(struct imapc_connection *conn, - struct imapc_command *cmd) -{ - imapc_connection_send_idle_done(conn); - switch (conn->state) { - case IMAPC_CONNECTION_STATE_AUTHENTICATING: - array_insert(&conn->cmd_send_queue, 0, &cmd, 1); - imapc_command_send_more(conn, cmd); - break; - case IMAPC_CONNECTION_STATE_DONE: - if (cmd->idle) { - if (conn->to != NULL) - timeout_remove(&conn->to); - } else if (conn->to == NULL) { - conn->to = timeout_add(IMAPC_COMMAND_TIMEOUT_MSECS, - imapc_command_timeout, conn); - } - - array_append(&conn->cmd_send_queue, &cmd, 1); - if (array_count(&conn->cmd_send_queue) == 1) - imapc_command_send_more(conn, cmd); - break; - default: - array_append(&conn->cmd_send_queue, &cmd, 1); - break; - } -} - -static int imapc_connection_output(struct imapc_connection *conn) -{ - struct imapc_command *const *cmds; - unsigned int count; - int ret; - - if (conn->to != NULL) - timeout_reset(conn->to); - - o_stream_cork(conn->output); - if ((ret = o_stream_flush(conn->output)) < 0) - return 1; - - cmds = array_get(&conn->cmd_send_queue, &count); - if (count > 0) { - if (imapc_command_get_sending_stream(cmds[0]) != NULL && - !cmds[0]->wait_for_literal) { - /* we're sending a stream. send more. */ - imapc_command_send_more(conn, cmds[0]); - } - } - o_stream_uncork(conn->output); - return ret; -} - -static struct imapc_command * -imapc_connection_cmd_build(const char *cmdline, - imapc_command_callback_t *callback, void *context) -{ - struct imapc_command *cmd; - unsigned int len = strlen(cmdline); - - cmd = imapc_command_begin(callback, context); - cmd->data = str_new(cmd->pool, 6 + len + 2); - str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmdline); - return cmd; -} - -void imapc_connection_cmd(struct imapc_connection *conn, bool mailboxcmd, - const char *cmdline, - imapc_command_callback_t *callback, void *context) -{ - struct imapc_command *cmd; - - cmd = imapc_connection_cmd_build(cmdline, callback, context); - cmd->mailboxcmd = mailboxcmd; - imapc_command_send(conn, cmd); -} - -void imapc_connection_cmdf(struct imapc_connection *conn, bool mailboxcmd, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, ...) -{ - va_list args; - - va_start(args, cmd_fmt); - imapc_connection_cmdvf(conn, mailboxcmd, callback, context, - cmd_fmt, args); - va_end(args); -} - -void imapc_connection_cmdvf(struct imapc_connection *conn, bool mailboxcmd, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, va_list args) -{ - struct imapc_command *cmd; - unsigned int i; - - cmd = imapc_command_begin(callback, context); - cmd->mailboxcmd = mailboxcmd; - cmd->data = str_new(cmd->pool, 128); - str_printfa(cmd->data, "%u ", cmd->tag); - - for (i = 0; cmd_fmt[i] != '\0'; i++) { - if (cmd_fmt[i] != '%') { - str_append_c(cmd->data, cmd_fmt[i]); - continue; - } - - switch (cmd_fmt[++i]) { - case '\0': - i_unreached(); - case 'u': { - unsigned int arg = va_arg(args, unsigned int); - - str_printfa(cmd->data, "%u", arg); - break; - } - case 'p': { - struct istream *input = va_arg(args, struct istream *); - struct imapc_command_stream *s; - uoff_t size; - - if (!array_is_created(&cmd->streams)) - p_array_init(&cmd->streams, cmd->pool, 2); - if (i_stream_get_size(input, TRUE, &size) < 0) - size = 0; - str_printfa(cmd->data, "{%"PRIuUOFF_T"}\r\n", size); - s = array_append_space(&cmd->streams); - s->pos = str_len(cmd->data); - s->size = size; - s->input = input; - i_stream_ref(input); - break; - } - case 's': { - const char *arg = va_arg(args, const char *); - - if (!need_literal(arg)) - imap_dquote_append(cmd->data, arg); - else if ((conn->capabilities & - IMAPC_CAPABILITY_LITERALPLUS) != 0) { - str_printfa(cmd->data, "{%"PRIuSIZE_T"+}\r\n%s", - strlen(arg), arg); - } else { - str_printfa(cmd->data, "{%"PRIuSIZE_T"}\r\n%s", - strlen(arg), arg); - } - break; - } - case '1': { - /* %1s - no quoting */ - const char *arg = va_arg(args, const char *); - - i_assert(cmd_fmt[++i] == 's'); - str_append(cmd->data, arg); - break; - } - } - } - str_append(cmd->data, "\r\n"); - - imapc_command_send(conn, cmd); -} - -enum imapc_connection_state -imapc_connection_get_state(struct imapc_connection *conn) -{ - return conn->state; -} - -enum imapc_capability -imapc_connection_get_capabilities(struct imapc_connection *conn) -{ - return conn->capabilities; -} - -void imapc_connection_select(struct imapc_client_mailbox *box, - const char *name, bool examine, - imapc_command_callback_t *callback, void *context) -{ - struct imapc_connection *conn = box->conn; - - i_assert(conn->selecting_box == NULL); - - if (conn->selected_box != NULL && - (conn->capabilities & IMAPC_CAPABILITY_QRESYNC) != 0) { - /* server will send a [CLOSED] once selected mailbox is - closed */ - conn->selecting_box = box; - } else { - /* we'll have to assume that all the future untagged messages - are for the mailbox we're selecting */ - conn->selected_box = box; - } - - imapc_connection_cmdf(conn, FALSE, callback, context, - examine ? "EXAMINE %s" : "SELECT %s", name); -} - -void imapc_connection_unselect(struct imapc_client_mailbox *box) -{ - struct imapc_connection *conn = box->conn; - struct imapc_command *const *cmdp, *cmd; - struct imapc_command_reply reply; - unsigned int i; - - /* mailbox is being closed. if there are any pending commands, we must - finish them immediately so callbacks don't access any freed - contexts */ - memset(&reply, 0, sizeof(reply)); - reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; - reply.text_without_resp = reply.text_full = "Closing mailbox"; - - imapc_connection_send_idle_done(conn); - - array_foreach(&conn->cmd_wait_list, cmdp) { - if ((*cmdp)->callback != NULL && (*cmdp)->mailboxcmd) { - (*cmdp)->callback(&reply, (*cmdp)->context); - (*cmdp)->callback = NULL; - } - } - for (i = 0; i < array_count(&conn->cmd_send_queue); ) { - cmdp = array_idx(&conn->cmd_send_queue, i); - cmd = *cmdp; - if (!cmd->mailboxcmd) - i++; - else { - array_delete(&conn->cmd_send_queue, i, 1); - if (cmd->callback != NULL) - cmd->callback(&reply, cmd->context); - imapc_command_free(cmd); - } - } - - if (conn->selected_box == NULL && conn->selecting_box == NULL) { - i_assert(conn->state == IMAPC_CONNECTION_STATE_DISCONNECTED); - } else { - i_assert(conn->selected_box == box || - conn->selecting_box == box); - - conn->selected_box = NULL; - conn->selecting_box = NULL; - } -} - -struct imapc_client_mailbox * -imapc_connection_get_mailbox(struct imapc_connection *conn) -{ - if (conn->selecting_box != NULL) - return conn->selecting_box; - return conn->selected_box; -} - -static void -imapc_connection_idle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, - void *context) -{ - struct imapc_connection *conn = context; - - conn->idling = FALSE; - conn->idle_plus_waiting = FALSE; - conn->idle_stopping = FALSE; -} - -void imapc_connection_idle(struct imapc_connection *conn) -{ - struct imapc_command *cmd; - - if (array_count(&conn->cmd_send_queue) != 0 || - array_count(&conn->cmd_wait_list) != 0 || - conn->idling || conn->idle_plus_waiting || - (conn->capabilities & IMAPC_CAPABILITY_IDLE) == 0) - return; - - cmd = imapc_connection_cmd_build("IDLE", imapc_connection_idle_callback, - conn); - cmd->idle = TRUE; - imapc_command_send(conn, cmd); -}
--- a/src/lib-storage/index/imapc/imapc-connection.h Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -#ifndef IMAPC_CONNECTION_H -#define IMAPC_CONNECTION_H - -#include "imapc-client.h" - -struct imapc_client; -struct imapc_connection; - -enum imapc_connection_state { - /* No connection */ - IMAPC_CONNECTION_STATE_DISCONNECTED = 0, - /* Trying to connect */ - IMAPC_CONNECTION_STATE_CONNECTING, - /* Connected, trying to authenticate */ - IMAPC_CONNECTION_STATE_AUTHENTICATING, - /* Authenticated, ready to accept commands */ - IMAPC_CONNECTION_STATE_DONE -}; - -struct imapc_connection * -imapc_connection_init(struct imapc_client *client); -void imapc_connection_deinit(struct imapc_connection **conn); - -void imapc_connection_connect(struct imapc_connection *conn); -void imapc_connection_disconnect(struct imapc_connection *conn); -void imapc_connection_ioloop_changed(struct imapc_connection *conn); -void imapc_connection_input_pending(struct imapc_connection *conn); - -void imapc_connection_cmd(struct imapc_connection *conn, bool mailboxcmd, - const char *cmdline, - imapc_command_callback_t *callback, void *context); -void imapc_connection_cmdf(struct imapc_connection *conn, bool mailboxcmd, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, ...) ATTR_FORMAT(5, 6); -void imapc_connection_cmdvf(struct imapc_connection *conn, bool mailboxcmd, - imapc_command_callback_t *callback, void *context, - const char *cmd_fmt, va_list args) - ATTR_FORMAT(5, 0); -void imapc_connection_select(struct imapc_client_mailbox *box, - const char *name, bool examine, - imapc_command_callback_t *callback, void *context); -void imapc_connection_unselect(struct imapc_client_mailbox *box); - -enum imapc_connection_state -imapc_connection_get_state(struct imapc_connection *conn); -enum imapc_capability -imapc_connection_get_capabilities(struct imapc_connection *conn); - -struct imapc_client_mailbox * -imapc_connection_get_mailbox(struct imapc_connection *conn); - -void imapc_connection_idle(struct imapc_connection *conn); - -#endif
--- a/src/lib-storage/index/imapc/imapc-msgmap.c Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ - -#include "lib.h" -#include "array.h" -#include "imapc-msgmap.h" - -struct imapc_msgmap { - ARRAY_TYPE(uint32_t) uids; - uint32_t uid_next; -}; - -struct imapc_msgmap *imapc_msgmap_init(void) -{ - struct imapc_msgmap *msgmap; - - msgmap = i_new(struct imapc_msgmap, 1); - i_array_init(&msgmap->uids, 128); - msgmap->uid_next = 1; - return msgmap; -} - -void imapc_msgmap_deinit(struct imapc_msgmap **_msgmap) -{ - struct imapc_msgmap *msgmap = *_msgmap; - - *_msgmap = NULL; - - array_free(&msgmap->uids); - i_free(msgmap); -} - -uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap) -{ - return array_count(&msgmap->uids); -} - -uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap) -{ - return msgmap->uid_next; -} - -uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq) -{ - const uint32_t *uidp; - - uidp = array_idx(&msgmap->uids, rseq-1); - return *uidp; -} - -static int uint32_cmp(const uint32_t *p1, const uint32_t *p2) -{ - return *p1 < *p2 ? -1 : - (*p1 > *p2 ? 1 : 0); -} - -bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, - uint32_t uid, uint32_t *rseq_r) -{ - const uint32_t *p, *first; - - p = array_bsearch(&msgmap->uids, &uid, uint32_cmp); - if (p == NULL) { - *rseq_r = 0; - return FALSE; - } - - first = array_idx(&msgmap->uids, 0); - *rseq_r = (p - first) + 1; - return TRUE; -} - -void imapc_msgmap_append(struct imapc_msgmap *msgmap, - uint32_t rseq, uint32_t uid) -{ - i_assert(rseq == imapc_msgmap_count(msgmap) + 1); - i_assert(uid >= msgmap->uid_next); - - msgmap->uid_next = uid + 1; - array_append(&msgmap->uids, &uid, 1); -} - -void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq) -{ - i_assert(rseq > 0); - i_assert(rseq <= imapc_msgmap_count(msgmap)); - - array_delete(&msgmap->uids, rseq-1, 1); -}
--- a/src/lib-storage/index/imapc/imapc-msgmap.h Sun Sep 25 22:32:04 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -#ifndef IMAPC_MSGMAP_H -#define IMAPC_MSGMAP_H - -struct imapc_msgmap *imapc_msgmap_init(void); -void imapc_msgmap_deinit(struct imapc_msgmap **msgmap); - -uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap); -uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap); -uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq); -bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, - uint32_t uid, uint32_t *rseq_r); - -void imapc_msgmap_append(struct imapc_msgmap *msgmap, - uint32_t rseq, uint32_t uid); -void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq); - -#endif