pop3-migration-plugin.c revision 45af47783693b3ba2768c5ad34eeff68132382d0
/* 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-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];
unsigned int 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;
unsigned int all_mailboxes:1;
unsigned int pop3_all_hdr_sha1_set:1;
unsigned int ignore_missing_uidls:1;
unsigned int 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;
unsigned int cache_field_registered:1;
unsigned int uidl_synced:1;
unsigned int uidl_sync_failed:1;
unsigned int 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;
/* matched is handled differently for eoh by
istream-header-filter. a design bug I guess.. */
}
} 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. */
}
}
}
unsigned char sha1_r[SHA1_RESULTLEN],
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 */
/* - Dovecot IMAP replaces NULs with 0x80 character.
- Dovecot POP3 with outlook-no-nuls workaround replaces NULs
with 0x80 character.
- Zimbra replaces 8bit chars with '?' in header fetches,
but not body fetches.
- Yahoo replaces 8bit chars with '?' in partial header
fetches, but not POP3 TOP.
So we'll just replace all control and 8bit chars with '?',
which hopefully will satisfy everybody.
(Keep this code in sync with dsync.)
*/
start = i+1;
}
}
}
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
{
struct message_size hdr_size;
bool have_eoh;
i_error("pop3_migration: Failed to get header for msg %u: %s",
return -1;
}
return -1;
if (have_eoh) {
return 0;
}
/* 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",
return -1;
}
}
static bool
unsigned char sha1_r[SHA1_RESULTLEN])
{
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;
else
}
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;
}
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;
}
}
"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;
if (mbox->uidl_synced)
return 0;
/* 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;
}
static int
const char **value_r)
{
if (field == MAIL_FETCH_UIDL_BACKEND ||
field == MAIL_FETCH_POP3_ORDER) {
if (mbox->uidl_sync_failed ||
"POP3 UIDLs couldn't be synced");
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;
}
}
{
struct pop3_migration_mailbox *mbox;
}
{
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") != NULL;
"pop3_migration_ignore_missing_uidls") != NULL;
"pop3_migration_skip_size_check") != NULL;
}
static struct mail_storage_hooks pop3_migration_mail_storage_hooks = {
};
{
}
void pop3_migration_plugin_deinit(void)
{
}