summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/sieve-interpreter.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pigeonhole/src/lib-sieve/sieve-interpreter.c1196
1 files changed, 1196 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-interpreter.c b/pigeonhole/src/lib-sieve/sieve-interpreter.c
new file mode 100644
index 0000000..274e142
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-interpreter.c
@@ -0,0 +1,1196 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "mempool.h"
+#include "array.h"
+#include "hash.h"
+#include "cpu-limit.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-comparators.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-interpreter.h"
+
+#include <string.h>
+
+static struct event_category event_category_sieve_runtime = {
+ .parent = &event_category_sieve,
+ .name = "sieve-runtime",
+};
+
+/*
+ * Interpreter extension
+ */
+
+struct sieve_interpreter_extension_reg {
+ const struct sieve_interpreter_extension *intext;
+ const struct sieve_extension *ext;
+
+ void *context;
+
+ bool deferred:1;
+ bool started:1;
+};
+
+/*
+ * Code loop
+ */
+
+struct sieve_interpreter_loop {
+ unsigned int level;
+ sieve_size_t begin, end;
+ const struct sieve_extension_def *ext_def;
+ pool_t pool;
+ void *context;
+};
+
+/*
+ * Interpreter
+ */
+
+struct sieve_interpreter {
+ pool_t pool;
+ struct sieve_interpreter *parent;
+
+ /* Runtime data for extensions */
+ ARRAY(struct sieve_interpreter_extension_reg) extensions;
+
+ sieve_size_t reset_vector;
+
+ /* Execution status */
+ sieve_size_t pc; /* Program counter */
+
+ /* Loop stack */
+ ARRAY(struct sieve_interpreter_loop) loop_stack;
+ sieve_size_t loop_limit;
+ unsigned int parent_loop_level;
+
+ /* Runtime environment */
+ struct sieve_runtime_env runenv;
+ struct sieve_runtime_trace trace;
+ struct sieve_resource_usage rusage;
+
+ /* Current operation */
+ struct sieve_operation oprtn;
+
+ /* Location information */
+ struct sieve_binary_debug_reader *dreader;
+ unsigned int command_line;
+
+ bool running:1; /* Interpreter is running
+ (may be interrupted) */
+ bool interrupted:1; /* Interpreter interrupt requested */
+ bool test_result:1; /* Result of previous test command */
+};
+
+static struct sieve_interpreter *
+_sieve_interpreter_create(struct sieve_binary *sbin,
+ struct sieve_binary_block *sblock,
+ struct sieve_script *script,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler) ATTR_NULL(3, 4)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ unsigned int i, ext_count;
+ struct sieve_interpreter *interp;
+ pool_t pool;
+ struct sieve_instance *svinst;
+ const struct sieve_extension *const *ext_preloaded;
+ unsigned int debug_block_id;
+ sieve_size_t *address;
+ bool success = TRUE;
+
+ pool = pool_alloconly_create("sieve_interpreter", 4096);
+ interp = p_new(pool, struct sieve_interpreter, 1);
+ interp->parent = parent;
+ interp->pool = pool;
+
+ interp->runenv.ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ interp->runenv.exec_env = eenv;
+ interp->runenv.interp = interp;
+ interp->runenv.oprtn = &interp->oprtn;
+ interp->runenv.sbin = sbin;
+ interp->runenv.sblock = sblock;
+ sieve_binary_ref(sbin);
+
+ interp->runenv.event = event_create(eenv->event);
+ event_add_category(interp->runenv.event, &event_category_sieve_runtime);
+ event_add_str(interp->runenv.event, "script_name",
+ sieve_binary_script_name(sbin));
+ event_add_str(interp->runenv.event, "script_location",
+ sieve_binary_script_location(sbin));
+ event_add_str(interp->runenv.event, "binary_path",
+ sieve_binary_path(sbin));
+
+ svinst = sieve_binary_svinst(sbin);
+
+ if (senv->trace_log != NULL) {
+ interp->trace.log = senv->trace_log;
+ interp->trace.config = senv->trace_config;
+ interp->trace.indent = 0;
+ interp->runenv.trace = &interp->trace;
+ }
+
+ if (script == NULL)
+ interp->runenv.script = sieve_binary_script(sbin);
+ else
+ interp->runenv.script = script;
+
+ interp->runenv.pc = 0;
+ address = &(interp->runenv.pc);
+
+ sieve_runtime_trace_begin(&(interp->runenv));
+
+ p_array_init(&interp->extensions, pool,
+ sieve_extensions_get_count(svinst));
+
+ interp->parent_loop_level = 0;
+ if (parent != NULL && array_is_created(&parent->loop_stack)) {
+ interp->parent_loop_level = parent->parent_loop_level +
+ array_count(&parent->loop_stack);
+ }
+
+ /* Pre-load core language features implemented as 'extensions' */
+ ext_preloaded = sieve_extensions_get_preloaded(svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension_def *ext_def =
+ ext_preloaded[i]->def;
+
+ if (ext_def != NULL && ext_def->interpreter_load != NULL) {
+ (void)ext_def->interpreter_load(ext_preloaded[i],
+ &interp->runenv,
+ address);
+ }
+ }
+
+ /* Load debug block */
+ if (sieve_binary_read_unsigned(sblock, address, &debug_block_id)) {
+ struct sieve_binary_block *debug_block =
+ sieve_binary_block_get(sbin, debug_block_id);
+
+ if (debug_block == NULL) {
+ sieve_runtime_trace_error(&interp->runenv,
+ "invalid id for debug block");
+ success = FALSE;
+ } else {
+ /* Initialize debug reader */
+ interp->dreader =
+ sieve_binary_debug_reader_init(debug_block);
+ }
+ }
+
+ /* Load other extensions listed in code */
+ if (success && sieve_binary_read_unsigned(sblock, address,
+ &ext_count)) {
+
+ for (i = 0; i < ext_count; i++) {
+ unsigned int code = 0, deferred;
+ struct sieve_interpreter_extension_reg *reg;
+ const struct sieve_extension *ext;
+
+ if (!sieve_binary_read_extension(sblock, address,
+ &code, &ext) ||
+ !sieve_binary_read_byte(sblock, address,
+ &deferred)) {
+ success = FALSE;
+ break;
+ }
+
+ if (deferred > 0 && ext->id >= 0) {
+ reg = array_idx_get_space(
+ &interp->extensions,
+ (unsigned int)ext->id);
+ reg->deferred = TRUE;
+ }
+
+ if (ext->def != NULL) {
+ if (ext->global &&
+ (eenv->flags & SIEVE_EXECUTE_FLAG_NOGLOBAL) != 0) {
+ sieve_runtime_error(&interp->runenv, NULL,
+ "failed to enable extension `%s': "
+ "its use is restricted to global scripts",
+ sieve_extension_name(ext));
+ success = FALSE;
+ break;
+ }
+
+ if (ext->def->interpreter_load != NULL &&
+ !ext->def->interpreter_load(ext, &interp->runenv,
+ address)) {
+ success = FALSE;
+ break;
+ }
+ }
+ }
+ } else {
+ success = FALSE;
+ }
+
+ if (!success) {
+ sieve_interpreter_free(&interp);
+ interp = NULL;
+ } else {
+ interp->reset_vector = *address;
+ }
+
+ return interp;
+}
+
+struct sieve_interpreter *
+sieve_interpreter_create(struct sieve_binary *sbin,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler)
+{
+ struct sieve_binary_block *sblock;
+
+ if ((sblock = sieve_binary_block_get(
+ sbin, SBIN_SYSBLOCK_MAIN_PROGRAM)) == NULL)
+ return NULL;
+
+ return _sieve_interpreter_create(sbin, sblock, NULL, parent, eenv,
+ ehandler);
+}
+
+struct sieve_interpreter *
+sieve_interpreter_create_for_block(struct sieve_binary_block *sblock,
+ struct sieve_script *script,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler)
+{
+ if (sblock == NULL) return NULL;
+
+ return _sieve_interpreter_create(sieve_binary_block_get_binary(sblock),
+ sblock, script, parent, eenv,
+ ehandler);
+}
+
+void sieve_interpreter_free(struct sieve_interpreter **_interp)
+{
+ struct sieve_interpreter *interp = *_interp;
+ struct sieve_runtime_env *renv = &interp->runenv;
+ const struct sieve_interpreter_extension_reg *eregs;
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (interp->running) {
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_finished")->
+ add_str("error", "Aborted");
+ e_debug(e->event(), "Aborted running script `%s'",
+ sieve_binary_source(interp->runenv.sbin));
+
+ interp->running = FALSE;
+ }
+
+ if (array_is_created(&interp->loop_stack)) {
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = 0; i < count; i++)
+ pool_unref(&loops[i].pool);
+ }
+
+ interp->trace.indent = 0;
+ sieve_runtime_trace_end(renv);
+
+ /* Signal registered extensions that the interpreter is being destroyed */
+ eregs = array_get(&interp->extensions, &count);
+ for (i = 0; i < count; i++) {
+ if (eregs[i].intext != NULL && eregs[i].intext->free != NULL) {
+ eregs[i].intext->free(eregs[i].ext, interp,
+ eregs[i].context);
+ }
+ }
+
+ sieve_binary_debug_reader_deinit(&interp->dreader);
+ sieve_binary_unref(&renv->sbin);
+ sieve_error_handler_unref(&renv->ehandler);
+ event_unref(&renv->event);
+
+ pool_unref(&interp->pool);
+ *_interp = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_interpreter_pool(struct sieve_interpreter *interp)
+{
+ return interp->pool;
+}
+
+struct sieve_interpreter *
+sieve_interpreter_get_parent(struct sieve_interpreter *interp)
+{
+ return interp->parent;
+}
+
+struct sieve_script *sieve_interpreter_script(struct sieve_interpreter *interp)
+{
+ return interp->runenv.script;
+}
+
+struct sieve_error_handler *
+sieve_interpreter_get_error_handler(struct sieve_interpreter *interp)
+{
+ return interp->runenv.ehandler;
+}
+
+struct sieve_instance *
+sieve_interpreter_svinst(struct sieve_interpreter *interp)
+{
+ return interp->runenv.exec_env->svinst;
+}
+
+/* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+void sieve_interpreter_set_result(struct sieve_interpreter *interp,
+ struct sieve_result *result)
+{
+ sieve_result_unref(&interp->runenv.result);
+ interp->runenv.result = result;
+ interp->runenv.msgctx = sieve_result_get_message_context(result);
+ sieve_result_ref(result);
+}
+
+/*
+ * Source location
+ */
+
+unsigned int
+sieve_runtime_get_source_location(const struct sieve_runtime_env *renv,
+ sieve_size_t code_address)
+{
+ struct sieve_interpreter *interp = renv->interp;
+
+ if (interp->dreader == NULL)
+ return 0;
+
+ if (interp->command_line == 0) {
+ interp->command_line =
+ sieve_binary_debug_read_line(interp->dreader,
+ renv->oprtn->address);
+ }
+
+ return sieve_binary_debug_read_line(interp->dreader, code_address);
+}
+
+unsigned int
+sieve_runtime_get_command_location(const struct sieve_runtime_env *renv)
+{
+ struct sieve_interpreter *interp = renv->interp;
+
+ if (interp->dreader == NULL)
+ return 0;
+
+ if (interp->command_line == 0) {
+ interp->command_line =
+ sieve_binary_debug_read_line(interp->dreader,
+ renv->oprtn->address);
+ }
+
+ return interp->command_line;
+}
+
+const char *
+sieve_runtime_get_full_command_location(const struct sieve_runtime_env *renv)
+{
+ return sieve_error_script_location(
+ renv->script, sieve_runtime_get_command_location(renv));
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_interpreter_extension_register(
+ struct sieve_interpreter *interp, const struct sieve_extension *ext,
+ const struct sieve_interpreter_extension *intext, void *context)
+{
+ struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+ reg->intext = intext;
+ reg->ext = ext;
+ reg->context = context;
+}
+
+void sieve_interpreter_extension_set_context(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+ reg->context = context;
+}
+
+void *sieve_interpreter_extension_get_context(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&interp->extensions))
+ return NULL;
+
+ reg = array_idx(&interp->extensions, (unsigned int) ext->id);
+
+ return reg->context;
+}
+
+int sieve_interpreter_extension_start(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext)
+{
+ struct sieve_interpreter_extension_reg *reg;
+ int ret;
+
+ i_assert(ext->id >= 0);
+
+ if (ext->id >= (int) array_count(&interp->extensions))
+ return SIEVE_EXEC_OK;
+
+ reg = array_idx_modifiable(&interp->extensions, (unsigned int)ext->id);
+
+ if (!reg->deferred)
+ return SIEVE_EXEC_OK;
+ reg->deferred = FALSE;
+ reg->started = TRUE;
+
+ if (reg->intext != NULL && reg->intext->run != NULL &&
+ (ret = reg->intext->run(ext, &interp->runenv,
+ reg->context, TRUE)) <= 0)
+ return ret;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Loop handling
+ */
+
+int sieve_interpreter_loop_start(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def,
+ struct sieve_interpreter_loop **loop_r)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loop;
+
+ i_assert(loop_end > interp->runenv.pc);
+
+ /* Check supplied end offset */
+ if (loop_end > sieve_binary_block_get_size(renv->sblock)) {
+ sieve_runtime_trace_error(renv, "loop end offset out of range");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int line =
+ sieve_runtime_get_source_location(renv, loop_end);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "loop ends at line %d [%08llx]",
+ line,
+ (long long unsigned int) loop_end);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "loop ends at line %d", line);
+ }
+ }
+
+ /* Check loop nesting limit */
+ if (!array_is_created(&interp->loop_stack))
+ p_array_init(&interp->loop_stack, interp->pool, 8);
+ if ((interp->parent_loop_level +
+ array_count(&interp->loop_stack)) >= SIEVE_MAX_LOOP_DEPTH) {
+ /* Should normally be caught at compile time */
+ sieve_runtime_error(renv, NULL,
+ "new program loop exceeds "
+ "the nesting limit (<= %u levels)",
+ SIEVE_MAX_LOOP_DEPTH);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Create new loop */
+ loop = array_append_space(&interp->loop_stack);
+ loop->level = array_count(&interp->loop_stack)-1;
+ loop->ext_def = ext_def;
+ loop->begin = interp->runenv.pc;
+ loop->end = loop_end;
+ loop->pool = pool_alloconly_create("sieve_interpreter", 128);
+
+ /* Set new loop limit */
+ interp->loop_limit = loop_end;
+
+ *loop_r = loop;
+ return SIEVE_EXEC_OK;
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return NULL;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = count; i > 0; i--) {
+ /* We're really making sure our loop matches */
+ if (loops[i-1].end == loop_end &&
+ loops[i-1].ext_def == ext_def)
+ return &loops[i-1];
+ }
+ return NULL;
+}
+
+int sieve_interpreter_loop_next(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ sieve_size_t loop_begin)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loops;
+ unsigned int count;
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int line =
+ sieve_runtime_get_source_location(renv, loop_begin);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "looping back to line %d [%08llx]",
+ line,
+ (long long unsigned int) loop_begin);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "looping back to line %d", line);
+ }
+ }
+
+ /* Check the code for corruption */
+ if (loop->begin != loop_begin) {
+ sieve_runtime_trace_error(renv, "loop begin offset invalid");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Check invariants */
+ i_assert(array_is_created(&interp->loop_stack));
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(&loops[count-1] == loop);
+
+ /* Return to beginning */
+ interp->runenv.pc = loop_begin;
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_interpreter_loop_break(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loops;
+ sieve_size_t loop_end = loop->end;
+ unsigned int count, i;
+
+ /* Find the loop */
+ i_assert(array_is_created(&interp->loop_stack));
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(count > 0);
+
+ i = count;
+ do {
+ pool_unref(&loops[i-1].pool);
+ i--;
+ } while (i > 0 && &loops[i] != loop);
+ i_assert(&loops[i] == loop);
+
+ /* Set new loop limit */
+ if (i > 0)
+ interp->loop_limit = loops[i].end;
+ else
+ interp->loop_limit = 0;
+
+ /* Delete it and all deeper loops */
+ array_delete(&interp->loop_stack, i, count - i);
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int jmp_line =
+ sieve_runtime_get_source_location(renv, loop_end);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "exiting loops at line %d [%08llx]",
+ jmp_line,
+ (long long unsigned int) loop_end);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "exiting loops at line %d",
+ jmp_line);
+ }
+ }
+
+ /* Exit loop */
+ interp->runenv.pc = loop->end;
+ return SIEVE_EXEC_OK;
+}
+
+static int
+sieve_interpreter_loop_break_out(struct sieve_interpreter *interp,
+ sieve_size_t target)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return SIEVE_EXEC_OK;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = count; i > 0; i--) {
+ /* We're really making sure our loop matches */
+ if (loops[i-1].end > target)
+ break;
+ }
+ if (i == count)
+ return SIEVE_EXEC_OK;
+
+ return sieve_interpreter_loop_break(interp, &loops[i]);
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_local(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return NULL;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(loop == NULL || loop->level < count);
+
+ for (i = (loop == NULL ? count : loop->level); i > 0; i--) {
+ if (ext_def == NULL || loops[i-1].ext_def == ext_def)
+ return &loops[i-1];
+ }
+ return NULL;
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_global(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *result;
+
+ while (interp != NULL) {
+ result = sieve_interpreter_loop_get_local(interp, loop,
+ ext_def);
+ if (result != NULL)
+ return result;
+ interp = interp->parent;
+ }
+ return NULL;
+}
+
+pool_t sieve_interpreter_loop_get_pool(struct sieve_interpreter_loop *loop)
+{
+ return loop->pool;
+}
+
+void *sieve_interpreter_loop_get_context(struct sieve_interpreter_loop *loop)
+{
+ return loop->context;
+}
+
+void sieve_interpreter_loop_set_context(struct sieve_interpreter_loop *loop,
+ void *context)
+{
+ loop->context = context;
+}
+
+/*
+ * Program flow
+ */
+
+void sieve_interpreter_reset(struct sieve_interpreter *interp)
+{
+ interp->runenv.pc = interp->reset_vector;
+ interp->interrupted = FALSE;
+ interp->test_result = FALSE;
+ interp->runenv.result = NULL;
+}
+
+void sieve_interpreter_interrupt(struct sieve_interpreter *interp)
+{
+ interp->interrupted = TRUE;
+}
+
+sieve_size_t sieve_interpreter_program_counter(struct sieve_interpreter *interp)
+{
+ return interp->runenv.pc;
+}
+
+static int
+sieve_interpreter_check_program_jump(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t loop_limit = (break_loops ? 0 : interp->loop_limit);
+
+ if (jmp_target == 0 ||
+ jmp_target > sieve_binary_block_get_size(renv->sblock) ||
+ (loop_limit > 0 && jmp_target >= loop_limit)) {
+ if (interp->loop_limit != 0) {
+ sieve_runtime_trace_error(
+ renv, "jump target crosses loop boundary");
+ } else {
+ sieve_runtime_trace_error(
+ renv, "jump target out of range");
+ }
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+static int
+sieve_interpreter_do_program_jump(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t *address = &(interp->runenv.pc);
+ int ret;
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int jmp_line =
+ sieve_runtime_get_source_location(renv, jmp_target);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0, "jumping to line %d [%08llx]",
+ jmp_line,
+ (long long unsigned int)jmp_target);
+ } else {
+ sieve_runtime_trace(renv, 0, "jumping to line %d",
+ jmp_line);
+ }
+ }
+
+ if (break_loops &&
+ (ret = sieve_interpreter_loop_break_out(interp,
+ jmp_target)) <= 0)
+ return ret;
+
+ *address = jmp_target;
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_interpreter_program_jump_to(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target,
+ bool break_loops)
+{
+ int ret;
+
+ ret = sieve_interpreter_check_program_jump(interp, jmp_target,
+ break_loops);
+ if (ret <= 0)
+ return ret;
+
+ return sieve_interpreter_do_program_jump(
+ interp, jmp_target, break_loops);
+}
+
+int sieve_interpreter_program_jump(struct sieve_interpreter *interp,
+ bool jump, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t *address = &(interp->runenv.pc);
+ sieve_size_t jmp_start = *address, jmp_target;
+ sieve_offset_t jmp_offset;
+ int ret;
+
+ if (!sieve_binary_read_offset(renv->sblock, address, &jmp_offset)) {
+ sieve_runtime_trace_error(renv, "invalid jump offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ jmp_target = jmp_start + jmp_offset;
+
+ ret = sieve_interpreter_check_program_jump(interp, jmp_target,
+ break_loops);
+ if (ret <= 0)
+ return ret;
+
+ if (!jump) {
+ sieve_runtime_trace(renv, 0, "not jumping");
+ return SIEVE_EXEC_OK;
+ }
+
+ return sieve_interpreter_do_program_jump(
+ interp, jmp_target, break_loops);
+}
+
+/*
+ * Test results
+ */
+
+void sieve_interpreter_set_test_result(struct sieve_interpreter *interp,
+ bool result)
+{
+ interp->test_result = result;
+}
+
+bool sieve_interpreter_get_test_result(struct sieve_interpreter *interp)
+{
+ return interp->test_result;
+}
+
+/*
+ * Code execute
+ */
+
+static int sieve_interpreter_operation_execute(struct sieve_interpreter *interp)
+{
+ struct sieve_operation *oprtn = &(interp->oprtn);
+ sieve_size_t *address = &(interp->runenv.pc);
+
+ sieve_runtime_trace_toplevel(&interp->runenv);
+
+ /* Read the operation */
+ if (sieve_operation_read(interp->runenv.sblock, address, oprtn)) {
+ const struct sieve_operation_def *op = oprtn->def;
+ int result = SIEVE_EXEC_OK;
+
+ /* Reset cached command location */
+ interp->command_line = 0;
+
+ /* Execute the operation */
+ if (op->execute != NULL) { /* Noop ? */
+ T_BEGIN {
+ result = op->execute(&(interp->runenv),
+ address);
+ } T_END;
+ } else {
+ sieve_runtime_trace(&interp->runenv,
+ SIEVE_TRLVL_COMMANDS,
+ "OP: %s (NOOP)",
+ sieve_operation_mnemonic(oprtn));
+ }
+
+ return result;
+ }
+
+ /* Binary corrupt */
+ sieve_runtime_trace_error(&interp->runenv,
+ "Encountered invalid operation");
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_interpreter_continue(struct sieve_interpreter *interp,
+ bool *interrupted)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct cpu_limit *climit = NULL;
+ sieve_size_t *address = &(interp->runenv.pc);
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_exec_status *exec_status = eenv->exec_status;
+ struct sieve_resource_usage rusage;
+ int ret = SIEVE_EXEC_OK;
+
+ sieve_result_ref(renv->result);
+ interp->interrupted = FALSE;
+
+ if (interrupted != NULL)
+ *interrupted = FALSE;
+
+ if (svinst->max_cpu_time_secs > 0) {
+ climit = cpu_limit_init(svinst->max_cpu_time_secs,
+ CPU_LIMIT_TYPE_USER);
+ }
+
+ while (ret == SIEVE_EXEC_OK && !interp->interrupted &&
+ *address < sieve_binary_block_get_size(renv->sblock)) {
+ if (climit != NULL && cpu_limit_exceeded(climit)) {
+ sieve_runtime_error(
+ renv, NULL,
+ "execution exceeded CPU time limit");
+ ret = SIEVE_EXEC_RESOURCE_LIMIT;
+ break;
+ }
+ if (interp->loop_limit != 0 && *address > interp->loop_limit) {
+ sieve_runtime_trace_error(
+ renv, "program crossed loop boundary");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ break;
+ }
+
+ ret = sieve_interpreter_operation_execute(interp);
+ }
+
+ if (climit != NULL) {
+ sieve_resource_usage_init(&rusage);
+ rusage.cpu_time_msecs =
+ cpu_limit_get_usage_msecs(climit, CPU_LIMIT_TYPE_USER);
+ sieve_resource_usage_add(&interp->rusage, &rusage);
+
+ cpu_limit_deinit(&climit);
+ }
+
+ if (ret != SIEVE_EXEC_OK) {
+ sieve_runtime_trace(&interp->runenv, SIEVE_TRLVL_NONE,
+ "[[EXECUTION ABORTED]]");
+ }
+
+ if (interrupted != NULL)
+ *interrupted = interp->interrupted;
+
+ if (!interp->interrupted) {
+ exec_status->resource_usage = interp->rusage;
+
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_finished");
+ switch (ret) {
+ case SIEVE_EXEC_OK:
+ break;
+ case SIEVE_EXEC_FAILURE:
+ e->add_str("error", "Failed");
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ e->add_str("error", "Failed temporarily");
+ break;
+ case SIEVE_EXEC_BIN_CORRUPT:
+ e->add_str("error", "Binary corrupt");
+ break;
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ e->add_str("error", "Resource limit exceeded");
+ break;
+ case SIEVE_EXEC_KEEP_FAILED:
+ /* Not supposed to occur at runtime */
+ i_unreached();
+ }
+ e_debug(e->event(), "Finished running script `%s' "
+ "(status=%s, resource usage: %s)",
+ sieve_binary_source(interp->runenv.sbin),
+ sieve_execution_exitcode_to_str(ret),
+ sieve_resource_usage_get_summary(&interp->rusage));
+ interp->running = FALSE;
+ }
+
+ sieve_result_unref(&interp->runenv.result);
+ return ret;
+}
+
+int sieve_interpreter_start(struct sieve_interpreter *interp,
+ struct sieve_result *result, bool *interrupted)
+{
+ struct sieve_interpreter_extension_reg *eregs;
+ unsigned int ext_count, i;
+ int ret;
+
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_started");
+ e_debug(e->event(), "Started running script `%s'",
+ sieve_binary_source(interp->runenv.sbin));
+
+ interp->running = TRUE;
+ interp->runenv.result = result;
+ interp->runenv.msgctx = sieve_result_get_message_context(result);
+
+ sieve_resource_usage_init(&interp->rusage);
+
+ /* Signal registered extensions that the interpreter is being run */
+ eregs = array_get_modifiable(&interp->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (!eregs[i].deferred) {
+ eregs[i].started = TRUE;
+ if (eregs[i].intext != NULL &&
+ eregs[i].intext->run != NULL &&
+ (ret = eregs[i].intext->run(
+ eregs[i].ext, &interp->runenv,
+ eregs[i].context, FALSE)) <= 0)
+ return ret;
+ }
+ }
+
+ return sieve_interpreter_continue(interp, interrupted);
+}
+
+int sieve_interpreter_run(struct sieve_interpreter *interp,
+ struct sieve_result *result)
+{
+ sieve_interpreter_reset(interp);
+
+ return sieve_interpreter_start(interp, result, NULL);
+}
+
+/*
+ * Error handling
+ */
+
+static inline void ATTR_FORMAT(3, 0)
+sieve_runtime_logv(const struct sieve_runtime_env *renv,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args)
+{
+ struct sieve_error_params new_params = *params;
+
+ new_params.event = renv->event;
+ T_BEGIN {
+ if (new_params.location == NULL) {
+ new_params.location =
+ sieve_runtime_get_full_command_location(renv);
+ }
+
+ sieve_logv(renv->ehandler, params, fmt, args);
+ } T_END;
+}
+
+#undef sieve_runtime_error
+void sieve_runtime_error(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_warning
+void sieve_runtime_warning(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_log
+void sieve_runtime_log(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_INFO,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_critical
+void sieve_runtime_critical(const struct sieve_runtime_env *renv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *user_prefix,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ params.event = renv->event;
+ T_BEGIN {
+ if (params.location == NULL) {
+ params.location =
+ sieve_runtime_get_full_command_location(renv);
+ }
+
+ sieve_criticalv(eenv->svinst, renv->ehandler, &params,
+ user_prefix, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_runtime_mail_error
+int sieve_runtime_mail_error(const struct sieve_runtime_env *renv,
+ struct mail *mail,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const char *error_msg, *user_prefix;
+ va_list args;
+
+ error_msg = mailbox_get_last_internal_error(mail->box, NULL);
+
+ va_start(args, fmt);
+ user_prefix = t_strdup_vprintf(fmt, args);
+ sieve_runtime_critical(renv, csrc_filename, csrc_linenum,
+ NULL, user_prefix, "%s: %s",
+ user_prefix, error_msg);
+ va_end(args);
+
+ return SIEVE_EXEC_TEMP_FAILURE;
+}