pop3-migration-plugin.c revision 65e19f9777ebcc705d8adc33a84735c31baee66a
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2007-2017 Dovecot authors, see the included COPYING file */
bbadd5331f534017cf62d5183003b3d9fdad079eTimo Sirainen MODULE_CONTEXT(obj, pop3_migration_storage_module)
9f0f2de10e4ea0c99052bf4b2bef8179f2536228Timo Sirainen MODULE_CONTEXT(obj, pop3_migration_mail_module)
9f0f2de10e4ea0c99052bf4b2bef8179f2536228Timo Sirainen /* sha1(header) - set only when needed */
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen /* LIST size */
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen/* NOTE: these headers must be sorted */
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic const char *hdr_hash_skip_headers[] = {
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "Content-Length",
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "X-IMAPbase",
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "X-Keywords",
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "X-Message-Flag",
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen "X-Yahoo-Newman-Property"
ba8498efbf886ca8b69fdb20c0ba2f5dba9416e3Timo Sirainenconst char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module,
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module,
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic int imap_msg_map_uid_cmp(const struct imap_msg_map *map1,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1,
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainenstatic int pop3_uidl_map_uidl_cmp(const struct pop3_uidl_map *map1,
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen return strcmp(map1->pop3_uidl, map2->pop3_uidl);
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic int imap_msg_map_uidl_cmp(const struct imap_msg_map *map1,
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen return null_strcmp(map1->pop3_uidl, map2->pop3_uidl);
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainenstatic int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1,
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1,
7744586e3e0fd60158abfbb03a233d3bd8d6c48bTimo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainenstatic bool header_name_is_valid(const char *name)
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen unsigned int i;
1b823b2b7790a1e1b7974fcf11a4c48a28e70f37Timo Sirainen if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f)
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainenstatic bool header_value_want_skip(const struct message_header_line *hdr)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (hdr->value[i] != ' ' && hdr->value[i] != '\t')
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* "header: \r\n \r\n" - Zimbra's BODY[HEADER] strips this line away. */
401b0787fff2dc986a5321ddb32acb1947ff66b0Timo Sirainenpop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED,
50e20db49f29917fe9adcf1b56b11badf28bd0e4Timo Sirainen if (strspn(hdr->name, "\r") == hdr->name_len) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* CR+CR+LF - some servers stop the header processing
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen here while others don't. To make sure they can be
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen matched correctly we want to stop here entirely. */
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen } else if (hdr->continued && header_value_want_skip(hdr)) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* Yahoo IMAP drops headers with invalid names, while
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen Yahoo POP3 preserves them. Drop them all. */
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenint pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const unsigned char *data;
7744586e3e0fd60158abfbb03a233d3bd8d6c48bTimo Sirainen /* hide headers that might change or be different in IMAP vs. POP3 */
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY |
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen while (i_stream_read_more(input, &data, &size) > 0) {
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen message_header_hash_more(&hash_ctx, &hash_method_sha1, &sha1_ctx,
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen i_error("pop3_migration: Failed to read header for msg %u: %s",
401b0787fff2dc986a5321ddb32acb1947ff66b0Timo Sirainenstatic unsigned int get_cache_idx(struct mail *mail)
401b0787fff2dc986a5321ddb32acb1947ff66b0Timo Sirainen struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT(mail->box);
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen mbox->cache_field.name = "pop3-migration.hdr";
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen mbox->cache_field.field_size = SHA1_RESULTLEN;
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenget_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
d477acb83e14a776ece4ca94dcd1869e75d0c6eeTimo Sirainen if (mail_get_hdr_stream(mail, NULL, &input) < 0) {
15f526e5ac611b4532568d131fcd0abf664abe41Timo Sirainen errstr = mailbox_get_last_internal_error(mail->box, &error);
15f526e5ac611b4532568d131fcd0abf664abe41Timo Sirainen i_error("pop3_migration: Failed to get header for msg %u: %s",
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0)
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
0f62889d833767acf9c2ad010c3269806b4cfae3Timo Sirainen index_mail_cache_add_idx(imail, get_cache_idx(mail),
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen /* The empty "end of headers" line is missing. Either this means that
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen the headers ended unexpectedly (which is ok) or that the remote
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen server is buggy. Some servers have problems with
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen 1) header line continuations that contain only whitespace and
7289c5600711b45f30fe289ab5b0293b51d87041Timo Sirainen 2) headers that have no ":". The header gets truncated when such
d477acb83e14a776ece4ca94dcd1869e75d0c6eeTimo Sirainen line is reached.
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen returning the whitespace line and 2) by returning the line but
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen truncating the rest. POP3 TOP instead returns the entire header.
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen This causes the IMAP and POP3 hashes not to match.
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen So we'll try to avoid this by falling back to full FETCH BODY[]
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen (and/or RETR) and we'll parse the header ourself from it. This
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen should work around any similar bugs in all IMAP/POP3 servers. */
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) {
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen errstr = mailbox_get_last_internal_error(mail->box, &error);
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen i_error("pop3_migration: Failed to get body for msg %u: %s",
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh);
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid);
272aca0a772140d3a45a425a3fd67854ae2ccec2Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
9dd1c256910f1fb42823116a641e7edb3ad11970Timo Sirainen index_mail_cache_add_idx(imail, get_cache_idx(mail),
5d60e31c7b701b606067a20bc88dcc8a6de7bbd6Timo Sirainenget_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf,
5d60e31c7b701b606067a20bc88dcc8a6de7bbd6Timo Sirainen unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (index_mail_cache_lookup_field(imail, cache_buf,
b215a8a123623782554a83f3025ef4e771bd8f01Timo Sirainen memcpy(sha1_r, cache_buf->data, cache_buf->used);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic struct mailbox *pop3_mailbox_alloc(struct mail_storage *storage)
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct pop3_migration_mail_storage *mstorage =
5d60e31c7b701b606067a20bc88dcc8a6de7bbd6Timo Sirainen ns = mail_namespace_find(storage->user->namespaces,
return box;
struct mailbox_transaction_context *t;
const char *uidl;
int ret = 0;
(void)mailbox_transaction_commit(&t);
return ret;
struct mailbox_transaction_context *t;
int ret = 0;
if (ret > 0)
(void)mailbox_transaction_commit(&t);
unsigned first_seq)
first_seq) < 0)
const unsigned int uidl_cache_idx =
struct mailbox_transaction_context *t;
int ret = 0;
(void)mailbox_transaction_commit(&t);
return ret;
int ret;
if (ret >= 0)
if (ret == 0) {
for (i = 0; i < count; i++) {
uidl_match++;
size_match++;
return i == count;
int ret;
pop3_idx++;
imap_idx++;
if (ret < 0)
pop3_idx++;
else if (ret > 0)
imap_idx++;
missing_uids_count = 0;
if (all_imap_mails_found)
struct mailbox_transaction_context *t;
unsigned int i, count;
unsigned int field_idx;
for (i = 0; i < count; i++) {
i_unreached();
(void)mailbox_transaction_commit(&t);
const char **value_r)
static struct mail_search_context *
MAIL_FETCH_POP3_ORDER)) != 0 &&
const char *pop3_box_vname;
void pop3_migration_plugin_deinit(void)