Exemplo n.º 1
0
int mail_send_forward(struct mail_deliver_context *ctx, const char *forwardto)
{
    static const char *hide_headers[] = {
        "Return-Path"
    };
    struct istream *input;
    struct ostream *output;
    struct smtp_client *smtp_client;
    const char *return_path;

    if (mail_get_stream(ctx->src_mail, NULL, NULL, &input) < 0)
	    return -1;

    return_path = mail_deliver_get_return_address(ctx);
    if (mailbox_get_settings(ctx->src_mail->box)->mail_debug) {
	    i_debug("Sending a forward to <%s> with return path <%s>",
		    forwardto, return_path);
    }

    smtp_client = smtp_client_open(ctx->set, forwardto, return_path, &output);

    input = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE |
                                          HEADER_FILTER_NO_CR, hide_headers,
                                          N_ELEMENTS(hide_headers),
					  *null_header_filter_callback,
					  (void *)NULL);

    (void)o_stream_send_istream(output, input);
    i_stream_unref(&input);

    return smtp_client_close(smtp_client);
}
Exemplo n.º 2
0
static int
mail_storage_try_copy(struct mail_save_context **_ctx, struct mail *mail)
{
	struct mail_save_context *ctx = *_ctx;
	struct mail_private *pmail = (struct mail_private *)mail;
	struct istream *input;

	ctx->copying_via_save = TRUE;

	/* we need to open the file in any case. caching metadata is unlikely
	   to help anything. */
	pmail->v.set_uid_cache_updates(mail, TRUE);

	if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
		mail_copy_set_failed(ctx, mail, "stream");
		return -1;
	}
	if (mail_save_copy_default_metadata(ctx, mail) < 0)
		return -1;

	if (mailbox_save_begin(_ctx, input) < 0)
		return -1;

	do {
		if (mailbox_save_continue(ctx) < 0)
			break;
	} while (i_stream_read(input) != -1);

	if (input->stream_errno != 0) {
		mail_storage_set_critical(ctx->transaction->box->storage,
					  "copy: i_stream_read() failed: %m");
		return -1;
	}
	return 0;
}
Exemplo n.º 3
0
static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
		      const struct imap_fetch_body_data *body)
{
	const struct message_size *fetch_size;
	struct istream *input;
	struct message_size hdr_size, body_size;

	if (body->section[0] == '\0') {
		if (mail_get_stream(mail, NULL, NULL, &input) < 0 ||
		    mail_get_virtual_size(mail, &body_size.virtual_size) < 0 ||
		    mail_get_physical_size(mail, &body_size.physical_size) < 0)
			return -1;
	} else {
		if (mail_get_stream(mail, &hdr_size,
				    body->section[0] == 'H' ? NULL : &body_size,
				    &input) < 0)
			return -1;
	}

	ctx->cur_input = input;
	i_stream_ref(ctx->cur_input);
	ctx->update_partial = TRUE;

	switch (body->section[0]) {
	case '\0':
		/* BODY[] - fetch everything */
                fetch_size = &body_size;
		ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
		break;
	case 'H':
		/* BODY[HEADER] - fetch only header */
                fetch_size = &hdr_size;
		ctx->cur_size_field = MAIL_FETCH_MESSAGE_PARTS;
		break;
	case 'T':
		/* BODY[TEXT] - skip header */
		i_stream_skip(ctx->cur_input, hdr_size.physical_size);
                fetch_size = &body_size;
		ctx->cur_size_field = MAIL_FETCH_VIRTUAL_SIZE;
		break;
	default:
		i_unreached();
	}

	return fetch_data(ctx, body, fetch_size);
}
Exemplo n.º 4
0
static void
maildir_mail_remove_sizes_from_filename(struct mail *mail,
					enum mail_fetch_field field)
{
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box;
	struct mail_private *pmail = (struct mail_private *)mail;
	enum maildir_uidlist_rec_flag flags;
	const char *fname;
	uoff_t size;
	struct maildir_size_fix_ctx ctx;

	if (mbox->storage->set->maildir_broken_filename_sizes) {
		/* never try to fix sizes in maildir filenames */
		return;
	}

