summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/pop3c/pop3c-sync.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/pop3c/pop3c-sync.c')
-rw-r--r--src/lib-storage/index/pop3c/pop3c-sync.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/src/lib-storage/index/pop3c/pop3c-sync.c b/src/lib-storage/index/pop3c/pop3c-sync.c
new file mode 100644
index 0000000..2d2dbe3
--- /dev/null
+++ b/src/lib-storage/index/pop3c/pop3c-sync.c
@@ -0,0 +1,361 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "bsearch-insert-pos.h"
+#include "str.h"
+#include "sort.h"
+#include "strnum.h"
+#include "index-mail.h"
+#include "pop3c-client.h"
+#include "pop3c-storage.h"
+#include "pop3c-sync.h"
+#include "mailbox-recent-flags.h"
+
+struct pop3c_sync_msg {
+ uint32_t seq;
+ const char *uidl;
+};
+ARRAY_DEFINE_TYPE(pop3c_sync_msg, struct pop3c_sync_msg);
+
+int pop3c_sync_get_uidls(struct pop3c_mailbox *mbox)
+{
+ ARRAY_TYPE(const_string) uidls;
+ struct istream *input;
+ const char *error, *cline;
+ char *line, *p;
+ unsigned int seq, line_seq;
+
+ if (mbox->msg_uidls != NULL)
+ return 0;
+ if ((pop3c_client_get_capabilities(mbox->client) &
+ POP3C_CAPABILITY_UIDL) == 0) {
+ mail_storage_set_error(mbox->box.storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "UIDLs not supported by server");
+ return -1;
+ }
+
+ if (pop3c_client_cmd_stream(mbox->client, "UIDL\r\n",
+ &input, &error) < 0) {
+ mailbox_set_critical(&mbox->box, "UIDL failed: %s", error);
+ return -1;
+ }
+
+ mbox->uidl_pool = pool_alloconly_create("POP3 UIDLs", 1024*32);
+ p_array_init(&uidls, mbox->uidl_pool, 64); seq = 0;
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ seq++;
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ mailbox_set_critical(&mbox->box,
+ "Invalid UIDL line: %s", line);
+ break;
+ }
+ *p++ = '\0';
+ if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
+ mailbox_set_critical(&mbox->box,
+ "Unexpected UIDL seq: %s != %u", line, seq);
+ break;
+ }
+
+ cline = p_strdup(mbox->uidl_pool, p);
+ array_push_back(&uidls, &cline);
+ }
+ i_stream_destroy(&input);
+ if (line != NULL) {
+ pool_unref(&mbox->uidl_pool);
+ return -1;
+ }
+ if (seq == 0) {
+ /* make msg_uidls non-NULL */
+ array_append_zero(&uidls);
+ }
+ mbox->msg_uidls = array_front(&uidls);
+ mbox->msg_count = seq;
+ return 0;
+}
+
+int pop3c_sync_get_sizes(struct pop3c_mailbox *mbox)
+{
+ struct istream *input;
+ const char *error;
+ char *line, *p;
+ unsigned int seq, line_seq;
+
+ i_assert(mbox->msg_sizes == NULL);
+
+ if (mbox->msg_uidls == NULL) {
+ if (pop3c_sync_get_uidls(mbox) < 0)
+ return -1;
+ }
+ if (mbox->msg_count == 0) {
+ mbox->msg_sizes = i_new(uoff_t, 1);
+ return 0;
+ }
+
+ if (pop3c_client_cmd_stream(mbox->client, "LIST\r\n",
+ &input, &error) < 0) {
+ mailbox_set_critical(&mbox->box, "LIST failed: %s", error);
+ return -1;
+ }
+
+ mbox->msg_sizes = i_new(uoff_t, mbox->msg_count); seq = 0;
+ while ((line = i_stream_read_next_line(input)) != NULL) {
+ if (++seq > mbox->msg_count) {
+ mailbox_set_critical(&mbox->box,
+ "Too much data in LIST: %s", line);
+ break;
+ }
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ mailbox_set_critical(&mbox->box,
+ "Invalid LIST line: %s", line);
+ break;
+ }
+ *p++ = '\0';
+ if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
+ mailbox_set_critical(&mbox->box,
+ "Unexpected LIST seq: %s != %u", line, seq);
+ break;
+ }
+ if (str_to_uoff(p, &mbox->msg_sizes[seq-1]) < 0) {
+ mailbox_set_critical(&mbox->box,
+ "Invalid LIST size: %s", p);
+ break;
+ }
+ }
+ i_stream_destroy(&input);
+ if (line != NULL) {
+ i_free_and_null(mbox->msg_sizes);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+pop3c_get_local_msgs(pool_t pool, ARRAY_TYPE(pop3c_sync_msg) *local_msgs,
+ uint32_t messages_count,
+ struct mail_cache_view *cache_view,
+ unsigned int cache_idx)
+{
+ string_t *str = t_str_new(128);
+ struct pop3c_sync_msg msg;
+ uint32_t seq;
+
+ i_zero(&msg);
+ for (seq = 1; seq <= messages_count; seq++) {
+ str_truncate(str, 0);
+ if (mail_cache_lookup_field(cache_view, str, seq,
+ cache_idx) > 0)
+ msg.uidl = p_strdup(pool, str_c(str));
+ msg.seq = seq;
+ array_idx_set(local_msgs, seq-1, &msg);
+ }
+}
+
+static void
+pop3c_get_remote_msgs(ARRAY_TYPE(pop3c_sync_msg) *remote_msgs,
+ struct pop3c_mailbox *mbox)
+{
+ struct pop3c_sync_msg *msg;
+ uint32_t seq;
+
+ for (seq = 1; seq <= mbox->msg_count; seq++) {
+ msg = array_append_space(remote_msgs);
+ msg->seq = seq;
+ msg->uidl = mbox->msg_uidls[seq-1];
+ }
+}
+
+static int pop3c_sync_msg_uidl_cmp(const struct pop3c_sync_msg *msg1,
+ const struct pop3c_sync_msg *msg2)
+{
+ return null_strcmp(msg1->uidl, msg2->uidl);
+}
+
+static void
+pop3c_sync_messages(struct pop3c_mailbox *mbox,
+ struct mail_index_view *sync_view,
+ struct mail_index_transaction *sync_trans,
+ struct mail_cache_view *cache_view)
+{
+ struct index_mailbox_context *ibox =
+ INDEX_STORAGE_CONTEXT(&mbox->box);
+ const struct mail_index_header *hdr;
+ struct mail_cache_transaction_ctx *cache_trans;
+ ARRAY_TYPE(pop3c_sync_msg) local_msgs, remote_msgs;
+ const struct pop3c_sync_msg *lmsg, *rmsg;
+ uint32_t seq1, seq2, next_uid;
+ unsigned int lidx, ridx, lcount, rcount;
+ unsigned int cache_idx = ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
+ pool_t pool;
+
+ i_assert(mbox->msg_uids == NULL);
+
+ /* set our uidvalidity */
+ hdr = mail_index_get_header(sync_view);
+ if (hdr->uid_validity == 0) {
+ uint32_t uid_validity = ioloop_time;
+ mail_index_update_header(sync_trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &uid_validity, sizeof(uid_validity), TRUE);
+ }
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"pop3c sync", 10240);
+ p_array_init(&local_msgs, pool, hdr->messages_count);
+ pop3c_get_local_msgs(pool, &local_msgs, hdr->messages_count,
+ cache_view, cache_idx);
+ p_array_init(&remote_msgs, pool, mbox->msg_count);
+ pop3c_get_remote_msgs(&remote_msgs, mbox);
+
+ /* sort the messages by UIDLs, because some servers reorder messages */
+ array_sort(&local_msgs, pop3c_sync_msg_uidl_cmp);
+ array_sort(&remote_msgs, pop3c_sync_msg_uidl_cmp);
+
+ /* skip over existing messages with matching UIDLs and expunge the ones
+ that no longer exist in remote. */
+ mbox->msg_uids = mbox->msg_count == 0 ?
+ i_new(uint32_t, 1) : /* avoid malloc(0) assert */
+ i_new(uint32_t, mbox->msg_count);
+ cache_trans = mail_cache_get_transaction(cache_view, sync_trans);
+
+ lmsg = array_get(&local_msgs, &lcount);
+ rmsg = array_get(&remote_msgs, &rcount);
+ next_uid = hdr->next_uid;
+ lidx = ridx = 0;
+ while (lidx < lcount || ridx < rcount) {
+ uint32_t lseq = lidx < lcount ? lmsg[lidx].seq : 0;
+ uint32_t rseq = ridx < rcount ? rmsg[ridx].seq : 0;
+ int ret;
+
+ if (lidx >= lcount)
+ ret = 1;
+ else if (ridx >= rcount || lmsg[lidx].uidl == NULL)
+ ret = -1;
+ else
+ ret = strcmp(lmsg[lidx].uidl, rmsg[ridx].uidl);
+ if (ret < 0) {
+ /* message expunged in remote, or we didn't have a
+ local message's UIDL in cache. */
+ mail_index_expunge(sync_trans, lseq);
+ lidx++;
+ } else if (ret > 0) {
+ /* new message in remote */
+ i_assert(mbox->msg_uids[rseq-1] == 0);
+ mbox->msg_uids[rseq-1] = next_uid;
+ mail_index_append(sync_trans, next_uid++, &lseq);
+ mail_cache_add(cache_trans, lseq, cache_idx,
+ rmsg[ridx].uidl,
+ strlen(rmsg[ridx].uidl));
+ ridx++;
+ } else {
+ /* UIDL matched */
+ i_assert(mbox->msg_uids[rseq-1] == 0);
+ mail_index_lookup_uid(sync_view, lseq,
+ &mbox->msg_uids[rseq-1]);
+ lidx++;
+ ridx++;
+ }
+ }
+
+ /* mark the newly seen messages as recent */
+ if (mail_index_lookup_seq_range(sync_view, hdr->first_recent_uid,
+ hdr->next_uid, &seq1, &seq2))
+ mailbox_recent_flags_set_seqs(&mbox->box, sync_view, seq1, seq2);
+ pool_unref(&pool);
+}
+
+int pop3c_sync(struct pop3c_mailbox *mbox)
+{
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index_view *sync_view, *trans_view;
+ struct mail_index_transaction *sync_trans;
+ struct mail_index_sync_rec sync_rec;
+ struct mail_cache_view *cache_view = NULL;
+ enum mail_index_sync_flags sync_flags;
+ unsigned int idx;
+ string_t *str;
+ const char *reply;
+ int ret;
+ bool deletions = FALSE;
+
+ if (pop3c_sync_get_uidls(mbox) < 0)
+ return -1;
+
+ sync_flags = index_storage_get_sync_flags(&mbox->box) |
+ MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
+
+ ret = mail_index_sync_begin(mbox->box.index, &index_sync_ctx,
+ &sync_view, &sync_trans, sync_flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ mailbox_set_index_error(&mbox->box);
+ return ret;
+ }
+
+ if (mbox->msg_uids == NULL) {
+ trans_view = mail_index_transaction_open_updated_view(sync_trans);
+ cache_view = mail_cache_view_open(mbox->box.cache, trans_view);
+ pop3c_sync_messages(mbox, sync_view, sync_trans, cache_view);
+ }
+
+ /* mark expunges messages as deleted in this pop3 session,
+ if those exist */
+ str = t_str_new(32);
+ while (mail_index_sync_next(index_sync_ctx, &sync_rec)) {
+ if (sync_rec.type != MAIL_INDEX_SYNC_TYPE_EXPUNGE)
+ continue;
+
+ if (!bsearch_insert_pos(&sync_rec.uid1, mbox->msg_uids,
+ mbox->msg_count, sizeof(uint32_t),
+ uint32_cmp, &idx)) {
+ /* no such messages in this session */
+ continue;
+ }
+ for (; idx < mbox->msg_count; idx++) {
+ i_assert(mbox->msg_uids[idx] >= sync_rec.uid1);
+ if (mbox->msg_uids[idx] > sync_rec.uid2)
+ break;
+
+ str_truncate(str, 0);
+ str_printfa(str, "DELE %u\r\n", idx+1);
+ pop3c_client_cmd_line_async_nocb(mbox->client, str_c(str));
+ deletions = TRUE;
+ }
+ }
+
+ if (mail_index_sync_commit(&index_sync_ctx) < 0) {
+ mailbox_set_index_error(&mbox->box);
+ return -1;
+ }
+ if (cache_view != NULL) {
+ mail_cache_view_close(&cache_view);
+ mail_index_view_close(&trans_view);
+ }
+ if (deletions) {
+ if (pop3c_client_cmd_line(mbox->client, "QUIT\r\n",
+ &reply) < 0) {
+ mail_storage_set_error(mbox->box.storage,
+ MAIL_ERROR_TEMP, reply);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+struct mailbox_sync_context *
+pop3c_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+ struct pop3c_mailbox *mbox = POP3C_MAILBOX(box);
+ int ret = 0;
+
+ if ((flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0 &&
+ mbox->msg_uidls == NULL) {
+ /* FIXME: reconnect */
+ }
+
+ ret = pop3c_sync(mbox);
+ return index_mailbox_sync_init(box, flags, ret < 0);
+}