diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-validator.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-validator.c | 1706 |
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, ®, 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, ®, 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, ®, 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, ®_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, ®_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, ®_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(®s->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(®s->registrations); i++) { + const struct sieve_validator_object_reg *reg = + array_idx(®s->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(®s->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, ¶ms, 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, ¶ms, fmt, args); + va_end(args); + +} |