Example #1
0
int mdbox_map_view_lookup_rec(struct mdbox_map *map,
			      struct mail_index_view *view, uint32_t seq,
			      struct dbox_mail_lookup_rec *rec_r)
{
	const uint16_t *ref16_p;
	const void *data;

	memset(rec_r, 0, sizeof(*rec_r));
	mail_index_lookup_uid(view, seq, &rec_r->map_uid);

	mail_index_lookup_ext(view, seq, map->map_ext_id, &data, NULL);
	if (data == NULL) {
		mdbox_map_set_corrupted(map, "missing map extension");
		return -1;
	}
	memcpy(&rec_r->rec, data, sizeof(rec_r->rec));

	mail_index_lookup_ext(view, seq, map->ref_ext_id, &data, NULL);
	if (data == NULL) {
		mdbox_map_set_corrupted(map, "missing ref extension");
		return -1;
	}
	ref16_p = data;
	rec_r->refcount = *ref16_p;
	return 0;
}
Example #2
0
static void virtual_mail_set_seq(struct mail *mail, uint32_t seq)
{
	struct virtual_mail *vmail = (struct virtual_mail *)mail;
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
	struct virtual_backend_box *bbox;
	const struct virtual_mail_index_record *vrec;
	const void *data;
	bool expunged;

	mail_index_lookup_ext(mail->box->view, seq, mbox->virtual_ext_id,
			      &data, &expunged);
	vrec = data;

	bbox = virtual_backend_box_lookup(mbox, vrec->mailbox_id);
	vmail->backend_mail = backend_mail_find(vmail, bbox->box);
	if (vmail->backend_mail == NULL)
		virtual_mail_set_backend_mail(mail, bbox);
	vmail->lost = !mail_set_uid(vmail->backend_mail, vrec->real_uid);
	memset(&vmail->imail.data, 0, sizeof(vmail->imail.data));
	p_clear(vmail->imail.data_pool);

	vmail->imail.data.seq = seq;
	mail->seq = seq;
	mail_index_lookup_uid(mail->box->view, seq, &mail->uid);

	if (!vmail->lost) {
		mail->expunged = vmail->backend_mail->expunged;
		mail->has_nuls = vmail->backend_mail->has_nuls;
		mail->has_no_nuls = vmail->backend_mail->has_no_nuls;
	} else {
		mail->expunged = TRUE;
		mail->has_nuls = FALSE;
		mail->has_no_nuls = FALSE;
	}
}
static int
index_list_get_cached_status(struct mailbox *box, struct mailbox_status *status)
{
	struct index_mailbox_list *ilist;
	struct mail_index_view *view;
	const void *data;
	uint32_t seq, *ext_id_p, *counter_p;
	unsigned int i;
	bool expunged;
	int ret;

	memset(status, 0, sizeof(*status));

	ret = index_list_mailbox_open_unchanged_view(box, &view, &seq);
	if (ret <= 0)
		return ret;

	ilist = INDEX_LIST_CONTEXT(box->list);
	for (i = 0; index_list_map[i].name != NULL; i++) {
		ext_id_p = PTR_OFFSET(ilist, index_list_map[i].eid_offset);
		mail_index_lookup_ext(view, seq, *ext_id_p, &data, &expunged);
		if (expunged || data == NULL) {
			ret = 0;
			break;
		}

		counter_p = PTR_OFFSET(status, index_list_map[i].status_offset);
		*counter_p = *(const uint32_t *)data;
	}

	mail_index_view_close(&view);
	return ret;
}
Example #4
0
void mbox_list_index_update_sync(struct mailbox *box,
				 struct mail_index_transaction *trans,
				 uint32_t seq)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;
	struct mail_index_view *list_view;
	const struct mbox_index_header *mhdr = &mbox->mbox_hdr;
	const struct mbox_list_index_record *old_rec;
	struct mbox_list_index_record new_rec;
	const void *data;
	uint32_t ext_id;
	bool expunged;

	index_storage_list_index_update_sync(box, trans, seq);

	/* get the current record */
	list_view = mail_index_transaction_get_view(trans);
	ext_id = mbox_list_get_ext_id(mbox, list_view);
	mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
	if (expunged)
		return;
	old_rec = data;

	memset(&new_rec, 0, sizeof(new_rec));
	new_rec.mtime = mhdr->sync_mtime;
	new_rec.size = mhdr->sync_size & 0xffffffffU;

	if (old_rec == NULL ||
	    memcmp(old_rec, &new_rec, sizeof(*old_rec)) != 0)
		mail_index_update_ext(trans, seq, ext_id, &new_rec, NULL);
}
static int
index_list_update(struct index_mailbox_list *ilist, struct mailbox *box,
		  struct mail_index_view *view, uint32_t seq,
		  const struct mailbox_status *status)
{
	struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
	struct mail_index_transaction *trans;
	struct mail_index_transaction_commit_result result;
	const void *data;
	const uint32_t *counter_p;
	uint32_t *ext_id_p;
	unsigned int i;
	bool expunged;
	int ret = 0;

	trans = mail_index_transaction_begin(view,
					MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);

	/* update counters */
	for (i = 0; index_list_map[i].name != NULL; i++) {
		ext_id_p = PTR_OFFSET(ilist, index_list_map[i].eid_offset);
		mail_index_lookup_ext(view, seq, *ext_id_p, &data, &expunged);
		if (expunged) {
			ret = -1;
			break;
		}

		counter_p = CONST_PTR_OFFSET(status,
					     index_list_map[i].status_offset);
		if (data == NULL ||
		    *(const uint32_t *)data != *counter_p) {
			mail_index_update_ext(trans, seq, *ext_id_p,
					      counter_p, NULL);
		}
	}

	if (box->v.list_index_update_sync(box, trans, seq) < 0)
		ret = -1;
	if (ret < 0) {
		mail_index_transaction_rollback(&trans);
		return -1;
	}

	if (mail_index_transaction_commit_full(&trans, &result) < 0)
		return -1;

	ibox->log_seq = result.log_file_seq;
	ibox->log_offset = result.log_file_offset;
	return 0;
}
Example #6
0
static int
mdbox_map_lookup_seq(struct mdbox_map *map, uint32_t seq,
		     const struct mdbox_map_mail_index_record **rec_r)
{
	const struct mdbox_map_mail_index_record *rec;
	const void *data;
	uint32_t uid;

	mail_index_lookup_ext(map->view, seq, map->map_ext_id, &data, NULL);
	rec = data;

	if (rec == NULL || rec->file_id == 0) {
		mail_index_lookup_uid(map->view, seq, &uid);
		mdbox_map_set_corrupted(map, "file_id=0 for map_uid=%u", uid);
		return -1;
	}
	*rec_r = rec;
	return 0;
}
Example #7
0
static int mdbox_mail_get_save_date(struct mail *mail, time_t *date_r)
{
	struct mdbox_mailbox *mbox =
		(struct mdbox_mailbox *)mail->transaction->box;
	const struct mdbox_mail_index_record *dbox_rec;
	const void *data;
	bool expunged;

	mail_index_lookup_ext(mail->transaction->view, mail->seq,
			      mbox->ext_id, &data, &expunged);
	dbox_rec = data;
	if (dbox_rec == NULL || dbox_rec->map_uid == 0) {
		/* lost for some reason, use fallback */
		return dbox_mail_get_save_date(mail, date_r);
	}

	*date_r = dbox_rec->save_date;
	return TRUE;
}
Example #8
0
int mdbox_mail_lookup(struct mdbox_mailbox *mbox, struct mail_index_view *view,
		      uint32_t seq, uint32_t *map_uid_r)
{
	const struct mdbox_mail_index_record *dbox_rec;
	struct mdbox_index_header hdr;
	const void *data;
	uint32_t uid, cur_map_uid_validity;
	bool expunged;

	mail_index_lookup_ext(view, seq, mbox->ext_id, &data, &expunged);
	dbox_rec = data;
	if (dbox_rec == NULL || dbox_rec->map_uid == 0) {
		mail_index_lookup_uid(view, seq, &uid);
		mail_storage_set_critical(&mbox->storage->storage.storage,
			"mdbox %s: map uid lost for uid %u",
			mbox->box.path, uid);
		mdbox_storage_set_corrupted(mbox->storage);
		return -1;
	}

	if (mbox->map_uid_validity == 0) {
		if (mdbox_read_header(mbox, &hdr) < 0) {
			mdbox_storage_set_corrupted(mbox->storage);
			return -1;
		}
		mbox->map_uid_validity = hdr.map_uid_validity;
	}
	if (mdbox_map_open_or_create(mbox->storage->map) < 0)
		return -1;

	cur_map_uid_validity = mdbox_map_get_uid_validity(mbox->storage->map);
	if (cur_map_uid_validity != mbox->map_uid_validity) {
		mail_storage_set_critical(&mbox->storage->storage.storage,
			"mdbox %s: map uidvalidity mismatch (%u vs %u)",
			mbox->box.path, mbox->map_uid_validity,
			cur_map_uid_validity);
		mdbox_storage_set_corrupted(mbox->storage);
		return -1;
	}
	*map_uid_r = dbox_rec->map_uid;
	return 0;
}
Example #9
0
int mbox_list_index_has_changed(struct mailbox *box,
				struct mail_index_view *list_view,
				uint32_t seq)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)box;
	const struct mbox_list_index_record *rec;
	const void *data;
	const char *path;
	struct stat st;
	uint32_t ext_id;
	bool expunged;
	int ret;

	ret = index_storage_list_index_has_changed(box, list_view, seq);
	if (ret != 0 || box->storage->set->mailbox_list_index_very_dirty_syncs)
		return ret;

	ext_id = mbox_list_get_ext_id(mbox, list_view);
	mail_index_lookup_ext(list_view, seq, ext_id, &data, &expunged);
	rec = data;

	if (rec == NULL || expunged || rec->mtime == 0) {
		/* doesn't exist or not synced */
		return 1;
	}

	ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path);
	if (ret < 0)
		return ret;
	i_assert(ret > 0);

	if (stat(path, &st) < 0) {
		mail_storage_set_critical(box->storage,
					  "stat(%s) failed: %m", path);
		return -1;
	}
	if ((time_t)rec->mtime != st.st_mtime ||
	    rec->size != (uint32_t)(st.st_size & 0xffffffffU))
		return 1;
	return 0;
}
Example #10
0
static int
dbox_sync_verify_expunge_guid(struct mdbox_sync_context *ctx, uint32_t seq,
			      const guid_128_t guid_128)
{
	const void *data;
	uint32_t uid;

	mail_index_lookup_uid(ctx->sync_view, seq, &uid);
	mail_index_lookup_ext(ctx->sync_view, seq,
			      ctx->mbox->guid_ext_id, &data, NULL);
	if (guid_128_is_empty(guid_128) ||
	    memcmp(data, guid_128, GUID_128_SIZE) == 0)
		return 0;

	mail_storage_set_critical(&ctx->mbox->storage->storage.storage,
		"Mailbox %s: Expunged GUID mismatch for UID %u: %s vs %s",
		ctx->mbox->box.vname, uid,
		guid_128_to_string(data), guid_128_to_string(guid_128));
	mdbox_storage_set_corrupted(ctx->mbox->storage);
	return -1;
}
Example #11
0
int mdbox_map_lookup_seq_full(struct mdbox_map *map, uint32_t seq,
			      struct mdbox_map_mail_index_record *rec_r,
			      uint16_t *refcount_r)
{
	const struct mdbox_map_mail_index_record *rec;
	const uint16_t *ref16_p;
	const void *data;

	if (mdbox_map_lookup_seq(map, seq, &rec) < 0)
		return -1;
	*rec_r = *rec;

	mail_index_lookup_ext(map->view, seq, map->ref_ext_id, &data, NULL);
	if (data == NULL) {
		mdbox_map_set_corrupted(map, "missing ref extension");
		return -1;
	}
	ref16_p = data;
	*refcount_r = *ref16_p;
	return 1;
}
Example #12
0
int mbox_file_lookup_offset(struct mbox_mailbox *mbox,
			    struct mail_index_view *view,
			    uint32_t seq, uoff_t *offset_r)
{
	const void *data;
	bool deleted;

	mail_index_lookup_ext(view, seq, mbox->mbox_ext_idx, &data, &deleted);
	if (deleted)
		return -1;

	if (data == NULL) {
		mail_storage_set_critical(&mbox->storage->storage,
			"Cached message offset lost for seq %u in mbox file %s",
			seq, mailbox_get_path(&mbox->box));
		mbox->mbox_hdr.dirty_flag = 1;
                mbox->mbox_broken_offsets = TRUE;
		return 0;
	}

	*offset_r = *((const uint64_t *)data);
	return 1;
}
Example #13
0
static int dbox_sync_mark_expunges(struct mdbox_sync_context *ctx)
{
	enum mail_index_transaction_flags flags =
		MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL;
	struct mailbox *box = &ctx->mbox->box;
	struct mail_index_transaction *trans;
	struct seq_range_iter iter;
	unsigned int n;
	const void *data;
	uint32_t seq, uid;

	/* use a separate transaction here so that we can commit the changes
	   during map transaction */
	trans = mail_index_transaction_begin(ctx->sync_view, flags);
	seq_range_array_iter_init(&iter, &ctx->expunged_seqs); n = 0;
	while (seq_range_array_iter_nth(&iter, n++, &seq)) {
		mail_index_lookup_uid(ctx->sync_view, seq, &uid);
		mail_index_lookup_ext(ctx->sync_view, seq,
				      ctx->mbox->guid_ext_id, &data, NULL);
		mail_index_expunge_guid(trans, seq, data);
	}
	if (mail_index_transaction_commit(&trans) < 0)
		return -1;

	if (box->v.sync_notify != NULL) {
		/* do notifications after commit finished successfully */
		box->tmp_sync_view = ctx->sync_view;
		seq_range_array_iter_init(&iter, &ctx->expunged_seqs); n = 0;
		while (seq_range_array_iter_nth(&iter, n++, &seq)) {
			mail_index_lookup_uid(ctx->sync_view, seq, &uid);
			box->v.sync_notify(box, uid, MAILBOX_SYNC_TYPE_EXPUNGE);
		}
		box->tmp_sync_view = NULL;
	}
	return 0;
}
static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist,
					    struct mail_index_view *view,
					    const char **error_r)
{
	struct mailbox_list_index_node *node;
	const struct mail_index_record *rec;
	const struct mailbox_list_index_record *irec;
	const void *data;
	bool expunged;
	uint32_t seq, uid, count;

	*error_r = NULL;

	count = mail_index_view_get_messages_count(view);
	for (seq = 1; seq <= count; seq++) {
		node = p_new(ilist->mailbox_pool,
			     struct mailbox_list_index_node, 1);
		rec = mail_index_lookup(view, seq);
		node->uid = rec->uid;
		node->flags = rec->flags;

		mail_index_lookup_ext(view, seq, ilist->ext_id,
				      &data, &expunged);
		if (data == NULL) {
			*error_r = "Missing list extension data";
			return -1;
		}
		irec = data;

		node->name_id = irec->name_id;
		node->name = hash_table_lookup(ilist->mailbox_names,
					       POINTER_CAST(irec->name_id));
		if (node->name == NULL) {
			*error_r = "name_id not in index header";
			if (ilist->has_backing_store)
				return -1;
			/* generate a new name and use it */
			mailbox_list_index_generate_name(ilist, node);
		}
		hash_table_insert(ilist->mailbox_hash,
				  POINTER_CAST(node->uid), node);
	}

	/* do a second scan to create the actual mailbox tree hierarchy.
	   this is needed because the parent_uid may be smaller or higher than
	   the current node's uid */
	for (seq = 1; seq <= count; seq++) {
		mail_index_lookup_uid(view, seq, &uid);
		mail_index_lookup_ext(view, seq, ilist->ext_id,
				      &data, &expunged);
		irec = data;

		node = mailbox_list_index_lookup_uid(ilist, uid);
		i_assert(node != NULL);

		if (irec->parent_uid != 0) {
			/* node should have a parent */
			node->parent = mailbox_list_index_lookup_uid(ilist,
							irec->parent_uid);
			if (node->parent != NULL) {
				node->next = node->parent->children;
				node->parent->children = node;
				continue;
			}
			*error_r = "parent_uid points to nonexistent record";
			if (ilist->has_backing_store)
				return -1;
			/* just place it under the root */
		}
		node->next = ilist->mailbox_tree;
		ilist->mailbox_tree = node;
	}
	return *error_r == NULL ? 0 : -1;
}
Example #15
0
bool mbox_sync_parse_match_mail(struct mbox_mailbox *mbox,
				struct mail_index_view *view, uint32_t seq)
{
        struct mbox_sync_mail_context ctx;
	struct message_header_parser_ctx *hdr_ctx;
	struct message_header_line *hdr;
	struct header_func *func;
	struct mbox_md5_context *mbox_md5_ctx;
	const void *data;
	bool expunged;
	uint32_t uid;
	int ret;

