diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c')
-rw-r--r-- | pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c | 1273 |
1 files changed, 1273 insertions, 0 deletions
diff --git a/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c b/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c new file mode 100644 index 0000000..f8e5efe --- /dev/null +++ b/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c @@ -0,0 +1,1273 @@ +/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file */ + +#include "imap-common.h" +#include "array.h" +#include "hash.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "module-context.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mailbox-attribute.h" +#include "mailbox-list-private.h" +#include "imap-match.h" +#include "imap-util.h" + +#include "imap-sieve.h" +#include "imap-sieve-storage.h" + +#define MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT "imapsieve/script" +#define MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT "imapsieve/script" + +#define IMAP_SIEVE_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_sieve_user_module) +#define IMAP_SIEVE_USER_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, imap_sieve_user_module) +#define IMAP_SIEVE_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_sieve_storage_module) +#define IMAP_SIEVE_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, imap_sieve_storage_module) +#define IMAP_SIEVE_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, imap_sieve_mail_module) + +struct imap_sieve_mailbox_rule; +struct imap_sieve_user; +struct imap_sieve_mailbox_event; +struct imap_sieve_mailbox_transaction; +struct imap_sieve_mail; + +enum imap_sieve_command { + IMAP_SIEVE_CMD_NONE = 0, + IMAP_SIEVE_CMD_APPEND, + IMAP_SIEVE_CMD_COPY, + IMAP_SIEVE_CMD_MOVE, + IMAP_SIEVE_CMD_STORE, + IMAP_SIEVE_CMD_OTHER +}; + +ARRAY_DEFINE_TYPE(imap_sieve_mailbox_rule, + struct imap_sieve_mailbox_rule *); +ARRAY_DEFINE_TYPE(imap_sieve_mailbox_event, + struct imap_sieve_mailbox_event); + +HASH_TABLE_DEFINE_TYPE(imap_sieve_mailbox_rule, + struct imap_sieve_mailbox_rule *, + struct imap_sieve_mailbox_rule *); + +struct imap_sieve_mailbox_rule { + unsigned int index; + const char *mailbox; + const char *from; + const char *const *causes; + const char *before, *after; + const char *copy_source_after; +}; + +struct imap_sieve_user { + union mail_user_module_context module_ctx; + struct client *client; + struct imap_sieve *isieve; + + enum imap_sieve_command cur_cmd; + + HASH_TABLE_TYPE(imap_sieve_mailbox_rule) mbox_rules; + ARRAY_TYPE(imap_sieve_mailbox_rule) mbox_patterns; + + bool sieve_active:1; + bool user_script:1; + bool expunge_discarded:1; +}; + +struct imap_sieve_mailbox_event { + uint32_t dest_mail_uid, src_mail_uid; + unsigned int save_seq; + + const char *changed_flags; +}; + +struct imap_sieve_mailbox_transaction { + pool_t pool; + + union mailbox_transaction_module_context module_ctx; + + struct mailbox *src_box; + struct mailbox_transaction_context *src_mail_trans; + + ARRAY_TYPE(imap_sieve_mailbox_event) events; +}; + +struct imap_sieve_mail { + union mail_module_context module_ctx; + + string_t *flags; +}; + +static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_mail_module, + &mail_module_register); + +static void +imap_sieve_mailbox_rules_get(struct mail_user *user, + struct mailbox *dst_box, struct mailbox *src_box, + const char *cause, + ARRAY_TYPE(imap_sieve_mailbox_rule) *rules); + +/* + * Logging + */ + +static inline void +imap_sieve_debug(struct mail_user *user, + const char *format, ...) ATTR_FORMAT(2, 3); +static inline void +imap_sieve_debug(struct mail_user *user, + const char *format, ...) +{ + va_list args; + + if (user->mail_debug) { + va_start(args, format); + i_debug("imapsieve: %s", + t_strdup_vprintf(format, args)); + va_end(args); + } +} + +static inline void +imap_sieve_warning(struct mail_user *user ATTR_UNUSED, + const char *format, ...) ATTR_FORMAT(2, 3); +static inline void +imap_sieve_warning(struct mail_user *user ATTR_UNUSED, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + i_warning("imapsieve: %s", + t_strdup_vprintf(format, args)); + va_end(args); +} + +static inline void +imap_sieve_mailbox_debug(struct mailbox *box, + const char *format, ...) ATTR_FORMAT(2, 3); +static inline void +imap_sieve_mailbox_debug(struct mailbox *box, + const char *format, ...) +{ + va_list args; + + if (box->storage->user->mail_debug) { + va_start(args, format); + i_debug("imapsieve: mailbox %s: %s", + mailbox_get_vname(box), + t_strdup_vprintf(format, args)); + va_end(args); + } +} + +static inline void +imap_sieve_mailbox_warning(struct mailbox *box, + const char *format, ...) ATTR_FORMAT(2, 3); +static inline void +imap_sieve_mailbox_warning(struct mailbox *box, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + i_warning("imapsieve: mailbox %s: %s", + mailbox_get_vname(box), + t_strdup_vprintf(format, args)); + va_end(args); +} + +static inline void +imap_sieve_mailbox_error(struct mailbox *box, + const char *format, ...) ATTR_FORMAT(2, 3); +static inline void +imap_sieve_mailbox_error(struct mailbox *box, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + i_error("imapsieve: mailbox %s: %s", + mailbox_get_vname(box), + t_strdup_vprintf(format, args)); + va_end(args); +} + +/* + * Events + */ + +static int imap_sieve_mailbox_get_script_real +(struct mailbox *box, + const char **script_name_r) +{ + struct mail_user *user = box->storage->user; + struct mail_attribute_value value; + int ret; + + *script_name_r = NULL; + + /* get the name of the Sieve script from mailbox METADATA */ + if ((ret=mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED, + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value)) < 0) { + imap_sieve_mailbox_error(box, + "Failed to read /shared/" + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "mailbox attribute: %s", + mailbox_get_last_internal_error(box, NULL)); + return -1; + } + + if (ret > 0) { + imap_sieve_mailbox_debug(box, + "Mailbox attribute /shared/" + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "points to Sieve script `%s'", value.value); + + /* if not found, get the name of the Sieve script from + server METADATA */ + } else { + struct mail_namespace *ns; + struct mailbox *inbox; + + imap_sieve_mailbox_debug(box, + "Mailbox attribute /shared/" + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "not found"); + + ns = mail_namespace_find_inbox(user->namespaces); + inbox = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + ret = mailbox_attribute_get(inbox, + MAIL_ATTRIBUTE_TYPE_SHARED, + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value); + + if (ret <= 0) { + if (ret < 0) { + imap_sieve_mailbox_error(box, + "Failed to read /shared/" + MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "server attribute: %s", + mailbox_get_last_internal_error(inbox, NULL)); + } else if (ret == 0) { + imap_sieve_mailbox_debug(box, + "Server attribute /shared/" + MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "not found"); + } + mailbox_free(&inbox); + return ret; + } + mailbox_free(&inbox); + + imap_sieve_mailbox_debug(box, + "Server attribute /shared/" + MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "points to Sieve script `%s'", value.value); + } + + *script_name_r = value.value; + return 1; +} + +static int imap_sieve_mailbox_get_script +(struct mailbox *box, const char **script_name_r) +{ + int ret; + + ret = imap_sieve_mailbox_get_script_real + (box, script_name_r); + return ret; +} + +static struct imap_sieve_mailbox_event * +imap_sieve_create_mailbox_event +(struct mailbox_transaction_context *t, struct mail *dest_mail) +{ + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t); + struct imap_sieve_mailbox_event *event; + + if (!array_is_created(&ismt->events)) + i_array_init(&ismt->events, 64); + + event = array_append_space(&ismt->events); + event->save_seq = t->save_count; + event->dest_mail_uid = dest_mail->uid; + return event; +} + +static void imap_sieve_add_mailbox_event +(struct mailbox_transaction_context *t, + struct mail *dest_mail, struct mailbox *src_box, + const char *changed_flags) +{ + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t); + struct imap_sieve_mailbox_event *event; + + i_assert(ismt->src_box == NULL || ismt->src_box == src_box); + ismt->src_box = src_box; + + event = imap_sieve_create_mailbox_event(t, dest_mail); + event->changed_flags = p_strdup(ismt->pool, changed_flags); +} + +static void imap_sieve_add_mailbox_copy_event +(struct mailbox_transaction_context *t, + struct mail *dest_mail, struct mail *src_mail) +{ + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t); + struct imap_sieve_mailbox_event *event; + + i_assert(ismt->src_box == NULL || ismt->src_box == src_mail->box); + i_assert(ismt->src_mail_trans == NULL || + ismt->src_mail_trans == src_mail->transaction); + + ismt->src_box = src_mail->box; + ismt->src_mail_trans = src_mail->transaction; + + event = imap_sieve_create_mailbox_event(t, dest_mail); + event->src_mail_uid = src_mail->uid; +} + +/* + * Mail + */ + +static void +imap_sieve_mail_update_flags(struct mail *_mail, + enum modify_type modify_type, enum mail_flags flags) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail); + enum mail_flags old_flags, new_flags, changed_flags; + + old_flags = mail_get_flags(_mail); + ismail->module_ctx.super.update_flags(_mail, modify_type, flags); + new_flags = mail_get_flags(_mail); + + changed_flags = old_flags ^ new_flags; + if (changed_flags == 0) + return; + + if (ismail->flags == NULL) + ismail->flags = str_new(default_pool, 64); + imap_write_flags(ismail->flags, changed_flags, NULL); +} + +static void +imap_sieve_mail_update_keywords(struct mail *_mail, + enum modify_type modify_type, struct mail_keywords *keywords) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail); + const char *const *old_keywords, *const *new_keywords; + unsigned int i, j; + + old_keywords = mail_get_keywords(_mail); + ismail->module_ctx.super.update_keywords(_mail, modify_type, keywords); + new_keywords = mail_get_keywords(_mail); + + if (ismail->flags == NULL) + ismail->flags = str_new(default_pool, 64); + + /* Removed flags */ + for (i = 0; old_keywords[i] != NULL; i++) { + for (j = 0; new_keywords[j] != NULL; j++) { + if (strcmp(old_keywords[i], new_keywords[j]) == 0) + break; + } + if (new_keywords[j] == NULL) { + if (str_len(ismail->flags) > 0) + str_append_c(ismail->flags, ' '); + str_append(ismail->flags, old_keywords[i]); + } + } + + /* Added flags */ + for (i = 0; new_keywords[i] != NULL; i++) { + for (j = 0; old_keywords[j] != NULL; j++) { + if (strcmp(new_keywords[i], old_keywords[j]) == 0) + break; + } + if (old_keywords[j] == NULL) { + if (str_len(ismail->flags) > 0) + str_append_c(ismail->flags, ' '); + str_append(ismail->flags, new_keywords[i]); + } + } +} + +static void imap_sieve_mail_close(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct mailbox_transaction_context *t = _mail->transaction; + struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail); + + if (ismail->flags != NULL && str_len(ismail->flags) > 0) { + if (!_mail->expunged) { + imap_sieve_mailbox_debug(_mail->box, + "FLAG event (changed flags: %s)", + str_c(ismail->flags)); + + imap_sieve_add_mailbox_event(t, + _mail, _mail->box, str_c(ismail->flags)); + } + str_truncate(ismail->flags, 0); + } + + ismail->module_ctx.super.close(_mail); +} + +static void imap_sieve_mail_free(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail); + string_t *flags = ismail->flags; + + ismail->module_ctx.super.free(_mail); + + if (flags != NULL) + str_free(&flags); +} + +static void imap_sieve_mail_allocated(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct imap_sieve_mailbox_transaction *ismt = + IMAP_SIEVE_CONTEXT(_mail->transaction); + struct mail_user *user = _mail->box->storage->user; + struct imap_sieve_user *isuser = + IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + struct mail_vfuncs *v = mail->vlast; + struct imap_sieve_mail *ismail; + + if (ismt == NULL || isuser->sieve_active) + return; + + ismail = p_new(mail->pool, struct imap_sieve_mail, 1); + ismail->module_ctx.super = *v; + mail->vlast = &ismail->module_ctx.super; + + v->close = imap_sieve_mail_close; + v->free = imap_sieve_mail_free; + v->update_flags = imap_sieve_mail_update_flags; + v->update_keywords = imap_sieve_mail_update_keywords; + MODULE_CONTEXT_SET(mail, imap_sieve_mail_module, ismail); +} + +/* + * Save/copy + */ + +static int +imap_sieve_mailbox_copy(struct mail_save_context *ctx, struct mail *mail) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct mail_storage *storage = t->box->storage; + struct mail_user *user = storage->user; + struct imap_sieve_user *isuser = + IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + union mailbox_module_context *lbox = + IMAP_SIEVE_CONTEXT_REQUIRE(t->box); + struct imap_sieve_mailbox_transaction *ismt = + IMAP_SIEVE_CONTEXT(t); + + if (lbox->super.copy(ctx, mail) < 0) + return -1; + + if (ismt != NULL && !isuser->sieve_active && + !ctx->dest_mail->expunged && + (isuser->cur_cmd == IMAP_SIEVE_CMD_COPY || + isuser->cur_cmd == IMAP_SIEVE_CMD_MOVE)) { + imap_sieve_mailbox_debug(t->box, "%s event", + (isuser->cur_cmd == IMAP_SIEVE_CMD_COPY ? + "COPY" : "MOVE")); + imap_sieve_add_mailbox_copy_event(t, ctx->dest_mail, + ctx->copy_src_mail); + } + + return 0; +} + +static int +imap_sieve_mailbox_save_finish(struct mail_save_context *ctx) +{ + struct mailbox_transaction_context *t = ctx->transaction; + struct mailbox *box = t->box; + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t); + union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(box); + struct mail_user *user = box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + struct mail *dest_mail = ctx->copying_via_save ? NULL : ctx->dest_mail; + + if (lbox->super.save_finish(ctx) < 0) + return -1; + + if (ismt != NULL && !isuser->sieve_active && + dest_mail != NULL && !dest_mail->expunged && + isuser->cur_cmd == IMAP_SIEVE_CMD_APPEND) { + + imap_sieve_mailbox_debug(t->box, "APPEND event"); + imap_sieve_add_mailbox_event(t, dest_mail, box, NULL); + } + return 0; +} + +/* + * Mailbox + */ + +static struct mailbox_transaction_context * +imap_sieve_mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(box); + struct mail_user *user = box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(user); + struct mailbox_transaction_context *t; + struct imap_sieve_mailbox_transaction *ismt; + pool_t pool; + + /* commence parent transaction */ + t = lbox->super.transaction_begin(box, flags, reason); + + if (isuser == NULL || isuser->sieve_active || + isuser->cur_cmd == IMAP_SIEVE_CMD_NONE) + return t; + + i_assert(isuser->client != NULL); + + pool = pool_alloconly_create("imap_sieve_mailbox_transaction", 1024); + ismt = p_new(pool, struct imap_sieve_mailbox_transaction, 1); + ismt->pool = pool; + MODULE_CONTEXT_SET(t, imap_sieve_storage_module, ismt); + + return t; +} + +static void +imap_sieve_mailbox_transaction_free +(struct imap_sieve_mailbox_transaction *ismt) +{ + if (array_is_created(&ismt->events)) + array_free(&ismt->events); + pool_unref(&ismt->pool); +} + +static void +imap_sieve_mailbox_run_copy_source( + struct imap_sieve_mailbox_transaction *ismt, + struct imap_sieve_run *isrun, + const struct imap_sieve_mailbox_event *mevent, + struct mail **src_mail, bool *fatal_r) +{ + struct mailbox *src_box = ismt->src_box; + int ret; + + *fatal_r = FALSE; + + if (isrun == NULL) + return; + + i_assert(ismt->src_mail_trans->box == src_box); + i_assert(mevent->src_mail_uid > 0); + + if (*src_mail == NULL) + *src_mail = mail_alloc(ismt->src_mail_trans, 0, NULL); + + /* Select source message */ + if (!mail_set_uid(*src_mail, mevent->src_mail_uid)) { + imap_sieve_mailbox_warning(src_box, + "Failed to find source message for Sieve event " + "(UID=%llu)", (unsigned long long)mevent->src_mail_uid); + return; + } + + imap_sieve_mailbox_debug(src_box, + "Running copy_source_after scripts."); + + /* Run scripts for source mail */ + ret = imap_sieve_run_mail(isrun, *src_mail, NULL, fatal_r); + if (ret > 0) { + /* Discard */ + mail_update_flags(*src_mail, MODIFY_ADD, MAIL_DELETED); + } +} + +static int +imap_sieve_mailbox_transaction_run( + struct imap_sieve_mailbox_transaction *ismt, + struct mailbox *dest_box, + struct mail_transaction_commit_changes *changes) +{ + static const char *wanted_headers[] = { + "From", "To", "Message-ID", "Subject", "Return-Path", + NULL + }; + struct mailbox *src_box = ismt->src_box; + struct mail_user *user = dest_box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + const struct imap_sieve_mailbox_event *mevent; + struct mailbox_header_lookup_ctx *headers_ctx; + struct mailbox_transaction_context *st; + struct mailbox *sbox; + struct imap_sieve_run *isrun, *isrun_src; + struct seq_range_iter siter; + const char *cause, *script_name = NULL; + bool can_discard; + struct mail *mail, *src_mail = NULL; + int ret; + + if (ismt == NULL || !array_is_created(&ismt->events)) { + /* Nothing to do */ + return 0; + } + + i_assert(isuser->client != NULL); + + /* Get user script for this mailbox */ + if (isuser->user_script && imap_sieve_mailbox_get_script + (dest_box, &script_name) < 0) { + return 0; // FIXME: some errors may warrant -1 + } + + /* Make sure IMAPSIEVE is initialized for this user */ + if (isuser->isieve == NULL) + isuser->isieve = imap_sieve_init(isuser->client); + + can_discard = FALSE; + switch (isuser->cur_cmd) { + case IMAP_SIEVE_CMD_APPEND: + cause = "APPEND"; + can_discard = TRUE; + break; + case IMAP_SIEVE_CMD_COPY: + case IMAP_SIEVE_CMD_MOVE: + cause = "COPY"; + can_discard = TRUE; + break; + case IMAP_SIEVE_CMD_STORE: + case IMAP_SIEVE_CMD_OTHER: + cause = "FLAG"; + break; + default: + i_unreached(); + } + + /* Initialize execution */ + T_BEGIN { + ARRAY_TYPE(imap_sieve_mailbox_rule) mbrules; + ARRAY_TYPE(const_string) scripts_before, scripts_after; + ARRAY_TYPE(const_string) scripts_copy_source; + struct imap_sieve_mailbox_rule *rule; + + /* Find matching rules */ + t_array_init(&mbrules, 16); + imap_sieve_mailbox_rules_get + (user, dest_box, src_box, cause, &mbrules); + + /* Apply all matched rules */ + t_array_init(&scripts_before, 8); + t_array_init(&scripts_after, 8); + t_array_init(&scripts_copy_source, 4); + array_foreach_elem(&mbrules, rule) { + if (rule->before != NULL) + array_append(&scripts_before, &rule->before, 1); + if (rule->after != NULL) + array_append(&scripts_after, &rule->after, 1); + if (rule->copy_source_after != NULL) + array_append(&scripts_copy_source, &rule->copy_source_after, 1); + } + (void)array_append_space(&scripts_before); + (void)array_append_space(&scripts_after); + + /* Initialize */ + ret = imap_sieve_run_init + (isuser->isieve, dest_box, src_box, cause, script_name, + array_idx(&scripts_before, 0), + array_idx(&scripts_after, 0), &isrun); + + /* Initialize source script execution */ + isrun_src = NULL; + if (ret > 0 && ismt->src_mail_trans != NULL && + isuser->cur_cmd == IMAP_SIEVE_CMD_COPY && + array_count(&scripts_copy_source) > 0) { + const char *no_scripts = NULL; + + (void)array_append_space(&scripts_copy_source); + if (imap_sieve_run_init(isuser->isieve, + dest_box, src_box, cause, NULL, + &no_scripts, array_idx(&scripts_copy_source, 0), + &isrun_src) <= 0) + isrun_src = NULL; + } + } T_END; + + if (ret <= 0) { + // FIXME: temp fail should be handled properly + return 0; + } + + /* Get synchronized view on the destination mailbox */ + sbox = mailbox_alloc(dest_box->list, dest_box->vname, 0); + if (mailbox_sync(sbox, 0) < 0) { + mailbox_free(&sbox); + imap_sieve_run_deinit(&isrun); + if (isrun_src != NULL) + imap_sieve_run_deinit(&isrun_src); + return -1; + } + + /* Create transaction for event messages */ + st = mailbox_transaction_begin(sbox, 0, __func__); + headers_ctx = mailbox_header_lookup_init(sbox, wanted_headers); + mail = mail_alloc(st, 0, headers_ctx); + mailbox_header_lookup_unref(&headers_ctx); + + /* Iterate through all events */ + seq_range_array_iter_init(&siter, &changes->saved_uids); + array_foreach(&ismt->events, mevent) { + uint32_t uid; + bool fatal; + + /* Determine UID for saved message */ + if (mevent->dest_mail_uid > 0) + uid = mevent->dest_mail_uid; + else if (!seq_range_array_iter_nth(&siter, mevent->save_seq, + &uid)) { + /* already gone for some reason */ + imap_sieve_mailbox_debug( + sbox, "Message for Sieve event gone"); + continue; + } + + /* Select event message */ + i_assert(uid > 0); + if (!mail_set_uid(mail, uid) || mail->expunged) { + /* already gone for some reason */ + imap_sieve_mailbox_debug(sbox, + "Message for Sieve event gone (UID=%llu)", + (unsigned long long)uid); + continue; + } + + /* Run scripts for this mail */ + ret = imap_sieve_run_mail(isrun, mail, mevent->changed_flags, + &fatal); + if (fatal) + break; + + /* Handle the result */ + if (ret < 0) { + /* Sieve error; keep */ + } else { + if (ret <= 0 || !can_discard) { + /* Keep */ + } else if (!isuser->expunge_discarded) { + /* Mark as \Deleted */ + mail_update_flags(mail, + MODIFY_ADD, MAIL_DELETED); + } else { + /* Expunge */ + mail_expunge(mail); + } + + imap_sieve_mailbox_run_copy_source( + ismt, isrun_src, mevent, &src_mail, &fatal); + if (fatal) + break; + } + } + + /* Cleanup */ + mail_free(&mail); + ret = mailbox_transaction_commit(&st); + if (src_mail != NULL) + mail_free(&src_mail); + imap_sieve_run_deinit(&isrun); + if (isrun_src != NULL) + imap_sieve_run_deinit(&isrun_src); + mailbox_free(&sbox); + return ret; +} + +static int +imap_sieve_mailbox_transaction_commit( + struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r) +{ + struct mailbox *box = t->box; + struct mail_user *user = box->storage->user; + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t); + union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(t->box); + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + int ret = 0; + + if ((lbox->super.transaction_commit(t, changes_r)) < 0) + ret = -1; + else if (ismt != NULL) { + isuser->sieve_active = TRUE; + if (imap_sieve_mailbox_transaction_run + (ismt, box, changes_r) < 0) + ret = -1; + isuser->sieve_active = FALSE; + } + + if (ismt != NULL) + imap_sieve_mailbox_transaction_free(ismt); + return ret; +} + +static void +imap_sieve_mailbox_transaction_rollback( + struct mailbox_transaction_context *t) +{ + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t); + union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(t->box); + + lbox->super.transaction_rollback(t); + + if (ismt != NULL) + imap_sieve_mailbox_transaction_free(ismt); +} + +static void imap_sieve_mailbox_allocated(struct mailbox *box) +{ + struct mail_user *user = box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + struct mailbox_vfuncs *v = box->vlast; + union mailbox_module_context *lbox; + + if (isuser->client == NULL || isuser->sieve_active || + (box->flags & MAILBOX_FLAG_READONLY) != 0) + return; + + lbox = p_new(box->pool, union mailbox_module_context, 1); + lbox->super = *v; + box->vlast = &lbox->super; + + v->copy = imap_sieve_mailbox_copy; + v->save_finish = imap_sieve_mailbox_save_finish; + v->transaction_begin = imap_sieve_mailbox_transaction_begin; + v->transaction_commit = imap_sieve_mailbox_transaction_commit; + v->transaction_rollback = imap_sieve_mailbox_transaction_rollback; + MODULE_CONTEXT_SET_SELF(box, imap_sieve_storage_module, lbox); +} + +/* + * Mailbox rules + */ + +static unsigned int imap_sieve_mailbox_rule_hash +(const struct imap_sieve_mailbox_rule *rule) +{ + unsigned int hash = str_hash(rule->mailbox); + + if (rule->from != NULL) + hash += str_hash(rule->from); + return hash; +} + +static int imap_sieve_mailbox_rule_cmp +(const struct imap_sieve_mailbox_rule *rule1, + const struct imap_sieve_mailbox_rule *rule2) +{ + int ret; + + if ((ret=strcmp(rule1->mailbox, rule2->mailbox)) != 0) + return ret; + return null_strcmp(rule1->from, rule2->from); +} + +static bool rule_pattern_has_wildcards(const char *pattern) +{ + for (; *pattern != '\0'; pattern++) { + if (*pattern == '%' || *pattern == '*') + return TRUE; + } + return FALSE; +} + +static void +imap_sieve_mailbox_rules_init(struct mail_user *user) +{ + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + string_t *identifier; + unsigned int i = 0; + size_t prefix_len; + + if (hash_table_is_created(isuser->mbox_rules)) + return; + + hash_table_create(&isuser->mbox_rules, default_pool, 0, + imap_sieve_mailbox_rule_hash, imap_sieve_mailbox_rule_cmp); + i_array_init(&isuser->mbox_patterns, 8); + + identifier = t_str_new(256); + str_append(identifier, "imapsieve_mailbox"); + prefix_len = str_len(identifier); + + for (i = 1; ; i++) { + struct imap_sieve_mailbox_rule *mbrule; + const char *setval; + size_t id_len; + + str_truncate(identifier, prefix_len); + str_printfa(identifier, "%u", i); + id_len = str_len(identifier); + + str_append(identifier, "_name"); + setval = mail_user_plugin_getenv + (user, str_c(identifier)); + if (setval == NULL || *setval == '\0') + break; + setval = t_str_trim(setval, "\t "); + if (strcasecmp(setval, "INBOX") == 0) + setval = t_str_ucase(setval); + + mbrule = p_new(user->pool, + struct imap_sieve_mailbox_rule, 1); + mbrule->index = i; + mbrule->mailbox = p_strdup(user->pool, setval); + + str_truncate(identifier, id_len); + str_append(identifier, "_from"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + if (setval != NULL && *setval != '\0') { + setval = t_str_trim(setval, "\t "); + if (strcasecmp(setval, "INBOX") == 0) + setval = t_str_ucase(setval); + mbrule->from = p_strdup(user->pool, setval); + if (strcmp(mbrule->from, "*") == 0) + mbrule->from = NULL; + } + + if ((strcmp(mbrule->mailbox, "*") == 0 || + !rule_pattern_has_wildcards(mbrule->mailbox)) && + (mbrule->from == NULL || + !rule_pattern_has_wildcards(mbrule->from)) && + hash_table_lookup(isuser->mbox_rules, mbrule) != NULL) { + imap_sieve_warning(user, + "Duplicate static mailbox rule [%u] for mailbox `%s' " + "(skipped)", i, mbrule->mailbox); + continue; + } + + str_truncate(identifier, id_len); + str_append(identifier, "_causes"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + if (setval != NULL && *setval != '\0') { + const char *const *cause; + + mbrule->causes = (const char *const *) + p_strsplit_spaces(user->pool, setval, " \t,"); + + for (cause = mbrule->causes; *cause != NULL; cause++) { + if (!imap_sieve_event_cause_valid(*cause)) + break; + } + if (*cause != NULL) { + imap_sieve_warning(user, + "Static mailbox rule [%u] has invalid event cause `%s' " + "(skipped)", i, *cause); + continue; + } + } + + str_truncate(identifier, id_len); + str_append(identifier, "_before"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + mbrule->before = p_strdup_empty(user->pool, setval); + + str_truncate(identifier, id_len); + str_append(identifier, "_after"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + mbrule->after = p_strdup_empty(user->pool, setval); + + str_truncate(identifier, id_len); + str_append(identifier, "_copy_source_after"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + mbrule->copy_source_after = p_strdup_empty(user->pool, setval); + + if (user->mail_debug) { + imap_sieve_debug(user, "Static mailbox rule [%u]: " + "mailbox=`%s' from=`%s' causes=(%s) => " + "before=%s after=%s%s", + mbrule->index, mbrule->mailbox, + (mbrule->from == NULL ? "*" : mbrule->from), + t_strarray_join(mbrule->causes, " "), + (mbrule->before == NULL ? "(none)" : + t_strconcat("`", mbrule->before, "'", NULL)), + (mbrule->after == NULL ? "(none)" : + t_strconcat("`", mbrule->after, "'", NULL)), + (mbrule->copy_source_after == NULL ? "": + t_strconcat(" copy_source_after=`", + mbrule->copy_source_after, "'", NULL))); + } + + if ((strcmp(mbrule->mailbox, "*") == 0 || + !rule_pattern_has_wildcards(mbrule->mailbox)) && + (mbrule->from == NULL || + !rule_pattern_has_wildcards(mbrule->from))) { + hash_table_insert(isuser->mbox_rules, mbrule, mbrule); + } else { + array_append(&isuser->mbox_patterns, &mbrule, 1); + } + } + + if (i == 0) + imap_sieve_debug(user, "No static mailbox rules"); +} + +static bool +imap_sieve_mailbox_rule_match_cause +(struct imap_sieve_mailbox_rule *rule, const char *cause) +{ + const char *const *cp; + + if (rule->causes == NULL || *rule->causes == NULL) + return TRUE; + + for (cp = rule->causes; *cp != NULL; cp++) { + if (strcasecmp(cause, *cp) == 0) + return TRUE; + } + return FALSE; +} + +static void +imap_sieve_mailbox_rules_match_patterns(struct mail_user *user, + struct mailbox *dst_box, struct mailbox *src_box, + const char *cause, + ARRAY_TYPE(imap_sieve_mailbox_rule) *rules) +{ + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + struct imap_sieve_mailbox_rule *rule; + struct mail_namespace *dst_ns, *src_ns; + + if (array_count(&isuser->mbox_patterns) == 0) + return; + + dst_ns = mailbox_get_namespace(dst_box); + src_ns = (src_box == NULL ? NULL : + mailbox_get_namespace(src_box)); + + array_foreach_elem(&isuser->mbox_patterns, rule) { + struct imap_match_glob *glob; + + if (src_ns == NULL && rule->from != NULL) + continue; + if (!imap_sieve_mailbox_rule_match_cause(rule, cause)) + continue; + + if (strcmp(rule->mailbox, "*") != 0) { + glob = imap_match_init(pool_datastack_create(), + rule->mailbox, TRUE, mail_namespace_get_sep(dst_ns)); + if (imap_match(glob, mailbox_get_vname(dst_box)) + != IMAP_MATCH_YES) + continue; + } + if (rule->from != NULL) { + glob = imap_match_init(pool_datastack_create(), + rule->from, TRUE, mail_namespace_get_sep(src_ns)); + if (imap_match(glob, mailbox_get_vname(src_box)) + != IMAP_MATCH_YES) + continue; + } + + imap_sieve_debug(user, + "Matched static mailbox rule [%u]", + rule->index); + array_append(rules, &rule, 1); + } +} + +static void +imap_sieve_mailbox_rules_match(struct mail_user *user, + const char *dst_box, const char *src_box, + const char *cause, + ARRAY_TYPE(imap_sieve_mailbox_rule) *rules) +{ + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + struct imap_sieve_mailbox_rule lookup_rule; + struct imap_sieve_mailbox_rule *rule; + + i_zero(&lookup_rule); + lookup_rule.mailbox = dst_box; + lookup_rule.from = src_box; + rule = hash_table_lookup(isuser->mbox_rules, &lookup_rule); + + if (rule != NULL && + imap_sieve_mailbox_rule_match_cause(rule, cause)) { + struct imap_sieve_mailbox_rule *const *rule_idx; + unsigned int insert_idx = array_count(rules); + + /* Insert sorted by rule index */ + array_foreach(rules, rule_idx) { + if (rule->index < (*rule_idx)->index) { + insert_idx = array_foreach_idx(rules, rule_idx); + break; + } + } + array_insert(rules, insert_idx, &rule, 1); + + imap_sieve_debug(user, + "Matched static mailbox rule [%u]", + rule->index); + } +} + +static void +imap_sieve_mailbox_rules_get(struct mail_user *user, + struct mailbox *dst_box, struct mailbox *src_box, + const char *cause, + ARRAY_TYPE(imap_sieve_mailbox_rule) *rules) +{ + const char *dst_name, *src_name; + + imap_sieve_mailbox_rules_init(user); + + imap_sieve_mailbox_rules_match_patterns + (user, dst_box, src_box, cause, rules); + + dst_name = mailbox_get_vname(dst_box); + src_name = (src_box == NULL ? NULL : + mailbox_get_vname(src_box)); + + imap_sieve_mailbox_rules_match + (user, dst_name, src_name, cause, rules); + imap_sieve_mailbox_rules_match + (user, "*", src_name, cause, rules); + if (src_name != NULL) { + imap_sieve_mailbox_rules_match + (user, dst_name, NULL, cause, rules); + imap_sieve_mailbox_rules_match + (user, "*", NULL, cause, rules); + } +} + +/* + * User + */ + +static void imap_sieve_user_deinit(struct mail_user *user) +{ + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + + if (isuser->isieve != NULL) + imap_sieve_deinit(&isuser->isieve); + + hash_table_destroy(&isuser->mbox_rules); + if (array_is_created(&isuser->mbox_patterns)) + array_free(&isuser->mbox_patterns); + + isuser->module_ctx.super.deinit(user); +} + +static void imap_sieve_user_created(struct mail_user *user) +{ + struct imap_sieve_user *isuser; + struct mail_user_vfuncs *v = user->vlast; + + isuser = p_new(user->pool, struct imap_sieve_user, 1); + isuser->module_ctx.super = *v; + user->vlast = &isuser->module_ctx.super; + v->deinit = imap_sieve_user_deinit; + MODULE_CONTEXT_SET(user, imap_sieve_user_module, isuser); +} + +/* + * Hooks + */ + +static struct mail_storage_hooks imap_sieve_mail_storage_hooks = { + .mail_user_created = imap_sieve_user_created, + .mailbox_allocated = imap_sieve_mailbox_allocated, + .mail_allocated = imap_sieve_mail_allocated +}; + +/* + * Commands + */ + +static void imap_sieve_command_pre(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct mail_user *user = client->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(user); + + if (isuser == NULL) + return; + + if (strcasecmp(cmd->name, "APPEND") == 0) { + isuser->cur_cmd = IMAP_SIEVE_CMD_APPEND; + } else if (strcasecmp(cmd->name, "COPY") == 0 || + strcasecmp(cmd->name, "UID COPY") == 0) { + isuser->cur_cmd = IMAP_SIEVE_CMD_COPY; + } else if (strcasecmp(cmd->name, "MOVE") == 0 || + strcasecmp(cmd->name, "UID MOVE") == 0) { + isuser->cur_cmd = IMAP_SIEVE_CMD_MOVE; + } else if (strcasecmp(cmd->name, "STORE") == 0 || + strcasecmp(cmd->name, "UID STORE") == 0) { + isuser->cur_cmd = IMAP_SIEVE_CMD_STORE; + } else { + isuser->cur_cmd = IMAP_SIEVE_CMD_OTHER; + } +} + +static void imap_sieve_command_post(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct mail_user *user = client->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(user); + + if (isuser == NULL) + return; + isuser->cur_cmd = IMAP_SIEVE_CMD_NONE; +} + +/* + * Client + */ + +void imap_sieve_storage_client_created(struct client *client, + bool user_script) +{ + struct mail_user *user = client->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user); + const char *set; + + isuser->client = client; + isuser->user_script = user_script; + + set = mail_user_plugin_getenv(user, "imapsieve_expunge_discarded"); + isuser->expunge_discarded = + (set != NULL && strcasecmp(set, "yes") == 0); +} + +/* + * + */ + +void imap_sieve_storage_init(struct module *module) +{ + command_hook_register(imap_sieve_command_pre, imap_sieve_command_post); + mail_storage_hooks_add(module, &imap_sieve_mail_storage_hooks); +} + +void imap_sieve_storage_deinit(void) +{ + mail_storage_hooks_remove(&imap_sieve_mail_storage_hooks); + command_hook_unregister(imap_sieve_command_pre, imap_sieve_command_post); +} |