Skip to content
Snippets Groups Projects
  • Stephan Bosch's avatar
    8f90c58c
    Improved runtime error handing. · 8f90c58c
    Stephan Bosch authored
    - Changed operand read functions to prove exec status return codes in stead of boolean.
    - Restructured optional-operand, stringlist and string-match APIs to provide execution status (error code) upon failure.
    - Corruption errors are only produced when the binary is actually corrupt. Normal failures should never cause a recompile attempt anymore.
    - Simplified optional-operand API.
    - Cleaned up test command implementations that use the string-match API.
    8f90c58c
    History
    Improved runtime error handing.
    Stephan Bosch authored
    - Changed operand read functions to prove exec status return codes in stead of boolean.
    - Restructured optional-operand, stringlist and string-match APIs to provide execution status (error code) upon failure.
    - Corruption errors are only produced when the binary is actually corrupt. Normal failures should never cause a recompile attempt anymore.
    - Simplified optional-operand API.
    - Cleaned up test command implementations that use the string-match API.
sieve-actions.c 17.56 KiB
/* Copyright (c) 2002-2010 Dovecot Sieve authors, see the included COPYING file 
 */

#include "lib.h"
#include "str.h"
#include "strfuncs.h"
#include "str-sanitize.h"
#include "unichar.h"
#include "mail-deliver.h"
#include "mail-storage.h"

#include "sieve-code.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 <ctype.h>

/*
 * Side-effect operand
 */
 
const struct sieve_operand_class sieve_side_effect_operand_class = 
	{ "SIDE-EFFECT" };

bool 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;

	seffect->context = NULL;

	if ( !sieve_opr_object_read
		(renv, &sieve_side_effect_operand_class, address, &seffect->object) )
		return FALSE;

	sdef = seffect->def = 
		(const struct sieve_side_effect_def *) seffect->object.def;

	if ( sdef->read_context != NULL && 
		!sdef->read_context(seffect, renv, address, &seffect->context) ) {
		return FALSE;
	}

	return TRUE;
}

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;
}

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

		if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 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;

		if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 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 );
				
			if ( (ret=sieve_opr_side_effect_read(renv, address, &seffect)) <= 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 bool act_store_start
	(const struct sieve_action *action,
		const struct sieve_action_exec_env *aenv, void **tr_context);
static bool act_store_execute
	(const struct sieve_action *action, 
		const struct sieve_action_exec_env *aenv, void *tr_context);
static bool act_store_commit
	(const struct sieve_action *action, 
		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
static void act_store_rollback
	(const struct sieve_action *action, 
		const struct sieve_action_exec_env *aenv, void *tr_context, bool success);
		
/* Action object */

const struct sieve_action_def act_store = {
	"store",
	SIEVE_ACTFLAG_TRIES_DELIVER,
	act_store_equals,
	act_store_check_duplicate, 
	NULL, 
	act_store_print,
	act_store_start,
	act_store_execute,
	act_store_commit,
	act_store_rollback,
};

/* API */

int sieve_act_store_add_to_result
(const struct sieve_runtime_env *renv, 
	struct sieve_side_effects_list *seffects, const char *mailbox,
	unsigned int source_line)
{
	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, &act_store, seffects, 
		source_line, (void *) act, 0);
}

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;

	/* 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 ) {

			const char *kw_error;

			if ( trans->box != NULL ) {
				if ( mailbox_keyword_is_valid(trans->box, *kw, &kw_error) )
					array_append(&trans->keywords, kw, 1);
				else {
					char *error = "";
					if ( kw_error != NULL && *kw_error != '\0' ) {
						error = t_strdup_noconst(kw_error);
						error[0] = i_tolower(error[0]);
					}
				
					sieve_result_warning(aenv, 
						"specified IMAP keyword '%s' is invalid (ignored): %s", 
						str_sanitize(*kw, 64), error);
				}
			}

			kw++;
		}
	}

	/* Assign mail flags for subsequent mailbox_copy() */
	trans->flags |= flags;

	trans->flags_altered = TRUE;
}

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,
		mail_storage_get_last_error(mailbox_get_storage(trans->box), 
		&trans->error_code));
}

/* 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)
{
	return ( act_store_equals(renv->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 */

static bool act_store_mailbox_open
(const struct sieve_action_exec_env *aenv, const char *mailbox,
	struct mailbox **box_r, enum mail_error *error_code_r, const char **error_r)
{
	struct mail_storage **storage = &(aenv->exec_status->last_storage);
	struct mail_deliver_save_open_context save_ctx;

	*box_r = NULL;