	/* we only wish to be sure that this mail actually is what we expect
	   it to be. If there's X-UID header and it matches our UID, we use it.
	   Otherwise it could mean that the X-UID header is invalid and it's
	   just not yet been rewritten. In that case use MD5 sum, if it
	   exists. */

	mail_index_lookup_uid(view, seq, &uid);
	memset(&ctx, 0, sizeof(ctx));
        mbox_md5_ctx = mbox->md5_v.init();

	hdr_ctx = message_parse_header_init(mbox->mbox_stream, NULL, 0);
	while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
		if (hdr->eoh)
			break;

		func = bsearch(hdr->name, header_funcs,
			       N_ELEMENTS(header_funcs), sizeof(*header_funcs),
			       mbox_sync_bsearch_header_func_cmp);
		if (func != NULL) {
			if (strcasecmp(hdr->name, "X-UID") == 0) {
				if (hdr->continues) {
					hdr->use_full_value = TRUE;
					continue;
				}
				(void)parse_x_uid(&ctx, hdr);

				if (ctx.mail.uid == uid)
					break;
			}
		} else {
			mbox->md5_v.more(mbox_md5_ctx, hdr);
		}
	}
	i_assert(ret != 0);
	message_parse_header_deinit(&hdr_ctx);

	mbox->md5_v.finish(mbox_md5_ctx, ctx.hdr_md5_sum);

	if (ctx.mail.uid == uid)
		return TRUE;

	/* match by MD5 sum */
	mbox->mbox_save_md5 = TRUE;

	mail_index_lookup_ext(view, seq, mbox->md5hdr_ext_idx,
			      &data, &expunged);
	return data == NULL ? 0 :
		memcmp(data, ctx.hdr_md5_sum, 16) == 0;
}
Example #16
0
static int mbox_mail_seek(struct index_mail *mail)
{
	struct mbox_transaction_context *t =
		(struct mbox_transaction_context *)mail->mail.mail.transaction;
	struct mail *_mail = &mail->mail.mail;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	enum mbox_sync_flags sync_flags = 0;
	int ret, try;
	bool deleted;

	if (_mail->expunged || mbox->syncing)
		return -1;

	if (_mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) {
		mail_set_aborted(_mail);
		return -1;
	}

	if (mbox->mbox_stream != NULL &&
	    istream_raw_mbox_is_corrupted(mbox->mbox_stream)) {
		/* clear the corruption by forcing a full resync */
		sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC;
	}

	for (try = 0; try < 2; try++) {
		if ((sync_flags & MBOX_SYNC_FORCE_SYNC) != 0) {
			/* dirty offsets are broken. make sure we can sync. */
			mbox_prepare_resync(_mail);
		}
		if (mbox->mbox_lock_type == F_UNLCK) {
			i_assert(t->read_lock_id == 0);
			sync_flags |= MBOX_SYNC_LOCK_READING;
			if (mbox_sync(mbox, sync_flags) < 0)
				return -1;
			t->read_lock_id = mbox_get_cur_lock_id(mbox);
			i_assert(t->read_lock_id != 0);

			/* refresh index file after mbox has been locked to
			   make sure we get only up-to-date mbox offsets. */
			if (mail_index_refresh(mbox->box.index) < 0) {
				mailbox_set_index_error(&mbox->box);
				return -1;
			}

			i_assert(mbox->mbox_lock_type != F_UNLCK);
		} else if (t->read_lock_id == 0) {
			/* file is already locked by another transaction, but
			   we must keep it locked for the entire transaction,
			   so increase the lock counter. */
			if (mbox_lock(mbox, mbox->mbox_lock_type,
				      &t->read_lock_id) < 0)
				i_unreached();
		}

		if (mbox_file_open_stream(mbox) < 0)
			return -1;

		ret = mbox_file_seek(mbox, _mail->transaction->view,
				     _mail->seq, &deleted);
		if (ret > 0) {
			/* success */
			break;
		}
		if (ret < 0) {
			if (deleted)
				mail_set_expunged(_mail);
			return -1;
		}

		/* we'll need to re-sync it completely */
		sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC;
	}
	if (ret == 0) {
		mail_storage_set_critical(&mbox->storage->storage,
			"Losing sync for mail uid=%u in mbox file %s",
			_mail->uid, mailbox_get_path(&mbox->box));
	}
	return 0;
}

