bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi MODULE_CONTEXT(obj, pop3_migration_storage_module)
a8703ce24540b7efaa51a8c7d3c72e72727f9789Aki Tuomi MODULE_CONTEXT_REQUIRE(obj, pop3_migration_storage_module)
a8703ce24540b7efaa51a8c7d3c72e72727f9789Aki Tuomi MODULE_CONTEXT_REQUIRE(obj, pop3_migration_mail_module)
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen /* sha1(header) - set only when needed */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* LIST size */
35ef661bd85c64834e3e34eeeb3c393b81108760Timo Sirainen/* NOTE: these headers must be sorted */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "Content-Length",
35ef661bd85c64834e3e34eeeb3c393b81108760Timo Sirainen "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "X-IMAPbase",
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "X-Keywords",
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "X-Message-Flag",
c39c3d8089fbdd8eb34646c25167aa4551064cf4Timo Sirainen "X-Yahoo-Newman-Property"
bd63b5b860658b01b1f46f26d406e1e4a9dc019aTimo Sirainenconst char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int imap_msg_map_uid_cmp(const struct imap_msg_map *map1,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1,
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainenstatic int pop3_uidl_map_uidl_cmp(const struct pop3_uidl_map *map1,
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen return strcmp(map1->pop3_uidl, map2->pop3_uidl);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainenstatic int imap_msg_map_uidl_cmp(const struct imap_msg_map *map1,
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen return null_strcmp(map1->pop3_uidl, map2->pop3_uidl);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
5fa253bd316540ec280ca76b39d62a9e32da228bTimo Sirainenstatic bool header_name_is_valid(const char *name)
5fa253bd316540ec280ca76b39d62a9e32da228bTimo Sirainen unsigned int i;
5fa253bd316540ec280ca76b39d62a9e32da228bTimo Sirainen if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f)
29fc8f1dc678f9698363181ea599e6db105ea50fTimo Sirainenstatic bool header_value_want_skip(const struct message_header_line *hdr)
29fc8f1dc678f9698363181ea599e6db105ea50fTimo Sirainen if (hdr->value[i] != ' ' && hdr->value[i] != '\t')
29fc8f1dc678f9698363181ea599e6db105ea50fTimo Sirainen /* "header: \r\n \r\n" - Zimbra's BODY[HEADER] strips this line away. */
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainenpop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED,
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen if (strspn(hdr->name, "\r") == hdr->name_len) {
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen /* CR+CR+LF - some servers stop the header processing
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen here while others don't. To make sure they can be
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen matched correctly we want to stop here entirely. */
cefa6f3df2f30d84f8279109e9152cada9f67e16Timo Sirainen } else if (!hdr->continued && hdr->middle_len == 0) {
cefa6f3df2f30d84f8279109e9152cada9f67e16Timo Sirainen /* not a valid "key: value" header -
cefa6f3df2f30d84f8279109e9152cada9f67e16Timo Sirainen Zimbra's BODY[HEADER] strips this line away. */
29fc8f1dc678f9698363181ea599e6db105ea50fTimo Sirainen } else if (hdr->continued && header_value_want_skip(hdr)) {
5fa253bd316540ec280ca76b39d62a9e32da228bTimo Sirainen /* Yahoo IMAP drops headers with invalid names, while
5fa253bd316540ec280ca76b39d62a9e32da228bTimo Sirainen Yahoo POP3 preserves them. Drop them all. */
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainenint pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
9625595c47c665f5aee57ebfcb1fcbe9ad1bf3a0Martti Rannanjärvi unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* hide headers that might change or be different in IMAP vs. POP3 */
061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2Timo Sirainen input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY |
3858a7a5da361c35f1e6e50c8e3214dc0cf379d6Phil Carmody while (i_stream_read_more(input, &data, &size) > 0) {
e17e53f2a05478b3306d382b87d92c32334cbcf5Timo Sirainen message_header_hash_more(&hash_ctx, &hash_method_sha1, &sha1_ctx,
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen i_error("pop3_migration: Failed to read header for msg %u: %s",
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainenstatic unsigned int get_cache_idx(struct mail *mail)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(mail->box);
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen mbox->cache_field.name = "pop3-migration.hdr";
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE;
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen mbox->cache_field.field_size = SHA1_RESULTLEN;
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1);
9625595c47c665f5aee57ebfcb1fcbe9ad1bf3a0Martti Rannanjärviget_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2Timo Sirainen if (mail_get_hdr_stream(mail, NULL, &input) < 0) {
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi errstr = mailbox_get_last_internal_error(mail->box, &error);
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen i_error("pop3_migration: Failed to get header for msg %u: %s",
061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2Timo Sirainen if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0)
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen struct index_mail *imail = (struct index_mail *)mail;
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen index_mail_cache_add_idx(imail, get_cache_idx(mail),
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen /* The empty "end of headers" line is missing. Either this means that
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen the headers ended unexpectedly (which is ok) or that the remote
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen server is buggy. Some servers have problems with
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen 1) header line continuations that contain only whitespace and
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen 2) headers that have no ":". The header gets truncated when such
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen line is reached.
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen returning the whitespace line and 2) by returning the line but
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen truncating the rest. POP3 TOP instead returns the entire header.
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen This causes the IMAP and POP3 hashes not to match.
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
e18e90938ffd9e31c796c405404be0b7dcd5c807Timo Sirainen FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen So we'll try to avoid this by falling back to full FETCH BODY[]
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen (and/or RETR) and we'll parse the header ourself from it. This
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen should work around any similar bugs in all IMAP/POP3 servers. */
061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2Timo Sirainen if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) {
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi errstr = mailbox_get_last_internal_error(mail->box, &error);
31fd39a3a3d544b1a8afb9aef07f180d0d40fda2Timo Sirainen i_error("pop3_migration: Failed to get body for msg %u: %s",
061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2Timo Sirainen ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh);
2bc67190c90d08703ceb421fc8dcf16780020886Aki Tuomi if (ret == 0) {
2bc67190c90d08703ceb421fc8dcf16780020886Aki Tuomi i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid);
2bc67190c90d08703ceb421fc8dcf16780020886Aki Tuomi struct index_mail *imail = (struct index_mail *)mail;
2bc67190c90d08703ceb421fc8dcf16780020886Aki Tuomi index_mail_cache_add_idx(imail, get_cache_idx(mail),
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainenget_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf,
9625595c47c665f5aee57ebfcb1fcbe9ad1bf3a0Martti Rannanjärvi unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen if (index_mail_cache_lookup_field(imail, cache_buf,
57e1fdc2f8f2bf1c6fcd9523f93459404c2359c8Timo Sirainen memcpy(sha1_r, cache_buf->data, cache_buf->used);
1c3dc4c08ced3948f52c3c6c171ed77310b2cbfdTimo Sirainenstatic struct mailbox *pop3_mailbox_alloc(struct mail_storage *storage)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ns = mail_namespace_find(storage->user->namespaces,
61cf001f1944d92eb25f113ba4c08985d6e30d53Timo Sirainen box = mailbox_alloc(ns->list, mstorage->pop3_box_vname,
61cf001f1944d92eb25f113ba4c08985d6e30d53Timo Sirainen MAILBOX_FLAG_READONLY | MAILBOX_FLAG_POP3_SESSION);
1c3dc4c08ced3948f52c3c6c171ed77310b2cbfdTimo Sirainenstatic int pop3_map_read(struct mail_storage *storage, struct mailbox *pop3_box)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (array_is_created(&mstorage->pop3_uidl_map)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* already read these, just reset the imap_uids */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen array_foreach_modifiable(&mstorage->pop3_uidl_map, map)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_error("pop3_migration: Couldn't sync mailbox %s: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi pop3_box->vname, mailbox_get_last_internal_error(pop3_box, NULL));
0dab9cb35a976c49b28a11e28d5570f5191f1a7aMartti Rannanjärvi t = mailbox_transaction_begin(pop3_box, 0, __func__);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx = mailbox_search_init(t, search_args, NULL,
7e8bfb5b0af9606f131fc440e61f3752da335ac9Timo Sirainen /* get the size with LIST instead of RETR */
7e8bfb5b0af9606f131fc440e61f3752da335ac9Timo Sirainen mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
b215322367dbd94df3e2e4bb643b53460e6adc51Timo Sirainen else if (mail_get_physical_size(mail, &size) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_error("pop3_migration: Failed to get size for msg %u: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(pop3_box, NULL));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &uidl) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_error("pop3_migration: Failed to get UIDL for msg %u: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(pop3_box, NULL));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_warning("pop3_migration: UIDL for msg %u is empty",
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen map = array_append_space(&mstorage->pop3_uidl_map);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen map->pop3_uidl = p_strdup(storage->pool, uidl);
ba57ea2c696f9e9aae909f073069848876a641f4Timo Sirainen i_error("pop3_migration: Failed to search all POP3 mails: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(pop3_box, NULL));
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainenpop3_map_read_cached_hdr_hashes(struct mailbox_transaction_context *t,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen map = array_idx_modifiable_i(msg_map, mail->seq-1);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen if (get_cached_hdr_sha1(mail, cache_buf, map->hdr_sha1))
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen i_warning("pop3_migration: Failed to search all cached POP3 header hashes: %s - ignoring",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(t->box, NULL));
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainenstatic void map_remove_found_seqs(struct mail_search_arg *search_arg,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen seq_range_array_remove(&search_arg->value.seqset, seq);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainenmap_read_hdr_hashes(struct mailbox *box, struct array *msg_map, uint32_t seq1)
0dab9cb35a976c49b28a11e28d5570f5191f1a7aMartti Rannanjärvi t = mailbox_transaction_begin(box, 0, __func__);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen /* get all the cached hashes */
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen mail_search_build_add_seqset(search_args, seq1, array_count_i(msg_map));
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen pop3_map_read_cached_hdr_hashes(t, search_args, msg_map);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen /* read all the non-cached hashes. doing this in two passes allows
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen us to set wanted_fields=MAIL_FETCH_STREAM_HEADER, which allows
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen prefetching to work without downloading all the headers even
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen for mails that already are cached. */
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen map_remove_found_seqs(search_args->args, msg_map, seq1);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx = mailbox_search_init(t, search_args, NULL,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen map = array_idx_modifiable_i(msg_map, mail->seq-1);
a58963a8bdac0438a31abc03711e8870b4dfa4f4Timo Sirainen if ((ret = get_hdr_sha1(mail, map->hdr_sha1)) < 0) {
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen i_error("pop3_migration: Failed to search all mail headers: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(box, NULL));
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainenpop3_map_read_hdr_hashes(struct mail_storage *storage, struct mailbox *pop3_box,
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen struct pop3_migration_mail_storage *mstorage =
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen /* we may be matching against multiple mailboxes.
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen read all the hashes only once. */
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen if (map_read_hdr_hashes(pop3_box, &mstorage->pop3_uidl_map.arr,
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
b215322367dbd94df3e2e4bb643b53460e6adc51Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mailbox_get_open_status(box, STATUS_MESSAGES, &status);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_assert(!array_is_created(&mbox->imap_msg_map));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen p_array_init(&mbox->imap_msg_map, box->pool, status.messages);
0dab9cb35a976c49b28a11e28d5570f5191f1a7aMartti Rannanjärvi t = mailbox_transaction_begin(box, 0, __func__);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ctx = mailbox_search_init(t, search_args, NULL,
b215322367dbd94df3e2e4bb643b53460e6adc51Timo Sirainen else if (mail_get_physical_size(mail, &psize) < 0) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen i_error("pop3_migration: Failed to get psize for imap uid %u: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(box, NULL));
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen (void)mail_cache_lookup_field(mail->transaction->cache_view,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen map = array_append_space(&mbox->imap_msg_map);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen map->pop3_uidl = p_strdup_empty(box->pool, str_c(uidl));
ba57ea2c696f9e9aae909f073069848876a641f4Timo Sirainen i_error("pop3_migration: Failed to search all IMAP mails: %s",
bf7dc750b95039981c0e9d728f313d50cf38a156Martti Rannanjärvi mailbox_get_last_internal_error(box, NULL));
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int imap_map_read_hdr_hashes(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen return map_read_hdr_hashes(box, &mbox->imap_msg_map.arr,
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainenstatic void pop3_uidl_assign_cached(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen struct pop3_migration_mail_storage *mstorage =
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen unsigned int imap_idx, pop3_idx, pop3_count, imap_count;
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_uidl_cmp);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen array_sort(&mbox->imap_msg_map, imap_msg_map_uidl_cmp);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen /* see if we can match the messages using sizes */
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen for (imap_idx = pop3_idx = 0; imap_idx < imap_count; imap_idx++) {
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic bool pop3_uidl_assign_by_size(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen unsigned int i, pop3_count, imap_count, count;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* see if we can match the messages using sizes */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen for (i = 0; i < count; i++) {
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen /* some of the UIDLs were already found cached. */
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen if (strcmp(pop3_map[i].pop3_uidl, imap_map[i].pop3_uidl) == 0) {
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen /* mismatch - can't trust the sizes */
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen if (i+1 < count && pop3_map[i].size == pop3_map[i+1].size) {
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen /* two messages with same size, don't trust them */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen imap_map[i].pop3_uidl = pop3_map[i].pop3_uidl;
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen i_debug("pop3_migration: cached uidls=%u, size matches=%u, total=%u",
3bfdab4b3bea87b5c89f31502ef59380afc9e079Timo Sirainen return i == count && imap_count == pop3_count;
1c3dc4c08ced3948f52c3c6c171ed77310b2cbfdTimo Sirainenpop3_uidl_assign_by_hdr_hash(struct mailbox *box, struct mailbox *pop3_box)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen unsigned int pop3_idx, imap_idx, pop3_count, imap_count;
a415de5243c4a22df91496274762ca6637e9a104Timo Sirainen uint32_t first_missing_idx = 0, first_missing_seq = (uint32_t)-1;
1c3dc4c08ced3948f52c3c6c171ed77310b2cbfdTimo Sirainen if (pop3_map_read_hdr_hashes(box->storage, pop3_box, first_seq) < 0 ||
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_hdr_cmp);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen array_sort(&mbox->imap_msg_map, imap_msg_map_hdr_cmp);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen pop3_map = array_get_modifiable(&mstorage->pop3_uidl_map, &pop3_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen imap_map = array_get_modifiable(&mbox->imap_msg_map, &imap_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen while (pop3_idx < pop3_count && imap_idx < imap_count) {
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen if (!pop3_map[pop3_idx].common.hdr_sha1_set ||
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen if (!imap_map[imap_idx].common.hdr_sha1_set ||
9abc6ac61e70b809f7e1c352c7a3ad1081994d2eTimo Sirainen ret = memcmp(pop3_map[pop3_idx].common.hdr_sha1,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen else if (ret > 0)
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen pop3_map[pop3_idx].imap_uid = imap_map[imap_idx].uid;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen for (pop3_idx = 0; pop3_idx < pop3_count; pop3_idx++) {
a58963a8bdac0438a31abc03711e8870b4dfa4f4Timo Sirainen /* matched */
a58963a8bdac0438a31abc03711e8870b4dfa4f4Timo Sirainen } else if (!pop3_map[pop3_idx].common.hdr_sha1_set) {
a58963a8bdac0438a31abc03711e8870b4dfa4f4Timo Sirainen /* we treated this mail as expunged - ignore */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (missing_uids_count > 0 && !mstorage->all_mailboxes) {
af99ca825f4b7674ec6dd0269bbca665775205aaTimo Sirainen str_printfa(str, "pop3_migration: %u POP3 messages have no "
af99ca825f4b7674ec6dd0269bbca665775205aaTimo Sirainen "matching IMAP messages (first POP3 msg %u UIDL %s)",
e9d659ad49a3cf2190606a62289c86347608bffaTimo Sirainen if (imap_count + missing_uids_count == pop3_count) {
e9d659ad49a3cf2190606a62289c86347608bffaTimo Sirainen str_append(str, " - all IMAP messages were found "
e9d659ad49a3cf2190606a62289c86347608bffaTimo Sirainen "(POP3 contains more than IMAP INBOX - you may want to set pop3_migration_all_mailboxes=yes)");
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen if (all_imap_mails_found && mstorage->ignore_extra_uidls) {
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen /* pop3 had more mails than imap. maybe it was just
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen that a new mail was just delivered. */
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen str_append(str, " - set pop3_migration_ignore_missing_uidls=yes");
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen str_append(str, " or pop3_migration_ignore_extra_uidls=yes");
ba57ea2c696f9e9aae909f073069848876a641f4Timo Sirainen i_debug("pop3_migration: %u mails matched by headers", pop3_count);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp);
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainenstatic void imap_uidls_add_to_cache(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
0dab9cb35a976c49b28a11e28d5570f5191f1a7aMartti Rannanjärvi t = mailbox_transaction_begin(box, 0, __func__);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen field_idx = imail->ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen imap_map = array_get_modifiable(&mbox->imap_msg_map, &count);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen for (i = 0; i < count; i++) {
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen if (mail_cache_field_can_add(t->cache_trans, mail->seq, field_idx)) {
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen imap_map[i].pop3_uidl, strlen(imap_map[i].pop3_uidl)+1);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic int pop3_migration_uidl_sync(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen struct pop3_migration_mail_storage *mstorage =
0e7d5ff38f28d8c85e197a031bbb66b322ff89e6Timo Sirainen /* the POP3 server isn't connected to yet. handle all IMAP traffic
0e7d5ff38f28d8c85e197a031bbb66b322ff89e6Timo Sirainen first before connecting, so POP3 server won't disconnect us due to
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen array_sort(&mstorage->pop3_uidl_map, pop3_uidl_map_pop3_seq_cmp);
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen array_sort(&mbox->imap_msg_map, imap_msg_map_uid_cmp);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* everything wasn't assigned, figure out the rest with
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen header hashes */
1c3dc4c08ced3948f52c3c6c171ed77310b2cbfdTimo Sirainen if (pop3_uidl_assign_by_hdr_hash(box, pop3_box) < 0) {
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainenstatic int pop3_migration_uidl_sync_if_needed(struct mailbox *box)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(box);
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen mail_storage_set_error(box->storage, MAIL_ERROR_TEMP,
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen "POP3 UIDLs couldn't be synced");
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenpop3_migration_get_special(struct mail *_mail, enum mail_fetch_field field,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen const char **value_r)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_private *mail = (struct mail_private *)_mail;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen union mail_module_context *mmail = POP3_MIGRATION_MAIL_CONTEXT(mail);
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(_mail->box);
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen if (pop3_migration_uidl_sync_if_needed(_mail->box) < 0)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen map = array_bsearch(&mbox->imap_msg_map, &map_key,
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen *value_r = t_strdup_printf("%u", map->pop3_seq);
b10c3f9ed997748fdbb03b9daadc8c31eed02120Timo Sirainen /* not found from POP3 server, fallback to default */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen return mmail->super.get_special(_mail, field, value_r);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic void pop3_migration_mail_allocated(struct mail *_mail)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_private *mail = (struct mail_private *)_mail;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen (!mstorage->all_mailboxes && !_mail->box->inbox_user)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* assigns UIDLs only for INBOX */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen ns = mail_namespace_find(_mail->box->storage->user->namespaces,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (ns == mailbox_get_namespace(_mail->box)) {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen /* we're accessing the pop3-migration namespace itself */
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mmail = p_new(mail->pool, union mail_module_context, 1);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen MODULE_CONTEXT_SET_SELF(mail, pop3_migration_mail_module, mmail);
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainenpop3_migration_mailbox_search_init(struct mailbox_transaction_context *t,
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen struct mailbox_header_lookup_ctx *wanted_headers)
615b4468c088b674aad109c9420d7d5c14eebf43Aki Tuomi struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT_REQUIRE(t->box);
d430698b6d9d47ff9136ee137cc58a1c657baeddTimo Sirainen struct pop3_migration_mail_storage *mstorage =
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen if ((wanted_fields & (MAIL_FETCH_UIDL_BACKEND |
d430698b6d9d47ff9136ee137cc58a1c657baeddTimo Sirainen (mstorage->all_mailboxes || t->box->inbox_user)) {
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen /* Start POP3 UIDL syncing before the search, so we'll do it
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen before we start sending any FETCH BODY[]s to IMAP. It
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen shouldn't matter much, except this works around a bug in
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen Yahoo IMAP where it sometimes breaks its state when doing
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen a FETCH BODY[] followed by FETCH BODY[HEADER].. */
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen (void)pop3_migration_uidl_sync_if_needed(t->box);
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen return mbox->module_ctx.super.search_init(t, args, sort_program,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic void pop3_migration_mailbox_allocated(struct mailbox *box)
6d2e2c79173f8af12b17c89ac5ec07b8be23971dTimo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mbox = p_new(box->pool, struct pop3_migration_mailbox, 1);
5407a86a03963261bf5ba8793b8c97879ad2e224Timo Sirainen v->search_init = pop3_migration_mailbox_search_init;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen MODULE_CONTEXT_SET(box, pop3_migration_storage_module, mbox);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic void pop3_migration_mail_storage_destroy(struct mail_storage *storage)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct pop3_migration_mail_storage *mstorage =
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen if (array_is_created(&mstorage->pop3_uidl_map))
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic void pop3_migration_mail_storage_created(struct mail_storage *storage)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen struct mail_storage_vfuncs *v = storage->vlast;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen pop3_box_vname = mail_user_plugin_getenv(storage->user,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen "pop3_migration_mailbox");
ba57ea2c696f9e9aae909f073069848876a641f4Timo Sirainen i_debug("pop3_migration: No pop3_migration_mailbox setting - disabled");
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mstorage = p_new(storage->pool, struct pop3_migration_mail_storage, 1);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen v->destroy = pop3_migration_mail_storage_destroy;
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mstorage->pop3_box_vname = p_strdup(storage->pool, pop3_box_vname);
71748cca1bacd74451fd228db5536828bdfeb190Baofeng Wang "pop3_migration_all_mailboxes");
71748cca1bacd74451fd228db5536828bdfeb190Baofeng Wang "pop3_migration_ignore_missing_uidls");
65e19f9777ebcc705d8adc33a84735c31baee66aTimo Sirainen "pop3_migration_ignore_extra_uidls");
71748cca1bacd74451fd228db5536828bdfeb190Baofeng Wang "pop3_migration_skip_size_check");
a1d3ff734507eae5b46c0e75e7975344fe060771Timo Sirainen "pop3_migration_skip_uidl_cache");
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen MODULE_CONTEXT_SET(storage, pop3_migration_storage_module, mstorage);
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenstatic struct mail_storage_hooks pop3_migration_mail_storage_hooks = {
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen .mail_allocated = pop3_migration_mail_allocated,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen .mailbox_allocated = pop3_migration_mailbox_allocated,
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen .mail_storage_created = pop3_migration_mail_storage_created
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainenvoid pop3_migration_plugin_init(struct module *module)
c8593b070319d0ff83f8d6c4b5ed5abf2d578a06Timo Sirainen mail_storage_hooks_add(module, &pop3_migration_mail_storage_hooks);