summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c
parentInitial commit. (diff)
downloaddovecot-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.c1273
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);
+}