diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-actions.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-actions.c | 1096 |
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; +} + + |