diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-lda/mail-deliver.c | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/src/lib-lda/mail-deliver.c b/src/lib-lda/mail-deliver.c new file mode 100644 index 0000000..e0f69da --- /dev/null +++ b/src/lib-lda/mail-deliver.c @@ -0,0 +1,810 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "str-sanitize.h" +#include "time-util.h" +#include "unichar.h" +#include "var-expand.h" +#include "message-address.h" +#include "smtp-address.h" +#include "lda-settings.h" +#include "mail-storage.h" +#include "mail-namespace.h" +#include "mail-storage-private.h" +#include "mail-duplicate.h" +#include "mail-deliver.h" + +#define DUPLICATE_DB_NAME "lda-dupes" + +#define MAIL_DELIVER_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_deliver_user_module) +#define MAIL_DELIVER_STORAGE_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mail_deliver_storage_module) + +struct event_category event_category_mail_delivery = { + .name = "local-delivery", +}; + +struct mail_deliver_user { + union mail_user_module_context module_ctx; + struct mail_deliver_context *deliver_ctx; + bool want_storage_id; +}; + +deliver_mail_func_t *deliver_mail = NULL; + +struct mail_deliver_mailbox { + union mailbox_module_context module_ctx; +}; + +struct mail_deliver_transaction { + union mailbox_transaction_module_context module_ctx; + + struct mail_deliver_fields deliver_fields; +}; + +static const char *lda_log_wanted_headers[] = { + "From", "Message-ID", "Subject", + NULL +}; +static enum mail_fetch_field lda_log_wanted_fetch_fields = + MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE; +static MODULE_CONTEXT_DEFINE_INIT(mail_deliver_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mail_deliver_storage_module, + &mail_storage_module_register); + +static struct message_address * +mail_deliver_get_message_address(struct mail *mail, const char *header) +{ + struct message_address *addr; + const char *str; + + if (mail_get_first_header(mail, header, &str) <= 0) + return NULL; + addr = message_address_parse(pool_datastack_create(), + (const unsigned char *)str, + strlen(str), 1, 0); + if (addr == NULL || addr->mailbox == NULL || addr->domain == NULL || + *addr->mailbox == '\0' || *addr->domain == '\0') + return NULL; + return addr; +} + +const struct smtp_address * +mail_deliver_get_address(struct mail *mail, const char *header) +{ + struct message_address *addr; + struct smtp_address *smtp_addr; + + addr = mail_deliver_get_message_address(mail, header); + if (addr == NULL || + smtp_address_create_from_msg_temp(addr, &smtp_addr) < 0) + return NULL; + return smtp_addr; +} + +static void +mail_deliver_update_event(struct mail_deliver_context *ctx) +{ + event_add_str(ctx->event, "message_id", ctx->fields.message_id); + event_add_str(ctx->event, "message_subject", ctx->fields.subject); + event_add_str(ctx->event, "message_from", ctx->fields.from); + if (ctx->fields.psize != UOFF_T_MAX) + event_add_int(ctx->event, "message_size", ctx->fields.psize); + if (ctx->fields.vsize != UOFF_T_MAX) + event_add_int(ctx->event, "message_vsize", ctx->fields.vsize); +} + +static void +update_str_field(pool_t pool, const char **old_str, const char *new_str) +{ + if (new_str == NULL || new_str[0] == '\0') + *old_str = NULL; + else if (*old_str == NULL || strcmp(*old_str, new_str) != 0) + *old_str = p_strdup(pool, new_str); +} + +static void +mail_deliver_fields_update(struct mail_deliver_fields *fields, pool_t pool, + struct mail *mail) +{ + const char *message_id = NULL, *subject = NULL, *from_envelope = NULL; + static struct message_address *from_addr; + const char *from; + + if (fields->filled) + return; + fields->filled = TRUE; + + if (mail_get_message_id(mail, &message_id) > 0) + message_id = str_sanitize(message_id, 200); + update_str_field(pool, &fields->message_id, message_id); + + if (mail_get_first_header_utf8(mail, "Subject", &subject) > 0) + subject = str_sanitize(subject, 80); + update_str_field(pool, &fields->subject, subject); + + from_addr = mail_deliver_get_message_address(mail, "From"); + from = (from_addr == NULL ? NULL : + t_strconcat(from_addr->mailbox, "@", from_addr->domain, NULL)); + update_str_field(pool, &fields->from, from); + + if (mail_get_special(mail, MAIL_FETCH_FROM_ENVELOPE, &from_envelope) > 0) + from_envelope = str_sanitize(from_envelope, 80); + update_str_field(pool, &fields->from_envelope, from_envelope); + + if (mail_get_physical_size(mail, &fields->psize) < 0) + fields->psize = 0; + if (mail_get_virtual_size(mail, &fields->vsize) < 0) + fields->vsize = 0; +} + +const struct var_expand_table * +mail_deliver_ctx_get_log_var_expand_table(struct mail_deliver_context *ctx, + const char *message) +{ + unsigned int delivery_time_msecs; + + /* If a mail was saved/copied, the fields are already filled and the + following call is ignored. Otherwise, only the source mail exists. */ + mail_deliver_fields_update(&ctx->fields, ctx->pool, ctx->src_mail); + /* This call finishes a mail delivery. With Sieve there may be multiple + mail deliveries. */ + ctx->fields.filled = FALSE; + + mail_deliver_update_event(ctx); + + io_loop_time_refresh(); + delivery_time_msecs = timeval_diff_msecs(&ioloop_timeval, + &ctx->delivery_time_started); + + const struct var_expand_table stack_tab[] = { + { '$', message, NULL }, + { 'm', ctx->fields.message_id != NULL ? + ctx->fields.message_id : "unspecified", "msgid" }, + { 's', ctx->fields.subject, "subject" }, + { 'f', ctx->fields.from, "from" }, + { 'e', ctx->fields.from_envelope, "from_envelope" }, + { 'p', dec2str(ctx->fields.psize), "size" }, + { 'w', dec2str(ctx->fields.vsize), "vsize" }, + { '\0', dec2str(delivery_time_msecs), "delivery_time" }, + { '\0', dec2str(ctx->session_time_msecs), "session_time" }, + { '\0', smtp_address_encode(ctx->rcpt_params.orcpt.addr), "to_envelope" }, + { '\0', ctx->fields.storage_id, "storage_id" }, + { '\0', NULL, NULL } + }; + return p_memdup(unsafe_data_stack_pool, stack_tab, sizeof(stack_tab)); +} + +void mail_deliver_log(struct mail_deliver_context *ctx, const char *fmt, ...) +{ + va_list args; + string_t *str; + const struct var_expand_table *tab; + const char *msg, *error; + + if (*ctx->set->deliver_log_format == '\0') + return; + + va_start(args, fmt); + msg = t_strdup_vprintf(fmt, args); + + str = t_str_new(256); + tab = mail_deliver_ctx_get_log_var_expand_table(ctx, msg); + if (var_expand(str, ctx->set->deliver_log_format, tab, &error) <= 0) { + e_error(ctx->event, + "Failed to expand deliver_log_format=%s: %s", + ctx->set->deliver_log_format, error); + } + + e_info(ctx->event, "%s", str_c(str)); + va_end(args); +} + +struct mail_deliver_session *mail_deliver_session_init(void) +{ + struct mail_deliver_session *session; + pool_t pool; + + pool = pool_alloconly_create("mail deliver session", 1024); + session = p_new(pool, struct mail_deliver_session, 1); + session->pool = pool; + return session; +} + +void mail_deliver_session_deinit(struct mail_deliver_session **_session) +{ + struct mail_deliver_session *session = *_session; + + *_session = NULL; + pool_unref(&session->pool); +} + +int mail_deliver_save_open(struct mail_deliver_save_open_context *ctx, + const char *name, struct mailbox **box_r, + enum mail_error *error_r, const char **error_str_r) +{ + struct mailbox *box; + enum mailbox_flags flags = MAILBOX_FLAG_POST_SESSION; + + *box_r = NULL; + *error_r = MAIL_ERROR_NONE; + *error_str_r = NULL; + + if (!uni_utf8_str_is_valid(name)) { + *error_str_r = "Mailbox name not valid UTF-8"; + *error_r = MAIL_ERROR_PARAMS; + return -1; + } + + if (ctx->lda_mailbox_autocreate) + flags |= MAILBOX_FLAG_AUTO_CREATE; + if (ctx->lda_mailbox_autosubscribe) + flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE; + *box_r = box = mailbox_alloc_for_user(ctx->user, name, flags); + + if (mailbox_open(box) == 0) + return 0; + *error_str_r = mailbox_get_last_internal_error(box, error_r); + return -1; +} + +static bool mail_deliver_check_duplicate(struct mail_deliver_session *session, + struct mailbox *box) +{ + struct mailbox_metadata metadata; + const guid_128_t *guid; + + if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) { + /* just play it safe and assume a duplicate */ + return TRUE; + } + + /* there shouldn't be all that many recipients, + so just do a linear search */ + if (!array_is_created(&session->inbox_guids)) + p_array_init(&session->inbox_guids, session->pool, 8); + array_foreach(&session->inbox_guids, guid) { + if (memcmp(metadata.guid, *guid, sizeof(metadata.guid)) == 0) + return TRUE; + } + array_push_back(&session->inbox_guids, &metadata.guid); + return FALSE; +} + +void mail_deliver_deduplicate_guid_if_needed(struct mail_deliver_session *session, + struct mail_save_context *save_ctx) +{ + struct mailbox_transaction_context *trans = + mailbox_save_get_transaction(save_ctx); + struct mailbox *box = mailbox_transaction_get_mailbox(trans); + guid_128_t guid; + + if (strcmp(mailbox_get_name(box), "INBOX") != 0) + return; + + /* avoid storing duplicate GUIDs to delivered mails to INBOX. this + happens if mail is delivered to same user multiple times within a + session. the problem with this is that if GUIDs are used as POP3 + UIDLs, some clients can't handle the duplicates well. */ + if (mail_deliver_check_duplicate(session, box)) { + guid_128_generate(guid); + mailbox_save_set_guid(save_ctx, guid_128_to_string(guid)); + } +} + +void mail_deliver_init(struct mail_deliver_context *ctx, + struct mail_deliver_input *input) +{ + i_zero(ctx); + ctx->set = input->set; + ctx->smtp_set = input->smtp_set; + + ctx->session = input->session; + ctx->pool = input->session->pool; + pool_ref(ctx->pool); + + ctx->session_time_msecs = input->session_time_msecs; + ctx->delivery_time_started = input->delivery_time_started; + ctx->session_id = p_strdup(ctx->pool, input->session_id); + ctx->src_mail = input->src_mail; + ctx->save_dest_mail = input->save_dest_mail; + + ctx->mail_from = smtp_address_clone(ctx->pool, input->mail_from); + smtp_params_mail_copy(ctx->pool, &ctx->mail_params, + &input->mail_params); + ctx->rcpt_to = smtp_address_clone(ctx->pool, input->rcpt_to); + smtp_params_rcpt_copy(ctx->pool, &ctx->rcpt_params, + &input->rcpt_params); + ctx->rcpt_user = input->rcpt_user; + ctx->rcpt_default_mailbox = p_strdup(ctx->pool, + input->rcpt_default_mailbox); + + ctx->event = event_create(input->event_parent); + event_add_category(ctx->event, &event_category_mail_delivery); + + mail_deliver_fields_update(&ctx->fields, ctx->pool, ctx->src_mail); + mail_deliver_update_event(ctx); + + if (ctx->rcpt_to != NULL) { + event_add_str(ctx->event, "rcpt_to", + smtp_address_encode(ctx->rcpt_to)); + } + smtp_params_rcpt_add_to_event(&ctx->rcpt_params, ctx->event); +} + +void mail_deliver_deinit(struct mail_deliver_context *ctx) +{ + event_unref(&ctx->event); + pool_unref(&ctx->pool); +} + +static struct mail * +mail_deliver_open_mail(struct mailbox *box, uint32_t uid, + enum mail_fetch_field wanted_fields, + struct mailbox_transaction_context **trans_r) +{ + struct mailbox_transaction_context *t; + struct mail *mail; + + *trans_r = NULL; + + if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) + return NULL; + + t = mailbox_transaction_begin(box, 0, __func__); + mail = mail_alloc(t, wanted_fields, NULL); + + if (!mail_set_uid(mail, uid)) { + mail_free(&mail); + mailbox_transaction_rollback(&t); + } + *trans_r = t; + return mail; +} + +int mail_deliver_save(struct mail_deliver_context *ctx, const char *mailbox, + enum mail_flags flags, const char *const *keywords, + struct mail_storage **storage_r) +{ + struct mail_deliver_save_open_context open_ctx; + struct mailbox *box; + enum mailbox_transaction_flags trans_flags; + struct mailbox_transaction_context *t; + struct mail_save_context *save_ctx; + struct mailbox_header_lookup_ctx *headers_ctx; + struct mail_keywords *kw; + struct mail *dest_mail; + enum mail_error error; + const char *mailbox_name, *errstr, *guid; + struct mail_transaction_commit_changes changes; + bool default_save; + int ret = 0; + + i_assert(ctx->dest_mail == NULL); + + default_save = strcmp(mailbox, ctx->rcpt_default_mailbox) == 0; + if (default_save) + ctx->tried_default_save = TRUE; + + i_zero(&open_ctx); + open_ctx.user = ctx->rcpt_user; + open_ctx.lda_mailbox_autocreate = ctx->set->lda_mailbox_autocreate; + open_ctx.lda_mailbox_autosubscribe = ctx->set->lda_mailbox_autosubscribe; + + mailbox_name = str_sanitize(mailbox, 80); + if (mail_deliver_save_open(&open_ctx, mailbox, &box, + &error, &errstr) < 0) { + if (box != NULL) { + *storage_r = mailbox_get_storage(box); + mailbox_free(&box); + } + mail_deliver_log(ctx, "save failed to open mailbox %s: %s", + mailbox_name, errstr); + return -1; + } + *storage_r = mailbox_get_storage(box); + + trans_flags = MAILBOX_TRANSACTION_FLAG_EXTERNAL; + if (ctx->save_dest_mail) + trans_flags |= MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS; + t = mailbox_transaction_begin(box, trans_flags, __func__); + + kw = str_array_length(keywords) == 0 ? NULL : + mailbox_keywords_create_valid(box, keywords); + save_ctx = mailbox_save_alloc(t); + if (ctx->mail_from != NULL) { + mailbox_save_set_from_envelope(save_ctx, + smtp_address_encode(ctx->mail_from)); + } + mailbox_save_set_flags(save_ctx, flags, kw); + + headers_ctx = mailbox_header_lookup_init(box, lda_log_wanted_headers); + dest_mail = mailbox_save_get_dest_mail(save_ctx); + mail_add_temp_wanted_fields(dest_mail, lda_log_wanted_fetch_fields, NULL); + mailbox_header_lookup_unref(&headers_ctx); + mail_deliver_deduplicate_guid_if_needed(ctx->session, save_ctx); + + if (mailbox_save_using_mail(&save_ctx, ctx->src_mail) < 0) + ret = -1; + if (kw != NULL) + mailbox_keywords_unref(&kw); + + if (ret < 0) + mailbox_transaction_rollback(&t); + else + ret = mailbox_transaction_commit_get_changes(&t, &changes); + + if (ret == 0) { + ctx->saved_mail = TRUE; + if (ctx->save_dest_mail) { + /* copying needs the message body. with maildir we also + need to get the GUID in case the message gets + expunged. get these early so the copying won't fail + later on. */ + i_assert(array_count(&changes.saved_uids) == 1); + const struct seq_range *range = + array_front(&changes.saved_uids); + i_assert(range->seq1 == range->seq2); + ctx->dest_mail = mail_deliver_open_mail(box, range->seq1, + MAIL_FETCH_STREAM_BODY | MAIL_FETCH_GUID, &t); + if (ctx->dest_mail == NULL) { + i_assert(t == NULL); + } else if (mail_get_special(ctx->dest_mail, MAIL_FETCH_GUID, &guid) < 0) { + mail_free(&ctx->dest_mail); + mailbox_transaction_rollback(&t); + } + } + mail_deliver_log(ctx, "saved mail to %s", mailbox_name); + pool_unref(&changes.pool); + } else { + mail_deliver_log(ctx, "save failed to %s: %s", mailbox_name, + mail_storage_get_last_internal_error(*storage_r, &error)); + } + + if (ctx->dest_mail == NULL) + mailbox_free(&box); + return ret; +} + +const struct smtp_address * +mail_deliver_get_return_address(struct mail_deliver_context *ctx) +{ + struct message_address *addr; + struct smtp_address *smtp_addr; + const char *path; + int ret; + + if (!smtp_address_isnull(ctx->mail_from)) + return ctx->mail_from; + + if ((ret=mail_get_first_header(ctx->src_mail, + "Return-Path", &path)) <= 0) { + if (ret < 0) { + struct mailbox *box = ctx->src_mail->box; + e_warning(ctx->event, + "Failed read return-path header: %s", + mailbox_get_last_internal_error(box, NULL)); + } + return NULL; + } + if (message_address_parse_path(pool_datastack_create(), + (const unsigned char *)path, + strlen(path), &addr) < 0 || + smtp_address_create_from_msg(ctx->pool, addr, &smtp_addr) < 0) { + e_warning(ctx->event, "Failed to parse return-path header"); + return NULL; + } + return smtp_addr; +} + +const char *mail_deliver_get_new_message_id(struct mail_deliver_context *ctx) +{ + static int count = 0; + struct mail_user *user = ctx->rcpt_user; + const struct mail_storage_settings *mail_set = + mail_user_set_get_storage_set(user); + + return t_strdup_printf("<dovecot-%s-%s-%d@%s>", + dec2str(ioloop_timeval.tv_sec), + dec2str(ioloop_timeval.tv_usec), + count++, mail_set->hostname); +} + +static bool mail_deliver_is_tempfailed(struct mail_deliver_context *ctx, + struct mail_storage *storage) +{ + enum mail_error error; + + if (ctx->tempfail_error != NULL) + return TRUE; + if (storage != NULL) { + (void)mail_storage_get_last_error(storage, &error); + return error == MAIL_ERROR_TEMP; + } + return FALSE; +} + +static int +mail_do_deliver(struct mail_deliver_context *ctx, + struct mail_storage **storage_r) +{ + int ret; + + *storage_r = NULL; + if (deliver_mail == NULL) + ret = -1; + else { + ctx->dup_db = mail_duplicate_db_init(ctx->rcpt_user, + DUPLICATE_DB_NAME); + if (deliver_mail(ctx, storage_r) <= 0) { + /* if message was saved, don't bounce it even though + the script failed later. */ + ret = ctx->saved_mail ? 0 : -1; + } else { + /* success. message may or may not have been saved. */ + ret = 0; + } + mail_duplicate_db_deinit(&ctx->dup_db); + if (ret < 0 && mail_deliver_is_tempfailed(ctx, *storage_r)) + return -1; + } + + if (ret < 0 && !ctx->tried_default_save) { + /* plugins didn't handle this. save into the default mailbox. */ + ret = mail_deliver_save(ctx, ctx->rcpt_default_mailbox, 0, NULL, + storage_r); + if (ret < 0 && mail_deliver_is_tempfailed(ctx, *storage_r)) + return -1; + } + if (ret < 0 && strcasecmp(ctx->rcpt_default_mailbox, "INBOX") != 0) { + /* still didn't work. try once more to save it + to INBOX. */ + ret = mail_deliver_save(ctx, "INBOX", 0, NULL, storage_r); + } + return ret; +} + +int mail_deliver(struct mail_deliver_context *ctx, + enum mail_deliver_error *error_code_r, + const char **error_r) +{ + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(ctx->rcpt_user); + struct event_passthrough *e; + struct mail_storage *storage = NULL; + enum mail_deliver_error error_code = MAIL_DELIVER_ERROR_NONE; + const char *error = NULL; + int ret; + + i_assert(muser->deliver_ctx == NULL); + + mail_deliver_fields_update(&ctx->fields, ctx->pool, ctx->src_mail); + mail_deliver_update_event(ctx); + + muser->want_storage_id = + var_has_key(ctx->set->deliver_log_format, '\0', "storage_id"); + + muser->deliver_ctx = ctx; + + e = event_create_passthrough(ctx->event)-> + set_name("mail_delivery_started"); + e_debug(e->event(), "Local delivery started"); + + ret = mail_do_deliver(ctx, &storage); + + if (ret >= 0) + i_assert(ret == 0); /* ret > 0 has no defined meaning */ + else if (ctx->tempfail_error != NULL) { + error = ctx->tempfail_error; + error_code = MAIL_DELIVER_ERROR_TEMPORARY; + } else if (storage != NULL) { + enum mail_error mail_error; + + error = mail_storage_get_last_error(storage, &mail_error); + if (mail_error == MAIL_ERROR_NOQUOTA) { + error_code = MAIL_DELIVER_ERROR_NOQUOTA; + } else { + error_code = MAIL_DELIVER_ERROR_TEMPORARY; + } + } else { + /* This shouldn't happen */ + e_error(ctx->event, "BUG: Saving failed to unknown storage"); + error = "Temporary internal error"; + error_code = MAIL_DELIVER_ERROR_INTERNAL; + } + + e = event_create_passthrough(ctx->event)-> + set_name("mail_delivery_finished"); + if (ret == 0) { + e_debug(e->event(), "Local delivery finished successfully"); + } else { + e->add_str("error", error); + e_debug(e->event(), "Local delivery failed: %s", error); + } + + muser->deliver_ctx = NULL; + + *error_code_r = error_code; + *error_r = error; + return ret; +} + +deliver_mail_func_t *mail_deliver_hook_set(deliver_mail_func_t *new_hook) +{ + deliver_mail_func_t *old_hook = deliver_mail; + + deliver_mail = new_hook; + return old_hook; +} + +static int mail_deliver_save_finish(struct mail_save_context *ctx) +{ + struct mailbox *box = ctx->transaction->box; + struct mail_deliver_mailbox *mbox = MAIL_DELIVER_STORAGE_CONTEXT(box); + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(box->storage->user); + struct mail_deliver_transaction *dt = + MAIL_DELIVER_STORAGE_CONTEXT(ctx->transaction); + + if (mbox->module_ctx.super.save_finish(ctx) < 0) + return -1; + + /* initialize most of the fields from dest_mail */ + mail_deliver_fields_update(&dt->deliver_fields, + muser->deliver_ctx->pool, + ctx->dest_mail); + return 0; +} + +static int mail_deliver_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox *box = ctx->transaction->box; + struct mail_deliver_mailbox *mbox = MAIL_DELIVER_STORAGE_CONTEXT(box); + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(box->storage->user); + struct mail_deliver_transaction *dt = + MAIL_DELIVER_STORAGE_CONTEXT(ctx->transaction); + + if (mbox->module_ctx.super.copy(ctx, mail) < 0) + return -1; + + /* initialize most of the fields from dest_mail */ + mail_deliver_fields_update(&dt->deliver_fields, + muser->deliver_ctx->pool, + ctx->dest_mail); + return 0; +} + +static void +mail_deliver_fields_update_post_commit(struct mailbox *orig_box, uint32_t uid) +{ + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(orig_box->storage->user); + struct mailbox *box; + struct mailbox_transaction_context *t; + struct mail *mail; + const char *storage_id; + + if (!muser->want_storage_id) + return; + + /* getting storage_id requires a whole new mailbox view that is + synced, so it'll contain the newly written mail. this is racy, so + it's possible another process has already deleted the mail. */ + box = mailbox_alloc(orig_box->list, orig_box->vname, 0); + mail = mail_deliver_open_mail(box, uid, MAIL_FETCH_STORAGE_ID, &t); + if (mail != NULL) { + if (mail_get_special(mail, MAIL_FETCH_STORAGE_ID, &storage_id) < 0 || + storage_id[0] == '\0') + storage_id = NULL; + muser->deliver_ctx->fields.storage_id = + p_strdup(muser->deliver_ctx->pool, storage_id); + mail_free(&mail); + (void)mailbox_transaction_commit(&t); + } else { + muser->deliver_ctx->fields.storage_id = NULL; + } + mailbox_free(&box); +} + +static struct mailbox_transaction_context * +mail_deliver_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct mail_deliver_mailbox *mbox = MAIL_DELIVER_STORAGE_CONTEXT(box); + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(box->storage->user); + struct mailbox_transaction_context *t; + struct mail_deliver_transaction *dt; + + i_assert(muser->deliver_ctx != NULL); + + t = mbox->module_ctx.super.transaction_begin(box, flags, reason); + dt = p_new(muser->deliver_ctx->pool, struct mail_deliver_transaction, 1); + + MODULE_CONTEXT_SET(t, mail_deliver_storage_module, dt); + return t; +} + +static int +mail_deliver_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct mailbox *box = ctx->box; + struct mail_deliver_mailbox *mbox = MAIL_DELIVER_STORAGE_CONTEXT(box); + struct mail_deliver_transaction *dt = MAIL_DELIVER_STORAGE_CONTEXT(ctx); + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(box->storage->user); + + i_assert(muser->deliver_ctx != NULL); + + /* sieve creates multiple transactions, saves the mails and + then commits all of them at the end. we'll need to keep + switching the deliver_ctx->fields for each commit. + + we also want to do this only for commits generated by sieve. + other plugins or storage backends may be creating transactions as + well, which we need to ignore. */ + if ((box->flags & MAILBOX_FLAG_POST_SESSION) != 0) { + muser->deliver_ctx->fields = dt->deliver_fields; + mail_deliver_update_event(muser->deliver_ctx); + } + + if (mbox->module_ctx.super.transaction_commit(ctx, changes_r) < 0) + return -1; + + if (array_count(&changes_r->saved_uids) > 0) { + const struct seq_range *range = + array_front(&changes_r->saved_uids); + + mail_deliver_fields_update_post_commit(box, range->seq1); + } + return 0; +} + +static void mail_deliver_mail_user_created(struct mail_user *user) +{ + struct mail_deliver_user *muser; + + muser = p_new(user->pool, struct mail_deliver_user, 1); + MODULE_CONTEXT_SET(user, mail_deliver_user_module, muser); +} + +static void mail_deliver_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mail_deliver_mailbox *mbox; + struct mail_deliver_user *muser = + MAIL_DELIVER_USER_CONTEXT(box->storage->user); + + /* we are doing something other than lda/lmtp delivery + and should not be involved */ + if (muser->deliver_ctx == NULL) + return; + + mbox = p_new(box->pool, struct mail_deliver_mailbox, 1); + mbox->module_ctx.super = *v; + box->vlast = &mbox->module_ctx.super; + v->save_finish = mail_deliver_save_finish; + v->copy = mail_deliver_copy; + v->transaction_begin = mail_deliver_transaction_begin; + v->transaction_commit = mail_deliver_transaction_commit; + + MODULE_CONTEXT_SET(box, mail_deliver_storage_module, mbox); + } + +static struct mail_storage_hooks mail_deliver_hooks = { + .mail_user_created = mail_deliver_mail_user_created, + .mailbox_allocated = mail_deliver_mailbox_allocated +}; + +void mail_deliver_hooks_init(void) +{ + mail_storage_hooks_add_internal(&mail_deliver_hooks); +} |