	if ( !uni_utf8_str_is_valid(mailbox) ) {
		/* FIXME: check utf-8 validity at compiletime/runtime */
		*error_r = t_strdup_printf("mailbox name not utf-8: %s", mailbox);
		*error_code_r = MAIL_ERROR_PARAMS;
		return FALSE;
	}

	memset(&save_ctx, 0, sizeof(save_ctx));
	save_ctx.user = aenv->scriptenv->user;
	save_ctx.lda_mailbox_autocreate = aenv->scriptenv->mailbox_autocreate;
	save_ctx.lda_mailbox_autosubscribe = aenv->scriptenv->mailbox_autosubscribe;

	if (mail_deliver_save_open(&save_ctx, mailbox, box_r, error_code_r, error_r) < 0)
		return FALSE;

	*storage = mailbox_get_storage(*box_r);
	return TRUE;
}

static bool act_store_start
(const struct sieve_action *action, 
	const struct sieve_action_exec_env *aenv, void **tr_context)
{  
	struct act_store_context *ctx = (struct act_store_context *) action->context;
	const struct sieve_script_env *senv = aenv->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, open_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));
	}

	/* 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_open(aenv, ctx->mailbox, &box, &error_code, &error) ) {
			open_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->disabled = disabled;

	if ( open_failed  ) {
		trans->error = error;
		trans->error_code = error_code;
	} else {
		trans->error_code = MAIL_ERROR_NONE;
	}

	*tr_context = (void *)trans;

	return ( trans->error_code == MAIL_ERROR_NONE || 
		trans->error_code == MAIL_ERROR_NOTFOUND );
}

static struct mail_keywords *act_store_keywords_create
(const struct sieve_action_exec_env *aenv, ARRAY_TYPE(const_string) *keywords, 
	struct mailbox *box)
{
	struct mail_keywords *box_keywords = NULL;
	
	if ( array_is_created(keywords) && array_count(keywords) > 0 ) 
	{
		const char *const *kwds;
		
		(void)array_append_space(keywords);
		kwds = array_idx(keywords, 0);
				
		/* FIXME: Do we need to clear duplicates? */
		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 act_store_execute
(const struct sieve_action *action ATTR_UNUSED, 
	const struct sieve_action_exec_env *aenv, void *tr_context)
{   
	struct act_store_transaction *trans = 
		(struct act_store_transaction *) tr_context;
	const struct sieve_message_data *msgdata = aenv->msgdata;
	struct mail_save_context *save_ctx;
	struct mail_keywords *keywords = NULL;
	bool result = TRUE;
	
	/* Verify transaction */
	if ( trans == NULL ) return FALSE;

	/* Check whether we need to do anything */
	if ( trans->disabled ) return TRUE;

	/* Exit early if mailbox is not available */
	if ( trans->box == NULL || trans->error_code != MAIL_ERROR_NONE ) 
		return FALSE;

	/* If the message originates from the target mailbox, only update the flags 
	 * and keywords 
	 */
	if ( mailbox_backends_equal(trans->box, msgdata->mail->box) ) {
		trans->redundant = TRUE;

		if ( trans->flags_altered ) {
			keywords = act_store_keywords_create
				(aenv, &trans->keywords, msgdata->mail->box);

			if ( keywords != NULL ) {
				mail_update_keywords(msgdata->mail, MODIFY_REPLACE, keywords);
				mailbox_keywords_unref(trans->box, &keywords);
			}

			mail_update_flags(msgdata->mail, MODIFY_REPLACE, trans->flags);
		}

		return TRUE;
	}

	/* Mark attempt to store in default mailbox */
	if ( strcmp(trans->context->mailbox, 
		SIEVE_SCRIPT_DEFAULT_MAILBOX(aenv->scriptenv)) == 0 ) 
		aenv->exec_status->tried_default_save = TRUE;

	/* Mark attempt to use storage. Can only get here when all previous actions
	 * succeeded. 
	 */
	aenv->exec_status->last_storage = mailbox_get_storage(trans->box);
	
	/* Start mail transaction */
	trans->mail_trans = mailbox_transaction_begin
		(trans->box, MAILBOX_TRANSACTION_FLAG_EXTERNAL);

	/* Create mail object for stored message */
	trans->dest_mail = mail_alloc(trans->mail_trans, 0, NULL);
 
	/* Store the message */
	save_ctx = mailbox_save_alloc(trans->mail_trans);
	mailbox_save_set_dest_mail(save_ctx, trans->dest_mail);

	/* Apply keywords and flags that side-effects may have added */
	if ( trans->flags_altered ) {
		keywords = act_store_keywords_create(aenv, &trans->keywords, trans->box);
		
		mailbox_save_set_flags(save_ctx, trans->flags, keywords);
	}

	if ( mailbox_copy(&save_ctx, aenv->msgdata->mail) < 0 ) {
		sieve_act_store_get_storage_error(aenv, trans);
 		result = FALSE;
	}
 	
	/* Deallocate keywords */
 	if ( keywords != NULL ) {
 		mailbox_keywords_unref(trans->box, &keywords);
 	}
 		 	
	return result;
}

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;

	if ( trans->box != NULL )
		mailbox_name = str_sanitize(mailbox_get_vname(trans->box), 128);
	else
		mailbox_name = str_sanitize(trans->context->mailbox, 128);

	/* Store disabled? */
	if ( trans->disabled ) {
		sieve_result_log(aenv, "store into mailbox '%s' skipped", mailbox_name);
	
	/* Store redundant? */
	} else if ( trans->redundant ) {
		sieve_result_log(aenv, "left message in mailbox '%s'", mailbox_name);

	/* Store failed? */
	} else if ( !status ) {
		const char *errstr;
		enum mail_error error;

		if ( trans->error != NULL )
			errstr = trans->error;
		else
			errstr = mail_storage_get_last_error
				(mailbox_get_storage(trans->box), &error);
	
		sieve_result_error(aenv, "failed to store into mailbox '%s': %s", 
			mailbox_name, errstr);

	/* Store aborted? */
	} else if ( rolled_back ) {
		sieve_result_log(aenv, "store into mailbox '%s' aborted", mailbox_name);

	/* Succeeded */
	} else {
		sieve_result_log(aenv, "stored mail into mailbox '%s'", mailbox_name);

	}
}

