pop3-migration-plugin.c revision 6d2e2c79173f8af12b17c89ac5ec07b8be23971d
/* Copyright (c) 2007-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "istream.h"
#include "istream-header-filter.h"
#include "str.h"
#include "sha1.h"
#include "message-size.h"
#include "message-header-hash.h"
#include "message-header-parser.h"
#include "mail-cache.h"
#include "mail-namespace.h"
#include "mail-search-build.h"
#include "index-storage.h"
#include "index-mail.h"
#include "pop3-migration-plugin.h"
#define POP3_MIGRATION_CONTEXT(obj) \
#define POP3_MIGRATION_MAIL_CONTEXT(obj) \
struct msg_map_common {
/* sha1(header) - set only when needed */
unsigned char hdr_sha1[SHA1_RESULTLEN];
bool hdr_sha1_set:1;
};
struct pop3_uidl_map {
struct msg_map_common common;
/* UIDL */
const char *pop3_uidl;
/* LIST size */
};
struct imap_msg_map {
struct msg_map_common common;
const char *pop3_uidl;
};
struct pop3_migration_mail_storage {
const char *pop3_box_vname;
bool all_mailboxes:1;
bool pop3_all_hdr_sha1_set:1;
bool ignore_missing_uidls:1;
bool skip_size_check:1;
};
struct pop3_migration_mailbox {
union mailbox_module_context module_ctx;
unsigned int first_unfound_idx;
struct mail_cache_field cache_field;
bool cache_field_registered:1;
bool uidl_synced:1;
bool uidl_sync_failed:1;
bool uidl_ordered:1;
};
/* NOTE: these headers must be sorted */
static const char *hdr_hash_skip_headers[] = {
"Content-Length",
"Return-Path", /* Yahoo IMAP has Return-Path, Yahoo POP3 doesn't */
"Status",
"X-IMAP",
"X-IMAPbase",
"X-Keywords",
"X-Message-Flag",
"X-Status",
"X-UID",
"X-UIDL",
"X-Yahoo-Newman-Property"
};
const char *pop3_migration_plugin_version = DOVECOT_ABI_VERSION;
const struct imap_msg_map *map2)
{
return -1;
return 1;
return 0;
}
const struct pop3_uidl_map *map2)
{
return -1;
return 1;
return 0;
}
const struct pop3_uidl_map *map2)
{
}
const struct imap_msg_map *map2)
{
}
struct pop3_hdr_context {
bool have_eoh;
bool stop;
};
static bool header_name_is_valid(const char *name)
{
unsigned int i;
for (i = 0; name[i] != '\0'; i++) {
return FALSE;
}
return TRUE;
}
static void
struct message_header_line *hdr,
{
return;
} else {
/* CR+CR+LF - some servers stop the header processing
here while others don't. To make sure they can be
matched correctly we want to stop here entirely. */
}
/* Yahoo IMAP drops headers with invalid names, while
Yahoo POP3 preserves them. Drop them all. */
}
}
}
bool *have_eoh_r)
{
const unsigned char *data;
struct pop3_hdr_context hdr_ctx;
/* hide headers that might change or be different in IMAP vs. POP3 */
}
if (input->stream_errno != 0) {
i_error("pop3_migration: Failed to read header for msg %u: %s",
return -1;
}
return 0;
}
{
if (mbox->cache_field_registered)
}
static int
{
const char *errstr;
enum mail_error error;
bool have_eoh;
int ret;
i_error("pop3_migration: Failed to get header for msg %u: %s",
}
return -1;
if (have_eoh) {
return 1;
}
/* The empty "end of headers" line is missing. Either this means that
the headers ended unexpectedly (which is ok) or that the remote
server is buggy. Some servers have problems with
1) header line continuations that contain only whitespace and
2) headers that have no ":". The header gets truncated when such
line is reached.
At least Oracle IMS IMAP FETCH BODY[HEADER] handles 1) by not
returning the whitespace line and 2) by returning the line but
truncating the rest. POP3 TOP instead returns the entire header.
This causes the IMAP and POP3 hashes not to match.
If there's LF+CR+CR+LF in the middle of headers, Courier IMAP's
FETCH BODY[HEADER] stops after that, but Courier POP3's TOP doesn't.
So we'll try to avoid this by falling back to full FETCH BODY[]
i_error("pop3_migration: Failed to get body for msg %u: %s",
}
if (ret == 0) {
if (!have_eoh)
return 1;
} else {
return -1;
}
}
static bool
{
get_cache_idx(mail)) > 0 &&
return TRUE;
}
return FALSE;
}
{
struct pop3_migration_mail_storage *mstorage =
struct mail_namespace *ns;
}
{
struct pop3_migration_mail_storage *mstorage =
struct mailbox_transaction_context *t;
struct mail_search_args *search_args;
struct mail_search_context *ctx;
struct pop3_uidl_map *map;
const char *uidl;
int ret = 0;
/* already read these, just reset the imap_uids */
return 0;
}
if (mailbox_sync(pop3_box, 0) < 0) {
i_error("pop3_migration: Couldn't sync mailbox %s: %s",
return -1;
}
t = mailbox_transaction_begin(pop3_box, 0);
mstorage->skip_size_check ? 0 :
/* get the size with LIST instead of RETR */
if (mstorage->skip_size_check)
;
i_error("pop3_migration: Failed to get size for msg %u: %s",
ret = -1;
break;
}
i_error("pop3_migration: Failed to get UIDL for msg %u: %s",
ret = -1;
break;
}
if (*uidl == '\0') {
i_warning("pop3_migration: UIDL for msg %u is empty",
continue;
}
}
if (mailbox_search_deinit(&ctx) < 0) {
i_error("pop3_migration: Failed to search all POP3 mails: %s",
ret = -1;
}
(void)mailbox_transaction_commit(&t);
return ret;
}
static void
struct mail_search_args *search_args,
{
struct mail_search_context *ctx;
struct msg_map_common *map;
}
if (mailbox_search_deinit(&ctx) < 0) {
i_warning("pop3_migration: Failed to search all cached POP3 header hashes: %s - ignoring",
}
}
{
const struct msg_map_common *map;
if (map->hdr_sha1_set)
}
}
static int
{
struct mailbox_transaction_context *t;
struct mail_search_args *search_args;
struct mail_search_context *ctx;
struct msg_map_common *map;
int ret = 0;
t = mailbox_transaction_begin(box, 0);
/* get all the cached hashes */
/* read all the non-cached hashes. doing this in two passes allows
us to set wanted_fields=MAIL_FETCH_STREAM_HEADER, which allows
prefetching to work without downloading all the headers even
for mails that already are cached. */
ret = -1;
break;
}
if (ret > 0)
}
if (mailbox_search_deinit(&ctx) < 0) {
i_error("pop3_migration: Failed to search all mail headers: %s",
ret = -1;
}
(void)mailbox_transaction_commit(&t);
return ret < 0 ? -1 : 0;
}
static int
unsigned first_seq)
{
struct pop3_migration_mail_storage *mstorage =
return 0;
if (mstorage->all_mailboxes) {
/* we may be matching against multiple mailboxes.
read all the hashes only once. */
first_seq = 1;
}
first_seq) < 0)
return -1;
if (first_seq == 1)
return 0;
}
{
struct pop3_migration_mail_storage *mstorage =
struct mailbox_status status;
struct mailbox_transaction_context *t;
struct mail_search_args *search_args;
struct mail_search_context *ctx;
struct imap_msg_map *map;
int ret = 0;
t = mailbox_transaction_begin(box, 0);
mstorage->skip_size_check ? 0 :
if (mstorage->skip_size_check)
;
i_error("pop3_migration: Failed to get psize for imap uid %u: %s",
ret = -1;
break;
}
}
if (mailbox_search_deinit(&ctx) < 0) {
i_error("pop3_migration: Failed to search all IMAP mails: %s",
ret = -1;
}
(void)mailbox_transaction_commit(&t);
return ret;
}
{
}
{
struct pop3_migration_mail_storage *mstorage =
struct pop3_uidl_map *pop3_map;
struct imap_msg_map *imap_map;
if (mstorage->skip_size_check)
return FALSE;
/* see if we can match the messages using sizes */
for (i = 0; i < count; i++) {
break;
/* two messages with same size, don't trust them */
break;
}
}
mbox->first_unfound_idx = i;
return i == count;
}
static int
{
struct pop3_migration_mail_storage *mstorage =
struct pop3_uidl_map *pop3_map;
struct imap_msg_map *imap_map;
unsigned int first_seq, missing_uids_count;
int ret;
imap_map_read_hdr_hashes(box) < 0)
return -1;
pop3_idx++;
continue;
}
imap_idx++;
continue;
}
if (ret < 0)
pop3_idx++;
else if (ret > 0)
imap_idx++;
else {
}
}
missing_uids_count = 0;
/* matched */
/* we treated this mail as expunged - ignore */
} else {
}
}
"matching IMAP messages (first POP3 msg %u UIDL %s)",
"(POP3 contains more than IMAP INBOX - you may want to set pop3_migration_all_mailboxes=yes)");
}
if (!mstorage->ignore_missing_uidls) {
i_error("%s - set pop3_migration_ignore_missing_uidls=yes to continue anyway",
return -1;
}
}
return 0;
}
{
struct pop3_migration_mail_storage *mstorage =
const struct pop3_uidl_map *pop3_map;
unsigned int i, count;
/* the POP3 server isn't connected to yet. handle all IMAP traffic
first before connecting, so POP3 server won't disconnect us due to
idling. */
if (imap_map_read(box) < 0 ||
return -1;
}
if (!pop3_uidl_assign_by_size(box)) {
/* everything wasn't assigned, figure out the rest with
header hashes */
return -1;
}
}
/* see if the POP3 UIDL order is the same as IMAP UID order */
prev_uid = 0;
for (i = 0; i < count; i++) {
continue;
break;
}
}
return 0;
}
{
if (mbox->uidl_synced)
return 0;
if (mbox->uidl_sync_failed ||
pop3_migration_uidl_sync(box) < 0) {
"POP3 UIDLs couldn't be synced");
return -1;
}
return 0;
}
static int
const char **value_r)
{
if (field == MAIL_FETCH_UIDL_BACKEND ||
field == MAIL_FETCH_POP3_ORDER) {
return -1;
if (field == MAIL_FETCH_UIDL_BACKEND)
else
return 0;
}
/* not found from POP3 server, fallback to default */
}
}
{
struct pop3_migration_mail_storage *mstorage =
union mail_module_context *mmail;
struct mail_namespace *ns;
/* assigns UIDLs only for INBOX */
return;
}
/* we're accessing the pop3-migration namespace itself */
return;
}
}
static struct mail_search_context *
struct mail_search_args *args,
const enum mail_sort_type *sort_program,
struct mailbox_header_lookup_ctx *wanted_headers)
{
struct pop3_migration_mail_storage *mstorage =
if ((wanted_fields & (MAIL_FETCH_UIDL_BACKEND |
MAIL_FETCH_POP3_ORDER)) != 0 &&
/* Start POP3 UIDL syncing before the search, so we'll do it
before we start sending any FETCH BODY[]s to IMAP. It
shouldn't matter much, except this works around a bug in
Yahoo IMAP where it sometimes breaks its state when doing
a FETCH BODY[] followed by FETCH BODY[HEADER].. */
(void)pop3_migration_uidl_sync_if_needed(t->box);
}
}
{
struct pop3_migration_mail_storage *mstorage =
struct pop3_migration_mailbox *mbox;
return;
}
{
struct pop3_migration_mail_storage *mstorage =
}
{
struct pop3_migration_mail_storage *mstorage;
const char *pop3_box_vname;
"pop3_migration_mailbox");
if (pop3_box_vname == NULL) {
i_debug("pop3_migration: No pop3_migration_mailbox setting - disabled");
return;
}
"pop3_migration_all_mailboxes");
"pop3_migration_ignore_missing_uidls");
"pop3_migration_skip_size_check");
}
static struct mail_storage_hooks pop3_migration_mail_storage_hooks = {
};
{
}
void pop3_migration_plugin_deinit(void)
{
}