pop3-migration-plugin.c revision 061046c9aa2eec5c6c2f148ec95a4e51db3d8fd2
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (c) 2007-2016 Dovecot authors, see the included COPYING file */
2f64a4c88de91c483fb378bc80d10e1caa6f2305Stephan Bosch MODULE_CONTEXT(obj, pop3_migration_storage_module)
2f64a4c88de91c483fb378bc80d10e1caa6f2305Stephan Bosch MODULE_CONTEXT(obj, pop3_migration_mail_module)
2f64a4c88de91c483fb378bc80d10e1caa6f2305Stephan Bosch /* sha1(header) - set only when needed */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch /* LIST size */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch/* NOTE: these headers must be sorted */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Boschstatic const char *hdr_hash_skip_headers[] = {
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "Content-Length",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "X-IMAPbase",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "X-Keywords",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "X-Message-Flag",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch "X-Yahoo-Newman-Property"
faa8995f1d300e7a8917407a52bbd1b98e10bf25Timo Sirainenconst char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
faa8995f1d300e7a8917407a52bbd1b98e10bf25Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_storage_module,
faa8995f1d300e7a8917407a52bbd1b98e10bf25Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(pop3_migration_mail_module,
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Boschstatic int imap_msg_map_uid_cmp(const struct imap_msg_map *map1,
9f8cef4cbc49797053c343209ea13022fdbc5a63Stephan Boschstatic int pop3_uidl_map_pop3_seq_cmp(const struct pop3_uidl_map *map1,
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Boschstatic int pop3_uidl_map_hdr_cmp(const struct pop3_uidl_map *map1,
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Boschstatic int imap_msg_map_hdr_cmp(const struct imap_msg_map *map1,
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch return memcmp(map1->common.hdr_sha1, map2->common.hdr_sha1,
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Boschstatic bool header_name_is_valid(const char *name)
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Bosch unsigned int i;
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Bosch if ((uint8_t)name[i] <= 0x20 || name[i] >= 0x7f)
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Boschpop3_header_filter_callback(struct header_filter_istream *input ATTR_UNUSED,
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Bosch if (strspn(hdr->name, "\r") == hdr->name_len) {
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch /* CR+CR+LF - some servers stop the header processing
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Bosch here while others don't. To make sure they can be
ee2633056e67353157bfbce4d9e0d1c3ceaa627aStephan Bosch matched correctly we want to stop here entirely. */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch /* Yahoo IMAP drops headers with invalid names, while
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch Yahoo POP3 preserves them. Drop them all. */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Boschint pop3_migration_get_hdr_sha1(uint32_t mail_seq, struct istream *input,
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN],
85f3bd5926fff0e70b6d259a5c8074bd8cdeb9adTimo Sirainen const unsigned char *data;
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch /* hide headers that might change or be different in IMAP vs. POP3 */
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch input = i_stream_create_header_filter(input, HEADER_FILTER_HIDE_BODY |
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch while (i_stream_read_more(input, &data, &size) > 0) {
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch message_header_hash_more(&hash_method_sha1, &sha1_ctx, 2,
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch i_error("pop3_migration: Failed to read header for msg %u: %s",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Boschstatic unsigned int get_cache_idx(struct mail *mail)
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch struct pop3_migration_mailbox *mbox = POP3_MIGRATION_CONTEXT(mail->box);
711e8e4c5c5d702dfa062f42a1ede5de14c151c9Stephan Bosch mbox->cache_field.name = "pop3-migration.hdr";
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch mbox->cache_field.type = MAIL_CACHE_FIELD_FIXED_SIZE;
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch mbox->cache_field.field_size = SHA1_RESULTLEN;
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch mail_cache_register_fields(mail->box->cache, &mbox->cache_field, 1);
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Boschget_hdr_sha1(struct mail *mail, unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch if (mail_get_hdr_stream(mail, NULL, &input) < 0) {
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch errstr = mailbox_get_last_error(mail->box, &error);
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch i_error("pop3_migration: Failed to get header for msg %u: %s",
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch if (pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh) < 0)
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch struct index_mail *imail = (struct index_mail *)mail;
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch index_mail_cache_add_idx(imail, get_cache_idx(mail),
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch /* The empty "end of headers" line is missing. Either this means that
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch the headers ended unexpectedly (which is ok) or that the remote
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch server is buggy. Some servers have problems with
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch 1) header line continuations that contain only whitespace and
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch 2) headers that have no ":". The header gets truncated when such
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch line is reached.
2f64a4c88de91c483fb378bc80d10e1caa6f2305Stephan Bosch At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch returning the whitespace line and 2) by returning the line but
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch truncating the rest. POP3 TOP instead returns the entire header.
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch This causes the IMAP and POP3 hashes not to match.
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch So we'll try to avoid this by falling back to full FETCH BODY[]
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch (and/or RETR) and we'll parse the header ourself from it. This
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch should work around any similar bugs in all IMAP/POP3 servers. */
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch if (mail_get_stream_because(mail, NULL, NULL, "pop3-migration", &input) < 0) {
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch errstr = mailbox_get_last_error(mail->box, &error);
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch i_error("pop3_migration: Failed to get body for msg %u: %s",
faa8995f1d300e7a8917407a52bbd1b98e10bf25Timo Sirainen ret = pop3_migration_get_hdr_sha1(mail->seq, input, sha1_r, &have_eoh);
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch i_warning("pop3_migration: Truncated email with UID %u stored as truncated", mail->uid);
faa8995f1d300e7a8917407a52bbd1b98e10bf25Timo Sirainen struct index_mail *imail = (struct index_mail *)mail;
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch index_mail_cache_add_idx(imail, get_cache_idx(mail),
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Boschget_cached_hdr_sha1(struct mail *mail, buffer_t *cache_buf,
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch unsigned char sha1_r[STATIC_ARRAY SHA1_RESULTLEN])
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch struct index_mail *imail = (struct index_mail *)mail;
b37e11d37fb1ebf50511eef5d9d96d1205818458Stephan Bosch if (index_mail_cache_lookup_field(imail, cache_buf,
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch memcpy(sha1_r, cache_buf->data, cache_buf->used);
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Boschstatic struct mailbox *pop3_mailbox_alloc(struct mail_storage *storage)
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch struct pop3_migration_mail_storage *mstorage =
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch ns = mail_namespace_find(storage->user->namespaces,
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch return mailbox_alloc(ns->list, mstorage->pop3_box_vname,
95e0b82fdff1bb511067d703bb8b67c22f242c38Timo Sirainen MAILBOX_FLAG_READONLY | MAILBOX_FLAG_POP3_SESSION);
95e0b82fdff1bb511067d703bb8b67c22f242c38Timo Sirainenstatic int pop3_map_read(struct mail_storage *storage, struct mailbox *pop3_box)
3fcb3d2d1f3583025ff62bae95ec706920f398b1Stephan Bosch struct pop3_migration_mail_storage *mstorage =
95e0b82fdff1bb511067d703bb8b67c22f242c38Timo Sirainen if (array_is_created(&mstorage->pop3_uidl_map)) {
95e0b82fdff1bb511067d703bb8b67c22f242c38Timo Sirainen /* already read these, just reset the imap_uids */
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch array_foreach_modifiable(&mstorage->pop3_uidl_map, map)
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch i_error("pop3_migration: Couldn't sync mailbox %s: %s",
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch pop3_box->vname, mailbox_get_last_error(pop3_box, NULL));
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch ctx = mailbox_search_init(t, search_args, NULL,
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch /* get the size with LIST instead of RETR */
564e117d86ce5b659f9b9570edddc566f9ebb5dfStephan Bosch mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
(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)