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