pop3-migration-plugin.c revision 61cf001f1944d92eb25f113ba4c08985d6e30d53
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2007-2017 Dovecot authors, see the included COPYING file */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen MODULE_CONTEXT(obj, pop3_migration_storage_module)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen MODULE_CONTEXT(obj, pop3_migration_mail_module)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* sha1(header) - set only when needed */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* LIST size */
e8762c2b4914db7997fa9eb644a91586952d1876Timo Sirainen/* NOTE: these headers must be sorted */
e8762c2b4914db7997fa9eb644a91586952d1876Timo Sirainenstatic const char *hdr_hash_skip_headers[] = {
e8762c2b4914db7997fa9eb644a91586952d1876Timo Sirainen "Content-Length",
e8762c2b4914db7997fa9eb644a91586952d1876Timo Sirainen "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen "X-IMAPbase",
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen "X-Keywords",
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen "X-Message-Flag",
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen "X-Yahoo-Newman-Property"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenconst char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic int imap_msg_map_uid_cmp(const struct imap_msg_map *map1,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1,
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic bool header_name_is_valid(const char *name)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen unsigned int i;
b8d314c6355009ad0b9e332b6acecdfac5cc8891Timo Sirainen if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenpop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (strspn(hdr->name, "\r") == hdr->name_len) {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* CR+CR+LF - some servers stop the header processing
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen here while others don't. To make sure they can be
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen matched correctly we want to stop here entirely. */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* Yahoo IMAP drops headers with invalid names, while
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen Yahoo POP3 preserves them. Drop them all. */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenint pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen const unsigned char *data;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* hide headers that might change or be different in IMAP vs. POP3 */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY |
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen while (i_stream_read_more(input, &data, &size) > 0) {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen message_header_hash_more(&hash_ctx, &hash_method_sha1, &sha1_ctx, 2,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_error("pop3_migration: Failed to read header for msg %u: %s",
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic unsigned int get_cache_idx(struct mail *mail)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT(mail->box);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen mbox->cache_field.name = "pop3-migration.hdr";
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen mbox->cache_field.field_size = SHA1_RESULTLEN;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenget_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (mail_get_hdr_stream(mail, NULL, &input) < 0) {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen errstr = mailbox_get_last_internal_error(mail->box, &error);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_error("pop3_migration: Failed to get header for msg %u: %s",
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen index_mail_cache_add_idx(imail, get_cache_idx(mail),
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* The empty "end of headers" line is missing. Either this means that
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen the headers ended unexpectedly (which is ok) or that the remote
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen server is buggy. Some servers have problems with
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen 1) header line continuations that contain only whitespace and
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen 2) headers that have no ":". The header gets truncated when such
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen line is reached.
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen returning the whitespace line and 2) by returning the line but
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen truncating the rest. POP3 TOP instead returns the entire header.
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainen This causes the IMAP and POP3 hashes not to match.
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen So we'll try to avoid this by falling back to full FETCH BODY[]
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen (and/or RETR) and we'll parse the header ourself from it. This
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen should work around any similar bugs in all IMAP/POP3 servers. */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen errstr = mailbox_get_last_internal_error(mail->box, &error);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_error("pop3_migration: Failed to get body for msg %u: %s",
9f19a50d5966643c4d1c5ca06868ac2ad31bc4d5Timo Sirainen ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
d22301419109ed4a38351715e6760011421dadecTimo Sirainen index_mail_cache_add_idx(imail, get_cache_idx(mail),
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenget_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (index_mail_cache_lookup_field(imail, cache_buf,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen memcpy(sha1_r, cache_buf->data, cache_buf->used);
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)
struct mailbox_transaction_context *t;
int ret = 0;
(void)mailbox_transaction_commit(&t);
return ret;
return FALSE;
for (i = 0; i < count; i++) {
return i == count;
int ret;
pop3_idx++;
imap_idx++;
if (ret < 0)
pop3_idx++;
else if (ret > 0)
imap_idx++;
missing_uids_count = 0;
unsigned int i, count;
prev_uid = 0;
for (i = 0; i < count; i++) {
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)