# HG changeset patch # User Timo Sirainen # Date 1295194103 -7200 # Node ID 7dd1e45721ae2621dbdd93224ffe0ee5e4f8c246 # Parent e1f0f4798b5e20619569a59839eeb8d08ede1c5e Added initial implementation of "imapc" storage. 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. diff -r e1f0f4798b5e -r 7dd1e45721ae configure.in --- a/configure.in Thu Jan 13 13:08:40 2011 +0200 +++ b/configure.in Sun Jan 16 18:08:23 2011 +0200 @@ -244,7 +244,7 @@ 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 @@ 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-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 diff -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/Makefile.am --- a/src/lib-storage/index/Makefile.am Thu Jan 13 13:08:40 2011 +0200 +++ b/src/lib-storage/index/Makefile.am Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/Makefile.am Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-client-private.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-client-private.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-client.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-client.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-client.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-client.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-connection.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 + +#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) { + /* */ + 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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-connection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-connection.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-mail.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-mail.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-save.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-save.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-search.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-search.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-seqmap.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-seqmap.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-seqmap.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-seqmap.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-storage.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-storage.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 + +#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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-storage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-storage.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-sync.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-sync.c Sun Jan 16 18:08:23 2011 +0200 @@ -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 -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/imapc/imapc-sync.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/imapc/imapc-sync.h Sun Jan 16 18:08:23 2011 +0200 @@ -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 diff -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/index-sync-private.h --- a/src/lib-storage/index/index-sync-private.h Thu Jan 13 13:08:40 2011 +0200 +++ b/src/lib-storage/index/index-sync-private.h Sun Jan 16 18:08:23 2011 +0200 @@ -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; diff -r e1f0f4798b5e -r 7dd1e45721ae src/lib-storage/index/index-sync.c --- a/src/lib-storage/index/index-sync.c Thu Jan 13 13:08:40 2011 +0200 +++ b/src/lib-storage/index/index-sync.c Sun Jan 16 18:08:23 2011 +0200 @@ -176,6 +176,7 @@ ctx = i_new(struct index_mailbox_sync_context, 1); ctx->ctx.box = box; + ctx->flags = flags; if (failed) { ctx->failed = TRUE;