static int mbox_mail_get_received_date(struct mail *_mail, time_t *date_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;

	if (index_mail_get_received_date(_mail, date_r) == 0)
		return 0;

	if (mbox_mail_seek(mail) < 0)
		return -1;
	data->received_date =
		istream_raw_mbox_get_received_time(mbox->mbox_stream);
	if (data->received_date == (time_t)-1) {
		/* it's broken and conflicts with our "not found"
		   return value. change it. */
		data->received_date = 0;
	}

	*date_r = data->received_date;
	return 0;
}

static int mbox_mail_get_save_date(struct mail *_mail, time_t *date_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;

	if (index_mail_get_save_date(_mail, date_r) == 0)
		return 0;

	/* no way to know this. save the current time into cache and use
	   that from now on. this works only as long as the index files
	   are permanent */
	data->save_date = ioloop_time;
	*date_r = data->save_date;
	return 0;
}

static int
mbox_mail_get_md5_header(struct index_mail *mail, const char **value_r)
{
	struct mail *_mail = &mail->mail.mail;
	static uint8_t empty_md5[16] =
		{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	const void *ext_data;

	if (mail->data.guid != NULL) {
		*value_r = mail->data.guid;
		return 1;
	}

	mail_index_lookup_ext(_mail->transaction->view, _mail->seq,
			      mbox->md5hdr_ext_idx, &ext_data, NULL);
	if (ext_data != NULL && memcmp(ext_data, empty_md5, 16) != 0) {
		mail->data.guid = p_strdup(mail->mail.data_pool,
					   binary_to_hex(ext_data, 16));
		*value_r = mail->data.guid;
		return 1;
	} else if (mail_index_is_expunged(_mail->transaction->view, _mail->seq)) {
		mail_set_expunged(_mail);
		return -1;
	} else {
		return 0;
	}
}

static int
mbox_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
		      const char **value_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	uoff_t offset;
	bool move_offset;
	int ret;

	switch (field) {
	case MAIL_FETCH_FROM_ENVELOPE:
		if (mbox_mail_seek(mail) < 0)
			return -1;

		*value_r = istream_raw_mbox_get_sender(mbox->mbox_stream);
		return 0;
	case MAIL_FETCH_GUID:
	case MAIL_FETCH_HEADER_MD5:
		if ((ret = mbox_mail_get_md5_header(mail, value_r)) != 0)
			return ret < 0 ? -1 : 0;

		/* i guess in theory the empty_md5 is valid and can happen,
		   but it's almost guaranteed that it means the MD5 sum is
		   missing. recalculate it. */
		if (mbox->mbox_lock_type == F_UNLCK ||
		    mbox->mbox_stream == NULL) {
			offset = 0;
			move_offset = FALSE;
		} else {
			offset = istream_raw_mbox_get_start_offset(mbox->mbox_stream);
			move_offset = TRUE;
		}
		mbox->mbox_save_md5 = TRUE;
		if (mbox_sync(mbox, MBOX_SYNC_FORCE_SYNC |
			      MBOX_SYNC_READONLY) < 0)
			return -1;
		if (move_offset) {
			if (istream_raw_mbox_seek(mbox->mbox_stream,
						  offset) < 0) {
				i_error("mbox %s sync lost during MD5 syncing",
					_mail->box->name);
				return -1;
			}
		}

		if ((ret = mbox_mail_get_md5_header(mail, value_r)) == 0) {
			i_error("mbox %s resyncing didn't save header MD5 values",
				_mail->box->name);
			return -1;
		}
		return ret < 0 ? -1 : 0;
	default:
		break;
	}

	return index_mail_get_special(_mail, field, value_r);
}