	if (maildir_sync_lookup(mbox, mail->uid, &flags, &fname) <= 0)
		return;
	if (strchr(fname, MAILDIR_EXTRA_SEP) == NULL)
		return;

	memset(&ctx, 0, sizeof(ctx));
	ctx.physical_size = (uoff_t)-1;
	if (field == MAIL_FETCH_VIRTUAL_SIZE &&
	    maildir_filename_get_size(fname, MAILDIR_EXTRA_VIRTUAL_SIZE,
				      &size)) {
		ctx.wrong_key = 'W';
	} else if (field == MAIL_FETCH_PHYSICAL_SIZE &&
		   maildir_filename_get_size(fname, MAILDIR_EXTRA_FILE_SIZE,
					     &size)) {
		ctx.wrong_key = 'S';
	} else {
		/* the broken size isn't in filename */
		return;
	}

	if (pmail->v.istream_opened != NULL) {
		/* the mail could be e.g. compressed. get the physical size
		   the slow way by actually reading the mail. */
		struct istream *input;
		const struct stat *stp;

		if (mail_get_stream(mail, NULL, NULL, &input) < 0)
			return;
		if (i_stream_stat(input, TRUE, &stp) < 0)
			return;
		ctx.physical_size = stp->st_size;
	}

	(void)maildir_file_do(mbox, mail->uid, do_fix_size, &ctx);
}
Exemplo n.º 5
0
static int imapc_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
{
	struct imapc_mailbox *mbox = (struct imapc_mailbox *)_mail->box;
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct istream *input;
	uoff_t old_offset;
	int ret;

	if (data->physical_size == (uoff_t)-1) {
		(void)index_mail_get_physical_size(_mail, size_r);
		if (data->physical_size != (uoff_t)-1) {
			*size_r = data->physical_size;
			return 0;
		}
	}

	if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE) &&
	    data->stream == NULL) {
		/* trust RFC822.SIZE to be correct */
		if (imapc_mail_fetch(_mail, MAIL_FETCH_PHYSICAL_SIZE) < 0)
			return -1;
		if (data->physical_size == (uoff_t)-1) {
			if (imapc_mail_failed(_mail, "RFC822.SIZE") < 0)
				return -1;
			/* assume that the server never returns RFC822.SIZE
			   for this mail (see BODY[] failure handling) */
			data->physical_size = 0;
		}
		*size_r = data->physical_size;
		return 0;
	}

	old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
	if (mail_get_stream(_mail, NULL, NULL, &input) < 0)
		return -1;
	i_stream_seek(data->stream, old_offset);

	ret = i_stream_get_size(data->stream, TRUE,
				&data->physical_size);
	if (ret <= 0) {
		i_assert(ret != 0);
		mail_storage_set_critical(_mail->box->storage,
					  "imapc: stat(%s) failed: %m",
					  i_stream_get_name(data->stream));
		return -1;
	}
	*size_r = data->physical_size;
	return 0;
}
Exemplo n.º 6
0
static int maildir_mail_stat(struct mail *mail, struct stat *st_r)
{
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)mail->box;
	struct index_mail *imail = (struct index_mail *)mail;
	const char *path;
	int fd, ret;

	if (mail->lookup_abort == MAIL_LOOKUP_ABORT_NOT_IN_CACHE) {
		mail_set_aborted(mail);
		return -1;
	}

	if (imail->data.access_part != 0 &&
	    imail->data.stream == NULL) {
		/* we're going to open the mail anyway */
		struct istream *input;

		(void)mail_get_stream(mail, NULL, NULL, &input);
	}

	if (imail->data.stream != NULL &&
	    (fd = i_stream_get_fd(imail->data.stream)) != -1) {
		mail->transaction->stats.fstat_lookup_count++;
		if (fstat(fd, st_r) < 0) {
			mail_storage_set_critical(mail->box->storage,
				"fstat(%s) failed: %m",
				i_stream_get_name(imail->data.stream));
			return -1;
		}
	} else if (!mail->saving) {
		mail->transaction->stats.stat_lookup_count++;
		ret = maildir_file_do(mbox, mail->uid, do_stat, st_r);
		if (ret <= 0) {
			if (ret == 0)
				mail_set_expunged(mail);
			return -1;
		}
	} else {
		mail->transaction->stats.stat_lookup_count++;
		path = maildir_save_file_get_path(mail->transaction, mail->seq);
		if (stat(path, st_r) < 0) {
			mail_storage_set_critical(mail->box->storage,
						  "stat(%s) failed: %m", path);
			return -1;
		}
	}
	return 0;
}
Exemplo n.º 7
0
static int
virtual_mail_get_stream(struct mail *mail, struct message_size *hdr_size,
			struct message_size *body_size,
			struct istream **stream_r)
{
	struct virtual_mail *vmail = (struct virtual_mail *)mail;

