diff --git a/TODO b/TODO index be9da82b3..bd184ffe4 100644 --- a/TODO +++ b/TODO @@ -88,11 +88,9 @@ Low priority items: within Dovecot) * Warn during compile if using non-existent folders. -* Implement IMAP plugin for IMAPSieve support: - - Requires some sort of Sieve transaction support. - - This may include support for manually running a script on a set of messages - through IMAP (no specification for something like this is available; we will - have to provide our own) +* Implement support for manually running a script on a set of messages through + IMAP (no specification for something like this is available; we will have to + provide our own) * Variables extension: implement compile time evaluation of constant values - Detect assignment of too large constant values to variables at compile time. diff --git a/configure.ac b/configure.ac index 821cad90e..fcd3854bb 100644 --- a/configure.ac +++ b/configure.ac @@ -223,6 +223,7 @@ src/plugins/Makefile src/plugins/doveadm-sieve/Makefile src/plugins/lda-sieve/Makefile src/plugins/sieve-extprograms/Makefile +src/plugins/imapsieve/Makefile src/plugins/settings/Makefile src/sieve-tools/Makefile src/managesieve/Makefile diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index a124d59ed..58a3997ee 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -2,4 +2,5 @@ SUBDIRS = \ doveadm-sieve \ lda-sieve \ sieve-extprograms \ + imapsieve \ settings diff --git a/src/plugins/imapsieve/Makefile.am b/src/plugins/imapsieve/Makefile.am new file mode 100644 index 000000000..1fea23e5e --- /dev/null +++ b/src/plugins/imapsieve/Makefile.am @@ -0,0 +1,40 @@ +imap_moduledir = $(dovecot_moduledir) +sieve_plugindir = $(dovecot_moduledir)/sieve + +imap_module_LTLIBRARIES = lib95_imap_sieve_plugin.la +sieve_plugin_LTLIBRARIES = lib90_sieve_imapsieve_plugin.la + +lib95_imap_sieve_plugin_la_LDFLAGS = -module -avoid-version +lib90_sieve_imapsieve_plugin_la_LDFLAGS = -module -avoid-version + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib-sieve \ + -I$(top_srcdir)/src/lib-sieve/util \ + -I$(top_srcdir)/src/lib-sieve/plugins/environment \ + $(LIBDOVECOT_IMAP_INCLUDE) \ + $(LIBDOVECOT_LDA_INCLUDE) \ + $(LIBDOVECOT_INCLUDE) \ + -DPKG_RUNDIR=\""$(rundir)"\" + +lib95_imap_sieve_plugin_la_SOURCES = \ + ext-imapsieve.c \ + ext-imapsieve-environment.c \ + imap-sieve.c \ + imap-sieve-storage.c \ + imap-sieve-plugin.c +lib95_imap_sieve_plugin_la_LIBADD = \ + $(top_builddir)/src/lib-sieve/libdovecot-sieve.la + +lib90_sieve_imapsieve_plugin_la_SOURCES = \ + ext-imapsieve.c \ + sieve-imapsieve-plugin.c +lib90_sieve_imapsieve_plugin_la_CPPFLAGS = \ + ${AM_CPPFLAGS} \ + -D__IMAPSIEVE_DUMMY + +noinst_HEADERS = \ + ext-imapsieve-common.h \ + imap-sieve.h \ + imap-sieve-storage.h \ + imap-sieve-plugin.h \ + sieve-imapsieve-plugin.h diff --git a/src/plugins/imapsieve/ext-imapsieve-common.h b/src/plugins/imapsieve/ext-imapsieve-common.h new file mode 100644 index 000000000..8fa971753 --- /dev/null +++ b/src/plugins/imapsieve/ext-imapsieve-common.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __EXT_IMAPSIEVE_COMMON_H +#define __EXT_IMAPSIEVE_COMMON_H + +#include "sieve-extensions.h" + +#include "imap-sieve.h" + +/* + * Extensions + */ + +extern const struct sieve_extension_def imapsieve_extension; +extern const struct sieve_extension_def imapsieve_extension_dummy; + +/* + * Environment items + */ + +void ext_imapsieve_environment_items_register + (const struct sieve_extension *ext, + const struct sieve_runtime_env *renv); + +#endif /* __EXT_IMAPSIEVE_COMMON_H */ diff --git a/src/plugins/imapsieve/ext-imapsieve-environment.c b/src/plugins/imapsieve/ext-imapsieve-environment.c new file mode 100644 index 000000000..7339720a7 --- /dev/null +++ b/src/plugins/imapsieve/ext-imapsieve-environment.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "array.h" +#include "mail-storage.h" + +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-runtime.h" + +#include "sieve-ext-environment.h" + +#include "ext-imapsieve-common.h" + +/* + * Environment items + */ + +/* imap.user */ + +static const char *envit_imap_user_get_value +(const struct sieve_runtime_env *renv) +{ + const struct sieve_script_env *senv = renv->scriptenv; + + return senv->user->username; +} + +const struct sieve_environment_item imap_user_env_item = { + .name = "imap.user", + .get_value = envit_imap_user_get_value +}; + +/* imap.email */ + +static const char *envit_imap_email_get_value +(const struct sieve_runtime_env *renv) +{ + struct sieve_instance *svinst = renv->svinst; + const struct sieve_script_env *senv = renv->scriptenv; + const char *username = senv->user->username; + + // FIXME: explicit configuration + if ( strchr(username, '@') != 0 ) + return username; + if ( svinst->domainname != NULL ) + return t_strconcat(username, "@", svinst->domainname, NULL); + return NULL; +} + +const struct sieve_environment_item imap_email_env_item = { + .name = "imap.email", + .get_value = envit_imap_email_get_value +}; + +/* imap.cause */ + +static const char *envit_imap_cause_get_value +(const struct sieve_runtime_env *renv) +{ + const struct sieve_script_env *senv = renv->scriptenv; + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + return isctx->event.cause; +} + +const struct sieve_environment_item imap_cause_env_item = { + .name = "imap.cause", + .get_value = envit_imap_cause_get_value +}; + +/* imap.mailbox */ + +static const char *envit_imap_mailbox_get_value +(const struct sieve_runtime_env *renv) +{ + const struct sieve_script_env *senv = renv->scriptenv; + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + return mailbox_get_vname(isctx->event.mailbox); +} + +const struct sieve_environment_item imap_mailbox_env_item = { + .name = "imap.mailbox", + .get_value = envit_imap_mailbox_get_value +}; + + +/* imap.changedflags */ + +static const char *envit_imap_changedflags_get_value +(const struct sieve_runtime_env *renv) +{ + const struct sieve_script_env *senv = renv->scriptenv; + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + return isctx->event.changed_flags; +} + +const struct sieve_environment_item imap_changedflags_env_item = { + .name = "imap.changedflags", + .get_value = envit_imap_changedflags_get_value +}; + +/* + * Register + */ + +void ext_imapsieve_environment_items_register +(const struct sieve_extension *ext, const struct sieve_runtime_env *renv) +{ + const struct sieve_extension *env_ext = + (const struct sieve_extension *) ext->context; + + sieve_environment_item_register + (env_ext, renv->interp, &imap_user_env_item); + sieve_environment_item_register + (env_ext, renv->interp, &imap_email_env_item); + sieve_environment_item_register + (env_ext, renv->interp, &imap_cause_env_item); + sieve_environment_item_register + (env_ext, renv->interp, &imap_mailbox_env_item); + sieve_environment_item_register + (env_ext, renv->interp, &imap_changedflags_env_item); +} diff --git a/src/plugins/imapsieve/ext-imapsieve.c b/src/plugins/imapsieve/ext-imapsieve.c new file mode 100644 index 000000000..d153fb6e6 --- /dev/null +++ b/src/plugins/imapsieve/ext-imapsieve.c @@ -0,0 +1,110 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +/* Extension imapsieve + * ------------------- + * + * Authors: Stephan Bosch + * Specification: rfc6785 + * Implementation: full + * Status: experimental + * + */ + +#include "lib.h" + +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-binary.h" + +#include "sieve-validator.h" +#include "sieve-interpreter.h" + +#include "sieve-ext-environment.h" + +#include "ext-imapsieve-common.h" + +/* + * Extension + */ + +static bool ext_imapsieve_load + (const struct sieve_extension *ext, void **context); +static bool ext_imapsieve_interpreter_load + (const struct sieve_extension *ext, + const struct sieve_runtime_env *renv, + sieve_size_t *address ATTR_UNUSED); + +#ifdef __IMAPSIEVE_DUMMY +const struct sieve_extension_def imapsieve_extension_dummy = { +#else +const struct sieve_extension_def imapsieve_extension = { +#endif + .name = "imapsieve", + .load = ext_imapsieve_load, + .interpreter_load = ext_imapsieve_interpreter_load +}; + +/* + * Context + */ + +static bool ext_imapsieve_load +(const struct sieve_extension *ext, void **context) +{ + *context = (void*) + sieve_ext_environment_require_extension(ext->svinst); + return TRUE; +} + +/* + * Interpreter + */ + +static int ext_imapsieve_interpreter_run + (const struct sieve_extension *this_ext, + const struct sieve_runtime_env *renv, + void *context, bool deferred); + +const struct sieve_interpreter_extension +imapsieve_interpreter_extension = { +#ifdef __IMAPSIEVE_DUMMY + .ext_def = &imapsieve_extension_dummy, +#else + .ext_def = &imapsieve_extension, +#endif + .run = ext_imapsieve_interpreter_run +}; + +static bool ext_imapsieve_interpreter_load +(const struct sieve_extension *ext ATTR_UNUSED, + const struct sieve_runtime_env *renv, + sieve_size_t *address ATTR_UNUSED) +{ + sieve_interpreter_extension_register(renv->interp, + ext, &imapsieve_interpreter_extension, NULL); + return TRUE; +} + +#ifdef __IMAPSIEVE_DUMMY +static int ext_imapsieve_interpreter_run +(const struct sieve_extension *ext ATTR_UNUSED, + const struct sieve_runtime_env *renv, + void *context ATTR_UNUSED, bool deferred) +{ + if ( !deferred ) { + sieve_runtime_error(renv, NULL, + "the imapsieve extension cannot be used outside IMAP"); + } + return SIEVE_EXEC_FAILURE; +} +#else +static int ext_imapsieve_interpreter_run +(const struct sieve_extension *ext ATTR_UNUSED, + const struct sieve_runtime_env *renv, + void *context ATTR_UNUSED, bool deferred ATTR_UNUSED) +{ + ext_imapsieve_environment_items_register(ext, renv); + return SIEVE_EXEC_OK; +} +#endif diff --git a/src/plugins/imapsieve/imap-sieve-plugin.c b/src/plugins/imapsieve/imap-sieve-plugin.c new file mode 100644 index 000000000..c81de2326 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve-plugin.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" + +#include "imap-sieve.h" +#include "imap-sieve-storage.h" + +#include "imap-sieve-plugin.h" + +static struct module *imap_sieve_module; +static imap_client_created_func_t *next_hook_client_created; + +/* + * Client + */ + +static void imap_sieve_client_created(struct client **clientp) +{ + struct client *client = *clientp; + struct mail_user *user = client->user; + const char *url = NULL; + + if (mail_user_is_plugin_loaded(user, imap_sieve_module)) { + url = mail_user_plugin_getenv(user, "imapsieve_url"); + // FIXME: parse the URL and report error if it is bad + if (url != NULL && strncasecmp(url, "sieve:", 6) == 0) { + str_append(client->capability_string, " IMAPSIEVE="); + str_append(client->capability_string, url); + } else { + url = NULL; + } + + imap_sieve_storage_client_created(client, (url != NULL)); + } + + if (next_hook_client_created != NULL) + next_hook_client_created(clientp); +} + +/* + * Plugin + */ + +const char *imap_sieve_plugin_version = DOVECOT_ABI_VERSION; +const char imap_sieve_plugin_binary_dependency[] = "imap"; + +void imap_sieve_plugin_init(struct module *module) +{ + imap_sieve_module = module; + next_hook_client_created = + imap_client_created_hook_set(imap_sieve_client_created); + imap_sieve_storage_init(module); +} + +void imap_sieve_plugin_deinit(void) +{ + imap_sieve_storage_deinit(); + imap_client_created_hook_set(next_hook_client_created); +} diff --git a/src/plugins/imapsieve/imap-sieve-plugin.h b/src/plugins/imapsieve/imap-sieve-plugin.h new file mode 100644 index 000000000..46b654991 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve-plugin.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#ifndef IMAP_SIEVE_PLUGIN_H +#define IMAP_SIEVE_PLUGIN_H + +struct module; + +extern const char imap_sieve_plugin_binary_dependency[]; + +void imap_sieve_plugin_init(struct module *module); +void imap_sieve_plugin_deinit(void); + +#endif diff --git a/src/plugins/imapsieve/imap-sieve-storage.c b/src/plugins/imapsieve/imap-sieve-storage.c new file mode 100644 index 000000000..40c410879 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve-storage.c @@ -0,0 +1,1159 @@ +/* Copyright (c) 2016 Dovecot 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 "strtrim.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_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_sieve_storage_module) +#define IMAP_SIEVE_MAIL_CONTEXT(obj) \ + MODULE_CONTEXT(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; +}; + +struct imap_sieve_user { + pool_t pool; + + 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; + + unsigned int sieve_active:1; + unsigned int user_script:1; +}; + +struct imap_sieve_mailbox_event { + uint32_t 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 mail *tmp_mail; + struct mailbox *src_box; + + 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_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, struct mailbox_transaction_context *t, + 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(t, MAIL_ATTRIBUTE_TYPE_SHARED, + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value)) < 0) { + imap_sieve_mailbox_error(t->box, + "Failed to read /shared/" + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "mailbox attribute"); // FIXME: details? + return -1; + } + + if (ret > 0) { + imap_sieve_mailbox_debug(t->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 *box; + struct mailbox_transaction_context *ibt; + + imap_sieve_mailbox_debug(t->box, + "Mailbox attribute /shared/" + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "not found"); + + ns = mail_namespace_find_inbox(user->namespaces); + box = mailbox_alloc(ns->list, "INBOX", + MAILBOX_FLAG_READONLY); + if ((ret=mailbox_open(box)) >= 0) { + ibt = mailbox_transaction_begin + (box, MAILBOX_TRANSACTION_FLAG_EXTERNAL); + ret = mailbox_attribute_get(ibt, + MAIL_ATTRIBUTE_TYPE_SHARED, + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER + MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value); + mailbox_transaction_rollback(&ibt); + } + mailbox_free(&box); + + if (ret <= 0) { + if (ret < 0) { + imap_sieve_mailbox_error(t->box, + "Failed to read /shared/" + MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "server attribute"); // FIXME: details? + } else if (ret == 0) { + imap_sieve_mailbox_debug(t->box, + "Server attribute /shared/" + MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" " + "not found"); + } + return ret; + } + + imap_sieve_mailbox_debug(t->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) +{ + struct mailbox_transaction_context *t; + int ret; + + t = mailbox_transaction_begin(box, 0); + ret = imap_sieve_mailbox_get_script_real + (box, t, script_name_r); + mailbox_transaction_rollback(&t); + return ret; +} + +static void imap_sieve_add_mailbox_event +(struct mailbox_transaction_context *t, + struct mail *mail, struct mailbox *src_box, + const char *changed_flags) +{ + struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t); + struct imap_sieve_mailbox_event *event; + + i_assert(ismt->src_box == NULL || ismt->src_box == src_box); + ismt->src_box = src_box; + + 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->mail_uid = mail->uid; + event->changed_flags = p_strdup(ismt->pool, changed_flags); +} + +/* + * 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 mail_user *user = _mail->box->storage->user; + 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); + + imap_sieve_debug(user, "Mail set keywords"); + + /* 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_vfuncs *v = mail->vlast; + struct imap_sieve_mail *ismail; + + if (ismt == NULL) + 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(user); + union mailbox_module_context *lbox = + IMAP_SIEVE_CONTEXT(t->box); + struct imap_sieve_mailbox_transaction *ismt = + IMAP_SIEVE_CONTEXT(t); + + if (ismt != NULL) { + if (ctx->dest_mail == NULL) { + /* Dest mail is required for our purposes */ + if (ismt->tmp_mail == NULL) { + ismt->tmp_mail = mail_alloc(t, + MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY, NULL); + } + ctx->dest_mail = ismt->tmp_mail; + } + } + + if (lbox->super.copy(ctx, mail) < 0) + return -1; + + if (ismt != NULL && !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_event + (t, ctx->dest_mail, mail->box, NULL); + } + + return 0; +} + +static int +imap_sieve_mailbox_save_begin(struct mail_save_context *ctx, + struct istream *input) +{ + struct imap_sieve_mailbox_transaction *ismt = + IMAP_SIEVE_CONTEXT(ctx->transaction); + union mailbox_module_context *lbox = + IMAP_SIEVE_CONTEXT(ctx->transaction->box); + + if (ismt != NULL) { + if (ctx->dest_mail == NULL) { + /* Dest mail is required for our purposes */ + if (ismt->tmp_mail == NULL) { + ismt->tmp_mail = mail_alloc(ctx->transaction, + MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY, NULL); + } + ctx->dest_mail = ismt->tmp_mail; + } + } + return lbox->super.save_begin(ctx, input); +} + +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(box); + struct mail_user *user = box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(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 && 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) +{ + union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT(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); + + if (isuser == NULL || isuser->sieve_active) + return t; + + 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) +{ + i_assert(ismt->tmp_mail == NULL); + if (array_is_created(&ismt->events)) + array_free(&ismt->events); + pool_unref(&ismt->pool); +} + +static int +imap_sieve_mailbox_transaction_run( + struct imap_sieve_mailbox_transaction *ismt, + struct mailbox *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 = box->storage->user; + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(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; + struct seq_range_iter siter; + const char *cause, *script_name = NULL; + bool can_discard; + struct mail *mail; + int ret; + + if (ismt == NULL || !array_is_created(&ismt->events)) { + /* Nothing to do */ + return 0; + } + + /* Get user script for this mailbox */ + if (isuser->user_script && imap_sieve_mailbox_get_script + (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 + (user, isuser->client->lda_set); + } + + /* Get synchronized view on the mailbox */ + sbox = mailbox_alloc(box->list, box->vname, 0); + if (mailbox_sync(sbox, 0) < 0) { + mailbox_free(&sbox); + return -1; + } + + 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; + struct imap_sieve_mailbox_rule *const *rule_idx; + + /* Find matching rules */ + t_array_init(&mbrules, 16); + imap_sieve_mailbox_rules_get + (user, box, src_box, cause, &mbrules); + + /* Apply all matched rules */ + t_array_init(&scripts_before, 8); + t_array_init(&scripts_after, 8); + array_foreach(&mbrules, rule_idx) { + struct imap_sieve_mailbox_rule *rule = *rule_idx; + + if (rule->before != NULL) + array_append(&scripts_before, &rule->before, 1); + if (rule->after != NULL) + array_append(&scripts_after, &rule->after, 1); + } + array_append_space(&scripts_before); + array_append_space(&scripts_after); + + /* Initialize */ + ret = imap_sieve_run_init + (isuser->isieve, box, cause, script_name, + array_idx(&scripts_before, 0), + array_idx(&scripts_after, 0), &isrun); + } T_END; + + if (ret <= 0) { + // FIXME: temp fail should be handled properly + return 0; + } + + /* Create transaction for event messages */ + st = mailbox_transaction_begin(sbox, 0); + 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; + + /* Determine UID for saved message */ + if (mevent->mail_uid > 0 || + !seq_range_array_iter_nth(&siter, mevent->save_seq, &uid)) + uid = mevent->mail_uid; + + /* Select event message */ + if (!mail_set_uid(mail, uid)) { + imap_sieve_mailbox_error(sbox, + "Failed to find message for Sieve event (UID=%llu)", + (unsigned long long)uid); + continue; + } + + i_assert(!mail->expunged); + + /* Run scripts for this mail */ + ret = imap_sieve_run_mail + (isrun, mail, mevent->changed_flags); + + /* Handle the result */ + if (ret < 0) { + /* Sieve error; keep */ + } else if (ret > 0 && can_discard) { + /* Discard */ + mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED); + } + } + + /* Cleanup */ + mail_free(&mail); + ret = mailbox_transaction_commit(&st); + imap_sieve_run_deinit(&isrun); + 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(t->box); + struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(user); + int ret = 0; + + if (ismt != NULL && ismt->tmp_mail != NULL) + mail_free(&ismt->tmp_mail); + + if ((lbox->super.transaction_commit(t, changes_r)) < 0) + ret = -1; + else { + 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(t->box); + + if (ismt != NULL && ismt->tmp_mail != NULL) + mail_free(&ismt->tmp_mail); + + 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(user); + struct mailbox_vfuncs *v = box->vlast; + union mailbox_module_context *lbox; + + if (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_begin = imap_sieve_mailbox_save_begin; + 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(user); + string_t *identifier = t_str_new(256); + unsigned int i = 0; + size_t prefix_len; + + 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; + + mbrule = p_new(user->pool, + struct imap_sieve_mailbox_rule, 1); + mbrule->index = i; + mbrule->mailbox = ph_p_str_trim(user->pool, setval, "\t "); + + str_truncate(identifier, id_len); + str_append(identifier, "_from"); + setval = mail_user_plugin_getenv(user, str_c(identifier)); + if (setval != NULL && *setval != '\0') { + mbrule->from = ph_p_str_trim(user->pool, setval, "\t "); + 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); + + if (user->mail_debug) { + imap_sieve_debug(user, "Static mailbox rule [%u]: " + "mailbox=`%s' from=`%s' causes=(%s) => " + "before=%s after=%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))); + } + + 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 == '\0') + 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(user); + struct imap_sieve_mailbox_rule *const *rule_idx; + 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(&isuser->mbox_patterns, rule_idx) { + struct imap_sieve_mailbox_rule *rule = *rule_idx; + 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(user); + struct imap_sieve_mailbox_rule lookup_rule; + struct imap_sieve_mailbox_rule *rule; + + memset(&lookup_rule, 0, sizeof(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 = 0; + + /* 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_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(user); + + if (isuser->isieve != NULL) + imap_sieve_deinit(&isuser->isieve); + + hash_table_destroy(&isuser->mbox_rules); + 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); + + 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); + + imap_sieve_mailbox_rules_init(user); +} + +/* + * 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 imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT(client->user); + + isuser->client = client; + isuser->user_script = user_script; +} + +/* + * + */ + +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); +} diff --git a/src/plugins/imapsieve/imap-sieve-storage.h b/src/plugins/imapsieve/imap-sieve-storage.h new file mode 100644 index 000000000..0921e0694 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve-storage.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __IMAP_SIEVE_STORAGE_H +#define __IMAP_SIEVE_STORAGE_H + +void imap_sieve_storage_init(struct module *module); +void imap_sieve_storage_deinit(void); + +void imap_sieve_storage_client_created(struct client *client, + bool user_script); + +#endif diff --git a/src/plugins/imapsieve/imap-sieve.c b/src/plugins/imapsieve/imap-sieve.c new file mode 100644 index 000000000..5904aab90 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve.c @@ -0,0 +1,755 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "home-expand.h" +#include "mail-storage.h" +#include "mail-user.h" +#include "lda-settings.h" +#include "mail-deliver.h" +#include "duplicate.h" +#include "smtp-client.h" + +#include "sieve.h" +#include "sieve-script.h" +#include "sieve-storage.h" + +#include "ext-imapsieve-common.h" + +#include "imap-sieve.h" + +/* + * Configuration + */ + +#define IMAP_SIEVE_MAX_USER_ERRORS 30 + +/* + * IMAP Sieve + */ + +struct imap_sieve { + pool_t pool; + struct mail_user *user; + const struct lda_settings *lda_set; + const char *home_dir; + + struct sieve_instance *svinst; + struct sieve_storage *storage; + const struct sieve_extension *ext_imapsieve; + + struct duplicate_context *dup_ctx; + + struct sieve_error_handler *master_ehandler; +}; + +static const char * +mail_sieve_get_setting(void *context, const char *identifier) +{ + struct imap_sieve *isieve = (struct imap_sieve *)context; + + return mail_user_plugin_getenv(isieve->user, identifier); +} + +static const struct sieve_callbacks mail_sieve_callbacks = { + NULL, + mail_sieve_get_setting +}; + + +struct imap_sieve *imap_sieve_init(struct mail_user *user, + const struct lda_settings *lda_set) +{ + struct sieve_environment svenv; + struct imap_sieve *isieve; + bool debug = user->mail_debug; + pool_t pool; + + pool = pool_alloconly_create("imap_sieve", 256); + isieve = p_new(pool, struct imap_sieve, 1); + isieve->pool = pool; + isieve->user = user; + isieve->lda_set = lda_set; + + isieve->dup_ctx = duplicate_init(user); + + memset(&svenv, 0, sizeof(svenv)); + svenv.username = user->username; + (void)mail_user_get_home(user, &svenv.home_dir); + svenv.hostname = lda_set->hostname; + svenv.base_dir = user->set->base_dir; + svenv.flags = SIEVE_FLAG_HOME_RELATIVE; + svenv.location = SIEVE_ENV_LOCATION_MS; + svenv.delivery_phase = SIEVE_DELIVERY_PHASE_POST; + + isieve->home_dir = p_strdup(pool, svenv.home_dir); + + isieve->svinst = sieve_init + (&svenv, &mail_sieve_callbacks, isieve, debug); + + isieve->ext_imapsieve = sieve_extension_replace + (isieve->svinst, &imapsieve_extension, TRUE); + + isieve->master_ehandler = sieve_master_ehandler_create + (isieve->svinst, NULL, 0); // FIXME: prefix? + sieve_system_ehandler_set(isieve->master_ehandler); + sieve_error_handler_accept_infolog(isieve->master_ehandler, TRUE); + sieve_error_handler_accept_debuglog(isieve->master_ehandler, debug); + + return isieve; +} + +void imap_sieve_deinit(struct imap_sieve **_isieve) +{ + struct imap_sieve *isieve = *_isieve; + + *_isieve = NULL; + + sieve_error_handler_unref(&isieve->master_ehandler); + + if (isieve->storage != NULL) + sieve_storage_unref(&isieve->storage); + sieve_extension_unregister(isieve->ext_imapsieve); + sieve_deinit(&isieve->svinst); + + duplicate_deinit(&isieve->dup_ctx); + + pool_unref(&isieve->pool); +} + +static int +imap_sieve_get_storage(struct imap_sieve *isieve, + struct sieve_storage **storage_r) +{ + enum sieve_storage_flags storage_flags = 0; + enum sieve_error error; + + if (isieve->storage != NULL) { + *storage_r = isieve->storage; + return 1; + } + + // FIXME: limit interval between retries + + isieve->storage = sieve_storage_create_main + (isieve->svinst, isieve->user, storage_flags, &error); + if (isieve->storage == NULL) { + if (error == SIEVE_ERROR_TEMP_FAILURE) + return -1; + return 0; + } + *storage_r = isieve->storage; + return 1; +} + +/* + * Mail transmission + */ + +static void *imap_sieve_smtp_start +(const struct sieve_script_env *senv, const char *return_path) +{ + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + return (void *)smtp_client_init + (isctx->isieve->lda_set, return_path); +} + +static void imap_sieve_smtp_add_rcpt +(const struct sieve_script_env *senv ATTR_UNUSED, void *handle, + const char *address) +{ + struct smtp_client *smtp_client = (struct smtp_client *) handle; + + smtp_client_add_rcpt(smtp_client, address); +} + +static struct ostream *imap_sieve_smtp_send +(const struct sieve_script_env *senv ATTR_UNUSED, void *handle) +{ + struct smtp_client *smtp_client = (struct smtp_client *) handle; + + return smtp_client_send(smtp_client); +} + +static int imap_sieve_smtp_finish +(const struct sieve_script_env *senv ATTR_UNUSED, void *handle, + const char **error_r) +{ + struct smtp_client *smtp_client = (struct smtp_client *) handle; + + return smtp_client_deinit_timeout + (smtp_client, LDA_SUBMISSION_TIMEOUT_SECS, error_r); +} + +/* + * Duplicate checking + */ + +static int imap_sieve_duplicate_check +(const struct sieve_script_env *senv, const void *id, + size_t id_size) +{ + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + return duplicate_check(isctx->isieve->dup_ctx, + id, id_size, senv->user->username); +} + +static void imap_sieve_duplicate_mark +(const struct sieve_script_env *senv, const void *id, + size_t id_size, time_t time) +{ + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + + duplicate_mark(isctx->isieve->dup_ctx, + id, id_size, senv->user->username, time); +} + +static void imap_sieve_duplicate_flush +(const struct sieve_script_env *senv) +{ + struct imap_sieve_context *isctx = + (struct imap_sieve_context *)senv->script_context; + duplicate_flush(isctx->isieve->dup_ctx); +} + +/* + * IMAP Sieve run + */ + +struct imap_sieve_run_script { + struct sieve_script *script; + struct sieve_binary *binary; + + /* Compile failed once; don't try again for this transaction */ + unsigned int compile_failed:1; + /* Binary corrupt after recompile; don't recompile again */ + unsigned int binary_corrupt:1; +}; + +struct imap_sieve_run { + pool_t pool; + struct imap_sieve *isieve; + struct mailbox *mailbox; + char *cause; + + struct sieve_error_handler *user_ehandler; + char *userlog; + + struct sieve_script *user_script; + struct imap_sieve_run_script *scripts; + unsigned int scripts_count; +}; + +static void +imap_sieve_run_init_user_log( + struct imap_sieve_run *isrun) +{ + struct imap_sieve *isieve = isrun->isieve; + struct sieve_instance *svinst = isieve->svinst; + const char *log_path; + + /* Determine user log file path */ // FIXME: code shared with LDA + if ( (log_path=mail_user_plugin_getenv + (isieve->user, "sieve_user_log")) == NULL ) { + const char *path; + + if ( isrun->user_script == NULL || + (path=sieve_file_script_get_path + (isrun->user_script)) == NULL ) { + /* Default */ + if ( isieve->home_dir != NULL ) { + log_path = t_strconcat + (isieve->home_dir, "/.dovecot.sieve.log", NULL); + } + } else { + /* Use script file as a basis (legacy behavior) */ + log_path = t_strconcat(path, ".log", NULL); + } + } else { + if ( isieve->home_dir != NULL ) { + /* Expand home dir if necessary */ + if ( log_path[0] == '~' ) { + log_path = home_expand_tilde + (log_path, isieve->home_dir); + } else if ( log_path[0] != '/' ) { + log_path = t_strconcat + (isieve->home_dir, "/", log_path, NULL); + } + } + } + + /* Initialize user error handler */ + if ( log_path != NULL ) { + isrun->userlog = p_strdup(isrun->pool, log_path); + isrun->user_ehandler = sieve_logfile_ehandler_create + (svinst, log_path, IMAP_SIEVE_MAX_USER_ERRORS); + } +} + +int imap_sieve_run_init(struct imap_sieve *isieve, + struct mailbox *mailbox, const char *cause, + const char *script_name, + const char *const *scripts_before, + const char *const *scripts_after, + struct imap_sieve_run **isrun_r) +{ + struct sieve_instance *svinst = isieve->svinst; + struct imap_sieve_run *isrun; + struct sieve_storage *storage; + struct imap_sieve_run_script *scripts; + struct sieve_script *user_script; + const char *const *sp; + enum sieve_error error; + pool_t pool; + unsigned int max_len, count; + int ret; + + /* Determine how many scripts we may run for this event */ + max_len = 0; + if (scripts_before != NULL) + max_len += str_array_length(scripts_before); + if (script_name != NULL) + max_len++; + if (scripts_after != NULL) + max_len += str_array_length(scripts_after); + if (max_len == 0) + return 0; + + /* Get storage for user script */ + storage = NULL; + if ((ret=imap_sieve_get_storage(isieve, &storage)) < 0) + return ret; + + /* Open all scripts */ + count = 0; + pool = pool_alloconly_create("imap_sieve_run", 256); + scripts = p_new(pool, struct imap_sieve_run_script, max_len); + + /* Admin scripts before user script */ + if (scripts_before != NULL) { + for (sp = scripts_before; *sp != NULL; sp++) { + i_assert(count < max_len); + scripts[count].script = sieve_script_create_open + (svinst, *sp, NULL, &error); + if (scripts[count].script != NULL) + count++; + else if (error == SIEVE_ERROR_TEMP_FAILURE) + return -1; + } + } + + /* The user script */ + user_script = NULL; + if (storage != NULL && script_name != NULL && + *script_name != '\0') { + i_assert(count < max_len); + scripts[count].script = sieve_storage_open_script + (storage, script_name, &error); + if (scripts[count].script != NULL) { + user_script = scripts[count].script; + count++; + } else if (error == SIEVE_ERROR_TEMP_FAILURE) { + return -1; + } + } + + /* Admin scripts after user script */ + if (scripts_after != NULL) { + for (sp = scripts_after; *sp != NULL; sp++) { + i_assert(count < max_len); + scripts[count].script = sieve_script_create_open + (svinst, *sp, NULL, &error); + if (scripts[count].script != NULL) + count++; + else if (error == SIEVE_ERROR_TEMP_FAILURE) + return -1; + } + } + + if (count == 0) { + /* None of the scripts could be opened */ + pool_unref(&pool); + return 0; + } + + /* Initialize */ + isrun = p_new(pool, struct imap_sieve_run, 1); + isrun->pool = pool; + isrun->isieve = isieve; + isrun->mailbox = mailbox; + isrun->cause = p_strdup(pool, cause); + isrun->user_script = user_script; + isrun->scripts = scripts; + isrun->scripts_count = count; + + imap_sieve_run_init_user_log(isrun); + + *isrun_r = isrun; + return 1; +} + +void imap_sieve_run_deinit(struct imap_sieve_run **_isrun) +{ + struct imap_sieve_run *isrun = *_isrun; + unsigned int i; + + *_isrun = NULL; + + for (i = 0; i < isrun->scripts_count; i++) { + if (isrun->scripts[i].binary != NULL) + sieve_close(&isrun->scripts[i].binary); + if (isrun->scripts[i].script != NULL) + sieve_script_unref(&isrun->scripts[i].script); + } + if (isrun->user_ehandler != NULL) + sieve_error_handler_unref(&isrun->user_ehandler); + + pool_unref(&isrun->pool); +} + +static struct sieve_binary * +imap_sieve_run_open_script( + struct imap_sieve_run *isrun, + struct sieve_script *script, + enum sieve_compile_flags cpflags, + bool recompile, enum sieve_error *error_r) +{ + struct imap_sieve *isieve = isrun->isieve; + struct sieve_instance *svinst = isieve->svinst; + struct sieve_error_handler *ehandler = isrun->user_ehandler; + struct sieve_binary *sbin; + const char *compile_name = "compile"; + bool debug = isieve->user->mail_debug; + + if ( recompile ) { + /* Warn */ + sieve_sys_warning(svinst, + "Encountered corrupt binary: re-compiling script %s", + sieve_script_location(script)); + compile_name = "re-compile"; + } else if ( debug ) { + sieve_sys_debug(svinst, + "Loading script %s", sieve_script_location(script)); + } + + if ( script == isrun->user_script ) + ehandler = isrun->user_ehandler; + else + ehandler = isieve->master_ehandler; + sieve_error_handler_reset(ehandler); + + /* Load or compile the sieve script */ + if ( recompile ) { + sbin = sieve_compile_script + (script, ehandler, cpflags, error_r); + } else { + sbin = sieve_open_script + (script, ehandler, cpflags, error_r); + } + + /* Handle error */ + if ( sbin == NULL ) { + switch ( *error_r ) { + /* Script not found */ + case SIEVE_ERROR_NOT_FOUND: + if ( debug ) { + sieve_sys_debug(svinst, "Script `%s' is missing for %s", + sieve_script_location(script), compile_name); + } + break; + /* Temporary failure */ + case SIEVE_ERROR_TEMP_FAILURE: + sieve_sys_error(svinst, + "Failed to open script `%s' for %s (temporary failure)", + sieve_script_location(script), compile_name); + break; + /* Compile failed */ + case SIEVE_ERROR_NOT_VALID: + if (script == isrun->user_script && isrun->userlog != NULL ) { + sieve_sys_info(svinst, + "Failed to %s script `%s' " + "(view user logfile `%s' for more information)", + compile_name, sieve_script_location(script), + isrun->userlog); + break; + } + sieve_sys_error(svinst, "Failed to %s script `%s'", + compile_name, sieve_script_location(script)); + break; + /* Something else */ + default: + sieve_sys_error(svinst, "Failed to open script `%s' for %s", + sieve_script_location(script), compile_name); + break; + } + + return NULL; + } + + if (!recompile) + (void)sieve_save(sbin, FALSE, NULL); + return sbin; +} + +static int imap_sieve_handle_exec_status +(struct imap_sieve_run *isrun, + struct sieve_script *script, int status, bool keep, + struct sieve_exec_status *estatus) + ATTR_NULL(2) +{ + struct imap_sieve *isieve = isrun->isieve; + struct sieve_instance *svinst = isieve->svinst; + const char *userlog_notice = ""; + sieve_sys_error_func_t error_func, user_error_func; + enum mail_error mail_error = MAIL_ERROR_NONE; + int ret = -1; + + error_func = user_error_func = sieve_sys_error; + + if ( estatus != NULL && estatus->last_storage != NULL && + estatus->store_failed) { + mail_storage_get_last_error(estatus->last_storage, &mail_error); + + /* Don't bother administrator too much with benign errors */ + if ( mail_error == MAIL_ERROR_NOQUOTA ) { + error_func = sieve_sys_info; + user_error_func = sieve_sys_info; + } + } + + if ( script == isrun->user_script && isrun->userlog != NULL ) { + userlog_notice = t_strdup_printf + (" (user logfile %s may reveal additional details)", + isrun->userlog); + user_error_func = sieve_sys_info; + } + + switch ( status ) { + case SIEVE_EXEC_FAILURE: + user_error_func(svinst, + "Execution of script %s failed%s", + sieve_script_location(script), userlog_notice); + ret = 0; + break; + case SIEVE_EXEC_TEMP_FAILURE: + error_func(svinst, + "Execution of script %s was aborted " + "due to temporary failure%s", + sieve_script_location(script), userlog_notice); + ret = -1; + break; + case SIEVE_EXEC_BIN_CORRUPT: + sieve_sys_error(svinst, + "!!BUG!!: Binary compiled from %s is still corrupt; " + "bailing out and reverting to default action", + sieve_script_location(script)); + ret = 0; + break; + case SIEVE_EXEC_KEEP_FAILED: + error_func(svinst, + "Execution of script %s failed " + "with unsuccessful implicit keep%s", + sieve_script_location(script), userlog_notice); + ret = 0; + break; + case SIEVE_EXEC_OK: + ret = (keep ? 0 : 1); + break; + } + + return ret; +} + +static int imap_sieve_run_scripts +(struct imap_sieve_run *isrun, + const struct sieve_message_data *msgdata, + const struct sieve_script_env *scriptenv) +{ + struct imap_sieve *isieve = isrun->isieve; + struct sieve_instance *svinst = isieve->svinst; + struct imap_sieve_run_script *scripts = isrun->scripts; + unsigned int count = isrun->scripts_count; + struct sieve_multiscript *mscript; + struct sieve_error_handler *ehandler; + struct sieve_script *last_script = NULL; + bool user_script = FALSE, more = TRUE, compile_error = FALSE; + bool debug = isieve->user->mail_debug, keep = TRUE; + enum sieve_compile_flags cpflags; + enum sieve_execute_flags exflags; + + enum sieve_error error; + unsigned int i; + int ret; + + /* Start execution */ + mscript = sieve_multiscript_start_execute + (svinst, msgdata, scriptenv); + + /* Execute scripts */ + for ( i = 0; i < count && more; i++ ) { + struct sieve_script *script = scripts[i].script; + struct sieve_binary *sbin = scripts[i].binary; + + cpflags = 0; + exflags = SIEVE_EXECUTE_FLAG_DEFER_KEEP | + SIEVE_EXECUTE_FLAG_NO_ENVELOPE; + + user_script = ( script == isrun->user_script ); + last_script = script; + + if ( user_script ) { + cpflags |= SIEVE_COMPILE_FLAG_NOGLOBAL; + exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL; + ehandler = isrun->user_ehandler; + } else { + cpflags |= SIEVE_COMPILE_FLAG_NO_ENVELOPE; + ehandler = isieve->master_ehandler; + } + + /* Open */ + if (sbin == NULL) { + if ( debug ) { + sieve_sys_debug(svinst, + "Opening script %d of %d from `%s'", + i+1, count, sieve_script_location(script)); + } + + /* Already known to fail */ + if (scripts[i].compile_failed) { + compile_error = TRUE; + break; + } + + /* Try to open/compile binary */ + scripts[i].binary = sbin = imap_sieve_run_open_script + (isrun, script, cpflags, FALSE, &error); + if ( sbin == NULL ) { + scripts[i].compile_failed = TRUE; + compile_error = TRUE; + break; + } + } + + /* Execute */ + if ( debug ) { + sieve_sys_debug(svinst, + "Executing script from `%s'", + sieve_get_source(sbin)); + } + more = sieve_multiscript_run(mscript, + sbin, ehandler, ehandler, exflags); + + if ( !more ) { + if ( !scripts[i].binary_corrupt && + sieve_multiscript_status(mscript) + == SIEVE_EXEC_BIN_CORRUPT && + sieve_is_loaded(sbin) ) { + + /* Close corrupt script */ + sieve_close(&sbin); + + /* Recompile */ + scripts[i].binary = sbin = imap_sieve_run_open_script + (isrun, script, cpflags, FALSE, &error); + if ( sbin == NULL ) { + scripts[i].compile_failed = TRUE; + compile_error = TRUE; + break; + } + + /* Execute again */ + more = sieve_multiscript_run(mscript, sbin, + ehandler, ehandler, exflags); + + /* Save new version */ + + if ( sieve_multiscript_status(mscript) + == SIEVE_EXEC_BIN_CORRUPT ) + scripts[i].binary_corrupt = TRUE; + else if ( more ) + (void)sieve_save(sbin, FALSE, NULL); + } + } + } + + /* Finish execution */ + exflags = SIEVE_EXECUTE_FLAG_DEFER_KEEP | + SIEVE_EXECUTE_FLAG_NO_ENVELOPE; + ehandler = (isrun->user_ehandler != NULL ? + isrun->user_ehandler : isieve->master_ehandler); + if ( compile_error && error == SIEVE_ERROR_TEMP_FAILURE ) { + ret = sieve_multiscript_tempfail + (&mscript, ehandler, exflags); + } else { + ret = sieve_multiscript_finish + (&mscript, ehandler, exflags, &keep); + } + + /* Don't log additional messages about compile failure */ + if ( compile_error && ret == SIEVE_EXEC_FAILURE ) { + sieve_sys_info(svinst, + "Aborted script execution sequence " + "with successful implicit keep"); + return 1; + } + + return imap_sieve_handle_exec_status + (isrun, last_script, ret, keep, scriptenv->exec_status); +} + +int imap_sieve_run_mail +(struct imap_sieve_run *isrun, struct mail *mail, + const char *changed_flags) +{ + struct imap_sieve *isieve = isrun->isieve; + const struct lda_settings *lda_set = isieve->lda_set; + struct sieve_message_data msgdata; + struct sieve_script_env scriptenv; + struct sieve_exec_status estatus; + struct imap_sieve_context context; + int ret; + + memset(&context, 0, sizeof(context)); + context.event.mailbox = isrun->mailbox; + context.event.cause = isrun->cause; + context.event.changed_flags = changed_flags; + context.isieve = isieve; + + T_BEGIN { + /* Collect necessary message data */ + + memset(&msgdata, 0, sizeof(msgdata)); + msgdata.mail = mail; + msgdata.auth_user = isieve->user->username; + (void)mail_get_first_header + (msgdata.mail, "Message-ID", &msgdata.id); + + /* Compose script execution environment */ + + memset(&scriptenv, 0, sizeof(scriptenv)); + memset(&estatus, 0, sizeof(estatus)); + scriptenv.default_mailbox = mailbox_get_vname(isrun->mailbox); + scriptenv.user = isieve->user; + scriptenv.postmaster_address = lda_set->postmaster_address; + scriptenv.smtp_start = imap_sieve_smtp_start; + scriptenv.smtp_add_rcpt = imap_sieve_smtp_add_rcpt; + scriptenv.smtp_send = imap_sieve_smtp_send; + scriptenv.smtp_finish = imap_sieve_smtp_finish; + scriptenv.duplicate_mark = imap_sieve_duplicate_mark; + scriptenv.duplicate_check = imap_sieve_duplicate_check; + scriptenv.duplicate_flush = imap_sieve_duplicate_flush; + scriptenv.exec_status = &estatus; + scriptenv.script_context = (void *)&context; + + /* Execute script(s) */ + + ret = imap_sieve_run_scripts(isrun, &msgdata, &scriptenv); + } T_END; + + return ret; +} diff --git a/src/plugins/imapsieve/imap-sieve.h b/src/plugins/imapsieve/imap-sieve.h new file mode 100644 index 000000000..c1057cb05 --- /dev/null +++ b/src/plugins/imapsieve/imap-sieve.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __IMAP_SIEVE_H +#define __IMAP_SIEVE_H + +struct lda_settings; + +/* + * IMAP event + */ + +struct imap_sieve_event { + struct mailbox *mailbox; + const char *cause; + const char *changed_flags; +}; + +struct imap_sieve_context { + struct imap_sieve_event event; + + struct imap_sieve *isieve; +}; + +static inline bool +imap_sieve_event_cause_valid(const char *cause) +{ + return (strcasecmp(cause, "APPEND") == 0 || + strcasecmp(cause, "COPY") == 0 || + strcasecmp(cause, "FLAG") == 0); +} + +/* + * IMAP Sieve + */ + +struct imap_sieve; + +struct imap_sieve *imap_sieve_init(struct mail_user *user, + const struct lda_settings *lda_set); +void imap_sieve_deinit(struct imap_sieve **_isieve); + +/* + * IMAP Sieve run + */ + +struct imap_sieve_run; + +int imap_sieve_run_init(struct imap_sieve *isieve, + struct mailbox *mailbox, const char *cause, + const char *script_name, + const char *const *scripts_before, + const char *const *scripts_after, + struct imap_sieve_run **isrun_r) + ATTR_NULL(4, 5, 6); + +int imap_sieve_run_mail +(struct imap_sieve_run *isrun, struct mail *mail, + const char *changed_flags); + +void imap_sieve_run_deinit(struct imap_sieve_run **_isrun); + +#endif diff --git a/src/plugins/imapsieve/sieve-imapsieve-plugin.c b/src/plugins/imapsieve/sieve-imapsieve-plugin.c new file mode 100644 index 000000000..9547160f3 --- /dev/null +++ b/src/plugins/imapsieve/sieve-imapsieve-plugin.c @@ -0,0 +1,56 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#include "sieve-common.h" +#include "sieve-error.h" +#include "sieve-extensions.h" + +#include "ext-imapsieve-common.h" + +#include "sieve-imapsieve-plugin.h" + +/* + * Sieve plugin interface + */ + +const char *sieve_imapsieve_plugin_version = PIGEONHOLE_ABI_VERSION; + +void sieve_imapsieve_plugin_load +(struct sieve_instance *svinst, void **context) +{ + const struct sieve_extension *ext; + + ext = sieve_extension_register + (svinst, &imapsieve_extension_dummy, TRUE); + + if ( svinst->debug ) { + sieve_sys_debug(svinst, + "Sieve imapsieve plugin for %s version %s loaded", + PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL); + } + + *context = (void *)ext; +} + +void sieve_imapsieve_plugin_unload +(struct sieve_instance *svinst ATTR_UNUSED, void *context) +{ + const struct sieve_extension *ext = + (const struct sieve_extension *)context; + + sieve_extension_unregister(ext); +} + +/* + * Module interface + */ + +void sieve_imapsieve_plugin_init(void) +{ + /* Nothing */ +} + +void sieve_imapsieve_plugin_deinit(void) +{ + /* Nothing */ +} diff --git a/src/plugins/imapsieve/sieve-imapsieve-plugin.h b/src/plugins/imapsieve/sieve-imapsieve-plugin.h new file mode 100644 index 000000000..971c38a2b --- /dev/null +++ b/src/plugins/imapsieve/sieve-imapsieve-plugin.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2016 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __SIEVE_IMAPSIEVE_PLUGIN_H +#define __SIEVE_IMAPSIEVE_PLUGIN_H + +/* + * Plugin interface + */ + +void sieve_imapsieve_plugin_load + (struct sieve_instance *svinst, void **context); +void sieve_imapsieve_plugin_unload + (struct sieve_instance *svinst, void *context); + +/* + * Module interface + */ + +void sieve_imapsieve_plugin_init(void); +void sieve_imapsieve_plugin_deinit(void); + +#endif /* __SIEVE_IMAPSIEVE_PLUGIN_H */