/* Copyright (c) 2007-2018 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"
struct msg_map_common {
/* sha1(header) - set only when needed */
};
struct pop3_uidl_map {
/* UIDL */
const char *pop3_uidl;
/* LIST size */
};
struct imap_msg_map {
const char *pop3_uidl;
};
struct pop3_migration_mail_storage {
const char *pop3_box_vname;
};
struct pop3_migration_mailbox {
unsigned int first_unfound_idx;
};
/* 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 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)
{
}
const struct pop3_uidl_map *map2)
{
}
const struct imap_msg_map *map2)
{
}
struct pop3_hdr_context {
bool have_eoh;
bool stop;
};
{
unsigned int i;
for (i = 0; name[i] != '\0'; i++) {
return FALSE;
}
return TRUE;
}
{
return FALSE;
}
/* "header: \r\n \r\n" - Zimbra's BODY[HEADER] strips this line away. */
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. */
/* not a valid "key: value" header -
Zimbra's BODY[HEADER] strips this line away. */
}
/* Yahoo IMAP drops headers with invalid names, while
Yahoo POP3 preserves them. Drop them all. */
}
}
}
bool *have_eoh_r)
{
const unsigned char *data;
/* 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;
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;
}
{
return box;
}
{
struct mailbox_transaction_context *t;
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;
}
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,
{
}
if (mailbox_search_deinit(&ctx) < 0) {
i_warning("pop3_migration: Failed to search all cached POP3 header hashes: %s - ignoring",
}
}
{
if (map->hdr_sha1_set)
}
}
static int
{
struct mailbox_transaction_context *t;
int ret = 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)
{
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;
}
{
const unsigned int uidl_cache_idx =
struct mailbox_transaction_context *t;
int ret = 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 (!mstorage->skip_uidl_cache) {
str_truncate(uidl, 0);
}
}
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;
}
{
}
{
int ret;
if (mstorage->skip_uidl_cache)
return;
/* see if we can match the messages using sizes */
continue;
ret = 1;
if (ret >= 0)
break;
}
if (ret == 0) {
}
}
}
{
/* see if we can match the messages using sizes */
for (i = 0; i < count; i++) {
/* some of the UIDLs were already found cached. */
uidl_match++;
continue;
}
/* mismatch - can't trust the sizes */
break;
}
break;
/* two messages with same size, don't trust them */
break;
}
size_match++;
}
mbox->first_unfound_idx = i;
i_debug("pop3_migration: cached uidls=%u, size matches=%u, total=%u",
}
}
static int
{
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 {
if (first_missing_seq > seq) {
}
}
}
"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)");
}
/* pop3 had more mails than imap. maybe it was just
that a new mail was just delivered. */
} else if (!mstorage->ignore_missing_uidls) {
if (all_imap_mails_found)
return -1;
}
}
return 0;
}
{
struct mailbox_transaction_context *t;
unsigned int i, count;
unsigned int field_idx;
for (i = 0; i < count; i++) {
continue;
i_unreached();
}
}
(void)mailbox_transaction_commit(&t);
}
{
/* 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;
}
}
if (!mstorage->skip_uidl_cache)
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 */
}
}
{
/* 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)
{
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);
}
}
{
return;
}
{
}
{
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_ignore_extra_uidls");
"pop3_migration_skip_size_check");
"pop3_migration_skip_uidl_cache");
}
};
{
}
void pop3_migration_plugin_deinit(void)
{
}