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