static int
mbox_mail_get_next_offset(struct index_mail *mail, uoff_t *next_offset_r)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box;
	struct mail_index_view *view;
	const struct mail_index_header *hdr;
	uint32_t seq;
	int trailer_size;
	int ret = 1;

	*next_offset_r = (uoff_t)-1;

	hdr = mail_index_get_header(mail->mail.mail.transaction->view);
	if (mail->mail.mail.seq > hdr->messages_count) {
		/* we're appending a new message */
		return 0;
	}

	/* We can't really trust trans_view. The next message may already be
	   expunged from it. Also hdr.messages_count may be incorrect there.
	   So refresh the index to get the latest changes and get the next
	   message's offset using a new view. */
	i_assert(mbox->mbox_lock_type != F_UNLCK);
	if (mbox_sync_header_refresh(mbox) < 0)
		return -1;

	view = mail_index_view_open(mail->mail.mail.box->index);
	hdr = mail_index_get_header(view);
	if (!mail_index_lookup_seq(view, mail->mail.mail.uid, &seq))
		i_panic("Message unexpectedly expunged from index");

	if (seq < hdr->messages_count) {
		if (mbox_file_lookup_offset(mbox, view, seq + 1,
					    next_offset_r) <= 0)
			ret = -1;
	} else if (mail->mail.mail.box->input != NULL) {
		/* opened the mailbox as input stream. we can't trust the
		   sync_size, since it's wrong with compressed mailboxes */
		ret = 0;
	} else {
		/* last message, use the synced mbox size */
		trailer_size =
			mbox->storage->storage.set->mail_save_crlf ? 2 : 1;
		*next_offset_r = mbox->mbox_hdr.sync_size - trailer_size;
	}
	mail_index_view_close(&view);
	return ret;
}

