ext-include-common.c [plain text]
#include "lib.h"
#include "array.h"
#include "str-sanitize.h"
#include "home-expand.h"
#include "sieve-common.h"
#include "sieve-settings.h"
#include "sieve-error.h"
#include "sieve-script.h"
#include "sieve-ast.h"
#include "sieve-binary.h"
#include "sieve-commands.h"
#include "sieve-validator.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "ext-include-common.h"
#include "ext-include-limits.h"
#include "ext-include-binary.h"
#include "ext-include-variables.h"
#include <stdlib.h>
struct ext_include_generator_context {
unsigned int nesting_depth;
struct sieve_script *script;
struct ext_include_generator_context *parent;
};
static inline struct ext_include_generator_context *
ext_include_get_generator_context
(const struct sieve_extension *ext_this, struct sieve_generator *gentr);
struct ext_include_interpreter_global {
ARRAY_DEFINE(included_scripts, struct sieve_script *);
struct sieve_variable_scope_binary *var_scope;
struct sieve_variable_storage *var_storage;
};
struct ext_include_interpreter_context {
struct ext_include_interpreter_context *parent;
struct ext_include_interpreter_global *global;
struct sieve_interpreter *interp;
pool_t pool;
unsigned int nesting_depth;
struct sieve_script *script;
const struct ext_include_script_info *script_info;
const struct ext_include_script_info *include;
bool returned;
};
bool ext_include_load
(const struct sieve_extension *ext, void **context)
{
struct sieve_instance *svinst = ext->svinst;
struct ext_include_context *ctx;
const char *sieve_dir, *home;
unsigned long long int uint_setting;
if ( *context != NULL ) {
ext_include_unload(ext);
}
ctx = i_new(struct ext_include_context, 1);
sieve_dir = sieve_setting_get(svinst, "sieve_global_dir");
if ( sieve_dir == NULL && svinst->debug ) {
sieve_sys_debug(svinst, "include: sieve_global_dir is not set; "
"it is currently not possible to include `:global' scripts.");
}
ctx->global_dir = i_strdup(sieve_dir);
sieve_dir = sieve_setting_get(svinst, "sieve_dir");
home = sieve_environment_get_homedir(svinst);
if ( sieve_dir == NULL ) {
if ( home == NULL ) {
if ( svinst->debug ) {
sieve_sys_debug(svinst, "include: sieve_dir is not set "
"and no home directory is set for the default `~/sieve'; "
"it is currently not possible to include `:personal' scripts.");
}
} else {
sieve_dir = "~/sieve";
}
}
if ( home != NULL )
sieve_dir = home_expand_tilde(sieve_dir, home);
ctx->personal_dir = i_strdup(sieve_dir);
ctx->max_nesting_depth = EXT_INCLUDE_DEFAULT_MAX_NESTING_DEPTH;
ctx->max_includes = EXT_INCLUDE_DEFAULT_MAX_INCLUDES;
if ( sieve_setting_get_uint_value
(svinst, "sieve_include_max_nesting_depth", &uint_setting) ) {
ctx->max_nesting_depth = (unsigned int) uint_setting;
}
if ( sieve_setting_get_uint_value
(svinst, "sieve_include_max_includes", &uint_setting) ) {
ctx->max_includes = (unsigned int) uint_setting;
}
ctx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
*context = (void *)ctx;
return TRUE;
}
void ext_include_unload
(const struct sieve_extension *ext)
{
struct ext_include_context *ctx =
(struct ext_include_context *) ext->context;
i_free(ctx->personal_dir);
i_free(ctx->global_dir);
i_free(ctx);
}
const char *ext_include_get_script_directory
(const struct sieve_extension *ext, enum ext_include_script_location location,
const char *script_name)
{
struct sieve_instance *svinst = ext->svinst;
struct ext_include_context *ctx =
(struct ext_include_context *) ext->context;
const char *sieve_dir;
switch ( location ) {
case EXT_INCLUDE_LOCATION_PERSONAL:
if ( ctx->personal_dir == NULL ) {
sieve_sys_error(svinst, "include: sieve_dir is unconfigured; "
"include of `:personal' script `%s' is therefore not possible",
str_sanitize(script_name, 80));
return NULL;
}
sieve_dir = ctx->personal_dir;
break;
case EXT_INCLUDE_LOCATION_GLOBAL:
if ( ctx->global_dir == NULL ) {
sieve_sys_error(svinst, "include: sieve_global_dir is unconfigured; "
"include of `:global' script `%s' is therefore not possible",
str_sanitize(script_name, 80));
return NULL;
}
sieve_dir = ctx->global_dir;
break;
default:
i_unreached();
}
return sieve_dir;
}
static void ext_include_ast_free
(const struct sieve_extension *ext ATTR_UNUSED,
struct sieve_ast *ast ATTR_UNUSED, void *context)
{
struct ext_include_ast_context *actx =
(struct ext_include_ast_context *) context;
struct sieve_script **scripts;
unsigned int count, i;
scripts = array_get_modifiable(&actx->included_scripts, &count);
for ( i = 0; i < count; i++ ) {
sieve_script_unref(&scripts[i]);
}
if ( actx->global_vars != NULL )
sieve_variable_scope_unref(&actx->global_vars);
}
static const struct sieve_ast_extension include_ast_extension = {
&include_extension,
ext_include_ast_free
};
struct ext_include_ast_context *ext_include_create_ast_context
(const struct sieve_extension *this_ext, struct sieve_ast *ast,
struct sieve_ast *parent)
{
struct ext_include_ast_context *actx;
pool_t pool = sieve_ast_pool(ast);
actx = p_new(pool, struct ext_include_ast_context, 1);
p_array_init(&actx->included_scripts, pool, 32);
if ( parent != NULL ) {
struct ext_include_ast_context *parent_ctx =
(struct ext_include_ast_context *)
sieve_ast_extension_get_context(parent, this_ext);
actx->global_vars = parent_ctx->global_vars;
i_assert( actx->global_vars != NULL );
sieve_variable_scope_ref(actx->global_vars);
} else {
actx->global_vars = sieve_variable_scope_create(this_ext->svinst, this_ext);
}
sieve_ast_extension_register
(ast, this_ext, &include_ast_extension, (void *) actx);
return actx;
}
struct ext_include_ast_context *ext_include_get_ast_context
(const struct sieve_extension *this_ext, struct sieve_ast *ast)
{
struct ext_include_ast_context *actx = (struct ext_include_ast_context *)
sieve_ast_extension_get_context(ast, this_ext);
if ( actx != NULL ) return actx;
return ext_include_create_ast_context(this_ext, ast, NULL);
}
void ext_include_ast_link_included_script
(const struct sieve_extension *this_ext, struct sieve_ast *ast,
struct sieve_script *script)
{
struct ext_include_ast_context *actx =
ext_include_get_ast_context(this_ext, ast);
array_append(&actx->included_scripts, &script, 1);
}
bool ext_include_validator_have_variables
(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
{
struct ext_include_context *ectx = ext_include_get_context(this_ext);
return sieve_ext_variables_is_active(ectx->var_ext, valdtr);
}
static struct ext_include_generator_context *
ext_include_create_generator_context
(struct sieve_generator *gentr, struct ext_include_generator_context *parent,
struct sieve_script *script)
{
struct ext_include_generator_context *ctx;
pool_t pool = sieve_generator_pool(gentr);
ctx = p_new(pool, struct ext_include_generator_context, 1);
ctx->parent = parent;
ctx->script = script;
if ( parent == NULL ) {
ctx->nesting_depth = 0;
} else {
ctx->nesting_depth = parent->nesting_depth + 1;
}
return ctx;
}
static inline struct ext_include_generator_context *
ext_include_get_generator_context
(const struct sieve_extension *this_ext, struct sieve_generator *gentr)
{
return (struct ext_include_generator_context *)
sieve_generator_extension_get_context(gentr, this_ext);
}
static inline void ext_include_initialize_generator_context
(const struct sieve_extension *this_ext, struct sieve_generator *gentr,
struct ext_include_generator_context *parent, struct sieve_script *script)
{
sieve_generator_extension_set_context(gentr, this_ext,
ext_include_create_generator_context(gentr, parent, script));
}
void ext_include_register_generator_context
(const struct sieve_extension *this_ext, const struct sieve_codegen_env *cgenv)
{
struct ext_include_generator_context *ctx =
ext_include_get_generator_context(this_ext, cgenv->gentr);
if ( ctx == NULL ) {
ctx = ext_include_create_generator_context(
cgenv->gentr, NULL, cgenv->script);
sieve_generator_extension_set_context
(cgenv->gentr, this_ext, (void *) ctx);
}
(void)ext_include_get_ast_context(this_ext, cgenv->ast);
(void)ext_include_binary_init(this_ext, cgenv->sbin, cgenv->ast);
}
static void ext_include_runtime_init
(const struct sieve_extension *this_ext, const struct sieve_runtime_env *renv,
void *context)
{
struct ext_include_interpreter_context *ctx =
(struct ext_include_interpreter_context *) context;
struct ext_include_context *ectx = ext_include_get_context(this_ext);
if ( ctx->parent == NULL ) {
ctx->global = p_new(ctx->pool, struct ext_include_interpreter_global, 1);
p_array_init(&ctx->global->included_scripts, ctx->pool, 10);
ctx->global->var_scope =
ext_include_binary_get_global_scope(this_ext, renv->sbin);
ctx->global->var_storage =
sieve_variable_storage_create(ctx->pool, ctx->global->var_scope);
} else {
ctx->global = ctx->parent->global;
}
sieve_ext_variables_runtime_set_storage
(ectx->var_ext, renv, this_ext, ctx->global->var_storage);
}
static struct sieve_interpreter_extension include_interpreter_extension = {
&include_extension,
ext_include_runtime_init,
NULL,
};
static struct ext_include_interpreter_context *
ext_include_interpreter_context_create
(struct sieve_interpreter *interp,
struct ext_include_interpreter_context *parent,
struct sieve_script *script, const struct ext_include_script_info *sinfo)
{
struct ext_include_interpreter_context *ctx;
pool_t pool = sieve_interpreter_pool(interp);
ctx = p_new(pool, struct ext_include_interpreter_context, 1);
ctx->pool = pool;
ctx->parent = parent;
ctx->interp = interp;
ctx->script = script;
ctx->script_info = sinfo;
if ( parent == NULL ) {
ctx->nesting_depth = 0;
} else {
ctx->nesting_depth = parent->nesting_depth + 1;
}
return ctx;
}
static inline struct ext_include_interpreter_context *
ext_include_get_interpreter_context
(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
{
return (struct ext_include_interpreter_context *)
sieve_interpreter_extension_get_context(interp, this_ext);
}
static inline struct ext_include_interpreter_context *
ext_include_interpreter_context_init_child
(const struct sieve_extension *this_ext, struct sieve_interpreter *interp,
struct ext_include_interpreter_context *parent,
struct sieve_script *script, const struct ext_include_script_info *sinfo)
{
struct ext_include_interpreter_context *ctx =
ext_include_interpreter_context_create(interp, parent, script, sinfo);
sieve_interpreter_extension_register
(interp, this_ext, &include_interpreter_extension, ctx);
return ctx;
}
void ext_include_interpreter_context_init
(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
{
struct ext_include_interpreter_context *ctx =
ext_include_get_interpreter_context(this_ext, interp);
if ( ctx == NULL ) {
struct sieve_script *script;
script = sieve_interpreter_script(interp);
ctx = ext_include_interpreter_context_create
(interp, NULL, script, NULL);
sieve_interpreter_extension_register
(interp, this_ext, &include_interpreter_extension, (void *) ctx);
}
}
struct sieve_variable_storage *ext_include_interpreter_get_global_variables
(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
{
struct ext_include_interpreter_context *ctx =
ext_include_get_interpreter_context(this_ext, interp);
return ctx->global->var_storage;
}
bool ext_include_generate_include
(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
enum ext_include_script_location location, struct sieve_script *script,
const struct ext_include_script_info **included_r, bool once)
{
const struct sieve_extension *this_ext = cmd->ext;
struct ext_include_context *ext_ctx =
(struct ext_include_context *)this_ext->context;
bool result = TRUE;
struct sieve_ast *ast;
struct sieve_binary *sbin = cgenv->sbin;
struct sieve_generator *gentr = cgenv->gentr;
struct ext_include_binary_context *binctx;
struct sieve_generator *subgentr;
struct ext_include_generator_context *ctx =
ext_include_get_generator_context(this_ext, gentr);
struct ext_include_generator_context *pctx;
struct sieve_error_handler *ehandler = sieve_generator_error_handler(gentr);
const struct ext_include_script_info *included;
*included_r = NULL;
if ( sieve_get_errors(ehandler) > 0 )
return FALSE;
if ( ctx->nesting_depth >= ext_ctx->max_nesting_depth ) {
sieve_command_generate_error
(gentr, cmd, "cannot nest includes deeper than %d levels",
ext_ctx->max_nesting_depth);
return FALSE;
}
if ( !once ) {
pctx = ctx;
while ( pctx != NULL ) {
if ( sieve_script_equals(pctx->script, script) ) {
sieve_command_generate_error(gentr, cmd, "circular include");
return FALSE;
}
pctx = pctx->parent;
}
}
binctx = ext_include_binary_init(this_ext, sbin, cgenv->ast);
if ( !ext_include_binary_script_is_included(binctx, script, &included) )
{
struct sieve_binary_block *inc_block;
const char *script_name = sieve_script_name(script);
if ( ext_include_binary_script_get_count(binctx) >=
ext_ctx->max_includes ) {
sieve_command_generate_error(gentr, cmd,
"failed to include script '%s': no more than %u includes allowed",
str_sanitize(script_name, 80), ext_ctx->max_includes);
return FALSE;
}
inc_block = sieve_binary_block_create(sbin);
included = ext_include_binary_script_include
(binctx, script, location, inc_block);
if ( (ast = sieve_parse(script, ehandler, NULL)) == NULL ) {
sieve_command_generate_error(gentr, cmd,
"failed to parse included script '%s'", str_sanitize(script_name, 80));
return FALSE;
}
(void)ext_include_create_ast_context(this_ext, ast, cmd->ast_node->ast);
if ( !sieve_validate(ast, ehandler, NULL) ) {
sieve_command_generate_error(gentr, cmd,
"failed to validate included script '%s'",
str_sanitize(script_name, 80));
sieve_ast_unref(&ast);
return FALSE;
}
subgentr = sieve_generator_create(ast, ehandler);
ext_include_initialize_generator_context(cmd->ext, subgentr, ctx, script);
if ( sieve_generator_run(subgentr, &inc_block) == NULL ) {
sieve_command_generate_error(gentr, cmd,
"failed to generate code for included script '%s'",
str_sanitize(script_name, 80));
result = FALSE;
}
sieve_generator_free(&subgentr);
sieve_ast_unref(&ast);
}
if ( result ) *included_r = included;
return result;
}
static int ext_include_runtime_check_circular
(struct ext_include_interpreter_context *ctx,
const struct ext_include_script_info *include)
{
struct ext_include_interpreter_context *pctx;
pctx = ctx;
while ( pctx != NULL ) {
if ( sieve_script_equals(include->script, pctx->script) )
return TRUE;
pctx = pctx->parent;
}
return FALSE;
}
static bool ext_include_runtime_include_mark
(struct ext_include_interpreter_context *ctx,
const struct ext_include_script_info *include, bool once)
{
struct sieve_script *const *includes;
unsigned int count, i;
includes = array_get(&ctx->global->included_scripts, &count);
for ( i = 0; i < count; i++ ) {
if ( sieve_script_equals(include->script, includes[i]) )
return ( !once );
}
array_append(&ctx->global->included_scripts, &include->script, 1);
return TRUE;
}
int ext_include_execute_include
(const struct sieve_runtime_env *renv, unsigned int include_id, bool once)
{
const struct sieve_extension *this_ext = renv->oprtn->ext;
int result = SIEVE_EXEC_OK;
struct ext_include_interpreter_context *ctx;
const struct ext_include_script_info *included;
struct ext_include_binary_context *binctx =
ext_include_binary_get_context(this_ext, renv->sbin);
unsigned int block_id;
included = ext_include_binary_script_get_included(binctx, include_id);
if ( included == NULL ) {
sieve_runtime_trace_error(renv, "include: include id %d is invalid",
include_id);
return SIEVE_EXEC_BIN_CORRUPT;
}
ctx = ext_include_get_interpreter_context(this_ext, renv->interp);
block_id = sieve_binary_block_get_id(included->block);
if ( ext_include_runtime_include_mark(ctx, included, once) ) {
sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
"include: start script '%s' [inc id: %d, block: %d]",
sieve_script_name(included->script), include_id, block_id);
} else {
sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
"include: skipped include for script '%s' [inc id: %d, block: %d]; "
"already run once",
sieve_script_name(included->script), include_id, block_id);
return result;
}
if ( ext_include_runtime_check_circular(ctx, included) ) {
sieve_runtime_trace_error(renv,
"include: circular include of script '%s' [inc id: %d, block: %d]",
sieve_script_name(included->script), include_id, block_id);
return SIEVE_EXEC_BIN_CORRUPT;
}
if ( ctx->parent == NULL ) {
struct ext_include_interpreter_context *curctx = NULL;
struct sieve_error_handler *ehandler =
sieve_interpreter_get_error_handler(renv->interp);
struct sieve_interpreter *subinterp;
bool interrupted = FALSE;
if ( result == SIEVE_EXEC_OK ) {
subinterp = sieve_interpreter_create_for_block
(included->block, included->script, renv->msgdata, renv->scriptenv,
ehandler);
if ( subinterp != NULL ) {
curctx = ext_include_interpreter_context_init_child
(this_ext, subinterp, ctx, included->script, included);
result = ( sieve_interpreter_start
(subinterp, renv->result, &interrupted) == 1 );
} else
result = SIEVE_EXEC_BIN_CORRUPT;
}
if ( result == SIEVE_EXEC_OK && interrupted && !curctx->returned ) {
while ( result == SIEVE_EXEC_OK ) {
if ( ( (interrupted && curctx->returned) || (!interrupted) ) &&
curctx->parent != NULL ) {
const struct ext_include_script_info *ended_script =
curctx->script_info;
curctx = curctx->parent;
sieve_interpreter_free(&subinterp);
sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
"include: script '%s' ended [inc id: %d, block: %d]",
sieve_script_name(ended_script->script), ended_script->id,
sieve_binary_block_get_id(ended_script->block));
if ( curctx->parent == NULL ) break;
subinterp = curctx->interp;
curctx->include = NULL;
curctx->returned = FALSE;
result = ( sieve_interpreter_continue(subinterp, &interrupted) == 1 );
} else {
if ( curctx->include != NULL ) {
if ( result == SIEVE_EXEC_OK ) {
subinterp = sieve_interpreter_create_for_block
(curctx->include->block, curctx->include->script, renv->msgdata,
renv->scriptenv, ehandler);
if ( subinterp != NULL ) {
curctx = ext_include_interpreter_context_init_child
(this_ext, subinterp, curctx, curctx->include->script,
curctx->include);
curctx->include = NULL;
curctx->returned = FALSE;
result = ( sieve_interpreter_start(subinterp, renv->result,
&interrupted) == 1 );
} else
result = SIEVE_EXEC_BIN_CORRUPT;
}
} else {
sieve_interpreter_interrupt(renv->interp);
break;
}
}
}
}
while ( curctx != NULL && curctx->parent != NULL ) {
struct ext_include_interpreter_context *nextctx = curctx->parent;
struct sieve_interpreter *killed_interp = curctx->interp;
const struct ext_include_script_info *ended_script =
curctx->script_info;
sieve_interpreter_free(&killed_interp);
sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
"include: script '%s' ended [id: %d, block: %d]",
sieve_script_name(ended_script->script),
ended_script->id, sieve_binary_block_get_id(ended_script->block));
curctx = nextctx;
}
} else {
ctx->include = included;
sieve_interpreter_interrupt(renv->interp);
}
return result;
}
void ext_include_execute_return
(const struct sieve_runtime_env *renv)
{
const struct sieve_extension *this_ext = renv->oprtn->ext;
struct ext_include_interpreter_context *ctx =
ext_include_get_interpreter_context(this_ext, renv->interp);
sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
"return: exiting included script");
ctx->returned = TRUE;
sieve_interpreter_interrupt(renv->interp);
}