diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-storage/mail.c | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/src/lib-storage/mail.c b/src/lib-storage/mail.c new file mode 100644 index 0000000..b3991d6 --- /dev/null +++ b/src/lib-storage/mail.c @@ -0,0 +1,694 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "buffer.h" +#include "hash.h" +#include "hex-binary.h" +#include "crc32.h" +#include "sha1.h" +#include "hostpid.h" +#include "istream.h" +#include "mail-cache.h" +#include "mail-storage-private.h" +#include "message-id.h" +#include "message-part-data.h" +#include "imap-bodystructure.h" + +#include <time.h> + +struct mail *mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct mail *mail; + + i_assert(wanted_headers == NULL || wanted_headers->box == t->box); + + T_BEGIN { + mail = t->box->v.mail_alloc(t, wanted_fields, wanted_headers); + hook_mail_allocated(mail); + } T_END; + + return mail; +} + +void mail_free(struct mail **mail) +{ + struct mail_private *p = (struct mail_private *)*mail; + + /* make sure mailbox_search_*() users don't try to free the mail + directly */ + i_assert(!p->search_mail); + + p->v.free(*mail); + *mail = NULL; +} + +void mail_set_seq(struct mail *mail, uint32_t seq) +{ + struct mail_private *p = (struct mail_private *)mail; + + T_BEGIN { + p->v.set_seq(mail, seq, FALSE); + } T_END; +} + +void mail_set_seq_saving(struct mail *mail, uint32_t seq) +{ + struct mail_private *p = (struct mail_private *)mail; + + T_BEGIN { + p->v.set_seq(mail, seq, TRUE); + } T_END; +} + +bool mail_set_uid(struct mail *mail, uint32_t uid) +{ + struct mail_private *p = (struct mail_private *)mail; + bool ret; + + T_BEGIN { + ret = p->v.set_uid(mail, uid); + } T_END; + return ret; +} + +bool mail_prefetch(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + bool ret; + + T_BEGIN { + ret = p->v.prefetch(mail); + } T_END; + return ret; +} + +void mail_add_temp_wanted_fields(struct mail *mail, + enum mail_fetch_field fields, + struct mailbox_header_lookup_ctx *headers) +{ + struct mail_private *p = (struct mail_private *)mail; + + i_assert(headers == NULL || headers->box == mail->box); + + p->v.add_temp_wanted_fields(mail, fields, headers); +} + +static bool index_mail_get_age_days(struct mail *mail, int *days_r) +{ + int age_days; + const struct mail_index_header *hdr = + mail_index_get_header(mail->transaction->view); + int n_days = N_ELEMENTS(hdr->day_first_uid); + + for (age_days = 0; age_days < n_days; age_days++) { + if (mail->uid >= hdr->day_first_uid[age_days]) + break; + } + + if (age_days == n_days) { + /* mail is too old, cannot determine its age from + day_first_uid[]. */ + return FALSE; + } + + if (hdr->day_stamp != 0) { + /* offset for hdr->day_stamp */ + age_days += (ioloop_time - hdr->day_stamp) / (3600 * 24); + } + *days_r = age_days; + return TRUE; +} + +void mail_event_create(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + int age_days; + + if (p->_event != NULL) + return; + p->_event = event_create(mail->box->event); + event_add_category(p->_event, &event_category_mail); + event_add_int(p->_event, "seq", mail->seq); + event_add_int(p->_event, "uid", mail->uid); + /* Add mail age field to event. */ + if (index_mail_get_age_days(mail, &age_days)) + event_add_int(p->_event, "mail_age_days", age_days); + + char uid_buf[MAX_INT_STRLEN]; + const char *prefix = t_strconcat( + p->mail.saving ? "saving UID " : "UID ", + dec2str_buf(uid_buf, p->mail.uid), ": ", NULL); + event_set_append_log_prefix(p->_event, prefix); +} + +struct event *mail_event(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + mail_event_create(mail); + return p->_event; +} + +enum mail_flags mail_get_flags(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_flags(mail); +} + +uint64_t mail_get_modseq(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_modseq(mail); +} + +uint64_t mail_get_pvt_modseq(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_pvt_modseq(mail); +} + +const char *const *mail_get_keywords(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_keywords(mail); +} + +const ARRAY_TYPE(keyword_indexes) *mail_get_keyword_indexes(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_keyword_indexes(mail); +} + +int mail_get_parts(struct mail *mail, struct message_part **parts_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_parts(mail, parts_r); + } T_END; + return ret; +} + +int mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_date(mail, date_r, timezone_r); + } T_END; + return ret; +} + +int mail_get_received_date(struct mail *mail, time_t *date_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_received_date(mail, date_r); + } T_END; + return ret; +} + +int mail_get_save_date(struct mail *mail, time_t *date_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_save_date(mail, date_r); + } T_END; + return ret; +} + +int mail_get_virtual_size(struct mail *mail, uoff_t *size_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_virtual_size(mail, size_r); + } T_END; + return ret; +} + +int mail_get_physical_size(struct mail *mail, uoff_t *size_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_physical_size(mail, size_r); + } T_END; + return ret; +} + +int mail_get_first_header(struct mail *mail, const char *field, + const char **value_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_first_header(mail, field, FALSE, value_r); + } T_END; + return ret; +} + +int mail_get_first_header_utf8(struct mail *mail, const char *field, + const char **value_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_first_header(mail, field, TRUE, value_r); + } T_END; + return ret; +} + +int mail_get_headers(struct mail *mail, const char *field, + const char *const **value_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_headers(mail, field, FALSE, value_r); + } T_END; + return ret; +} + +int mail_get_headers_utf8(struct mail *mail, const char *field, + const char *const **value_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.get_headers(mail, field, TRUE, value_r); + } T_END; + return ret; +} + +int mail_get_header_stream(struct mail *mail, + struct mailbox_header_lookup_ctx *headers, + struct istream **stream_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + i_assert(headers->count > 0); + i_assert(headers->box == mail->box); + + T_BEGIN { + ret = p->v.get_header_stream(mail, headers, stream_r); + } T_END; + return ret; +} + +void mail_set_aborted(struct mail *mail) +{ + mail_storage_set_error(mail->box->storage, MAIL_ERROR_LOOKUP_ABORTED, + "Mail field not cached"); +} + +int mail_get_stream(struct mail *mail, struct message_size *hdr_size, + struct message_size *body_size, struct istream **stream_r) +{ + return mail_get_stream_because(mail, hdr_size, body_size, + "mail stream", stream_r); +} + +int mail_get_stream_because(struct mail *mail, struct message_size *hdr_size, + struct message_size *body_size, + const char *reason, struct istream **stream_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + if (mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { + mail_set_aborted(mail); + return -1; + } + T_BEGIN { + p->get_stream_reason = reason; + ret = p->v.get_stream(mail, TRUE, hdr_size, body_size, stream_r); + p->get_stream_reason = ""; + } T_END; + i_assert(ret < 0 || (*stream_r)->blocking); + return ret; +} + +int mail_get_hdr_stream(struct mail *mail, struct message_size *hdr_size, + struct istream **stream_r) +{ + return mail_get_hdr_stream_because(mail, hdr_size, "header stream", stream_r); +} + +int mail_get_hdr_stream_because(struct mail *mail, + struct message_size *hdr_size, + const char *reason, struct istream **stream_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + if (mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { + mail_set_aborted(mail); + return -1; + } + T_BEGIN { + p->get_stream_reason = reason; + ret = p->v.get_stream(mail, FALSE, hdr_size, NULL, stream_r); + p->get_stream_reason = ""; + } T_END; + i_assert(ret < 0 || (*stream_r)->blocking); + return ret; +} + +int mail_get_binary_stream(struct mail *mail, const struct message_part *part, + bool include_hdr, uoff_t *size_r, + bool *binary_r, struct istream **stream_r) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + if (mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { + mail_set_aborted(mail); + return -1; + } + T_BEGIN { + ret = p->v.get_binary_stream(mail, part, include_hdr, + size_r, NULL, binary_r, stream_r); + } T_END; + i_assert(ret < 0 || (*stream_r)->blocking); + return ret; +} + +int mail_get_binary_size(struct mail *mail, const struct message_part *part, + bool include_hdr, uoff_t *size_r, + unsigned int *lines_r) +{ + struct mail_private *p = (struct mail_private *)mail; + bool binary; + int ret; + + T_BEGIN { + ret = p->v.get_binary_stream(mail, part, include_hdr, + size_r, lines_r, &binary, NULL); + } T_END; + return ret; +} + +int mail_get_special(struct mail *mail, enum mail_fetch_field field, + const char **value_r) +{ + struct mail_private *p = (struct mail_private *)mail; + + if (p->v.get_special(mail, field, value_r) < 0) + return -1; + i_assert(*value_r != NULL); + return 0; +} + +int mail_get_backend_mail(struct mail *mail, struct mail **real_mail_r) +{ + struct mail_private *p = (struct mail_private *)mail; + return p->v.get_backend_mail(mail, real_mail_r); +} + +int mail_get_message_id(struct mail *mail, const char **value_r) +{ + const char *hdr_value, *msgid_bare; + int ret; + + *value_r = NULL; + + ret = mail_get_first_header(mail, "Message-ID", &hdr_value); + if (ret <= 0) + return ret; + + msgid_bare = message_id_get_next(&hdr_value); + if (msgid_bare == NULL) + return 0; + + /* Complete the message ID with surrounding `<' and `>'. */ + *value_r = t_strconcat("<", msgid_bare, ">", NULL); + return 1; +} + +void mail_update_flags(struct mail *mail, enum modify_type modify_type, + enum mail_flags flags) +{ + struct mail_private *p = (struct mail_private *)mail; + + p->v.update_flags(mail, modify_type, flags); +} + +void mail_update_keywords(struct mail *mail, enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_private *p = (struct mail_private *)mail; + + p->v.update_keywords(mail, modify_type, keywords); +} + +void mail_update_modseq(struct mail *mail, uint64_t min_modseq) +{ + struct mail_private *p = (struct mail_private *)mail; + + p->v.update_modseq(mail, min_modseq); +} + +void mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq) +{ + struct mail_private *p = (struct mail_private *)mail; + + p->v.update_pvt_modseq(mail, min_pvt_modseq); +} + +void mail_update_pop3_uidl(struct mail *mail, const char *uidl) +{ + struct mail_private *p = (struct mail_private *)mail; + + if (p->v.update_pop3_uidl != NULL) + p->v.update_pop3_uidl(mail, uidl); +} + +void mail_expunge(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + T_BEGIN { + p->v.expunge(mail); + } T_END; + mail_expunge_requested_event(mail); +} + +void mail_autoexpunge(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + p->autoexpunged = TRUE; + mail_expunge(mail); + p->autoexpunged = FALSE; +} + +void mail_set_expunged(struct mail *mail) +{ + mail_storage_set_error(mail->box->storage, MAIL_ERROR_EXPUNGED, + "Message was expunged"); + mail->expunged = TRUE; +} + +int mail_precache(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + int ret; + + T_BEGIN { + ret = p->v.precache(mail); + } T_END; + return ret; +} + +void mail_set_cache_corrupted(struct mail *mail, + enum mail_fetch_field field, + const char *reason) +{ + struct mail_private *p = (struct mail_private *)mail; + p->v.set_cache_corrupted(mail, field, reason); +} + +void mail_generate_guid_128_hash(const char *guid, guid_128_t guid_128_r) +{ + unsigned char sha1_sum[SHA1_RESULTLEN]; + buffer_t buf; + + if (guid_128_from_string(guid, guid_128_r) < 0) { + /* not 128bit hex. use a hash of it instead. */ + buffer_create_from_data(&buf, guid_128_r, GUID_128_SIZE); + buffer_set_used_size(&buf, 0); + sha1_get_digest(guid, strlen(guid), sha1_sum); +#if SHA1_RESULTLEN < GUID_128_SIZE +# error not possible +#endif + buffer_append(&buf, + sha1_sum + SHA1_RESULTLEN - GUID_128_SIZE, + GUID_128_SIZE); + } +} + +static bool +mail_message_has_attachment(struct message_part *part, + const struct message_part_attachment_settings *set) +{ + for (; part != NULL; part = part->next) { + if (message_part_is_attachment(part, set) || + mail_message_has_attachment(part->children, set)) + return TRUE; + } + + return FALSE; +} + +bool mail_has_attachment_keywords(struct mail *mail) +{ + const char *const *kw = mail_get_keywords(mail); + return (str_array_icase_find(kw, MAIL_KEYWORD_HAS_ATTACHMENT) != + str_array_icase_find(kw, MAIL_KEYWORD_HAS_NO_ATTACHMENT)); +} + +static int mail_parse_parts(struct mail *mail, struct message_part **parts_r) +{ + const char *structure, *error; + struct mail_private *pmail = (struct mail_private*)mail; + + /* need to get bodystructure first */ + if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, + &structure) < 0) { + /* Don't bother logging an error. See + mail_set_attachment_keywords(). */ + return -1; + } + if (imap_bodystructure_parse_full(structure, pmail->data_pool, parts_r, + &error) < 0) { + mail_set_cache_corrupted(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, + error); + return -1; + } + return 0; +} + +int mail_set_attachment_keywords(struct mail *mail) +{ + int ret; + const struct mail_storage_settings *mail_set = + mail_storage_get_settings(mailbox_get_storage(mail->box)); + + const char *const keyword_has_attachment[] = { + MAIL_KEYWORD_HAS_ATTACHMENT, + NULL, + }; + const char *const keyword_has_no_attachment[] = { + MAIL_KEYWORD_HAS_NO_ATTACHMENT, + NULL + }; + struct message_part_attachment_settings set = { + .content_type_filter = + mail_set->parsed_mail_attachment_content_type_filter, + .exclude_inlined = + mail_set->parsed_mail_attachment_exclude_inlined, + }; + struct mail_keywords *kw_has = NULL, *kw_has_not = NULL; + + /* walk all parts and see if there is an attachment */ + struct message_part *parts; + if (mail_get_parts(mail, &parts) < 0) { + /* Callers don't really care about the exact error, and + critical errors were already logged. Most importantly we + don't want to log MAIL_ERROR_LOOKUP_ABORTED since that is + an expected error. */ + ret = -1; + } else if (parts->data == NULL && + mail_parse_parts(mail, &parts) < 0) { + ret = -1; + } else if (mailbox_keywords_create(mail->box, keyword_has_attachment, &kw_has) < 0 || + mailbox_keywords_create(mail->box, keyword_has_no_attachment, &kw_has_not) < 0) { + mail_set_critical(mail, "Failed to add attachment keywords: " + "mailbox_keywords_create(%s) failed: %s", + mailbox_get_vname(mail->box), + mail_storage_get_last_internal_error(mail->box->storage, NULL)); + ret = -1; + } else { + bool has_attachment = mail_message_has_attachment(parts, &set); + + /* make sure only one of the keywords gets set */ + mail_update_keywords(mail, MODIFY_REMOVE, has_attachment ? kw_has_not : kw_has); + mail_update_keywords(mail, MODIFY_ADD, has_attachment ? kw_has : kw_has_not); + ret = has_attachment ? 1 : 0; + } + + if (kw_has != NULL) + mailbox_keywords_unref(&kw_has); + if (kw_has_not != NULL) + mailbox_keywords_unref(&kw_has_not); + + return ret; +} + +bool mail_stream_access_start(struct mail *mail) +{ + if (mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { + mail_set_aborted(mail); + return FALSE; + } + mail->mail_stream_accessed = TRUE; + mail_event_create(mail); + return TRUE; +} + +bool mail_metadata_access_start(struct mail *mail) +{ + if (mail->lookup_abort >= MAIL_LOOKUP_ABORT_NOT_IN_CACHE) { + mail_set_aborted(mail); + return FALSE; + } + mail->mail_metadata_accessed = TRUE; + mail_event_create(mail); + return TRUE; +} + +void mail_opened_event(struct mail *mail) +{ + struct mail_private *pmail = + container_of(mail, struct mail_private, mail); + struct event_passthrough *e = + event_create_passthrough(mail_event(mail))-> + set_name("mail_opened")-> + add_str("reason", pmail->get_stream_reason); + if (pmail->get_stream_reason != NULL) + e_debug(e->event(), "Opened mail because: %s", + pmail->get_stream_reason); + else + e_debug(e->event(), "Opened mail"); +} + +void mail_expunge_requested_event(struct mail *mail) +{ + struct event_passthrough *e = + event_create_passthrough(mail_event(mail))-> + set_name("mail_expunge_requested")-> + add_int("uid", mail->uid)-> + add_int("seq", mail->seq); + e_debug(e->event(), "Expunge requested"); +} |