]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added initial implementation of "imapc" storage.
authorTimo Sirainen <tss@iki.fi>
Sun, 16 Jan 2011 16:08:23 +0000 (18:08 +0200)
committerTimo Sirainen <tss@iki.fi>
Sun, 16 Jan 2011 16:08:23 +0000 (18:08 +0200)
It can be used to create a "smart IMAP proxy" where Dovecot uses remote IMAP
server as a mail storage.

This is a very rough early implementation. Performance isn't good, many
required features are missing, error handling is lacking and code needs
de-uglification. Still, it should be enough for selecting INBOX and
accessing mails in it.

19 files changed:
configure.in
src/lib-storage/index/Makefile.am
src/lib-storage/index/imapc/Makefile.am [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-client-private.h [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-client.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-client.h [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-connection.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-connection.h [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-mail.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-save.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-search.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-seqmap.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-seqmap.h [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-storage.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-storage.h [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-sync.c [new file with mode: 0644]
src/lib-storage/index/imapc/imapc-sync.h [new file with mode: 0644]
src/lib-storage/index/index-sync-private.h
src/lib-storage/index/index-sync.c

index 110cfa4ac733af3b6a2e524825e133ad6b4da0b8..9a0bf6c0b822ef2ad8ee0bbf9e2c3a4eacfbaf2a 100644 (file)
@@ -244,7 +244,7 @@ AS_HELP_STRING([--with-storages], [Build with specified mail storage formats (ma
                AC_MSG_ERROR([--with-storages needs storage list as parameter])
        fi
        mail_storages="shared `echo "$withval"|sed 's/,/ /g'`" ],
-       mail_storages="shared maildir mbox sdbox mdbox cydir")
+       mail_storages="shared maildir mbox sdbox mdbox cydir imapc")
 AC_SUBST(mail_storages)
 
 DC_DOVECOT_MODULEDIR
@@ -2429,6 +2429,7 @@ dbox_common_libs='$(top_builddir)/src/lib-storage/index/dbox-common/libstorage_d
 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'
 raw_libs='$(top_builddir)/src/lib-storage/index/raw/libstorage_raw.la'
 shared_libs='$(top_builddir)/src/lib-storage/index/shared/libstorage_shared.la'
 
@@ -2667,6 +2668,7 @@ src/lib-test/Makefile
 src/lib-storage/Makefile
 src/lib-storage/list/Makefile
 src/lib-storage/index/Makefile
+src/lib-storage/index/imapc/Makefile
 src/lib-storage/index/maildir/Makefile
 src/lib-storage/index/mbox/Makefile
 src/lib-storage/index/dbox-common/Makefile
index b65d9525c8fee239ca27df14b6b0edc1088973fb..7e0f6c6e357d5b428eff796ba1f0ffff350bd243 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = maildir mbox dbox-common dbox-multi dbox-single cydir raw shared
+SUBDIRS = maildir mbox dbox-common dbox-multi dbox-single cydir imapc raw shared
 
 noinst_LTLIBRARIES = libstorage_index.la
 
diff --git a/src/lib-storage/index/imapc/Makefile.am b/src/lib-storage/index/imapc/Makefile.am
new file mode 100644 (file)
index 0000000..18d843f
--- /dev/null
@@ -0,0 +1,29 @@
+noinst_LTLIBRARIES = libstorage_imapc.la
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-dns \
+       -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-imap \
+       -I$(top_srcdir)/src/lib-index \
+       -I$(top_srcdir)/src/lib-storage \
+       -I$(top_srcdir)/src/lib-storage/index
+
+libstorage_imapc_la_SOURCES = \
+       imapc-client.c \
+       imapc-connection.c \
+       imapc-mail.c \
+       imapc-save.c \
+       imapc-search.c \
+       imapc-seqmap.c \
+       imapc-sync.c \
+       imapc-storage.c
+
+headers = \
+       imapc-connection.h \
+       imapc-seqmap.h \
+       imapc-storage.h \
+       imapc-sync.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
diff --git a/src/lib-storage/index/imapc/imapc-client-private.h b/src/lib-storage/index/imapc/imapc-client-private.h
new file mode 100644 (file)
index 0000000..0862cf4
--- /dev/null
@@ -0,0 +1,32 @@
+#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;
+
+       imapc_untagged_callback_t *untagged_callback;
+       void *untagged_context;
+
+       ARRAY_DEFINE(conns, struct imapc_client_connection *);
+
+       struct ioloop *ioloop;
+};
+
+struct imapc_client_mailbox {
+       struct imapc_client *client;
+       struct imapc_connection *conn;
+       struct imapc_seqmap *seqmap;
+
+       void *untagged_box_context;
+       unsigned int pending_box_command_count;
+};
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-client.c b/src/lib-storage/index/imapc/imapc-client.c
new file mode 100644 (file)
index 0000000..f963520
--- /dev/null
@@ -0,0 +1,227 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "imapc-seqmap.h"
+#include "imapc-connection.h"
+#include "imapc-client-private.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 },
+
+       { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 },
+       { NULL, 0 }
+};
+
+struct imapc_client *
+imapc_client_init(const struct imapc_client_settings *set)
+{
+       struct imapc_client *client;
+       pool_t pool;
+
+       pool = pool_alloconly_create("imapc client", 1024);
+       client = p_new(pool, struct imapc_client, 1);
+       client->pool = pool;
+
+       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);
+       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;
+
+       array_foreach_modifiable(&client->conns, connp)
+               imapc_connection_deinit(&(*connp)->conn);
+       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(struct imapc_client *client)
+{
+       struct imapc_client_connection *const *connp;
+       struct ioloop *prev_ioloop = current_ioloop;
+
+       i_assert(client->ioloop == NULL);
+
+       client->ioloop = io_loop_create();
+       array_foreach(&client->conns, connp) {
+               imapc_connection_ioloop_changed((*connp)->conn);
+               imapc_connection_connect((*connp)->conn);
+       }
+       io_loop_run(client->ioloop);
+
+       current_ioloop = prev_ioloop;
+       array_foreach(&client->conns, connp)
+               imapc_connection_ioloop_changed((*connp)->conn);
+
+       current_ioloop = client->ioloop;
+       io_loop_destroy(&client->ioloop);
+}
+
+void imapc_client_stop(struct imapc_client *client)
+{
+       io_loop_stop(client->ioloop);
+}
+
+static void
+imapc_connection_state_changed(struct imapc_connection *conn,
+                              struct imapc_client *client,
+                              enum imapc_connection_state prev_state)
+{
+}
+
+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,
+                                          imapc_connection_state_changed);
+       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, 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,
+                         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->seqmap = imapc_seqmap_init();
+
+       imapc_connection_select(box, name, callback, context);
+       return box;
+}
+
+void imapc_client_mailbox_close(struct imapc_client_mailbox **_box)
+{
+       struct imapc_client_mailbox *box = *_box;
+       struct imapc_client_connection *const *connp;
+
+       *_box = NULL;
+
+       array_foreach(&box->client->conns, connp) {
+               if ((*connp)->box == box) {
+                       (*connp)->box = NULL;
+                       break;
+               }
+       }
+
+       imapc_seqmap_deinit(&box->seqmap);
+       i_free(box);
+}
+
+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);
+}
+
+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;
+
+       ctx = i_new(struct imapc_client_command_context, 1);
+       ctx->box = box;
+       ctx->callback = callback;
+       ctx->context = context;
+
+       box->pending_box_command_count++;
+
+       va_start(args, cmd_fmt);
+       imapc_connection_cmdvf(box->conn, imapc_client_mailbox_cmd_cb,
+                              ctx, cmd_fmt, args);
+       va_end(args);
+}
+
+struct imapc_seqmap *
+imapc_client_mailbox_get_seqmap(struct imapc_client_mailbox *box)
+{
+       return box->seqmap;
+}
diff --git a/src/lib-storage/index/imapc/imapc-client.h b/src/lib-storage/index/imapc/imapc-client.h
new file mode 100644 (file)
index 0000000..5283a03
--- /dev/null
@@ -0,0 +1,94 @@
+#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_IMAP4REV1      = 0x400000000
+};
+struct imapc_capability_name {
+       const char *name;
+       enum imapc_capability capability;
+};
+extern const struct imapc_capability_name imapc_capability_names[];
+
+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;
+};
+
+struct imapc_command_reply {
+       enum imapc_command_state state;
+       /* "RESP TEXT" when the reply contains [RESP TEXT], otherwise NULL */
+       const char *resp_text;
+       /* The full tagged reply, including [RESP TEXT]. */
+       const char *text;
+};
+
+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;
+
+       /* "RESP TEXT" when the reply is "* OK [RESP TEXT]", otherwise NULL */
+       const char *resp_text;
+
+       /* 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(struct imapc_client *client);
+void imapc_client_stop(struct imapc_client *client);
+
+struct imapc_client_mailbox *
+imapc_client_mailbox_open(struct imapc_client *client, const char *name,
+                         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_cmdf(struct imapc_client_mailbox *box,
+                              imapc_command_callback_t *callback,
+                              void *context, const char *cmd_fmt, ...)
+       ATTR_FORMAT(4, 5);
+struct imapc_seqmap *
+imapc_client_mailbox_get_seqmap(struct imapc_client_mailbox *box);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-connection.c b/src/lib-storage/index/imapc/imapc-connection.c
new file mode 100644 (file)
index 0000000..a723ee2
--- /dev/null
@@ -0,0 +1,987 @@
+/* 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 "str.h"
+#include "dns-lookup.h"
+#include "imap-quote.h"
+#include "imap-util.h"
+#include "imap-parser.h"
+#include "imapc-client-private.h"
+#include "imapc-seqmap.h"
+#include "imapc-connection.h"
+
+#include <ctype.h>
+
+#define IMAPC_DNS_LOOKUP_TIMEOUT_MSECS (1000*30)
+#define IMAPC_CONNECT_TIMEOUT_MSECS (1000*30)
+
+enum imapc_input_state {
+       IMAPC_INPUT_STATE_NONE = 0,
+       IMAPC_INPUT_STATE_UNTAGGED,
+       IMAPC_INPUT_STATE_UNTAGGED_NUM,
+       IMAPC_INPUT_STATE_TAGGED,
+       IMAPC_INPUT_STATE_SKIPLINE
+};
+
+struct imapc_command {
+       pool_t pool;
+       buffer_t *data;
+       unsigned int send_pos;
+       unsigned int tag;
+
+       imapc_command_callback_t *callback;
+       void *context;
+};
+
+struct imapc_connection {
+       struct imapc_client *client;
+       char *name;
+
+       int fd;
+       struct io *io;
+       struct istream *input;
+       struct ostream *output;
+       struct imap_parser *parser;
+       struct timeout *to;
+
+       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;
+
+       imapc_connection_state_change *state_callback;
+       enum imapc_connection_state state;
+
+       enum imapc_capability capabilities;
+       char **capabilities_list;
+
+       ARRAY_DEFINE(cmd_send_queue, struct imapc_command *);
+       ARRAY_DEFINE(cmd_wait_list, struct imapc_command *);
+
+       unsigned int ips_count, prev_connect_idx;
+       struct ip_addr *ips;
+};
+
+static void imapc_connection_disconnect(struct imapc_connection *conn);
+
+static void imapc_command_send_more(struct imapc_connection *conn,
+                                   struct imapc_command *cmd);
+
+struct imapc_connection *
+imapc_connection_init(struct imapc_client *client,
+                     imapc_connection_state_change *state_callback)
+{
+       struct imapc_connection *conn;
+
+       conn = i_new(struct imapc_connection, 1);
+       conn->client = client;
+       conn->state_callback = state_callback;
+       conn->fd = -1;
+       conn->name = i_strdup_printf("%s:%u", client->set.host,
+                                    client->set.port);
+       i_array_init(&conn->cmd_send_queue, 8);
+       i_array_init(&conn->cmd_wait_list, 32);
+       return conn;
+}
+
+void imapc_connection_deinit(struct imapc_connection **_conn)
+{
+       struct imapc_connection *conn = *_conn;
+
+       *_conn = NULL;
+
+       imapc_connection_disconnect(conn);
+       p_strsplit_free(default_pool, conn->capabilities_list);
+       array_free(&conn->cmd_send_queue);
+       array_free(&conn->cmd_wait_list);
+       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);
+}
+
+static void imapc_connection_set_state(struct imapc_connection *conn,
+                                      enum imapc_connection_state state)
+{
+       enum imapc_connection_state prev_state = conn->state;
+
+       if (state == IMAPC_CONNECTION_STATE_DISCONNECTED) {
+               /* abort all pending commands */
+               struct imapc_command_reply reply;
+               struct imapc_command *const *cmdp, *cmd;
+
+               memset(&reply, 0, sizeof(reply));
+               reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+               reply.text = "Disconnected from server";
+
+               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);
+
+                       cmd->callback(&reply, cmd->context);
+                       pool_unref(&cmd->pool);
+               }
+               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);
+
+                       cmd->callback(&reply, cmd->context);
+                       pool_unref(&cmd->pool);
+               }
+       }
+       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;
+       conn->state_callback(conn, conn->client, prev_state);
+}
+
+static void imapc_connection_disconnect(struct imapc_connection *conn)
+{
+       if (conn->fd == -1)
+               return;
+
+       if (conn->to != NULL)
+               timeout_remove(&conn->to);
+       imap_parser_destroy(&conn->parser);
+       io_remove(&conn->io);
+       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));
+       imapc_connection_disconnect(conn);
+       va_end(va);
+}
+
+static int
+imapc_connection_read_line(struct imapc_connection *conn,
+                          const struct imap_arg **imap_args_r)
+{
+       int ret;
+       bool fatal;
+
+       ret = imap_parser_read_args(conn->parser, 0, 0, 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;
+       }
+       return 1;
+}
+
+static int
+imapc_connection_parse_capability(struct imapc_connection *conn,
+                                 const char *value)
+{
+       const char *const *tmp;
+       unsigned int i;
+
+       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 *text)
+{
+       const char *key, *value;
+
+       value = strchr(text, ' ');
+       if (value != NULL)
+               key = t_strdup_until(text, value++);
+       else {
+               key = text;
+               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 struct imap_arg *args,
+                                 const char **text_r)
+{
+       const char *text, *p;
+
+       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;
+       }
+       p = strchr(text, ']');
+       if (p == NULL) {
+               imapc_connection_input_error(conn, "Missing ']' in resp-text");
+               return -1;
+       }
+       if (p[1] == '\0' || p[1] != ' ' || p[2] == '\0') {
+               imapc_connection_input_error(conn, "Missing text in resp-text");
+               return -1;
+       }
+       *text_r = text = t_strdup_until(text + 1, p);
+       return imapc_connection_handle_resp_text_code(conn, text);
+}
+
+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;
+       imap_parser_reset(conn->parser);
+}
+
+static int imapc_connection_skip_line(struct imapc_connection *conn)
+{
+       const unsigned char *data;
+       size_t i, data_size;
+       int ret = 0;
+
+       data = i_stream_get_data(conn->input, &data_size);
+       for (i = 0; i < data_size; i++) {
+               if (data[i] == '\n') {
+                       imapc_connection_input_reset(conn);
+                       ret = 1;
+                       i++;
+                       break;
+               }
+       }
+       i_stream_skip(conn->input, i);
+       return ret;
+}
+
+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);
+       } else if (conn->capabilities == 0) {
+               imapc_connection_input_error(conn,
+                       "Capabilities not returned by server");
+       } else {
+               timeout_remove(&conn->to);
+               imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE);
+       }
+}
+
+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) {
+               imapc_connection_input_error(conn, "Authentication failed: %s",
+                                            reply->text);
+               return;
+       }
+
+       if (conn->capabilities == 0) {
+               /* server didn't send capabilities automatically.
+                  request them manually before we're done. */
+               imapc_connection_cmd(conn, "CAPABILITY",
+                                    imapc_connection_capability_cb, conn);
+               return;
+       }
+
+       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 int imapc_connection_input_banner(struct imapc_connection *conn)
+{
+       const struct imapc_client_settings *set = &conn->client->set;
+       const struct imap_arg *imap_args;
+       const char *cmd, *text;
+       int ret;
+
+       if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0)
+               return ret;
+
+       if (imapc_connection_handle_resp_text(conn, imap_args, &text) < 0)
+               return -1;
+       imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING);
+
+       if (set->master_user == NULL &&
+           need_literal(set->username) && need_literal(set->password)) {
+               /* We can use LOGIN command */
+               imapc_connection_cmdf(conn, 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, 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, "AUTHENTICATE PLAIN",
+                                    imapc_connection_login_cb, 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;
+       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_resp_text(conn, imap_args,
+                                                     &reply.resp_text) < 0)
+                       return -1;
+       }
+
+       reply.name = name;
+       reply.num = conn->cur_num;
+       reply.args = imap_args;
+       if (conn->selected_box != NULL) {
+               reply.untagged_box_context =
+                       conn->selected_box->untagged_box_context;
+       }
+       conn->client->untagged_callback(&reply, conn->client->untagged_context);
+       if (imap_arg_atom_equals(imap_args, "EXPUNGE") &&
+           conn->selected_box != NULL) {
+               /* keep track of expunge map internally */
+               imapc_seqmap_expunge(conn->selected_box->seqmap, conn->cur_num);
+       }
+       imapc_connection_input_reset(conn);
+       return 1;
+}
+
+static int imapc_connection_input_plus(struct imapc_connection *conn)
+{
+       struct imapc_command *const *cmd_p;
+
+       if (array_count(&conn->cmd_send_queue) == 0) {
+               imapc_connection_input_error(conn, "Unexpected '+'");
+               return -1;
+       }
+       cmd_p = array_idx(&conn->cmd_send_queue, 0);
+       imapc_command_send_more(conn, *cmd_p);
+
+       conn->input_state = IMAPC_INPUT_STATE_SKIPLINE;
+       return imapc_connection_skip_line(conn);
+}
+
+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 = "";
+       else {
+               *linep = '\0';
+               reply.text = 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 if (strcasecmp(line, "bad") == 0) {
+               imapc_connection_input_error(conn,
+                       "Invalid state in tagged reply: %u %s",
+                       conn->cur_tag, line);
+               return -1;
+       }
+
+       if (reply.text[0] == '[') {
+               /* get resp-text */
+               p = strchr(reply.text, ']');
+               if (p == NULL) {
+                       imapc_connection_input_error(conn,
+                               "Missing ']' from resp-text: %u %s",
+                               conn->cur_tag, line);
+                       return -1;
+               }
+               reply.resp_text = t_strndup(reply.text + 1, p - reply.text - 1);
+               if (imapc_connection_handle_resp_text_code(conn,
+                                                          reply.resp_text) < 0)
+                       return -1;
+       }
+
+       /* 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 (cmd == NULL) {
+               imapc_connection_input_error(conn,
+                       "Unknown tag in a reply: %u %s", conn->cur_tag, line);
+               return -1;
+       }
+
+       imapc_connection_input_reset(conn);
+       cmd->callback(&reply, cmd->context);
+       pool_unref(&cmd->pool);
+       return 0;
+}
+
+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) {
+                       /* FIXME: why do we get here.. */
+                       conn->input_state = IMAPC_INPUT_STATE_SKIPLINE;
+                       return imapc_connection_skip_line(conn);
+               } else 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) {
+                       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_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;
+       case IMAPC_INPUT_STATE_SKIPLINE:
+               ret = imapc_connection_skip_line(conn);
+               break;
+       }
+       return ret;
+}
+
+static void imapc_connection_input(struct imapc_connection *conn)
+{
+       int ret;
+
+       if (i_stream_read(conn->input) == -1) {
+               /* disconnected */
+               i_error("imapc(%s): Server disconnected unexpectedly",
+                       conn->name);
+               imapc_connection_disconnect(conn);
+               return;
+       }
+
+       o_stream_cork(conn->output);
+       do {
+               T_BEGIN {
+                       ret = imapc_connection_input_one(conn);
+               } T_END;
+       } while (ret > 0);
+
+       if (conn->output != NULL)
+               o_stream_uncork(conn->output);
+}
+
+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);
+}
+
+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_connection_connect_next_ip(struct imapc_connection *conn)
+{
+       int fd;
+
+       conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count;
+       fd = net_connect_ip(&conn->ips[conn->prev_connect_idx],
+                           conn->client->set.port, NULL);
+       if (fd == -1) {
+               imapc_connection_set_state(conn,
+                       IMAPC_CONNECTION_STATE_DISCONNECTED);
+               return;
+       }
+       conn->fd = fd;
+       conn->input = i_stream_create_fd(fd, (size_t)-1, FALSE);
+       conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+       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);
+}
+
+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;
+
+       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);
+       }
+}
+
+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", 1024);
+       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 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_more(struct imapc_connection *conn,
+                                   struct imapc_command *cmd)
+{
+       const unsigned char *p;
+       unsigned int seek_pos, end_pos, size;
+
+       i_assert(cmd->send_pos < cmd->data->used);
+
+       seek_pos = cmd->send_pos;
+       if (seek_pos != 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);
+               }
+       }
+
+       p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n',
+                  cmd->data->used - seek_pos);
+       i_assert(p != NULL);
+
+       end_pos = p - (const unsigned char *)cmd->data->data + 1;
+       o_stream_send(conn->output,
+                     CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos),
+                     end_pos - cmd->send_pos);
+       cmd->send_pos = end_pos;
+
+       if (cmd->send_pos == cmd->data->used) {
+               /* 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 void imapc_command_send(struct imapc_connection *conn,
+                              struct imapc_command *cmd)
+{
+       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:
+               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;
+       }
+}
+
+void imapc_connection_cmd(struct imapc_connection *conn, 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, len + 2);
+       str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmdline);
+       imapc_command_send(conn, cmd);
+}
+
+void imapc_connection_cmdf(struct imapc_connection *conn,
+                          imapc_command_callback_t *callback, void *context,
+                          const char *cmd_fmt, ...)
+{
+       va_list args;
+
+       va_start(args, cmd_fmt);
+       imapc_connection_cmdvf(conn, callback, context, cmd_fmt, args);
+       va_end(args);
+}
+
+void imapc_connection_cmdvf(struct imapc_connection *conn,
+                          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->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 '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;
+}
+
+void imapc_connection_select(struct imapc_client_mailbox *box, const char *name,
+                            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, callback, context, "SELECT %s", name);
+}
diff --git a/src/lib-storage/index/imapc/imapc-connection.h b/src/lib-storage/index/imapc/imapc-connection.h
new file mode 100644 (file)
index 0000000..f5ad141
--- /dev/null
@@ -0,0 +1,49 @@
+#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
+};
+
+/* Called when connection state changes */
+typedef void
+imapc_connection_state_change(struct imapc_connection *conn,
+                             struct imapc_client *client,
+                             enum imapc_connection_state prev_state);
+
+struct imapc_connection *
+imapc_connection_init(struct imapc_client *client,
+                     imapc_connection_state_change *state_callback);
+void imapc_connection_deinit(struct imapc_connection **conn);
+
+void imapc_connection_connect(struct imapc_connection *conn);
+void imapc_connection_ioloop_changed(struct imapc_connection *conn);
+
+void imapc_connection_cmd(struct imapc_connection *conn, const char *cmdline,
+                         imapc_command_callback_t *callback, void *context);
+void imapc_connection_cmdf(struct imapc_connection *conn,
+                          imapc_command_callback_t *callback, void *context,
+                          const char *cmd_fmt, ...) ATTR_FORMAT(4, 5);
+void imapc_connection_cmdvf(struct imapc_connection *conn,
+                           imapc_command_callback_t *callback, void *context,
+                           const char *cmd_fmt, va_list args)
+       ATTR_FORMAT(4, 0);
+void imapc_connection_select(struct imapc_client_mailbox *box, const char *name,
+                            imapc_command_callback_t *callback, void *context);
+
+enum imapc_connection_state
+imapc_connection_get_state(struct imapc_connection *conn);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-mail.c b/src/lib-storage/index/imapc/imapc-mail.c
new file mode 100644 (file)
index 0000000..5eb016e
--- /dev/null
@@ -0,0 +1,84 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "index-mail.h"
+#include "imapc-storage.h"
+
+static int imapc_mail_get_received_date(struct mail *_mail, time_t *date_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct index_mail_data *data = &mail->data;
+
+       if (data->received_date == (time_t)-1)
+               return -1;
+       *date_r = data->received_date;
+       return 0;
+}
+
+static int imapc_mail_get_save_date(struct mail *_mail, time_t *date_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct index_mail_data *data = &mail->data;
+
+       if (data->save_date == (time_t)-1)
+               return -1;
+       *date_r = data->save_date;
+       return 0;
+}
+
+static int imapc_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct index_mail_data *data = &mail->data;
+
+       if (data->physical_size == (uoff_t)-1)
+               return -1;
+       *size_r = data->physical_size;
+       return 0;
+}
+
+static int
+imapc_mail_get_stream(struct mail *_mail, struct message_size *hdr_size,
+                     struct message_size *body_size, struct istream **stream_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct index_mail_data *data = &mail->data;
+
+       if (data->stream == NULL)
+               return -1;
+
+       return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
+}
+
+struct mail_vfuncs imapc_mail_vfuncs = {
+       index_mail_close,
+       index_mail_free,
+       index_mail_set_seq,
+       index_mail_set_uid,
+       index_mail_set_uid_cache_updates,
+
+       index_mail_get_flags,
+       index_mail_get_keywords,
+       index_mail_get_keyword_indexes,
+       index_mail_get_modseq,
+       index_mail_get_parts,
+       index_mail_get_date,
+       imapc_mail_get_received_date,
+       imapc_mail_get_save_date,
+       imapc_mail_get_physical_size, /* physical = virtual in our case */
+       imapc_mail_get_physical_size,
+       index_mail_get_first_header,
+       index_mail_get_headers,
+       index_mail_get_header_stream,
+       imapc_mail_get_stream,
+       index_mail_get_special,
+       index_mail_get_real_mail,
+       index_mail_update_flags,
+       index_mail_update_keywords,
+       index_mail_update_modseq,
+       NULL,
+       index_mail_expunge,
+       index_mail_set_cache_corrupted,
+       index_mail_opened
+};
diff --git a/src/lib-storage/index/imapc/imapc-save.c b/src/lib-storage/index/imapc/imapc-save.c
new file mode 100644 (file)
index 0000000..6bb7ba0
--- /dev/null
@@ -0,0 +1,75 @@
+/* Copyright (c) 2007-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "index-mail.h"
+#include "imapc-storage.h"
+#include "imapc-sync.h"
+
+struct imapc_save_context {
+       struct mail_save_context ctx;
+
+       struct imapc_mailbox *mbox;
+       struct mail_index_transaction *trans;
+
+       unsigned int failed:1;
+};
+
+struct mail_save_context *
+imapc_save_alloc(struct mailbox_transaction_context *t)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)t->box;
+       struct imapc_save_context *ctx;
+
+       i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+       if (t->save_ctx == NULL) {
+               ctx = i_new(struct imapc_save_context, 1);
+               ctx->ctx.transaction = t;
+               ctx->mbox = mbox;
+               ctx->trans = t->itrans;
+               t->save_ctx = &ctx->ctx;
+       }
+       return t->save_ctx;
+}
+
+int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input)
+{
+       return -1;
+}
+
+int imapc_save_continue(struct mail_save_context *_ctx)
+{
+       return -1;
+}
+
+int imapc_save_finish(struct mail_save_context *_ctx)
+{
+       struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+
+       ctx->failed = TRUE;
+
+       index_save_context_free(_ctx);
+       return ctx->failed ? -1 : 0;
+}
+
+void imapc_save_cancel(struct mail_save_context *_ctx)
+{
+       struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+
+       ctx->failed = TRUE;
+       (void)imapc_save_finish(_ctx);
+}
+
+int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx)
+{
+       return -1;
+}
+
+void imapc_transaction_save_commit_post(struct mail_save_context *_ctx,
+                                       struct mail_index_transaction_commit_result *result)
+{
+}
+
+void imapc_transaction_save_rollback(struct mail_save_context *_ctx)
+{
+}
diff --git a/src/lib-storage/index/imapc/imapc-search.c b/src/lib-storage/index/imapc/imapc-search.c
new file mode 100644 (file)
index 0000000..60c3239
--- /dev/null
@@ -0,0 +1,149 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "safe-mkstemp.h"
+#include "write-full.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-date.h"
+#include "mail-user.h"
+#include "index-mail.h"
+#include "imapc-client.h"
+#include "imapc-storage.h"
+
+struct mail_search_context *
+imapc_search_init(struct mailbox_transaction_context *t,
+                 struct mail_search_args *args,
+                 const enum mail_sort_type *sort_program)
+{
+       return index_storage_search_init(t, args, sort_program);
+}
+
+bool imapc_search_next_nonblock(struct mail_search_context *_ctx,
+                               struct mail *mail, bool *tryagain_r)
+{
+       struct mail_private *pmail = (struct mail_private *)mail;
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)mail->box;
+       string_t *str;
+
+       if (!index_storage_search_next_nonblock(_ctx, mail, tryagain_r))
+               return FALSE;
+
+       str = t_str_new(64);
+       str_printfa(str, "UID FETCH %u (", mail->uid);
+       if ((pmail->wanted_fields & (MAIL_FETCH_MESSAGE_PARTS |
+                                    MAIL_FETCH_NUL_STATE |
+                                    MAIL_FETCH_IMAP_BODY |
+                                    MAIL_FETCH_IMAP_BODYSTRUCTURE |
+                                    MAIL_FETCH_PHYSICAL_SIZE |
+                                    MAIL_FETCH_VIRTUAL_SIZE)) != 0)
+               str_append(str, "BODY.PEEK[] ");
+       else if ((pmail->wanted_fields & (MAIL_FETCH_IMAP_ENVELOPE |
+                                         MAIL_FETCH_HEADER_MD5 |
+                                         MAIL_FETCH_DATE)) != 0 ||
+                pmail->wanted_headers != NULL)
+               str_append(str, "BODY.PEEK[HEADER] ");
+
+       if ((pmail->wanted_fields & MAIL_FETCH_RECEIVED_DATE) != 0)
+               str_append(str, "INTERNALDATE ");
+
+       str_truncate(str, str_len(str) - 1);
+       str_append_c(str, ')');
+
+       mbox->cur_fetch_mail = mail;
+       imapc_client_mailbox_cmdf(mbox->client_box, imapc_async_stop_callback,
+                                 mbox->storage, "%1s", str_c(str));
+       imapc_client_run(mbox->storage->client);
+       mbox->cur_fetch_mail = NULL;
+       return TRUE;
+}
+
+static int create_temp_fd(struct mail_user *user, const char **path_r)
+{
+       string_t *path;
+       int fd;
+
+       path = t_str_new(128);
+       mail_user_set_get_temp_prefix(path, user->set);
+       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;
+}
+
+static void
+imapc_fetch_stream(struct index_mail *imail, const char *value, bool body)
+{
+       struct mail *_mail = &imail->mail.mail;
+       struct istream *input;
+       size_t size = strlen(value);
+       const char *path;
+       int fd;
+
+       if (imail->data.stream != NULL)
+               return;
+
+       fd = create_temp_fd(_mail->box->storage->user, &path);
+       if (fd == -1)
+               return;
+       if (write_full(fd, value, size) < 0) {
+               (void)close(fd);
+               return;
+       }
+
+       imail->data.stream = i_stream_create_fd(fd, 0, TRUE);
+       i_stream_set_name(imail->data.stream, path);
+       index_mail_set_read_buffer_size(_mail, imail->data.stream);
+
+       if (body) {
+               imail->data.physical_size = size;
+               imail->data.virtual_size = size;
+       }
+
+       if (index_mail_init_stream(imail, NULL, NULL, &input) < 0)
+               i_stream_unref(&imail->data.stream);
+}
+
+void imapc_fetch_mail_update(struct mail *mail, const struct imap_arg *args)
+{
+       struct index_mail *imail = (struct index_mail *)mail;
+       const char *key, *value;
+       unsigned int i;
+       time_t t;
+       int tz;
+
+       for (i = 0; args[i].type != IMAP_ARG_EOL; i += 2) {
+               if (!imap_arg_get_atom(&args[i], &key) ||
+                   args[i+1].type == IMAP_ARG_EOL)
+                       return;
+
+               if (strcasecmp(key, "BODY[]") == 0) {
+                       if (!imap_arg_get_nstring(&args[i+1], &value))
+                               return;
+                       if (value != NULL)
+                               imapc_fetch_stream(imail, value, TRUE);
+               } else if (strcasecmp(key, "BODY[HEADER]") == 0) {
+                       if (!imap_arg_get_nstring(&args[i+1], &value))
+                               return;
+                       if (value != NULL)
+                               imapc_fetch_stream(imail, value, FALSE);
+               } else if (strcasecmp(key, "INTERNALDATE") == 0) {
+                       if (!imap_arg_get_astring(&args[i+1], &value) ||
+                           !imap_parse_datetime(value, &t, &tz))
+                               return;
+                       imail->data.received_date = t;
+               }
+       }
+}
diff --git a/src/lib-storage/index/imapc/imapc-seqmap.c b/src/lib-storage/index/imapc/imapc-seqmap.c
new file mode 100644 (file)
index 0000000..01f2cb4
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "imapc-seqmap.h"
+
+struct imapc_seqmap {
+       ARRAY_TYPE(uint32_t) expunges;
+};
+
+struct imapc_seqmap *imapc_seqmap_init(void)
+{
+       struct imapc_seqmap *seqmap;
+
+       seqmap = i_new(struct imapc_seqmap, 1);
+       i_array_init(&seqmap->expunges, 64);
+       return seqmap;
+}
+
+void imapc_seqmap_deinit(struct imapc_seqmap **_seqmap)
+{
+       struct imapc_seqmap *seqmap = *_seqmap;
+
+       *_seqmap = NULL;
+       array_free(&seqmap->expunges);
+       i_free(seqmap);
+}
+
+void imapc_seqmap_reset(struct imapc_seqmap *seqmap)
+{
+       array_clear(&seqmap->expunges);
+}
+
+void imapc_seqmap_expunge(struct imapc_seqmap *seqmap, uint32_t rseq)
+{
+       i_assert(rseq > 0);
+
+       array_append(&seqmap->expunges, &rseq, 1);
+}
+
+uint32_t imapc_seqmap_rseq_to_lseq(struct imapc_seqmap *seqmap, uint32_t rseq)
+{
+       i_assert(rseq > 0);
+       return rseq; // FIXME
+}
+
+uint32_t imapc_seqmap_lseq_to_rseq(struct imapc_seqmap *seqmap, uint32_t lseq)
+{
+       i_assert(lseq > 0);
+       return lseq; // FIXME
+}
diff --git a/src/lib-storage/index/imapc/imapc-seqmap.h b/src/lib-storage/index/imapc/imapc-seqmap.h
new file mode 100644 (file)
index 0000000..7c7bd19
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef IMAPC_SEQMAP_H
+#define IMAPC_SEQMAP_H
+
+/* Defines a mapping between remote and local sequence numbers.
+   Initially they start the same, but remote sequences can be marked as
+   expunged, which alters the mapping until the seqmap is reset (i.e. when the
+   mailbox is synced and local sequences are expunged too).
+
+   So for example calling imapc_seqmap_expunge(seqmap, 1) twice expunges the
+   first and the second local sequence. imapc_seqmap_rseq_to_lseq(seqmap, 1)
+   will afterward return 3. */
+
+struct imapc_seqmap *imapc_seqmap_init(void);
+void imapc_seqmap_deinit(struct imapc_seqmap **seqmap);
+
+/* Reset local and remote sequences to be equal. */
+void imapc_seqmap_reset(struct imapc_seqmap *seqmap);
+
+/* Mark given remote sequence expunged. */
+void imapc_seqmap_expunge(struct imapc_seqmap *seqmap, uint32_t rseq);
+/* Convert remote sequence to local sequence. */
+uint32_t imapc_seqmap_rseq_to_lseq(struct imapc_seqmap *seqmap, uint32_t rseq);
+/* Convert local sequence to remote sequence. */
+uint32_t imapc_seqmap_lseq_to_rseq(struct imapc_seqmap *seqmap, uint32_t lseq);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-storage.c b/src/lib-storage/index/imapc/imapc-storage.c
new file mode 100644 (file)
index 0000000..fa69d05
--- /dev/null
@@ -0,0 +1,572 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-util.h"
+#include "imap-arg.h"
+#include "imap-resp-code.h"
+#include "mail-copy.h"
+#include "index-mail.h"
+#include "mailbox-list-private.h"
+#include "imapc-client.h"
+#include "imapc-seqmap.h"
+#include "imapc-sync.h"
+#include "imapc-storage.h"
+
+#include <sys/stat.h>
+
+#define DNS_CLIENT_SOCKET_NAME "dns-client"
+
+struct imapc_simple_context {
+       struct imapc_storage *storage;
+       int ret;
+};
+
+struct imapc_open_context {
+       struct imapc_mailbox *mbox;
+       int ret;
+};
+
+struct imapc_status_context {
+       struct imapc_mailbox *mbox;
+       struct mailbox_status *status;
+       int ret;
+};
+
+struct imapc_resp_code_map {
+       const char *code;
+       enum mail_error error;
+};
+
+extern struct mail_storage imapc_storage;
+extern struct mailbox imapc_mailbox;
+
+static struct imapc_resp_code_map imapc_resp_code_map[] = {
+       { IMAP_RESP_CODE_UNAVAILABLE, MAIL_ERROR_TEMP },
+       { IMAP_RESP_CODE_AUTHFAILED, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_AUTHZFAILED, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_EXPIRED, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_PRIVACYREQUIRED, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_CONTACTADMIN, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_NOPERM, MAIL_ERROR_PERM },
+       { IMAP_RESP_CODE_INUSE, MAIL_ERROR_INUSE },
+       { IMAP_RESP_CODE_EXPUNGEISSUED, MAIL_ERROR_EXPUNGED },
+       { IMAP_RESP_CODE_CORRUPTION, MAIL_ERROR_TEMP },
+       { IMAP_RESP_CODE_SERVERBUG, MAIL_ERROR_TEMP },
+       /* { IMAP_RESP_CODE_CLIENTBUG, 0 }, */
+       { IMAP_RESP_CODE_CANNOT, MAIL_ERROR_NOTPOSSIBLE },
+       { IMAP_RESP_CODE_LIMIT, MAIL_ERROR_NOTPOSSIBLE },
+       { IMAP_RESP_CODE_OVERQUOTA, MAIL_ERROR_NOSPACE },
+       { IMAP_RESP_CODE_ALREADYEXISTS, MAIL_ERROR_EXISTS },
+       { IMAP_RESP_CODE_NONEXISTENT, MAIL_ERROR_NOTFOUND }
+};
+
+static bool
+imap_resp_text_code_parse(const char *str, enum mail_error *error_r)
+{
+       unsigned int i;
+
+       if (str == NULL)
+               return FALSE;
+
+       for (i = 0; i < N_ELEMENTS(imapc_resp_code_map); i++) {
+               if (strcmp(imapc_resp_code_map[i].code, str) == 0) {
+                       *error_r = imapc_resp_code_map[i].error;
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+static struct mail_storage *imapc_storage_alloc(void)
+{
+       struct imapc_storage *storage;
+       pool_t pool;
+
+       pool = pool_alloconly_create("imapc storage", 512+256);
+       storage = p_new(pool, struct imapc_storage, 1);
+       storage->storage = imapc_storage;
+       storage->storage.pool = pool;
+       return &storage->storage;
+}
+
+static void
+imapc_copy_error_from_reply(struct imapc_storage *storage,
+                           enum mail_error default_error,
+                           const struct imapc_command_reply *reply)
+{
+       enum mail_error error;
+       const char *p;
+
+       if (imap_resp_text_code_parse(reply->resp_text, &error)) {
+               p = strchr(reply->text, ']');
+               i_assert(p != NULL);
+               mail_storage_set_error(&storage->storage, error, p + 1);
+       } else {
+               mail_storage_set_error(&storage->storage, default_error,
+                                      reply->text);
+       }
+}
+
+static void
+imapc_simple_callback(const struct imapc_command_reply *reply,
+                     void *context)
+{
+       struct imapc_simple_context *ctx = context;
+
+       if (reply->state == IMAPC_COMMAND_STATE_OK)
+               ctx->ret = 0;
+       else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+               imapc_copy_error_from_reply(ctx->storage, MAIL_ERROR_PARAMS, reply);
+               ctx->ret = -1;
+       } else {
+               mail_storage_set_critical(&ctx->storage->storage,
+                       "imapc: Command failed: %s", reply->text);
+               ctx->ret = -1;
+       }
+       imapc_client_stop(ctx->storage->client);
+}
+
+void imapc_async_stop_callback(const struct imapc_command_reply *reply,
+                              void *context)
+{
+       struct imapc_storage *storage = context;
+
+       if (reply->state == IMAPC_COMMAND_STATE_OK)
+               ;
+       else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+               imapc_copy_error_from_reply(storage, MAIL_ERROR_PARAMS, reply);
+       } else {
+               mail_storage_set_critical(&storage->storage,
+                       "imapc: Command failed: %s", reply->text);
+       }
+       imapc_client_stop(storage->client);
+}
+
+static void
+imapc_mailbox_map_new_msgs(struct imapc_mailbox *mbox,
+                          struct imapc_seqmap *seqmap, uint32_t rcount)
+{
+       const struct mail_index_header *hdr;
+       uint32_t next_lseq, next_rseq;
+
+       next_lseq = mail_index_view_get_messages_count(mbox->box.view) + 1;
+       next_rseq = imapc_seqmap_lseq_to_rseq(seqmap, next_lseq);
+       if (next_rseq > rcount)
+               return;
+
+       hdr = mail_index_get_header(mbox->box.view);
+
+       mbox->new_msgs = TRUE;
+       imapc_client_mailbox_cmdf(mbox->client_box, imapc_async_stop_callback,
+                                 mbox->storage, "UID FETCH %u:* FLAGS",
+                                 hdr->next_uid);
+}
+
+static void
+imapc_mailbox_map_fetch_reply(struct imapc_mailbox *mbox,
+                             const struct imap_arg *args, uint32_t seq)
+{
+       struct imapc_seqmap *seqmap;
+       const struct imap_arg *list, *flags_list;
+       const char *atom;
+       const struct mail_index_record *rec;
+       enum mail_flags flags;
+       uint32_t uid, old_count;
+       unsigned int i, j;
+       bool seen_flags = FALSE;
+
+       if (seq == 0 || !imap_arg_get_list(args, &list))
+               return;
+
+       uid = 0; flags = 0;
+       for (i = 0; list[i].type != IMAP_ARG_EOL; i += 2) {
+               if (!imap_arg_get_atom(&list[i], &atom))
+                       return;
+
+               if (strcasecmp(atom, "UID") == 0) {
+                       if (!imap_arg_get_atom(&list[i+1], &atom) ||
+                           str_to_uint32(atom, &uid) < 0)
+                               return;
+               } else if (strcasecmp(atom, "FLAGS") == 0) {
+                       if (!imap_arg_get_list(&list[i+1], &flags_list))
+                               return;
+
+                       seen_flags = TRUE;
+                       for (j = 0; flags_list[j].type != IMAP_ARG_EOL; j++) {
+                               if (!imap_arg_get_atom(&flags_list[j], &atom))
+                                       return;
+                               if (atom[0] == '\\')
+                                       flags |= imap_parse_system_flag(atom);
+                       }
+               }
+       }
+
+       seqmap = imapc_client_mailbox_get_seqmap(mbox->client_box);
+       seq = imapc_seqmap_rseq_to_lseq(seqmap, seq);
+
+       if (mbox->cur_fetch_mail != NULL && mbox->cur_fetch_mail->seq == seq) {
+               i_assert(uid == 0 || mbox->cur_fetch_mail->uid == uid);
+               imapc_fetch_mail_update(mbox->cur_fetch_mail, list);
+       }
+
+       old_count = mail_index_view_get_messages_count(mbox->delayed_sync_view);
+       if (seq > old_count) {
+               if (uid == 0)
+                       return;
+               i_assert(seq == old_count + 1);
+               mail_index_append(mbox->delayed_sync_trans, uid, &seq);
+       }
+       rec = mail_index_lookup(mbox->delayed_sync_view, seq);
+       if (seen_flags && rec->flags != flags) {
+               mail_index_update_flags(mbox->delayed_sync_trans, seq,
+                                       MODIFY_REPLACE, flags);
+       }
+}
+
+static void imapc_storage_untagged_cb(const struct imapc_untagged_reply *reply,
+                                     void *context)
+{
+       struct imapc_storage *storage = context;
+       struct imapc_mailbox *mbox = reply->untagged_box_context;
+       struct imapc_seqmap *seqmap;
+       uint32_t lseq;
+
+       if (mbox == NULL)
+               return;
+
+       if (reply->resp_text != NULL) {
+               uint32_t uid_validity, uid_next;
+
+               if (strncasecmp(reply->resp_text, "UIDVALIDITY ", 12) == 0 &&
+                   str_to_uint32(reply->resp_text + 12, &uid_validity) == 0) {
+                       mail_index_update_header(mbox->delayed_sync_trans,
+                               offsetof(struct mail_index_header, uid_validity),
+                               &uid_validity, sizeof(uid_validity), TRUE);
+               }
+               if (strncasecmp(reply->resp_text, "UIDNEXT ", 8) == 0 &&
+                   str_to_uint32(reply->resp_text + 8, &uid_next) == 0) {
+                       mail_index_update_header(mbox->delayed_sync_trans,
+                               offsetof(struct mail_index_header, next_uid),
+                               &uid_next, sizeof(uid_next), FALSE);
+               }
+       }
+
+       seqmap = imapc_client_mailbox_get_seqmap(mbox->client_box);
+       if (strcasecmp(reply->name, "EXISTS") == 0) {
+               imapc_mailbox_map_new_msgs(mbox, seqmap, reply->num);
+       } else if (strcasecmp(reply->name, "FETCH") == 0) {
+               imapc_mailbox_map_fetch_reply(mbox, reply->args, reply->num);
+       } else if (strcasecmp(reply->name, "EXPUNGE") == 0) {
+               lseq = imapc_seqmap_rseq_to_lseq(seqmap, reply->num);
+               mail_index_expunge(mbox->delayed_sync_trans, lseq);
+       }
+}
+
+static int
+imapc_storage_create(struct mail_storage *_storage,
+                    struct mail_namespace *ns,
+                    const char **error_r)
+{
+       struct imapc_storage *storage = (struct imapc_storage *)_storage;
+       struct imapc_client_settings set;
+
+       memset(&set, 0, sizeof(set));
+       set.host = ns->list->set.root_dir;
+       set.port = 143;
+       set.username = _storage->user->username;
+       set.password = mail_user_plugin_getenv(_storage->user, "pass");
+       if (set.password == NULL) {
+               *error_r = "missing pass";
+               return -1;
+       }
+       set.dns_client_socket_path =
+               t_strconcat(_storage->user->set->base_dir, "/",
+                           DNS_CLIENT_SOCKET_NAME, NULL);
+       storage->client = imapc_client_init(&set);
+       imapc_client_register_untagged(storage->client,
+                                      imapc_storage_untagged_cb, storage);
+       return 0;
+}
+
+static void imapc_storage_destroy(struct mail_storage *_storage)
+{
+       struct imapc_storage *storage = (struct imapc_storage *)_storage;
+
+       imapc_client_deinit(&storage->client);
+}
+
+static void
+imapc_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
+                               struct mailbox_list_settings *set)
+{
+       set->layout = "none";
+}
+
+static struct mailbox *
+imapc_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
+                   const char *name, enum mailbox_flags flags)
+{
+       struct imapc_mailbox *mbox;
+       struct index_mailbox_context *ibox;
+       pool_t pool;
+
+       flags |= MAILBOX_FLAG_NO_INDEX_FILES;
+
+       pool = pool_alloconly_create("imapc mailbox", 1024*3);
+       mbox = p_new(pool, struct imapc_mailbox, 1);
+       mbox->box = imapc_mailbox;
+       mbox->box.pool = pool;
+       mbox->box.storage = storage;
+       mbox->box.list = list;
+       mbox->box.mail_vfuncs = &imapc_mail_vfuncs;
+
+       index_storage_mailbox_alloc(&mbox->box, name, flags, NULL);
+
+       ibox = INDEX_STORAGE_CONTEXT(&mbox->box);
+       ibox->save_commit_pre = imapc_transaction_save_commit_pre;
+       ibox->save_commit_post = imapc_transaction_save_commit_post;
+       ibox->save_rollback = imapc_transaction_save_rollback;
+
+       mbox->storage = (struct imapc_storage *)storage;
+       return &mbox->box;
+}
+
+static void
+imapc_mailbox_open_callback(const struct imapc_command_reply *reply,
+                           void *context)
+{
+       struct imapc_open_context *ctx = context;
+
+       if (reply->state == IMAPC_COMMAND_STATE_OK)
+               ctx->ret = 0;
+       else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+               imapc_copy_error_from_reply(ctx->mbox->storage,
+                                           MAIL_ERROR_NOTFOUND, reply);
+               ctx->ret = -1;
+       } else {
+               mail_storage_set_critical(ctx->mbox->box.storage,
+                       "imapc: Opening mailbox '%s' failed: %s",
+                       ctx->mbox->box.name, reply->text);
+               ctx->ret = -1;
+       }
+       if (!ctx->mbox->new_msgs)
+               imapc_client_stop(ctx->mbox->storage->client);
+}
+
+static int imapc_mailbox_open(struct mailbox *box)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+       struct imapc_open_context ctx;
+
+       if (index_storage_mailbox_open(box, FALSE) < 0)
+               return -1;
+
+       mbox->delayed_sync_trans =
+               mail_index_transaction_begin(box->view,
+                                       MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+       mbox->delayed_sync_view =
+               mail_index_transaction_open_updated_view(mbox->delayed_sync_trans);
+
+       ctx.mbox = mbox;
+       ctx.ret = -1;
+       mbox->client_box =
+               imapc_client_mailbox_open(mbox->storage->client, box->name,
+                                         imapc_mailbox_open_callback,
+                                         &ctx, mbox);
+       imapc_client_run(mbox->storage->client);
+       if (ctx.ret < 0) {
+               mailbox_close(box);
+               return -1;
+       }
+       return 0;
+}
+
+static void imapc_mailbox_close(struct mailbox *box)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+
+       mail_index_view_close(&mbox->delayed_sync_view);
+       if (mail_index_transaction_commit(&mbox->delayed_sync_trans) < 0)
+               mail_storage_set_index_error(&mbox->box);
+       return index_storage_mailbox_close(box);
+}
+
+static int
+imapc_mailbox_create(struct mailbox *box,
+                    const struct mailbox_update *update ATTR_UNUSED,
+                    bool directory)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+       struct imapc_simple_context ctx;
+       const char *name = box->name;
+
+       if (directory) {
+               /* FIXME: hardcoded separator.. */
+               name = t_strconcat(name, "/", NULL);
+       }
+       ctx.storage = mbox->storage;
+       imapc_client_cmdf(mbox->storage->client, imapc_simple_callback, &ctx,
+                         "CREATE %s", name);
+       imapc_client_run(mbox->storage->client);
+       return ctx.ret;
+}
+
+static int imapc_mailbox_update(struct mailbox *box,
+                               const struct mailbox_update *update ATTR_UNUSED)
+{
+       mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "Not supported");
+       return -1;
+}
+
+static void imapc_mailbox_get_selected_status(struct imapc_mailbox *mbox,
+                                             enum mailbox_status_items items,
+                                             struct mailbox_status *status_r)
+{
+       index_storage_get_status(&mbox->box, items, status_r);
+}
+
+static void
+imapc_mailbox_status_callback(const struct imapc_command_reply *reply,
+                             void *context)
+{
+       struct imapc_status_context *ctx = context;
+
+       if (reply->state == IMAPC_COMMAND_STATE_OK)
+               ctx->ret = 0;
+       else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+               imapc_copy_error_from_reply(ctx->mbox->storage,
+                                           MAIL_ERROR_NOTFOUND, reply);
+       } else {
+               mail_storage_set_critical(ctx->mbox->box.storage,
+                       "imapc: STATUS for mailbox '%s' failed: %s",
+                       ctx->mbox->box.name, reply->text);
+               ctx->ret = -1;
+       }
+       imapc_client_stop(ctx->mbox->storage->client);
+}
+
+static int imapc_mailbox_get_status(struct mailbox *box,
+                                   enum mailbox_status_items items,
+                                   struct mailbox_status *status_r)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+       struct imapc_status_context ctx;
+       string_t *str;
+
+       memset(status_r, 0, sizeof(*status_r));
+
+       if (box->opened) {
+               imapc_mailbox_get_selected_status(mbox, items, status_r);
+               return 0;
+       }
+
+       /* mailbox isn't opened yet */
+       if ((items & (STATUS_FIRST_UNSEEN_SEQ | STATUS_KEYWORDS)) != 0) {
+               /* getting these requires opening the mailbox */
+               if (mailbox_open(box) < 0)
+                       return -1;
+               imapc_mailbox_get_selected_status(mbox, items, status_r);
+               return 0;
+       }
+
+       str = t_str_new(256);
+       if ((items & STATUS_MESSAGES) != 0)
+               str_append(str, " MESSAGES");
+       if ((items & STATUS_RECENT) != 0)
+               str_append(str, " RECENT");
+       if ((items & STATUS_UIDNEXT) != 0)
+               str_append(str, " UIDNEXT");
+       if ((items & STATUS_UIDVALIDITY) != 0)
+               str_append(str, " UIDVALIDITY");
+       if ((items & STATUS_UNSEEN) != 0)
+               str_append(str, " UNSEEN");
+       if ((items & STATUS_HIGHESTMODSEQ) != 0)
+               str_append(str, " HIGHESTMODSEQ");
+
+       if (str_len(str) == 0) {
+               /* nothing requested */
+               return 0;
+       }
+
+       ctx.mbox = mbox;
+       ctx.status = status_r;
+       imapc_client_cmdf(mbox->storage->client,
+                         imapc_mailbox_status_callback, &ctx,
+                         "STATUS %s (%1s)", box->name, str_c(str));
+       imapc_client_run(mbox->storage->client);
+       return ctx.ret;
+}
+
+static int imapc_mailbox_get_metadata(struct mailbox *box,
+                                     enum mailbox_metadata_items items,
+                                     struct mailbox_metadata *metadata_r)
+{
+       mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "Not supported");
+       return -1;
+}
+
+static void imapc_notify_changes(struct mailbox *box)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+
+}
+
+struct mail_storage imapc_storage = {
+       .name = IMAPC_STORAGE_NAME,
+       .class_flags = 0,
+
+       .v = {
+               NULL,
+               imapc_storage_alloc,
+               imapc_storage_create,
+               imapc_storage_destroy,
+               NULL,
+               imapc_storage_get_list_settings,
+               NULL,
+               imapc_mailbox_alloc,
+               NULL
+       }
+};
+
+struct mailbox imapc_mailbox = {
+       .v = {
+               index_storage_is_readonly,
+               index_storage_allow_new_keywords,
+               index_storage_mailbox_enable,
+               imapc_mailbox_open,
+               imapc_mailbox_close,
+               index_storage_mailbox_free,
+               imapc_mailbox_create,
+               imapc_mailbox_update,
+               index_storage_mailbox_delete,
+               index_storage_mailbox_rename,
+               imapc_mailbox_get_status,
+               imapc_mailbox_get_metadata,
+               NULL,
+               NULL,
+               imapc_mailbox_sync_init,
+               index_mailbox_sync_next,
+               imapc_mailbox_sync_deinit,
+               NULL,
+               imapc_notify_changes,
+               index_transaction_begin,
+               index_transaction_commit,
+               index_transaction_rollback,
+               NULL,
+               index_mail_alloc,
+               imapc_search_init,
+               index_storage_search_deinit,
+               imapc_search_next_nonblock,
+               index_storage_search_next_update_seq,
+               imapc_save_alloc,
+               imapc_save_begin,
+               imapc_save_continue,
+               imapc_save_finish,
+               imapc_save_cancel,
+               mail_storage_copy,
+               index_storage_is_inconsistent
+       }
+};
diff --git a/src/lib-storage/index/imapc/imapc-storage.h b/src/lib-storage/index/imapc/imapc-storage.h
new file mode 100644 (file)
index 0000000..b4447e1
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef IMAPC_STORAGE_H
+#define IMAPC_STORAGE_H
+
+#include "index-storage.h"
+
+#define IMAPC_STORAGE_NAME "imapc"
+
+struct imap_arg;
+struct imapc_command_reply;
+
+struct imapc_storage {
+       struct mail_storage storage;
+       struct imapc_client *client;
+};
+
+struct imapc_mailbox {
+       struct mailbox box;
+       struct imapc_storage *storage;
+       struct imapc_client_mailbox *client_box;
+
+       struct mail_index_transaction *delayed_sync_trans;
+       struct mail_index_view *delayed_sync_view;
+
+       struct mail *cur_fetch_mail;
+
+       unsigned int new_msgs:1;
+};
+
+extern struct mail_vfuncs imapc_mail_vfuncs;
+
+struct mail_save_context *
+imapc_save_alloc(struct mailbox_transaction_context *_t);
+int imapc_save_begin(struct mail_save_context *ctx, struct istream *input);
+int imapc_save_continue(struct mail_save_context *ctx);
+int imapc_save_finish(struct mail_save_context *ctx);
+void imapc_save_cancel(struct mail_save_context *ctx);
+
+int imapc_transaction_save_commit_pre(struct mail_save_context *ctx);
+void imapc_transaction_save_commit_post(struct mail_save_context *ctx,
+                                       struct mail_index_transaction_commit_result *result);
+void imapc_transaction_save_rollback(struct mail_save_context *ctx);
+
+struct mail_search_context *
+imapc_search_init(struct mailbox_transaction_context *t,
+                 struct mail_search_args *args,
+                 const enum mail_sort_type *sort_program);
+bool imapc_search_next_nonblock(struct mail_search_context *_ctx,
+                               struct mail *mail, bool *tryagain_r);
+void imapc_fetch_mail_update(struct mail *mail, const struct imap_arg *args);
+
+void imapc_async_stop_callback(const struct imapc_command_reply *reply,
+                              void *context);
+
+#endif
diff --git a/src/lib-storage/index/imapc/imapc-sync.c b/src/lib-storage/index/imapc/imapc-sync.c
new file mode 100644 (file)
index 0000000..2ac6b75
--- /dev/null
@@ -0,0 +1,152 @@
+/* Copyright (c) 2007-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "index-sync-private.h"
+#include "imapc-storage.h"
+#include "imapc-client.h"
+#include "imapc-seqmap.h"
+#include "imapc-sync.h"
+
+static void imapc_sync_index(struct imapc_sync_context *ctx)
+{
+       struct mailbox *box = &ctx->mbox->box;
+       struct mail_index_sync_rec sync_rec;
+       uint32_t seq1, seq2;
+
+       while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) {
+               if (!mail_index_lookup_seq_range(ctx->sync_view,
+                                                sync_rec.uid1, sync_rec.uid2,
+                                                &seq1, &seq2)) {
+                       /* already expunged, nothing to do. */
+                       continue;
+               }
+
+               switch (sync_rec.type) {
+               case MAIL_INDEX_SYNC_TYPE_APPEND:
+                       /* don't care */
+                       break;
+               case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
+                       //imapc_sync_expunge(ctx, seq1, seq2);
+                       break;
+               case MAIL_INDEX_SYNC_TYPE_FLAGS:
+               case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
+               case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
+               case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
+                       /* FIXME: should be bother calling sync_notify()? */
+                       break;
+               }
+       }
+
+       if (box->v.sync_notify != NULL)
+               box->v.sync_notify(box, 0, 0);
+}
+
+static int
+imapc_sync_begin(struct imapc_mailbox *mbox,
+                struct imapc_sync_context **ctx_r, bool force)
+{
+       struct imapc_sync_context *ctx;
+       enum mail_index_sync_flags sync_flags;
+       int ret;
+
+       ctx = i_new(struct imapc_sync_context, 1);
+       ctx->mbox = mbox;
+
+       sync_flags = index_storage_get_sync_flags(&mbox->box) |
+               MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
+       if (!force)
+               sync_flags |= MAIL_INDEX_SYNC_FLAG_REQUIRE_CHANGES;
+
+       ret = mail_index_sync_begin(mbox->box.index, &ctx->index_sync_ctx,
+                                   &ctx->sync_view, &ctx->trans,
+                                   sync_flags);
+       if (ret <= 0) {
+               if (ret < 0)
+                       mail_storage_set_index_error(&mbox->box);
+               i_free(ctx);
+               *ctx_r = NULL;
+               return ret;
+       }
+
+       imapc_sync_index(ctx);
+       *ctx_r = ctx;
+       return 0;
+}
+
+static int imapc_sync_finish(struct imapc_sync_context **_ctx, bool success)
+{
+       struct imapc_sync_context *ctx = *_ctx;
+       int ret = success ? 0 : -1;
+
+       *_ctx = NULL;
+       if (success) {
+               if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) {
+                       mail_storage_set_index_error(&ctx->mbox->box);
+                       ret = -1;
+               }
+       } else {
+               mail_index_sync_rollback(&ctx->index_sync_ctx);
+       }
+       i_free(ctx);
+       return ret;
+}
+
+static int imapc_sync(struct imapc_mailbox *mbox)
+{
+       struct imapc_sync_context *sync_ctx;
+
+       if (imapc_sync_begin(mbox, &sync_ctx, FALSE) < 0)
+               return -1;
+
+       return sync_ctx == NULL ? 0 :
+               imapc_sync_finish(&sync_ctx, TRUE);
+}
+
+struct mailbox_sync_context *
+imapc_mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)box;
+       int ret = 0;
+
+       if (!box->opened) {
+               if (mailbox_open(box) < 0)
+                       ret = -1;
+       }
+
+       mail_index_view_close(&mbox->delayed_sync_view);
+       if (mail_index_transaction_commit(&mbox->delayed_sync_trans) < 0) {
+               // FIXME: mark inconsistent
+               mail_storage_set_index_error(&mbox->box);
+               ret = -1;
+       }
+
+       if (index_mailbox_want_full_sync(&mbox->box, flags) && ret == 0)
+               ret = imapc_sync(mbox);
+
+       return index_mailbox_sync_init(box, flags, ret < 0);
+}
+
+int imapc_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+                             struct mailbox_sync_status *status_r)
+{
+       struct index_mailbox_sync_context *ictx =
+               (struct index_mailbox_sync_context *)ctx;
+       struct imapc_mailbox *mbox = (struct imapc_mailbox *)ctx->box;
+       struct imapc_seqmap *seqmap;
+       int ret;
+
+       ret = index_mailbox_sync_deinit(ctx, status_r);
+
+       mbox->delayed_sync_trans =
+               mail_index_transaction_begin(mbox->box.view,
+                                       MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+       mbox->delayed_sync_view =
+               mail_index_transaction_open_updated_view(mbox->delayed_sync_trans);
+
+       if ((ictx->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0 && ret == 0) {
+               seqmap = imapc_client_mailbox_get_seqmap(mbox->client_box);
+               imapc_seqmap_reset(seqmap);
+       }
+       return ret;
+}
diff --git a/src/lib-storage/index/imapc/imapc-sync.h b/src/lib-storage/index/imapc/imapc-sync.h
new file mode 100644 (file)
index 0000000..6343de1
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef CYDIR_SYNC_H
+#define CYDIR_SYNC_H
+
+struct mailbox;
+
+struct imapc_sync_context {
+       struct imapc_mailbox *mbox;
+        struct mail_index_sync_ctx *index_sync_ctx;
+       struct mail_index_view *sync_view;
+       struct mail_index_transaction *trans;
+};
+
+struct mailbox_sync_context *
+imapc_mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+int imapc_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+                             struct mailbox_sync_status *status_r);
+
+#endif
index b033640f3c6936fb74d787dfdce0dc353c553c88..47b7e249461d0a85af8382d55b96a9c88a793f94 100644 (file)
@@ -5,6 +5,8 @@
 
 struct index_mailbox_sync_context {
        struct mailbox_sync_context ctx;
+       enum mailbox_sync_flags flags;
+
        struct mail_index_view_sync_ctx *sync_ctx;
        uint32_t messages_count;
 
index fc2b147b547d2c926ba3c65cf04beab20ec07bb4..d7f9e35709412b94d3f5ff0bd2cdcc4985cf20fe 100644 (file)
@@ -176,6 +176,7 @@ index_mailbox_sync_init(struct mailbox *box, enum mailbox_sync_flags flags,
 
        ctx = i_new(struct index_mailbox_sync_context, 1);
        ctx->ctx.box = box;
+       ctx->flags = flags;
 
        if (failed) {
                ctx->failed = TRUE;