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); }
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; }
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); }
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); }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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); } }
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 };