static bool act_store_commit
(const struct sieve_action *action ATTR_UNUSED, 
	const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep)
{  
	struct act_store_transaction *trans = 
		(struct act_store_transaction *) tr_context;
	bool status = TRUE;

	/* Verify transaction */
	if ( trans == NULL ) return FALSE;

	/* Check whether we need to do anything */
	if ( trans->disabled ) {
		act_store_log_status(trans, aenv, FALSE, status);
		*keep = FALSE;
		if ( trans->box != NULL )
			mailbox_free(&trans->box);
		return TRUE;
	} else if ( trans->redundant ) {
		act_store_log_status(trans, aenv, FALSE, status);
		aenv->exec_status->keep_original = TRUE;
		aenv->exec_status->message_saved = TRUE;
		if ( trans->box != NULL )
			mailbox_free(&trans->box);
		return TRUE;	
	}

	/* Mark attempt to use storage. Can only get here when all previous actions
	 * succeeded. 
	 */
	aenv->exec_status->last_storage = mailbox_get_storage(trans->box);

	/* Free mail object for stored message */
	if ( trans->dest_mail != NULL ) 
		mail_free(&trans->dest_mail);	

	/* Commit mailbox transaction */	
	status = ( mailbox_transaction_commit(&trans->mail_trans) == 0 );

	/* Note the fact that the message was stored at least once */
	if ( status )
		aenv->exec_status->message_saved = TRUE;
	
	/* Log our status */
	act_store_log_status(trans, aenv, FALSE, status);
	
	/* Cancel implicit keep if all went well */
	*keep = !status;
	
	/* Close mailbox */	
	if ( trans->box != NULL )
		mailbox_free(&trans->box);
	return status;
}

static void act_store_rollback
(const struct sieve_action *action ATTR_UNUSED, 
	const struct sieve_action_exec_env *aenv, void *tr_context, bool success)
{
	struct act_store_transaction *trans = 
		(struct act_store_transaction *) tr_context;

	/* Log status */
	act_store_log_status(trans, aenv, TRUE, success);

	/* Free mailobject for stored message */
	if ( trans->dest_mail != NULL ) 
		mail_free(&trans->dest_mail);	

	/* Rollback mailbox transaction */
	if ( trans->mail_trans != NULL )
		mailbox_transaction_rollback(&trans->mail_trans);
  
	/* Close the mailbox */
	if ( trans->box != NULL )  
		mailbox_free(&trans->box);
}

/*
 * Action utility functions
 */

bool sieve_action_duplicate_check_available
(const struct sieve_script_env *senv)
{
	return ( senv->duplicate_check != NULL && senv->duplicate_mark != NULL );
}

int sieve_action_duplicate_check
(const struct sieve_script_env *senv, const void *id, size_t id_size)
{
	if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
		return 0;

	return senv->duplicate_check
		(senv->script_context, id, id_size, senv->username); 
}

void sieve_action_duplicate_mark
(const struct sieve_script_env *senv, const void *id, size_t id_size,
	time_t time)
{
	if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
		return;

	senv->duplicate_mark
		(senv->script_context, id, id_size, senv->username, time);
}
	

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.