static int mbox_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box;
	struct istream *input;
	struct message_size hdr_size;
	uoff_t old_offset, body_offset, body_size, next_offset;

	if (index_mail_get_physical_size(_mail, size_r) == 0)
		return 0;

	/* we want to return the header size as seen by mail_get_stream(). */
	old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
	if (mail_get_stream(_mail, &hdr_size, NULL, &input) < 0)
		return -1;

	/* our header size varies, so don't do any caching */
	if (istream_raw_mbox_get_body_offset(mbox->mbox_stream, &body_offset) < 0) {
		mail_storage_set_critical(_mail->box->storage,
			"mbox %s: Couldn't get body offset for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}

	/* use the next message's offset to avoid reading through the entire
	   message body to find out its size */
	if (mbox_mail_get_next_offset(mail, &next_offset) > 0)
		body_size = next_offset - body_offset;
	else
		body_size = (uoff_t)-1;

	/* verify that the calculated body size is correct */
	if (istream_raw_mbox_get_body_size(mbox->mbox_stream,
					   body_size, &body_size) < 0) {
		mail_storage_set_critical(_mail->box->storage,
			"mbox %s: Couldn't get body size for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}

	data->physical_size = hdr_size.physical_size + body_size;
	*size_r = data->physical_size;

	i_stream_seek(input, old_offset);
	return 0;
}

static int mbox_mail_init_stream(struct index_mail *mail)
{
	struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box;
	struct istream *raw_stream;
	uoff_t hdr_offset, next_offset;
	int ret;

	if (mbox_mail_seek(mail) < 0)
		return -1;

	ret = mbox_mail_get_next_offset(mail, &next_offset);
	if (ret < 0) {
		if (mbox_mail_seek(mail) < 0)
			return -1;
		ret = mbox_mail_get_next_offset(mail, &next_offset);
		if (ret < 0) {
			i_warning("mbox %s: Can't find next message offset "
				  "for uid=%u", mailbox_get_path(&mbox->box),
				  mail->mail.mail.uid);
		}
	}

	raw_stream = mbox->mbox_stream;
	if (istream_raw_mbox_get_header_offset(raw_stream, &hdr_offset) < 0) {
		mail_storage_set_critical(mbox->box.storage,
			"mbox %s: Couldn't get header offset for uid=%u",
			mailbox_get_path(&mbox->box), mail->mail.mail.uid);
		return -1;
	}
	i_stream_seek(raw_stream, hdr_offset);

	if (next_offset != (uoff_t)-1)
		istream_raw_mbox_set_next_offset(raw_stream, next_offset);

	raw_stream = i_stream_create_limit(raw_stream, (uoff_t)-1);
	mail->data.stream =
		i_stream_create_header_filter(raw_stream,
				HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
				mbox_hide_headers, mbox_hide_headers_count,
				*null_header_filter_callback, (void *)NULL);
	i_stream_unref(&raw_stream);
	return 0;
}

static int mbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED,
				struct message_size *hdr_size,
				struct message_size *body_size,
				struct istream **stream_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;

	if (mail->data.stream == NULL) {
		if (mbox_mail_init_stream(mail) < 0)
			return -1;
	}

	return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
}

static void mbox_mail_set_seq(struct mail *_mail, uint32_t seq, bool saving)
{
	struct index_mail *mail = (struct index_mail *)_mail;

	index_mail_set_seq(_mail, seq, saving);
	mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
}

static bool mbox_mail_set_uid(struct mail *_mail, uint32_t uid)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	bool ret;

	ret = index_mail_set_uid(_mail, uid);
	mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
	return ret;
}

struct mail_vfuncs mbox_mail_vfuncs = {
	index_mail_close,
	index_mail_free,
	mbox_mail_set_seq,
	mbox_mail_set_uid,
	index_mail_set_uid_cache_updates,
	index_mail_prefetch,
	index_mail_precache,
	index_mail_add_temp_wanted_fields,

	index_mail_get_flags,
	index_mail_get_keywords,
	index_mail_get_keyword_indexes,
	index_mail_get_modseq,
	index_mail_get_pvt_modseq,
	index_mail_get_parts,
	index_mail_get_date,
	mbox_mail_get_received_date,
	mbox_mail_get_save_date,
	index_mail_get_virtual_size,
	mbox_mail_get_physical_size,
	index_mail_get_first_header,
	index_mail_get_headers,
	index_mail_get_header_stream,
	mbox_mail_get_stream,
	index_mail_get_binary_stream,
	mbox_mail_get_special,
	index_mail_get_real_mail,
	index_mail_update_flags,
	index_mail_update_keywords,
	index_mail_update_modseq,
	index_mail_update_pvt_modseq,
	NULL,
	index_mail_expunge,
	index_mail_set_cache_corrupted,
	index_mail_opened
};