Skip to content
Snippets Groups Projects
sieve-actions.c 29.02 KiB
/* 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;
}


Consent

On this website, we use the web analytics service Matomo to analyze and review the use of our website. Through the collected statistics, we can improve our offerings and make them more appealing for you. Here, you can decide whether to allow us to process your data and set corresponding cookies for these purposes, in addition to technically necessary cookies. Further information on data protection—especially regarding "cookies" and "Matomo"—can be found in our privacy policy. You can withdraw your consent at any time.