	if (virtual_mail_handle_lost(vmail) < 0)
		return -1;
	if (mail_get_stream(vmail->backend_mail, hdr_size, body_size,
			    stream_r) < 0) {
		virtual_box_copy_error(mail->box, vmail->backend_mail->box);
		return -1;
	}
	return 0;
}
Exemplo n.º 8
0
static int maildir_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r)
{
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box;
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct message_size hdr_size, body_size;
	struct istream *input;
	uoff_t old_offset;

	if (maildir_uidlist_is_read(mbox->uidlist) ||
	    (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) {
		/* try to get the size from uidlist. this is especially useful
		   with pop3 to avoid unnecessarily opening the cache file. */
		if (maildir_quick_size_lookup(mail, TRUE,
					      &data->virtual_size) < 0)
			return -1;
	}

	if (data->virtual_size == (uoff_t)-1) {
		if (index_mail_get_cached_virtual_size(mail, size_r)) {
			i_assert(mail->data.virtual_size != (uoff_t)-1);
			maildir_handle_size_caching(mail, TRUE, TRUE);
			return 0;
		}
		if (maildir_quick_size_lookup(mail, TRUE,
					      &data->virtual_size) < 0)
			return -1;
	}
	if (data->virtual_size != (uoff_t)-1) {
		data->dont_cache_fetch_fields |= MAIL_FETCH_VIRTUAL_SIZE;
		*size_r = data->virtual_size;
		return 0;
	}

	/* fallback to reading the file */
	old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
	if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0)
		return -1;
	i_stream_seek(data->stream, old_offset);

	maildir_handle_size_caching(mail, FALSE, TRUE);
	*size_r = data->virtual_size;
	return 0;
}
Exemplo n.º 9
0
static int zlib_mail_save_finish(struct mail_save_context *ctx)
{
	struct mailbox *box = ctx->transaction->box;
	union mailbox_module_context *zbox = ZLIB_CONTEXT(box);
	struct istream *input;

	if (zbox->super.save_finish(ctx) < 0)
		return -1;

	if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0)
		return -1;

	if (compression_detect_handler(input) != NULL) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			"Saving mails compressed by client isn't supported");
		return -1;
	}
	return 0;
}
Exemplo n.º 10
0
int index_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct message_size hdr_size, body_size;
	struct istream *input;
	uoff_t old_offset;

	if (index_mail_get_cached_virtual_size(mail, size_r))
		return 0;

	old_offset = data->stream == NULL ? 0 : data->stream->v_offset;
	if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0)
		return -1;
	i_stream_seek(data->stream, old_offset);

	i_assert(data->virtual_size != (uoff_t)-1);
	*size_r = data->virtual_size;
	return 0;
}
Exemplo n.º 11
0
static int pop3c_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)_mail->box;
	struct message_size hdr_size, body_size;
	struct istream *input;

	if (mail->data.virtual_size != (uoff_t)-1) {
		/* virtual size is already known. it's the same as our
		   (correct) physical size */
		*size_r = mail->data.virtual_size;
		return 0;
	}
	if (index_mail_get_physical_size(_mail, size_r) == 0) {
		*size_r = mail->data.physical_size;
		return 0;
	}

	if (_mail->lookup_abort == MAIL_LOOKUP_ABORT_READ_MAIL &&
	    (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) {
		/* kludge: we want output for POP3 LIST with
		   pop3_fast_size_lookups=yes. use the remote's LIST values
		   regardless of their correctness */
		if (mbox->msg_sizes == NULL) {
			if (pop3c_sync_get_sizes(mbox) < 0)
				return -1;
		}
		i_assert(_mail->seq <= mbox->msg_count);
		*size_r = mbox->msg_sizes[_mail->seq-1];
		return 0;
	}

	/* slow way: get the whole message body */
	if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0)
		return -1;

	i_assert(mail->data.physical_size != (uoff_t)-1);
	*size_r = mail->data.physical_size;
	return 0;
}
Exemplo n.º 12
0
static int mail_crypt_mail_save_finish(struct mail_save_context *ctx)
{
	struct mailbox *box = ctx->transaction->box;
	union mailbox_module_context *zbox = MAIL_CRYPT_CONTEXT(box);
	struct istream *input;

	if (zbox->super.save_finish(ctx) < 0)
		return -1;

	/* we're here only if mail-crypt plugin is disabled. we want to make
	   sure that even though we're saving an unencrypted mail, the mail
	   can't be faked to look like an encrypted mail. */
	if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0)
		return -1;

	if (mail_crypt_is_stream_encrypted(input)) {
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
			"Saving mails encrypted by client isn't supported");
		return -1;
	}
	return 0;
}
Exemplo n.º 13
0
int dsync_mail_fill(struct mail *mail, struct dsync_mail *dmail_r,
		    const char **error_field_r)
{
	const char *guid, *str;

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

	if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) {
		*error_field_r = "GUID";
		return -1;
	}
	dmail_r->guid = guid;
	dmail_r->uid = mail->uid;

	dmail_r->input_mail = mail;
	dmail_r->input_mail_uid = mail->uid;
	if (mail_get_stream(mail, NULL, NULL, &dmail_r->input) < 0) {
		*error_field_r = "body";
		return -1;
	}

	if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &dmail_r->pop3_uidl) < 0) {
		*error_field_r = "pop3-uidl";
		return -1;
	}
	if (mail_get_special(mail, MAIL_FETCH_POP3_ORDER, &str) < 0) {
		*error_field_r = "pop3-order";
		return -1;
	}
	if (*str != '\0') {
		if (str_to_uint(str, &dmail_r->pop3_order) < 0)
			i_unreached();
	}
	if (mail_get_received_date(mail, &dmail_r->received_date) < 0) {
		*error_field_r = "received-date";
		return -1;
	}
	return 0;
}
Exemplo n.º 14
0
static int maildir_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct maildir_mailbox *mbox = (struct maildir_mailbox *)_mail->box;
	struct index_mail_data *data = &mail->data;
	struct stat st;
	struct message_size hdr_size, body_size;
	struct istream *input;
	const char *path;
	int ret;

	if (maildir_uidlist_is_read(mbox->uidlist) ||
	    (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) {
		/* try to get the size from uidlist (see virtual size above) */
		if (maildir_quick_size_lookup(mail, FALSE,
					      &data->physical_size) < 0)
			return -1;
	}

	if (data->physical_size == (uoff_t)-1) {
		if (index_mail_get_physical_size(_mail, size_r) == 0) {
			i_assert(mail->data.physical_size != (uoff_t)-1);
			maildir_handle_size_caching(mail, TRUE, FALSE);
			return 0;
		}
		if (maildir_quick_size_lookup(mail, FALSE,
					      &data->physical_size) < 0)
			return -1;
	}
	if (data->physical_size != (uoff_t)-1) {
		data->dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE;
		*size_r = data->physical_size;
		return 0;
	}

	if (mail->mail.v.istream_opened != NULL) {
		/* we can't use stat(), because this may be a mail that some
		   plugin has changed (e.g. zlib). need to do it the slow
		   way. */
		if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0)
			return -1;
		st.st_size = hdr_size.physical_size + body_size.physical_size;
	} else if (!_mail->saving) {
		ret = maildir_file_do(mbox, _mail->uid, do_stat, &st);
		if (ret <= 0) {
			if (ret == 0)
				mail_set_expunged(_mail);
			return -1;
		}
	} else {
		/* saved mail which hasn't been committed yet */
		path = maildir_save_file_get_path(_mail->transaction,
						  _mail->seq);
		if (stat(path, &st) < 0) {
			mail_storage_set_critical(_mail->box->storage,
						  "stat(%s) failed: %m", path);
			return -1;
		}
	}

	data->physical_size = st.st_size;
	maildir_handle_size_caching(mail, FALSE, FALSE);
	*size_r = st.st_size;
	return 0;
}
Exemplo n.º 15
0
void index_mail_set_seq(struct mail *_mail, uint32_t seq)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mail_cache_field *cache_fields = mail->ibox->cache_fields;
	struct mail_cache_view *cache_view = mail->trans->cache_view;
	const struct mail_index_header *hdr;
	struct istream *input;

	if (data->seq == seq)
		return;

	index_mail_reset(mail);

	data->seq = seq;

	mail->mail.mail.seq = seq;
	mail_index_lookup_uid(_mail->transaction->view, seq,
			      &mail->mail.mail.uid);

	if (mail_index_view_is_inconsistent(_mail->transaction->view)) {
		mail_set_expunged(&mail->mail.mail);
		return;
	}

	if ((mail->wanted_fields & (MAIL_FETCH_NUL_STATE |
				    MAIL_FETCH_IMAP_BODY |
				    MAIL_FETCH_IMAP_BODYSTRUCTURE)) != 0) {
		(void)index_mail_get_fixed_field(mail, MAIL_CACHE_FLAGS,
						 &data->cache_flags,
						 sizeof(data->cache_flags));
		mail->mail.mail.has_nuls =
			(data->cache_flags & MAIL_CACHE_FLAG_HAS_NULS) != 0;
		mail->mail.mail.has_no_nuls =
			(data->cache_flags & MAIL_CACHE_FLAG_HAS_NO_NULS) != 0;
	}

	/* see if wanted_fields can tell us if we need to read/parse
	   header/body */
	if ((mail->wanted_fields & MAIL_FETCH_MESSAGE_PARTS) != 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_MESSAGE_PARTS].idx;

		if (mail_cache_field_exists(cache_view, seq,
					    cache_field) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_message_parts = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_ENVELOPE) != 0)
		check_envelope(mail);

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_BODY) != 0 &&
	    (data->cache_flags & MAIL_CACHE_FLAG_TEXT_PLAIN_7BIT_ASCII) == 0) {
		/* we need either imap.body or imap.bodystructure */
		unsigned int cache_field1 =
			cache_fields[MAIL_CACHE_IMAP_BODY].idx;
		unsigned int cache_field2 =
			cache_fields[MAIL_CACHE_IMAP_BODYSTRUCTURE].idx;

		if (mail_cache_field_exists(cache_view,
					    seq, cache_field1) <= 0 &&
		    mail_cache_field_exists(cache_view,
                                            seq, cache_field2) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_bodystructure_header = TRUE;
			data->save_bodystructure_body = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0 &&
	    (data->cache_flags & MAIL_CACHE_FLAG_TEXT_PLAIN_7BIT_ASCII) == 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_IMAP_BODYSTRUCTURE].idx;

                if (mail_cache_field_exists(cache_view, seq,
                                            cache_field) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_bodystructure_header = TRUE;
			data->save_bodystructure_body = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_DATE) != 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_SENT_DATE].idx;

		if (mail_cache_field_exists(cache_view, seq,
					    cache_field) <= 0) {
			data->access_part |= PARSE_HDR;
			data->save_sent_date = TRUE;
		}
	}

	if ((mail->wanted_fields & (MAIL_FETCH_STREAM_HEADER |
				    MAIL_FETCH_STREAM_BODY)) != 0) {
		/* open stream immediately to set expunged flag if
		   it's already lost */
		if ((mail->wanted_fields & MAIL_FETCH_STREAM_HEADER) != 0)
			data->access_part |= READ_HDR;
		if ((mail->wanted_fields & MAIL_FETCH_STREAM_BODY) != 0)
			data->access_part |= READ_BODY;

		/* open the stream only if we didn't get here from
		   mailbox_save_init() */
		hdr = mail_index_get_header(_mail->box->view);
		if (!_mail->saving && _mail->uid < hdr->next_uid)
			(void)mail_get_stream(_mail, NULL, NULL, &input);
	}
}
Exemplo n.º 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
};