summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/sieve-actions.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-actions.c')
-rw-r--r--pigeonhole/src/lib-sieve/sieve-actions.c1096
1 files changed, 1096 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-actions.c b/pigeonhole/src/lib-sieve/sieve-actions.c
new file mode 100644
index 0000000..86c9954
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-actions.c
@@ -0,0 +1,1096 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "smtp-params.h"
+#include "mail-storage.h"
+#include "message-date.h"
+#include "message-size.h"
+
+#include "rfc2822.h"
+
+#include "sieve-code.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-actions.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+/*
+ * Action execution environment
+ */
+
+struct event_passthrough *
+sieve_action_create_finish_event(const struct sieve_action_exec_env *aenv)
+{
+ struct event_passthrough *e =
+ event_create_passthrough(aenv->event)->
+ set_name("sieve_action_finished");
+
+ return e;
+}
+
+/*
+ * Action instance
+ */
+
+bool sieve_action_is_executed(const struct sieve_action *act,
+ struct sieve_result *result)
+{
+ unsigned int cur_exec_seq = sieve_result_get_exec_seq(result);
+
+ i_assert(act->exec_seq <= cur_exec_seq);
+ return (act->exec_seq < cur_exec_seq);
+}
+
+/*
+ * Side-effect operand
+ */
+
+const struct sieve_operand_class
+sieve_side_effect_operand_class = { "SIDE-EFFECT" };
+
+bool sieve_opr_side_effect_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ struct sieve_side_effect seffect;
+ const struct sieve_side_effect_def *sdef;
+
+ if (!sieve_opr_object_dump(denv, &sieve_side_effect_operand_class,
+ address, &seffect.object))
+ return FALSE;
+
+ sdef = seffect.def =
+ (const struct sieve_side_effect_def *)seffect.object.def;
+
+ if (sdef->dump_context != NULL) {
+ sieve_code_descend(denv);
+ if (!sdef->dump_context(&seffect, denv, address))
+ return FALSE;
+ sieve_code_ascend(denv);
+ }
+ return TRUE;
+}
+
+int sieve_opr_side_effect_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address,
+ struct sieve_side_effect *seffect)
+{
+ const struct sieve_side_effect_def *sdef;
+ int ret;
+
+ seffect->context = NULL;
+
+ if (!sieve_opr_object_read(renv, &sieve_side_effect_operand_class,
+ address, &seffect->object))
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ sdef = seffect->def =
+ (const struct sieve_side_effect_def *)seffect->object.def;
+
+ if (sdef->read_context != NULL &&
+ (ret = sdef->read_context(seffect, renv, address,
+ &seffect->context)) <= 0)
+ return ret;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_action_opr_optional_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address, signed int *opt_code)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if (opt_code == NULL) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while (opok) {
+ int opt;
+
+ opt = sieve_opr_optional_dump(denv, address, opt_code);
+ if (opt <= 0)
+ return opt;
+
+ if (*opt_code == SIEVE_OPT_SIDE_EFFECT)
+ opok = sieve_opr_side_effect_dump(denv, address);
+ else
+ return (final ? -1 : 1);
+ }
+
+ return -1;
+}
+
+int sieve_action_opr_optional_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address,
+ signed int *opt_code, int *exec_status,
+ struct sieve_side_effects_list **list)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE;
+ int ret;
+
+ if (opt_code == NULL) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if (exec_status != NULL)
+ *exec_status = SIEVE_EXEC_OK;
+
+ for (;;) {
+ int opt;
+
+ opt = sieve_opr_optional_read(renv, address, opt_code);
+ if (opt <= 0) {
+ if (opt < 0 && exec_status != NULL)
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return opt;
+ }
+
+ if (*opt_code == SIEVE_OPT_SIDE_EFFECT) {
+ struct sieve_side_effect seffect;
+
+ i_assert(list != NULL);
+
+ ret = sieve_opr_side_effect_read(renv, address,
+ &seffect);
+ if (ret <= 0) {
+ if (exec_status != NULL)
+ *exec_status = ret;
+ return -1;
+ }
+
+ if (*list == NULL) {
+ *list = sieve_side_effects_list_create(
+ renv->result);
+ }
+
+ sieve_side_effects_list_add(*list, &seffect);
+ } else {
+ if (final) {
+ sieve_runtime_trace_error(
+ renv, "invalid optional operand");
+ if (exec_status != NULL)
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+/*
+ * Store action
+ */
+
+/* Forward declarations */
+
+static bool
+act_store_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2);
+
+static int
+act_store_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_store_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context);
+static int
+act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep);
+static int
+act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+static void
+act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool success);
+
+/* Action object */
+
+const struct sieve_action_def act_store = {
+ .name = "store",
+ .flags =
+ SIEVE_ACTFLAG_TRIES_DELIVER |
+ SIEVE_ACTFLAG_MAIL_STORAGE,
+ .equals = act_store_equals,
+ .check_duplicate = act_store_check_duplicate,
+ .print = act_store_print,
+ .start = act_store_start,
+ .execute = act_store_execute,
+ .commit = act_store_commit,
+ .rollback = act_store_rollback,
+};
+
+/* API */
+
+int sieve_act_store_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const char *mailbox)
+{
+ pool_t pool;
+ struct act_store_context *act;
+
+ /* Add redirect action to the result */
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_store_context, 1);
+ act->mailbox = p_strdup(pool, mailbox);
+
+ return sieve_result_add_action(renv, NULL, name, &act_store, seffects,
+ (void *)act, 0, TRUE);
+}
+
+void sieve_act_store_add_flags(const struct sieve_action_exec_env *aenv,
+ void *tr_context, const char *const *keywords,
+ enum mail_flags flags)
+{
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+
+ i_assert(trans != NULL);
+
+ /* Assign mail keywords for subsequent mailbox_copy() */
+ if (*keywords != NULL) {
+ const char *const *kw;
+
+ if (!array_is_created(&trans->keywords)) {
+ pool_t pool = sieve_result_pool(aenv->result);
+ p_array_init(&trans->keywords, pool, 2);
+ }
+
+ kw = keywords;
+ while (*kw != NULL) {
+ array_append(&trans->keywords, kw, 1);
+ kw++;
+ }
+ }
+
+ /* Assign mail flags for subsequent mailbox_copy() */
+ trans->flags |= flags;
+
+ trans->flags_altered = TRUE;
+}
+
+/* Equality */
+
+static bool
+act_store_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2)
+{
+ struct act_store_context *st_ctx1 =
+ (act1 == NULL ?
+ NULL : (struct act_store_context *)act1->context);
+ struct act_store_context *st_ctx2 =
+ (act2 == NULL ?
+ NULL : (struct act_store_context *)act2->context);
+ const char *mailbox1, *mailbox2;
+
+ /* FIXME: consider namespace aliases */
+
+ if (st_ctx1 == NULL && st_ctx2 == NULL)
+ return TRUE;
+
+ mailbox1 = (st_ctx1 == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx1->mailbox);
+ mailbox2 = (st_ctx2 == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx2->mailbox);
+
+ if (strcmp(mailbox1, mailbox2) == 0)
+ return TRUE;
+
+ return (strcasecmp(mailbox1, "INBOX") == 0 &&
+ strcasecmp(mailbox2, "INBOX") == 0);
+}
+
+/* Result verification */
+
+static int
+act_store_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return (act_store_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
+}
+
+/* Result printing */
+
+static void
+act_store_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ struct act_store_context *ctx =
+ (struct act_store_context *)action->context;
+ const char *mailbox;
+
+ mailbox = (ctx == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(rpenv->scriptenv) :
+ ctx->mailbox);
+
+ sieve_result_action_printf(rpenv, "store message in folder: %s",
+ str_sanitize(mailbox, 128));
+
+ *keep = FALSE;
+}
+
+/* Action implementation */
+
+void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans)
+{
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ trans->error = p_strdup(pool,
+ mailbox_get_last_internal_error(trans->box,
+ &trans->error_code));
+}
+
+static bool
+act_store_mailbox_alloc(const struct sieve_action_exec_env *aenv,
+ const char *mailbox, struct mailbox **box_r,
+ enum mail_error *error_code_r, const char **error_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct mailbox *box;
+ struct mail_storage **storage = &(eenv->exec_status->last_storage);
+ enum mailbox_flags flags = MAILBOX_FLAG_POST_SESSION;
+
+ *box_r = NULL;
+ *error_code_r = MAIL_ERROR_NONE;
+ *error_r = NULL;
+
+ if (!uni_utf8_str_is_valid(mailbox)) {
+ /* Just a precaution; already (supposed to be) checked at
+ compiletime/runtime.
+ */
+ *error_r = t_strdup_printf("mailbox name not utf-8: %s",
+ mailbox);
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return FALSE;
+ }
+
+ if (eenv->scriptenv->mailbox_autocreate)
+ flags |= MAILBOX_FLAG_AUTO_CREATE;
+ if (eenv->scriptenv->mailbox_autosubscribe)
+ flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
+ *box_r = box = mailbox_alloc_for_user(eenv->scriptenv->user, mailbox,
+ flags);
+ *storage = mailbox_get_storage(box);
+
+ return TRUE;
+}
+
+static int
+act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context)
+{
+ const struct sieve_action *action = aenv->action;
+ struct act_store_context *ctx =
+ (struct act_store_context *)action->context;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct act_store_transaction *trans;
+ struct mailbox *box = NULL;
+ pool_t pool = sieve_result_pool(aenv->result);
+ const char *error = NULL;
+ enum mail_error error_code = MAIL_ERROR_NONE;
+ bool disabled = FALSE, alloc_failed = FALSE;
+
+ /* If context is NULL, the store action is the result of (implicit)
+ keep.
+ */
+ if (ctx == NULL) {
+ ctx = p_new(pool, struct act_store_context, 1);
+ ctx->mailbox =
+ p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv));
+ }
+
+ e_debug(aenv->event, "Start storing into mailbox %s", ctx->mailbox);
+
+ /* Open the requested mailbox */
+
+ /* NOTE: The caller of the sieve library is allowed to leave user set
+ to NULL. This implementation will then skip actually storing the
+ message.
+ */
+ if (senv->user != NULL) {
+ if (!act_store_mailbox_alloc(aenv, ctx->mailbox, &box,
+ &error_code, &error))
+ alloc_failed = TRUE;
+ } else {
+ disabled = TRUE;
+ }
+
+ /* Create transaction context */
+ trans = p_new(pool, struct act_store_transaction, 1);
+
+ trans->context = ctx;
+ trans->box = box;
+ trans->flags = 0;
+
+ trans->mailbox_name = ctx->mailbox;
+ trans->mailbox_identifier =
+ p_strdup_printf(pool, "'%s'", str_sanitize(ctx->mailbox, 256));
+
+ trans->disabled = disabled;
+
+ if (alloc_failed) {
+ trans->error = p_strdup(pool, error);
+ trans->error_code = error_code;
+ e_debug(aenv->event, "Failed to open mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+ } else {
+ trans->error_code = MAIL_ERROR_NONE;
+ }
+
+ *tr_context = (void *)trans;
+
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ case MAIL_ERROR_NOTFOUND:
+ return SIEVE_EXEC_OK;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ break;
+ }
+ return SIEVE_EXEC_FAILURE;
+}
+
+static struct mail_keywords *
+act_store_keywords_create(const struct sieve_action_exec_env *aenv,
+ ARRAY_TYPE(const_string) *keywords,
+ struct mailbox *box, bool create_empty)
+{
+ struct mail_keywords *box_keywords = NULL;
+ const char *const *kwds = NULL;
+
+ if (!array_is_created(keywords) || array_count(keywords) == 0) {
+ if (!create_empty)
+ return NULL;
+ } else {
+ ARRAY_TYPE(const_string) valid_keywords;
+ const char *error;
+ unsigned int count, i;
+
+ kwds = array_get(keywords, &count);
+ t_array_init(&valid_keywords, count);
+
+ for (i = 0; i < count; i++) {
+ if (mailbox_keyword_is_valid(box, kwds[i], &error)) {
+ array_append(&valid_keywords, &kwds[i], 1);
+ continue;
+ }
+
+ sieve_result_warning(aenv,
+ "specified IMAP keyword '%s' is invalid "
+ "(ignored): %s", str_sanitize(kwds[i], 64),
+ sieve_error_from_external(error));
+ }
+
+ array_append_zero(keywords);
+ kwds = array_idx(keywords, 0);
+ }
+
+ if (mailbox_keywords_create(box, kwds, &box_keywords) < 0) {
+ sieve_result_error(
+ aenv, "invalid keywords set for stored message");
+ return NULL;
+ }
+
+ return box_keywords;
+}
+
+static bool have_equal_keywords(struct mail *mail, struct mail_keywords *new_kw)
+{
+ const ARRAY_TYPE(keyword_indexes) *old_kw_arr =
+ mail_get_keyword_indexes(mail);
+ const unsigned int *old_kw;
+ unsigned int i, j;
+
+ if (array_count(old_kw_arr) != new_kw->count)
+ return FALSE;
+ if (new_kw->count == 0)
+ return TRUE;
+
+ old_kw = array_front(old_kw_arr);
+ for (i = 0; i < new_kw->count; i++) {
+ /* new_kw->count equals old_kw's count and it's easier to use */
+ for (j = 0; j < new_kw->count; j++) {
+ if (old_kw[j] == new_kw->idx[i])
+ break;
+ }
+ if (j == new_kw->count)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+ struct mail *mail = (action->mail != NULL ?
+ action->mail : eenv->msgdata->mail);
+ struct mail_save_context *save_ctx;
+ struct mail_keywords *keywords = NULL;
+ struct mailbox *box;
+ bool backends_equal = FALSE;
+ int status = SIEVE_EXEC_OK;
+
+ /* Verify transaction */
+ if (trans == NULL)
+ return SIEVE_EXEC_FAILURE;
+ box = trans->box;
+
+ /* Check whether we need to do anything */
+ if (trans->disabled) {
+ e_debug(aenv->event, "Skip storing into mailbox %s",
+ trans->mailbox_identifier);
+ *keep = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Exit early if mailbox is not available */
+ if (box == NULL)
+ return SIEVE_EXEC_FAILURE;
+
+ e_debug(aenv->event, "Execute storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ /* Mark attempt to use storage. Can only get here when all previous
+ actions succeeded.
+ */
+ eenv->exec_status->last_storage = mailbox_get_storage(box);
+
+ /* Open the mailbox (may already be open) */
+ if (trans->error_code == MAIL_ERROR_NONE) {
+ if (mailbox_open(box) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+ e_debug(aenv->event, "Failed to open mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+ }
+ }
+
+ /* Exit early if transaction already failed */
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ break;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* If the message originates from the target mailbox, only update the
+ flags and keywords (if not read-only)
+ */
+ if (mailbox_backends_equal(box, mail->box)) {
+ backends_equal = TRUE;
+ } else {
+ struct mail *real_mail;
+
+ if (mail_get_backend_mail(mail, &real_mail) < 0)
+ return SIEVE_EXEC_FAILURE;
+ if (real_mail != mail &&
+ mailbox_backends_equal(box, real_mail->box))
+ backends_equal = TRUE;
+ }
+ if (backends_equal) {
+ trans->redundant = TRUE;
+
+ if (trans->flags_altered && !mailbox_is_readonly(mail->box)) {
+ keywords = act_store_keywords_create(
+ aenv, &trans->keywords, mail->box, TRUE);
+
+ if (keywords != NULL) {
+ if (!have_equal_keywords(mail, keywords)) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mail_update_keywords(mail, MODIFY_REPLACE, keywords);
+ }
+ mailbox_keywords_unref(&keywords);
+ }
+
+ if ((mail_get_flags(mail) & MAIL_FLAGS_NONRECENT) != trans->flags) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mail_update_flags(mail, MODIFY_REPLACE, trans->flags);
+ }
+ }
+ e_debug(aenv->event, "Updated existing mail in mailbox %s",
+ trans->mailbox_identifier);
+ return SIEVE_EXEC_OK;
+
+ /* If the message is modified, only store it in the source mailbox when
+ it is not opened read-only. Mail structs of modified messages have
+ their own mailbox, unrelated to the orignal mail, so this case needs
+ to be handled separately.
+ */
+ } else if (mail != eenv->msgdata->mail &&
+ mailbox_is_readonly(eenv->msgdata->mail->box) &&
+ (mailbox_backends_equal(box, eenv->msgdata->mail->box))) {
+ e_debug(aenv->event,
+ "Not modifying exsiting mail in read-only mailbox %s",
+ trans->mailbox_identifier);
+ trans->redundant = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Mark attempt to store in default mailbox */
+ if (strcmp(trans->context->mailbox,
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(eenv->scriptenv)) == 0)
+ eenv->exec_status->tried_default_save = TRUE;
+
+ /* Start mail transaction */
+ trans->mail_trans = mailbox_transaction_begin(
+ box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
+
+ /* Store the message */
+ save_ctx = mailbox_save_alloc(trans->mail_trans);
+
+ /* Apply keywords and flags that side-effects may have added */
+ if (trans->flags_altered) {
+ keywords = act_store_keywords_create(aenv, &trans->keywords,
+ box, FALSE);
+
+ if (trans->flags != 0 || keywords != NULL) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mailbox_save_set_flags(save_ctx, trans->flags, keywords);
+ }
+ } else {
+ mailbox_save_copy_flags(save_ctx, mail);
+ }
+
+ if (mailbox_save_using_mail(&save_ctx, mail) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+ e_debug(aenv->event, "Failed to save to mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+
+ status = (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ } else {
+ e_debug(aenv->event, "Saving to mailbox %s successful so far",
+ trans->mailbox_identifier);
+ eenv->exec_status->significant_action_executed = TRUE;
+ }
+
+ /* Deallocate keywords */
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+
+ /* Cancel implicit keep if all went well so far */
+ *keep = (status < SIEVE_EXEC_OK);
+
+ return status;
+}
+
+static void
+act_store_log_status(struct act_store_transaction *trans,
+ const struct sieve_action_exec_env *aenv,
+ bool rolled_back, bool status)
+{
+ const char *mailbox_name = trans->mailbox_name;
+ const char *mailbox_identifier = trans->mailbox_identifier;
+
+ if (trans->box != NULL) {
+ const char *mailbox_vname = str_sanitize(mailbox_get_vname(trans->box), 128);
+
+ if (strcmp(trans->mailbox_name, mailbox_vname) != 0) {
+ mailbox_identifier = t_strdup_printf(
+ "%s (%s)", mailbox_identifier,
+ str_sanitize(mailbox_vname, 256));
+ }
+ }
+
+ /* Store disabled? */
+ if (trans->disabled) {
+ sieve_result_global_log(aenv, "store into mailbox %s skipped",
+ mailbox_identifier);
+ /* Store redundant? */
+ } else if (trans->redundant) {
+ sieve_result_global_log(aenv, "left message in mailbox %s",
+ mailbox_identifier);
+ /* Store failed? */
+ } else if (!status) {
+ const char *errstr;
+ enum mail_error error_code;
+
+ if (trans->error == NULL)
+ sieve_act_store_get_storage_error(aenv, trans);
+
+ errstr = trans->error;
+ error_code = trans->error_code;
+
+ if (error_code == MAIL_ERROR_NOQUOTA) {
+ /* Never log quota problems as error in global log */
+ sieve_result_global_log_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ } else if (error_code == MAIL_ERROR_NOTFOUND ||
+ error_code == MAIL_ERROR_PARAMS ||
+ error_code == MAIL_ERROR_PERM) {
+ sieve_result_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ } else {
+ sieve_result_global_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ }
+ /* Store aborted? */
+ } else if (rolled_back) {
+ if (!aenv->action->keep) {
+ sieve_result_global_log(
+ aenv, "store into mailbox %s aborted",
+ mailbox_identifier);
+ } else {
+ e_debug(aenv->event, "Store into mailbox %s aborted",
+ mailbox_identifier);
+ }
+ /* Succeeded */
+ } else {
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("fileinto_mailbox_name", mailbox_name)->
+ add_str("fileinto_mailbox", mailbox_identifier);
+ sieve_result_event_log(aenv, e->event(),
+ "stored mail into mailbox %s",
+ mailbox_identifier);
+ }
+}
+
+static void act_store_cleanup(struct act_store_transaction *trans)
+{
+ if (trans->mail_trans != NULL)
+ mailbox_transaction_rollback(&trans->mail_trans);
+ if (trans->box != NULL)
+ mailbox_free(&trans->box);
+}
+
+static int
+act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+ bool bail_out = FALSE, status = TRUE;
+ int ret = SIEVE_EXEC_OK;
+
+ /* Verify transaction */
+ if (trans == NULL)
+ return SIEVE_EXEC_FAILURE;
+
+ e_debug(aenv->event, "Commit storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ /* Check whether we can commit this transaction */
+ if (trans->error_code != MAIL_ERROR_NONE) {
+ /* Transaction already failed */
+ bail_out = TRUE;
+ status = FALSE;
+ if (trans->error_code == MAIL_ERROR_TEMP)
+ ret = SIEVE_EXEC_TEMP_FAILURE;
+ else
+ ret = SIEVE_EXEC_FAILURE;
+ /* Check whether we need to do anything */
+ } else if (trans->disabled) {
+ /* Nothing to do */
+ bail_out = TRUE;
+ } else if (trans->redundant) {
+ /* This transaction is redundant */
+ bail_out = TRUE;
+ eenv->exec_status->keep_original = TRUE;
+ eenv->exec_status->message_saved = TRUE;
+ }
+ if (bail_out) {
+ act_store_log_status(trans, aenv, FALSE, status);
+ act_store_cleanup(trans);
+ return ret;
+ }
+
+ i_assert(trans->box != NULL);
+ i_assert(trans->mail_trans != NULL);
+
+ /* Mark attempt to use storage. Can only get here when all previous
+ actions succeeded.
+ */
+ eenv->exec_status->last_storage = mailbox_get_storage(trans->box);
+
+ /* Commit mailbox transaction */
+ status = (mailbox_transaction_commit(&trans->mail_trans) == 0);
+
+ /* Note the fact that the message was stored at least once */
+ if (status)
+ eenv->exec_status->message_saved = TRUE;
+ else
+ eenv->exec_status->store_failed = TRUE;
+
+ /* Log our status */
+ act_store_log_status(trans, aenv, FALSE, status);
+
+ /* Clean up */
+ act_store_cleanup(trans);
+
+ if (status)
+ return SIEVE_EXEC_OK;
+
+ return (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+}
+
+static void
+act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool success)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+
+ if (trans == NULL)
+ return;
+
+ e_debug(aenv->event, "Roll back storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ i_assert(trans->box != NULL);
+
+ if (!success) {
+ eenv->exec_status->last_storage =
+ mailbox_get_storage(trans->box);
+ eenv->exec_status->store_failed = TRUE;
+ }
+
+ /* Log status */
+ act_store_log_status(trans, aenv, TRUE, success);
+
+ /* Rollback mailbox transaction and clean up */
+ act_store_cleanup(trans);
+}
+
+/*
+ * Redirect action
+ */
+
+int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const struct smtp_address *to_address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct act_redirect_context *act;
+ pool_t pool;
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_redirect_context, 1);
+ act->to_address = smtp_address_clone(pool, to_address);
+
+ if (sieve_result_add_action(renv, NULL, name, &act_redirect, seffects,
+ (void *)act, svinst->max_redirects,
+ TRUE) < 0)
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action utility functions
+ */
+
+/* Rejecting the mail */
+
+static int
+sieve_action_do_reject_mail(const struct sieve_action_exec_env *aenv,
+ const struct smtp_address *recipient,
+ const char *reason)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ const struct smtp_address *sender, *orig_recipient;
+ struct istream *input;
+ struct ostream *output;
+ struct sieve_smtp_context *sctx;
+ const char *new_msgid, *boundary, *error;
+ string_t *hdr;
+ int ret;
+
+ sender = sieve_message_get_sender(aenv->msgctx);
+ orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
+
+ sctx = sieve_smtp_start_single(senv, sender, NULL, &output);
+
+ /* Just to be sure */
+ if (sctx == NULL) {
+ sieve_result_global_warning(
+ aenv, "reject action has no means to send mail");
+ return SIEVE_EXEC_OK;
+ }
+
+ new_msgid = sieve_message_get_new_id(svinst);
+ boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
+
+ hdr = t_str_new(512);
+ rfc2822_header_write(hdr, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(hdr, "Message-ID", new_msgid);
+ rfc2822_header_write(hdr, "Date", message_date_create(ioloop_time));
+ rfc2822_header_write(hdr, "From", sieve_get_postmaster_address(senv));
+ rfc2822_header_printf(hdr, "To", "<%s>", smtp_address_encode(sender));
+ rfc2822_header_write(hdr, "Subject", "Automatically rejected mail");
+ rfc2822_header_write(hdr, "Auto-Submitted", "auto-replied (rejected)");
+ rfc2822_header_write(hdr, "Precedence", "bulk");
+
+ rfc2822_header_write(hdr, "MIME-Version", "1.0");
+ rfc2822_header_printf(hdr, "Content-Type",
+ "multipart/report; "
+ "report-type=disposition-notification;\r\n"
+ "boundary=\"%s\"", boundary);
+
+ str_append(hdr, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+
+ /* Human readable status report */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type", "text/plain; charset=utf-8");
+ rfc2822_header_write(hdr, "Content-Disposition", "inline");
+ rfc2822_header_write(hdr, "Content-Transfer-Encoding", "8bit");
+
+ str_printfa(hdr, "\r\nYour message to <%s> was automatically rejected:\r\n"
+ "%s\r\n", smtp_address_encode(recipient), reason);
+
+ /* MDN status report */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type",
+ "message/disposition-notification");
+ str_append(hdr, "\r\n");
+ rfc2822_header_write(hdr,
+ "Reporting-UA: %s; Dovecot Mail Delivery Agent",
+ svinst->hostname);
+ if (orig_recipient != NULL) {
+ rfc2822_header_printf(hdr, "Original-Recipient", "rfc822; %s",
+ smtp_address_encode(orig_recipient));
+ }
+ rfc2822_header_printf(hdr, "Final-Recipient", "rfc822; %s",
+ smtp_address_encode(recipient));
+
+ if (msgdata->id != NULL)
+ rfc2822_header_write(hdr, "Original-Message-ID", msgdata->id);
+ rfc2822_header_write(hdr, "Disposition",
+ "automatic-action/MDN-sent-automatically; deleted");
+ str_append(hdr, "\r\n");
+
+ /* original message's headers */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type", "message/rfc822");
+ str_append(hdr, "\r\n");
+ o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+ if (mail_get_hdr_stream(msgdata->mail, NULL, &input) == 0) {
+ /* NOTE: If you add more headers, they need to be sorted. We'll
+ drop Content-Type because we're not including the message
+ body, and having a multipart Content-Type may confuse some
+ MIME parsers when they don't see the message boundaries.
+ */
+ static const char *const exclude_headers[] = {
+ "Content-Type"
+ };
+
+ input = i_stream_create_header_filter(
+ input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR |
+ HEADER_FILTER_HIDE_BODY,
+ exclude_headers, N_ELEMENTS(exclude_headers),
+ *null_header_filter_callback, (void *)NULL);
+ o_stream_nsend_istream(output, input);
+ i_stream_unref(&input);
+ }
+
+ str_truncate(hdr, 0);
+ str_printfa(hdr, "\r\n\r\n--%s--\r\n", boundary);
+ o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(aenv,
+ "failed to send rejection message to <%s>: %s "
+ "(temporary failure)",
+ smtp_address_encode(sender),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(aenv,
+ "failed to send rejection message to <%s>: %s "
+ "(permanent failure)",
+ smtp_address_encode(sender),
+ str_sanitize(error, 512));
+ }
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_action_reject_mail(const struct sieve_action_exec_env *aenv,
+ const struct smtp_address *recipient,
+ const char *reason)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ int result;
+
+ T_BEGIN {
+ if (senv->reject_mail != NULL) {
+ result = (senv->reject_mail(senv, recipient,
+ reason) >= 0 ?
+ SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
+ } else {
+ result = sieve_action_do_reject_mail(aenv, recipient,
+ reason);
+ }
+ } T_END;
+
+ return result;
+}
+
+/*
+ * Mailbox
+ */
+
+bool sieve_mailbox_check_name(const char *mailbox, const char **error_r)
+{
+ if (!uni_utf8_str_is_valid(mailbox)) {
+ *error_r = "invalid utf-8";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+