summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/sieve-validator.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-validator.c')
-rw-r--r--pigeonhole/src/lib-sieve/sieve-validator.c1706
1 files changed, 1706 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-validator.c b/pigeonhole/src/lib-sieve/sieve-validator.c
new file mode 100644
index 0000000..bef4a7d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-validator.c
@@ -0,0 +1,1706 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+#include "buffer.h"
+#include "mempool.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+
+#include "sieve-comparators.h"
+#include "sieve-address-parts.h"
+
+/*
+ * Forward declarations
+ */
+
+static void
+sieve_validator_register_core_commands(struct sieve_validator *valdtr);
+static void
+sieve_validator_register_core_tests(struct sieve_validator *valdtr);
+
+/*
+ * Types
+ */
+
+/* Tag registration */
+
+struct sieve_tag_registration {
+ const struct sieve_argument_def *tag_def;
+ const struct sieve_extension *ext;
+
+ const char *identifier;
+ int id_code;
+};
+
+/* Command registration */
+
+struct sieve_command_registration {
+ const struct sieve_command_def *cmd_def;
+ const struct sieve_extension *ext;
+
+ ARRAY(struct sieve_tag_registration *) normal_tags;
+ ARRAY(struct sieve_tag_registration *) instanced_tags;
+ ARRAY(struct sieve_tag_registration *) persistent_tags;
+};
+
+/* Default (literal) arguments */
+
+struct sieve_default_argument {
+ const struct sieve_argument_def *arg_def;
+ const struct sieve_extension *ext;
+
+ struct sieve_default_argument *overrides;
+};
+
+/*
+ * Validator extension
+ */
+
+struct sieve_validator_extension_reg {
+ const struct sieve_validator_extension *valext;
+ const struct sieve_extension *ext;
+ struct sieve_ast_argument *arg;
+ void *context;
+
+ bool loaded:1;
+ bool required:1;
+};
+
+/*
+ * Validator
+ */
+
+struct sieve_validator {
+ pool_t pool;
+
+ struct sieve_instance *svinst;
+ struct sieve_ast *ast;
+ struct sieve_script *script;
+ enum sieve_compile_flags flags;
+
+ struct sieve_error_handler *ehandler;
+
+ bool finished_require;
+
+ /* Registries */
+
+ HASH_TABLE(const char *, struct sieve_command_registration *) commands;
+
+ ARRAY(struct sieve_validator_extension_reg) extensions;
+
+ /* This is currently a wee bit ugly and needs more thought */
+ struct sieve_default_argument default_arguments[SAT_COUNT];
+
+ /* Default argument processing state (FIXME: ugly) */
+ struct sieve_default_argument *current_defarg;
+ enum sieve_argument_type current_defarg_type;
+ bool current_defarg_constant;
+};
+
+/*
+ * Validator object
+ */
+
+struct sieve_validator *
+sieve_validator_create(struct sieve_ast *ast,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags)
+{
+ pool_t pool;
+ struct sieve_validator *valdtr;
+ const struct sieve_extension *const *ext_preloaded;
+ unsigned int i, ext_count;
+
+ pool = pool_alloconly_create("sieve_validator", 16384);
+ valdtr = p_new(pool, struct sieve_validator, 1);
+ valdtr->pool = pool;
+
+ valdtr->ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ valdtr->ast = ast;
+ sieve_ast_ref(ast);
+
+ valdtr->script = sieve_ast_script(ast);
+ valdtr->svinst = sieve_script_svinst(valdtr->script);
+ valdtr->flags = flags;
+
+ /* Setup default arguments */
+ valdtr->default_arguments[SAT_NUMBER].arg_def = &number_argument;
+ valdtr->default_arguments[SAT_NUMBER].ext = NULL;
+ valdtr->default_arguments[SAT_VAR_STRING].arg_def = &string_argument;
+ valdtr->default_arguments[SAT_VAR_STRING].ext = NULL;
+ valdtr->default_arguments[SAT_CONST_STRING].arg_def = &string_argument;
+ valdtr->default_arguments[SAT_CONST_STRING].ext = NULL;
+ valdtr->default_arguments[SAT_STRING_LIST].arg_def = &string_list_argument;
+ valdtr->default_arguments[SAT_STRING_LIST].ext = NULL;
+
+ /* Setup storage for extension contexts */
+ p_array_init(&valdtr->extensions, pool,
+ sieve_extensions_get_count(valdtr->svinst));
+
+ /* Setup command registry */
+ hash_table_create(&valdtr->commands, pool, 0, strcase_hash, strcasecmp);
+ sieve_validator_register_core_commands(valdtr);
+ sieve_validator_register_core_tests(valdtr);
+
+ /* Pre-load core language features implemented as 'extensions' */
+ ext_preloaded =
+ sieve_extensions_get_preloaded(valdtr->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->validator_load != NULL)
+ (void)ext_def->validator_load(ext_preloaded[i], valdtr);
+ }
+
+ return valdtr;
+}
+
+void sieve_validator_free(struct sieve_validator **valdtr)
+{
+ const struct sieve_validator_extension_reg *extrs;
+ unsigned int ext_count, i;
+
+ hash_table_destroy(&(*valdtr)->commands);
+ sieve_ast_unref(&(*valdtr)->ast);
+
+ sieve_error_handler_unref(&(*valdtr)->ehandler);
+
+ /* Signal registered extensions that the validator is being destroyed */
+ extrs = array_get(&(*valdtr)->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extrs[i].valext != NULL && extrs[i].valext->free != NULL)
+ extrs[i].valext->free(extrs[i].ext, *valdtr,
+ extrs[i].context);
+ }
+
+ pool_unref(&(*valdtr)->pool);
+
+ *valdtr = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+// FIXME: build validate environment
+
+pool_t sieve_validator_pool(struct sieve_validator *valdtr)
+{
+ return valdtr->pool;
+}
+
+struct sieve_error_handler *
+sieve_validator_error_handler(struct sieve_validator *valdtr)
+{
+ return valdtr->ehandler;
+}
+
+struct sieve_ast *sieve_validator_ast(struct sieve_validator *valdtr)
+{
+ return valdtr->ast;
+}
+
+struct sieve_script *sieve_validator_script(struct sieve_validator *valdtr)
+{
+ return valdtr->script;
+}
+
+struct sieve_instance *sieve_validator_svinst(struct sieve_validator *valdtr)
+{
+ return valdtr->svinst;
+}
+
+enum sieve_compile_flags
+sieve_validator_compile_flags(struct sieve_validator *valdtr)
+{
+ return valdtr->flags;
+}
+
+/*
+ * Command registry
+ */
+
+/* Dummy command object to mark unknown commands in the registry */
+
+static bool _cmd_unknown_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ i_unreached();
+ return FALSE;
+}
+
+static const struct sieve_command_def unknown_command = {
+ .identifier = "",
+ .type = SCT_NONE,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = _cmd_unknown_validate
+};
+
+/* Registration of the core commands of the language */
+
+static void
+sieve_validator_register_core_tests(struct sieve_validator *valdtr)
+{
+ unsigned int i;
+
+ for (i = 0; i < sieve_core_tests_count; i++) {
+ sieve_validator_register_command(valdtr, NULL,
+ sieve_core_tests[i]);
+ }
+}
+
+static void
+sieve_validator_register_core_commands(struct sieve_validator *valdtr)
+{
+ unsigned int i;
+
+ for (i = 0; i < sieve_core_commands_count; i++) {
+ sieve_validator_register_command(valdtr, NULL,
+ sieve_core_commands[i]);
+ }
+}
+
+/* Registry functions */
+
+static struct sieve_command_registration *
+sieve_validator_find_command_registration(struct sieve_validator *valdtr,
+ const char *command)
+{
+ return hash_table_lookup(valdtr->commands, command);
+}
+
+static struct sieve_command_registration *
+_sieve_validator_register_command(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def,
+ const char *identifier)
+{
+ struct sieve_command_registration *cmd_reg =
+ p_new(valdtr->pool, struct sieve_command_registration, 1);
+
+ cmd_reg->cmd_def = cmd_def;
+ cmd_reg->ext = ext;
+
+ hash_table_insert(valdtr->commands, identifier, cmd_reg);
+
+ return cmd_reg;
+}
+
+void sieve_validator_register_command(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(
+ valdtr, cmd_def->identifier);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, ext, cmd_def, cmd_def->identifier);
+ } else {
+ cmd_reg->cmd_def = cmd_def;
+ cmd_reg->ext = ext;
+ }
+
+ if (cmd_def->registered != NULL)
+ cmd_def->registered(valdtr, ext, cmd_reg);
+}
+
+static void
+sieve_validator_register_unknown_command(struct sieve_validator *valdtr,
+ const char *command)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ if (cmd_reg == NULL) {
+ (void)_sieve_validator_register_command(
+ valdtr, NULL, &unknown_command, command);
+ } else {
+ i_assert(cmd_reg->cmd_def == NULL);
+ cmd_reg->cmd_def = &unknown_command;
+ }
+}
+
+/*const struct sieve_command *sieve_validator_find_command
+(struct sieve_validator *valdtr, const char *command)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ return ( record == NULL ? NULL : record->command );
+}*/
+
+/*
+ * Per-command tagged argument registry
+ */
+
+/* Dummy argument object to mark unknown arguments in the registry */
+
+static bool
+_unknown_tag_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED)
+{
+ i_unreached();
+ return FALSE;
+}
+
+static const struct sieve_argument_def _unknown_tag = {
+ .identifier = "",
+ .validate = _unknown_tag_validate,
+};
+
+static inline bool
+_tag_registration_is_unknown(struct sieve_tag_registration *tag_reg)
+{
+ return (tag_reg != NULL && tag_reg->tag_def == &_unknown_tag);
+}
+
+/* Registry functions */
+
+static void
+_sieve_validator_register_tag(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def,
+ const char *identifier, int id_code)
+{
+ struct sieve_tag_registration *reg;
+
+ reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = id_code;
+ if (identifier == NULL)
+ reg->identifier = tag_def->identifier;
+ else
+ reg->identifier = p_strdup(valdtr->pool, identifier);
+
+ if (!array_is_created(&cmd_reg->normal_tags))
+ p_array_init(&cmd_reg->normal_tags, valdtr->pool, 4);
+
+ array_append(&cmd_reg->normal_tags, &reg, 1);
+}
+
+void sieve_validator_register_persistent_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def)
+{
+ /* Add the tag to the persistent tags list if necessary */
+ if (tag_def->validate_persistent != NULL) {
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(
+ valdtr, command);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, NULL, NULL, command);
+ }
+
+ struct sieve_tag_registration *reg;
+
+ if (!array_is_created(&cmd_reg->persistent_tags)) {
+ p_array_init(&cmd_reg->persistent_tags,
+ valdtr->pool, 4);
+ } else {
+ struct sieve_tag_registration *reg_idx;
+
+ /* Avoid dupplicate registration */
+ array_foreach_elem(&cmd_reg->persistent_tags, reg_idx) {
+ if (reg_idx->tag_def == tag_def)
+ return;
+ }
+ }
+
+ reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = -1;
+
+ array_append(&cmd_reg->persistent_tags, &reg, 1);
+ }
+}
+
+void sieve_validator_register_external_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def, int id_code)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, NULL, NULL, command);
+ }
+
+ _sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
+ NULL, id_code);
+}
+
+void sieve_validator_register_tag(
+ struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def, int id_code)
+{
+ if (tag_def->is_instance_of == NULL) {
+ _sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
+ NULL, id_code);
+ } else {
+ struct sieve_tag_registration *reg =
+ p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = id_code;
+
+ if (!array_is_created(&cmd_reg->instanced_tags))
+ p_array_init(&cmd_reg->instanced_tags, valdtr->pool, 4);
+
+ array_append(&cmd_reg->instanced_tags, &reg, 1);
+ }
+}
+
+static void
+sieve_validator_register_unknown_tag(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const char *tag)
+{
+ _sieve_validator_register_tag(valdtr, cmd_reg, NULL,
+ &_unknown_tag, tag, 0);
+}
+
+static struct sieve_tag_registration *
+_sieve_validator_command_tag_get(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ const char *tag, void **data)
+{
+ struct sieve_command_registration *cmd_reg = cmd->reg;
+ struct sieve_tag_registration * const *regs;
+ unsigned int i, reg_count;
+
+ /* First check normal tags */
+ if (array_is_created(&cmd_reg->normal_tags)) {
+ regs = array_get(&cmd_reg->normal_tags, &reg_count);
+
+ for (i = 0; i < reg_count; i++) {
+ if (regs[i]->tag_def != NULL &&
+ strcasecmp(regs[i]->identifier, tag) == 0) {
+
+ return regs[i];
+ }
+ }
+ }
+
+ /* Not found so far, try the instanced tags */
+ if (array_is_created(&cmd_reg->instanced_tags)) {
+ regs = array_get(&cmd_reg->instanced_tags, &reg_count);
+
+ for (i = 0; i < reg_count; i++) {
+ if (regs[i]->tag_def != NULL) {
+ if (regs[i]->tag_def->is_instance_of(
+ valdtr, cmd, regs[i]->ext, tag, data))
+ return regs[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static bool
+sieve_validator_command_tag_exists(struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *tag)
+{
+ return (_sieve_validator_command_tag_get(valdtr, cmd,
+ tag, NULL) != NULL);
+}
+
+static struct sieve_tag_registration *
+sieve_validator_command_tag_get(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, void **data)
+{
+ const char *tag = sieve_ast_argument_tag(arg);
+
+ return _sieve_validator_command_tag_get(valdtr, cmd, tag, data);
+}
+
+/*
+ * Extension support
+ */
+
+static bool
+sieve_validator_extensions_check_conficts(struct sieve_validator *valdtr,
+ struct sieve_ast_argument *ext_arg,
+ const struct sieve_extension *ext)
+{
+ struct sieve_validator_extension_reg *ext_reg;
+ struct sieve_validator_extension_reg *regs;
+ unsigned int count, i;
+
+ if (ext->id < 0)
+ return TRUE;
+
+ ext_reg = array_idx_get_space(&valdtr->extensions,
+ (unsigned int) ext->id);
+
+ regs = array_get_modifiable(&valdtr->extensions, &count);
+ for (i = 0; i < count; i++) {
+ bool required = ext_reg->required && regs[i].required;
+
+ if (regs[i].ext == NULL)
+ continue;
+ if (regs[i].ext == ext)
+ continue;
+ if (!regs[i].loaded)
+ continue;
+
+ /* Check this extension vs other extension */
+ if (ext_reg->valext != NULL &&
+ ext_reg->valext->check_conflict != NULL) {
+ struct sieve_ast_argument *this_ext_arg =
+ (ext_arg == NULL ? regs[i].arg : ext_arg);
+
+ if (!ext_reg->valext->check_conflict(
+ ext, valdtr, ext_reg->context, this_ext_arg,
+ regs[i].ext, required))
+ return FALSE;
+ }
+
+ /* Check other extension vs this extension */
+ if (regs[i].valext != NULL &&
+ regs[i].valext->check_conflict != NULL) {
+ if (!regs[i].valext->check_conflict(
+ regs[i].ext, valdtr, regs[i].context,
+ regs[i].arg, ext, required))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+bool sieve_validator_extension_load(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const struct sieve_extension *ext,
+ bool required)
+{
+ const struct sieve_extension_def *extdef = ext->def;
+ struct sieve_validator_extension_reg *reg = NULL;
+
+ if (ext->global &&
+ (valdtr->flags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0) {
+ const char *cmd_prefix = (cmd == NULL ? "" :
+ t_strdup_printf("%s %s: ",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd)));
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%sfailed to load Sieve capability `%s': "
+ "its use is restricted to global scripts",
+ cmd_prefix, sieve_extension_name(ext));
+ return FALSE;
+ }
+
+ /* Register extension no matter what and store the
+ * AST argument registering it */
+ if (ext->id >= 0) {
+ reg = array_idx_get_space(&valdtr->extensions,
+ (unsigned int)ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->required = reg->required || required;
+ if (reg->arg == NULL)
+ reg->arg = ext_arg;
+ }
+
+ if (extdef->validator_load != NULL &&
+ !extdef->validator_load(ext, valdtr)) {
+ const char *cmd_prefix = (cmd == NULL ? "" :
+ t_strdup_printf("%s %s: ",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd)));
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%sfailed to load Sieve capability `%s'",
+ cmd_prefix, sieve_extension_name(ext));
+ return FALSE;
+ }
+
+ /* Check conflicts with other extensions */
+ if (!sieve_validator_extensions_check_conficts(valdtr, ext_arg, ext))
+ return FALSE;
+
+ /* Link extension to AST for use at code generation */
+ if (reg != NULL) {
+ sieve_ast_extension_link(valdtr->ast, ext, reg->required);
+ reg->loaded = TRUE;
+ }
+
+ return TRUE;
+}
+
+const struct sieve_extension *
+sieve_validator_extension_load_by_name(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const char *ext_name)
+{
+ const struct sieve_extension *ext;
+
+ ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+ if (ext == NULL || ext->def == NULL || !ext->enabled) {
+ unsigned int i;
+ bool core_test = FALSE;
+ bool core_command = FALSE;
+
+ for (i = 0; !core_command && i < sieve_core_commands_count;
+ i++) {
+ if (strcasecmp(sieve_core_commands[i]->identifier,
+ ext_name) == 0)
+ core_command = TRUE;
+ }
+
+ for (i = 0; !core_test && i < sieve_core_tests_count; i++) {
+ if (strcasecmp(sieve_core_tests[i]->identifier,
+ ext_name) == 0)
+ core_test = TRUE;
+ }
+
+ if (core_test || core_command) {
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%s %s: `%s' is not known as a Sieve capability, "
+ "but it is known as a Sieve %s that is always available",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ str_sanitize(ext_name, 128),
+ (core_test ? "test" : "command"));
+ } else {
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%s %s: unknown Sieve capability `%s'",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ str_sanitize(ext_name, 128));
+ }
+ return NULL;
+ }
+
+ if (!sieve_validator_extension_load(valdtr, cmd, ext_arg, ext, TRUE))
+ return NULL;
+
+ return ext;
+}
+
+const struct sieve_extension *
+sieve_validator_extension_load_implicit(struct sieve_validator *valdtr,
+ const char *ext_name)
+{
+ const struct sieve_extension *ext;
+
+ ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+ if (ext == NULL || ext->def == NULL)
+ return NULL;
+
+ if (!sieve_validator_extension_load(valdtr, NULL, NULL, ext, TRUE))
+ return NULL;
+
+ return ext;
+}
+
+void sieve_validator_extension_register(
+ struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_validator_extension *valext, void *context)
+{
+ struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->valext = valext;
+ reg->context = context;
+}
+
+bool sieve_validator_extension_loaded(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
+ return FALSE;
+
+ reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+ return (reg->loaded);
+}
+
+void sieve_validator_extension_set_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+ reg->context = context;
+}
+
+void *sieve_validator_extension_get_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
+ return NULL;
+
+ reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+ return reg->context;
+}
+
+/*
+ * Overriding the default literal arguments
+ */
+
+void sieve_validator_argument_override(struct sieve_validator *valdtr,
+ enum sieve_argument_type type,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *arg_def)
+{
+ struct sieve_default_argument *arg;
+
+ if (valdtr->default_arguments[type].arg_def != NULL) {
+ arg = p_new(valdtr->pool, struct sieve_default_argument, 1);
+ *arg = valdtr->default_arguments[type];
+
+ valdtr->default_arguments[type].overrides = arg;
+ }
+
+ valdtr->default_arguments[type].arg_def = arg_def;
+ valdtr->default_arguments[type].ext = ext;
+}
+
+static bool
+sieve_validator_argument_default_activate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_default_argument *defarg,
+ struct sieve_ast_argument *arg)
+{
+ bool result = TRUE;
+ struct sieve_default_argument *prev_defarg;
+
+ prev_defarg = valdtr->current_defarg;
+ valdtr->current_defarg = defarg;
+
+ if (arg->argument == NULL) {
+ arg->argument = sieve_argument_create(arg->ast, defarg->arg_def,
+ defarg->ext, 0);
+ } else {
+ arg->argument->def = defarg->arg_def;
+ arg->argument->ext = defarg->ext;
+ }
+
+ if (defarg->arg_def != NULL && defarg->arg_def->validate != NULL)
+ result = defarg->arg_def->validate(valdtr, &arg, cmd);
+
+ valdtr->current_defarg = prev_defarg;
+
+ return result;
+}
+
+bool sieve_validator_argument_activate_super(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ bool constant ATTR_UNUSED)
+{
+ struct sieve_default_argument *defarg;
+
+ if (valdtr->current_defarg == NULL ||
+ valdtr->current_defarg->overrides == NULL)
+ return FALSE;
+
+ if (valdtr->current_defarg->overrides->arg_def == &string_argument) {
+ switch (valdtr->current_defarg_type) {
+ case SAT_CONST_STRING:
+ if (!valdtr->current_defarg_constant) {
+ valdtr->current_defarg_type = SAT_VAR_STRING;
+ defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+ } else {
+ defarg = valdtr->current_defarg->overrides;
+ }
+ break;
+ case SAT_VAR_STRING:
+ defarg = valdtr->current_defarg->overrides;
+ break;
+ default:
+ return FALSE;
+ }
+ } else {
+ defarg = valdtr->current_defarg->overrides;
+ }
+
+ return sieve_validator_argument_default_activate(valdtr, cmd,
+ defarg, arg);
+}
+
+/*
+ * Argument Validation API
+ */
+
+bool sieve_validator_argument_activate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ bool constant)
+{
+ struct sieve_default_argument *defarg;
+
+ switch (sieve_ast_argument_type(arg)) {
+ case SAAT_NUMBER:
+ valdtr->current_defarg_type = SAT_NUMBER;
+ break;
+ case SAAT_STRING:
+ valdtr->current_defarg_type = SAT_CONST_STRING;
+ break;
+ case SAAT_STRING_LIST:
+ valdtr->current_defarg_type = SAT_STRING_LIST;
+ break;
+ default:
+ return FALSE;
+ }
+
+ valdtr->current_defarg_constant = constant;
+ defarg = &valdtr->default_arguments[valdtr->current_defarg_type];
+
+ if (!constant && defarg->arg_def == &string_argument) {
+ valdtr->current_defarg_type = SAT_VAR_STRING;
+ defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+ }
+
+ return sieve_validator_argument_default_activate(valdtr, cmd,
+ defarg, arg);
+}
+
+bool sieve_validate_positional_argument(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ const char *arg_name,
+ unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type)
+{
+ i_assert(arg != NULL);
+
+ if (sieve_ast_argument_type(arg) != req_type &&
+ (sieve_ast_argument_type(arg) != SAAT_STRING ||
+ req_type != SAAT_STRING_LIST))
+ {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "the %s %s expects %s as argument %d (%s), "
+ "but %s was found",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type),
+ arg_pos, arg_name, sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool sieve_validate_tag_parameter(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *tag,
+ struct sieve_ast_argument *param,
+ const char *arg_name, unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type,
+ bool constant)
+{
+ i_assert(tag != NULL);
+
+ if (param == NULL) {
+ const char *position = (arg_pos == 0 ? "" :
+ t_strdup_printf(" %d (%s)", arg_pos, arg_name));
+
+ sieve_argument_validate_error(
+ valdtr, tag,
+ "the :%s tag for the %s %s requires %s as parameter%s, "
+ "but no parameters were found",
+ sieve_ast_argument_tag(tag),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type), position);
+ return FALSE;
+ }
+
+ if (sieve_ast_argument_type(param) != req_type &&
+ (sieve_ast_argument_type(param) != SAAT_STRING ||
+ req_type != SAAT_STRING_LIST))
+ {
+ const char *position = (arg_pos == 0 ? "" :
+ t_strdup_printf(" %d (%s)", arg_pos, arg_name));
+
+ sieve_argument_validate_error(
+ valdtr, param,
+ "the :%s tag for the %s %s requires %s as parameter%s, "
+ "but %s was found",
+ sieve_ast_argument_tag(tag),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type), position,
+ sieve_ast_argument_name(param));
+ return FALSE;
+ }
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, param, constant))
+ return FALSE;
+
+ param->argument->id_code = tag->argument->id_code;
+
+ return TRUE;
+}
+
+/*
+ * Command argument validation
+ */
+
+static bool
+sieve_validate_command_arguments(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ int arg_count = cmd->def->positional_args;
+ int real_count = 0;
+ struct sieve_ast_argument *arg;
+ struct sieve_command_registration *cmd_reg = cmd->reg;
+
+ /* Resolve tagged arguments */
+ arg = sieve_ast_argument_first(cmd->ast_node);
+ while (arg != NULL) {
+ void *arg_data = NULL;
+ struct sieve_tag_registration *tag_reg;
+ const struct sieve_argument_def *tag_def;
+
+ if (sieve_ast_argument_type(arg) != SAAT_TAG) {
+ arg = sieve_ast_argument_next(arg);
+ continue;
+ }
+
+ tag_reg = sieve_validator_command_tag_get(valdtr, cmd,
+ arg, &arg_data);
+
+ if (tag_reg == NULL) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "unknown tagged argument ':%s' for the %s %s "
+ "(reported only once at first occurrence)",
+ sieve_ast_argument_tag(arg),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ sieve_validator_register_unknown_tag(
+ valdtr, cmd_reg, sieve_ast_argument_tag(arg));
+ return FALSE;
+ }
+
+ /* Check whether previously tagged as unknown */
+ if (_tag_registration_is_unknown(tag_reg))
+ return FALSE;
+
+ tag_def = tag_reg->tag_def;
+
+ /* Assign the tagged argument type to the ast for later
+ reference */
+ arg->argument = sieve_argument_create(
+ arg->ast, tag_def, tag_reg->ext, tag_reg->id_code);
+ arg->argument->data = arg_data;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Validate tagged arguments */
+ arg = sieve_ast_argument_first(cmd->ast_node);
+ while (arg != NULL && sieve_ast_argument_type(arg) == SAAT_TAG) {
+ const struct sieve_argument_def *tag_def = arg->argument->def;
+ struct sieve_ast_argument *parg;
+
+ /* Scan backwards for any duplicates */
+ if ((tag_def->flags & SIEVE_ARGUMENT_FLAG_MULTIPLE) == 0) {
+ parg = sieve_ast_argument_prev(arg);
+ while (parg != NULL) {
+ if ((sieve_ast_argument_type(parg) == SAAT_TAG &&
+ parg->argument->def == tag_def) ||
+ (arg->argument->id_code > 0 &&
+ parg->argument != NULL &&
+ parg->argument->id_code == arg->argument->id_code))
+ {
+ const char *tag_id = sieve_ast_argument_tag(arg);
+ const char *tag_desc =
+ strcmp(tag_def->identifier, tag_id) != 0 ?
+ t_strdup_printf("%s argument (:%s)",
+ tag_def->identifier, tag_id) :
+ t_strdup_printf(":%s argument",
+ tag_def->identifier);
+
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "encountered duplicate %s for the %s %s",
+ tag_desc, sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+
+ return FALSE;
+ }
+
+ parg = sieve_ast_argument_prev(parg);
+ }
+ }
+
+ /* Call the validation function for the tag (if present)
+ Fail if the validation fails:
+ Let's not whine multiple times about a single command
+ having multiple bad arguments...
+ */
+ if (tag_def->validate != NULL) {
+ if (!tag_def->validate(valdtr, &arg, cmd))
+ return FALSE;
+ } else {
+ arg = sieve_ast_argument_next(arg);
+ }
+ }
+
+ /* Remaining arguments should be positional (tags are not allowed
+ here) */
+ cmd->first_positional = arg;
+
+ while (arg != NULL) {
+ if (sieve_ast_argument_type(arg) == SAAT_TAG) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "encountered an unexpected tagged argument ':%s' "
+ "while validating positional arguments for the %s %s",
+ sieve_ast_argument_tag(arg),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ real_count++;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Check the required count versus the real number of arguments */
+ if (arg_count >= 0 && real_count != arg_count) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires %d positional argument(s), "
+ "but %d is/are specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ arg_count, real_count);
+ return FALSE;
+ }
+
+ /* Call initial validation for persistent arguments */
+ if (array_is_created(&cmd_reg->persistent_tags)) {
+ struct sieve_tag_registration * const *regs;
+ unsigned int i, reg_count;
+
+ regs = array_get(&cmd_reg->persistent_tags, &reg_count);
+ for (i = 0; i < reg_count; i++) {
+ const struct sieve_argument_def *tag_def =
+ regs[i]->tag_def;
+
+ if (tag_def != NULL &&
+ tag_def->validate_persistent != NULL) {
+ /* To be sure */
+ if (!tag_def->validate_persistent(
+ valdtr, cmd, regs[i]->ext))
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_arguments_context(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg =
+ sieve_command_first_argument(cmd);
+
+ while (arg != NULL) {
+ const struct sieve_argument *argument = arg->argument;
+
+ if (argument != NULL && argument->def != NULL &&
+ argument->def->validate_context != NULL) {
+
+ if (!argument->def->validate_context(valdtr, arg, cmd))
+ return FALSE;
+ }
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Command Validation API
+ */
+
+static bool
+sieve_validate_command_subtests(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ const unsigned int count)
+{
+ switch (count) {
+ case 0:
+ if (sieve_ast_test_count(cmd->ast_node) > 0) {
+ /* Unexpected command specified */
+ enum sieve_command_type ctype = SCT_NONE;
+ struct sieve_command_registration *cmd_reg;
+ struct sieve_ast_node *test =
+ sieve_ast_test_first(cmd->ast_node);
+
+ cmd_reg = sieve_validator_find_command_registration(
+ valdtr, test->identifier);
+
+ /* First check what we are dealing with */
+ if (cmd_reg != NULL && cmd_reg->cmd_def != NULL)
+ ctype = cmd_reg->cmd_def->type;
+
+ switch (ctype) {
+ case SCT_TEST: /* Spurious test */
+ case SCT_HYBRID:
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s accepts no sub-tests, "
+ "but tests are specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ case SCT_NONE: /* Unknown command */
+ /* Is it perhaps a tag for which the ':' was
+ omitted ? */
+ if (sieve_validator_command_tag_exists(
+ valdtr, cmd, test->identifier)) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "missing colon ':' before ':%s' tag in %s %s",
+ test->identifier,
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ }
+ /* Fall through */
+ case SCT_COMMAND:
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "missing semicolon ';' after %s %s",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ }
+ return FALSE;
+ }
+ break;
+ case 1:
+ if (sieve_ast_test_count(cmd->ast_node) == 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires one sub-test, "
+ "but none is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+
+ } else if (sieve_ast_test_count(cmd->ast_node) > 1 ||
+ cmd->ast_node->test_list) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires one sub-test, "
+ "but a list of tests is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+ break;
+ default:
+ if (sieve_ast_test_count(cmd->ast_node) == 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires a list of sub-tests, "
+ "but none is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ } else if (sieve_ast_test_count(cmd->ast_node) == 1 &&
+ !cmd->ast_node->test_list) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires a list of sub-tests, "
+ "but a single test is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_command_block(struct sieve_validator *valdtr,
+ struct sieve_command *cmd, bool block_allowed,
+ bool block_required)
+{
+ i_assert(cmd->ast_node->type == SAT_COMMAND);
+
+ if (block_required) {
+ if (!cmd->ast_node->block) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s command requires a command block, "
+ "but it is missing",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+ } else if (!block_allowed && cmd->ast_node->block) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s command does not accept a command block, "
+ "but one is specified anyway",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * AST Validation
+ */
+
+static bool
+sieve_validate_test_list(struct sieve_validator *valdtr,
+ struct sieve_ast_node *test_list, int *const_r);
+static bool
+sieve_validate_block(struct sieve_validator *valdtr,
+ struct sieve_ast_node *block);
+static bool
+sieve_validate_command(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node, int *const_r);
+
+static bool
+sieve_validate_command_context(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node)
+{
+ enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+ struct sieve_command_registration *cmd_reg;
+
+ i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);
+
+ /* Verify the command specified by this node */
+ cmd_reg = sieve_validator_find_command_registration(
+ valdtr, cmd_node->identifier);
+
+ if (cmd_reg != NULL && cmd_reg->cmd_def != NULL) {
+ const struct sieve_command_def *cmd_def = cmd_reg->cmd_def;
+
+ /* Identifier = "" when the command was previously marked as
+ unknown */
+ if (*(cmd_def->identifier) != '\0') {
+ if ((cmd_def->type == SCT_COMMAND && ast_type == SAT_TEST) ||
+ (cmd_def->type == SCT_TEST && ast_type == SAT_COMMAND)) {
+ sieve_validator_error(
+ valdtr, cmd_node->source_line,
+ "attempted to use %s '%s' as %s",
+ sieve_command_def_type_name(cmd_def),
+ cmd_node->identifier,
+ sieve_ast_type_name(ast_type));
+ return FALSE;
+ }
+
+ cmd_node->command = sieve_command_create(
+ cmd_node, cmd_reg->ext, cmd_def, cmd_reg);
+ } else {
+ return FALSE;
+ }
+ } else {
+ sieve_validator_error(
+ valdtr, cmd_node->source_line,
+ "unknown %s '%s' (only reported once at first occurrence)",
+ sieve_ast_type_name(ast_type), cmd_node->identifier);
+
+ sieve_validator_register_unknown_command(
+ valdtr, cmd_node->identifier);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_command(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node, int *const_r)
+{
+ enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+ struct sieve_command *cmd =
+ (cmd_node == NULL ? NULL : cmd_node->command);
+ const struct sieve_command_def *cmd_def =
+ (cmd != NULL ? cmd->def : NULL);
+ bool result = TRUE;
+
+ i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);
+
+ if (cmd_def != NULL && *(cmd_def->identifier) != '\0') {
+ if (cmd_def->pre_validate == NULL ||
+ cmd_def->pre_validate(valdtr, cmd)) {
+ /* Check argument syntax */
+ if (!sieve_validate_command_arguments(valdtr, cmd)) {
+ result = FALSE;
+
+ /* A missing ':' causes a tag to become a test.
+ This can be the cause of the arguments
+ validation failing. Therefore we must produce
+ an error for the sub-tests as well if
+ appropriate. */
+ (void)sieve_validate_command_subtests(
+ valdtr, cmd, cmd_def->subtests);
+ } else if (!sieve_validate_command_subtests(
+ valdtr, cmd, cmd_def->subtests) ||
+ (ast_type == SAT_COMMAND &&
+ !sieve_validate_command_block(
+ valdtr, cmd, cmd_def->block_allowed,
+ cmd_def->block_required))) {
+ result = FALSE;
+ } else {
+ /* Call command validation function if specified
+ */
+ if (cmd_def->validate != NULL) {
+ result = cmd_def->validate(valdtr, cmd) &&
+ result;
+ }
+ }
+ } else {
+ /* If pre-validation fails, don't bother to validate
+ further as context might be missing and doing so is
+ not very useful for further error reporting anyway */
+ return FALSE;
+ }
+
+ result = result && sieve_validate_arguments_context(valdtr, cmd);
+ }
+
+ /*
+ * Descend further into the AST
+ */
+
+ if (cmd_def != NULL) {
+ /* Tests */
+ if (cmd_def->subtests > 0) {
+ if (result ||
+ sieve_errors_more_allowed(valdtr->ehandler)) {
+ result = sieve_validate_test_list(
+ valdtr, cmd_node, const_r) && result;
+ }
+ } else if (result) {
+ if (cmd_def->validate_const != NULL) {
+ (void)cmd_def->validate_const(
+ valdtr, cmd, const_r, -1);
+ } else {
+ *const_r = -1;
+ }
+ }
+
+ /* Skip block if result of test is const FALSE */
+ if (result && *const_r == 0)
+ return TRUE;
+
+ /* Command block */
+ if (cmd_def->block_allowed && ast_type == SAT_COMMAND &&
+ (result || sieve_errors_more_allowed(valdtr->ehandler))) {
+ result = sieve_validate_block(valdtr, cmd_node) &&
+ result;
+ }
+ }
+
+ return result;
+}
+
+static bool
+sieve_validate_test_list(struct sieve_validator *valdtr,
+ struct sieve_ast_node *test_node, int *const_r)
+{
+ struct sieve_command *tst = test_node->command;
+ const struct sieve_command_def *tst_def =
+ (tst != NULL ? tst->def : NULL);
+ struct sieve_ast_node *test;
+ bool result = TRUE;
+
+ if (tst_def != NULL && tst_def->validate_const != NULL) {
+ if (!tst_def->validate_const(valdtr, tst, const_r, -2))
+ return TRUE;
+ }
+
+ test = sieve_ast_test_first(test_node);
+ while (test != NULL &&
+ (result || sieve_errors_more_allowed(valdtr->ehandler))) {
+ int const_value = -2;
+
+ result = sieve_validate_command_context(valdtr, test) &&
+ sieve_validate_command(valdtr, test, &const_value) &&
+ result;
+
+ if (result) {
+ if (tst_def != NULL &&
+ tst_def->validate_const != NULL) {
+ if (!tst_def->validate_const(
+ valdtr, tst, const_r, const_value))
+ return TRUE;
+ } else {
+ *const_r = -1;
+ }
+ }
+
+ if (result && const_value >= 0)
+ test = sieve_ast_node_detach(test);
+ else
+ test = sieve_ast_test_next(test);
+ }
+
+ return result;
+}
+
+static bool
+sieve_validate_block(struct sieve_validator *valdtr,
+ struct sieve_ast_node *block)
+{
+ bool result = TRUE, fatal = FALSE;
+ struct sieve_ast_node *cmd_node, *next;
+
+ T_BEGIN {
+ cmd_node = sieve_ast_command_first(block);
+ while (!fatal && cmd_node != NULL &&
+ (result ||
+ sieve_errors_more_allowed(valdtr->ehandler))) {
+ bool command_success;
+ int const_value = -2;
+
+ next = sieve_ast_command_next(cmd_node);
+
+ /* Check if this is the first non-require command */
+ if (sieve_ast_node_type(block) == SAT_ROOT &&
+ !valdtr->finished_require &&
+ strcasecmp(cmd_node->identifier,
+ cmd_require.identifier) != 0) {
+ const struct sieve_validator_extension_reg *extrs;
+ const struct sieve_extension *const *exts;
+ unsigned int ext_count, i;
+
+ valdtr->finished_require = TRUE;
+
+ /* Load implicit extensions */
+ exts = sieve_extensions_get_all(valdtr->svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (exts[i]->implicit) {
+ (void)sieve_validator_extension_load(
+ valdtr, NULL, NULL, exts[i], TRUE);
+ }
+ }
+
+ /* Validate all 'require'd extensions */
+ extrs = array_get(&valdtr->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extrs[i].loaded && extrs[i].valext != NULL &&
+ extrs[i].valext->validate != NULL) {
+ if (!extrs[i].valext->validate(
+ extrs[i].ext, valdtr,
+ extrs[i].context, extrs[i].arg,
+ extrs[i].required)) {
+ fatal = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ command_success =
+ sieve_validate_command_context(valdtr, cmd_node);
+ result = command_success && result;
+
+ result = !fatal &&
+ sieve_validate_command(valdtr, cmd_node,
+ &const_value) && result;
+
+ cmd_node = next;
+ }
+ } T_END;
+
+ return result && !fatal;
+}
+
+bool sieve_validator_run(struct sieve_validator *valdtr)
+{
+ return sieve_validate_block(valdtr, sieve_ast_root(valdtr->ast));
+}
+
+/*
+ * Validator object registry
+ */
+
+struct sieve_validator_object_reg {
+ const struct sieve_object_def *obj_def;
+ const struct sieve_extension *ext;
+};
+
+struct sieve_validator_object_registry {
+ struct sieve_validator *valdtr;
+ ARRAY(struct sieve_validator_object_reg) registrations;
+};
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_get(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ return (struct sieve_validator_object_registry *)
+ sieve_validator_extension_get_context(valdtr, ext);
+}
+
+void sieve_validator_object_registry_add(
+ struct sieve_validator_object_registry *regs,
+ const struct sieve_extension *ext,
+ const struct sieve_object_def *obj_def)
+{
+ struct sieve_validator_object_reg *reg;
+
+ reg = array_append_space(&regs->registrations);
+ reg->ext = ext;
+ reg->obj_def = obj_def;
+}
+
+bool sieve_validator_object_registry_find(
+ struct sieve_validator_object_registry *regs, const char *identifier,
+ struct sieve_object *obj)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_count(&regs->registrations); i++) {
+ const struct sieve_validator_object_reg *reg =
+ array_idx(&regs->registrations, i);
+
+ if (strcasecmp(reg->obj_def->identifier, identifier) == 0) {
+ if (obj != NULL) {
+ obj->def = reg->obj_def;
+ obj->ext = reg->ext;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_create(struct sieve_validator *valdtr)
+{
+ pool_t pool = valdtr->pool;
+ struct sieve_validator_object_registry *regs =
+ p_new(pool, struct sieve_validator_object_registry, 1);
+
+ /* Setup registry */
+ p_array_init(&regs->registrations, valdtr->pool, 4);
+
+ regs->valdtr = valdtr;
+
+ return regs;
+}
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_init(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ struct sieve_validator_object_registry *regs =
+ sieve_validator_object_registry_create(valdtr);
+
+ sieve_validator_extension_set_context(valdtr, ext, regs);
+ return regs;
+}
+
+/*
+ * Error handling
+ */
+
+#undef sieve_validator_error
+void sieve_validator_error(struct sieve_validator *valdtr,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(valdtr->script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(valdtr->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_validator_warning
+void sieve_validator_warning(struct sieve_validator *valdtr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(valdtr->script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(valdtr->ehandler, &params, fmt, args);
+ va_end(args);
+
+}