diff --git a/Makefile.am b/Makefile.am index 865fdf6a2d96fe4d6e5c32078c01cca76d5625b2..3d1ff922ff6fdec80a9af8e109a45492d7550b0e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -172,6 +172,8 @@ test_cases = \ tests/extensions/mime/foreverypart.svtest \ tests/extensions/mime/extracttext.svtest \ tests/extensions/mime/calendar-example.svtest \ + tests/extensions/special-use/errors.svtest \ + tests/extensions/special-use/execute.svtest \ tests/extensions/vnd.dovecot/debug/execute.svtest \ tests/extensions/vnd.dovecot/environment/basic.svtest \ tests/extensions/vnd.dovecot/environment/variables.svtest \ diff --git a/configure.ac b/configure.ac index 551cddf685a6675e021c9a0188252f01fbb7286a..32fa6d6e7f670063aa1e56efa43fee03327cf187 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,7 @@ src/lib-sieve/plugins/metadata/Makefile src/lib-sieve/plugins/duplicate/Makefile src/lib-sieve/plugins/index/Makefile src/lib-sieve/plugins/mime/Makefile +src/lib-sieve/plugins/special-use/Makefile src/lib-sieve/plugins/vnd.dovecot/Makefile src/lib-sieve/plugins/vnd.dovecot/debug/Makefile src/lib-sieve/plugins/vnd.dovecot/environment/Makefile diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am index 91c3c08cc8f1b9be0c08da5e67096a2742bc813d..2e80871bb39b60852f9e422946a739f0e0a8bc22 100644 --- a/src/lib-sieve/Makefile.am +++ b/src/lib-sieve/Makefile.am @@ -78,6 +78,7 @@ plugins = \ $(extdir)/index/libsieve_ext_index.la \ $(extdir)/metadata/libsieve_ext_metadata.la \ $(extdir)/mime/libsieve_ext_mime.la \ + $(extdir)/special-use/libsieve_ext_special_use.la \ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \ $(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \ $(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \ diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am index 887235f1c1508003ca670403fc9b1e7a6cbf79fa..2a1c0548f7caf506670da0419e53caa8e29dcdd3 100644 --- a/src/lib-sieve/plugins/Makefile.am +++ b/src/lib-sieve/plugins/Makefile.am @@ -25,6 +25,7 @@ SUBDIRS = \ index \ metadata \ mime \ + special-use \ vnd.dovecot \ $(UNFINISHED) diff --git a/src/lib-sieve/plugins/special-use/Makefile.am b/src/lib-sieve/plugins/special-use/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..0f31aeee12ea74e40493af1a200c9f182dec5306 --- /dev/null +++ b/src/lib-sieve/plugins/special-use/Makefile.am @@ -0,0 +1,22 @@ +noinst_LTLIBRARIES = libsieve_ext_special_use.la + +AM_CPPFLAGS = \ + -I$(srcdir)/../.. \ + $(LIBDOVECOT_INCLUDE) + +tags = \ + tag-specialuse.c + +tests = \ + tst-specialuse-exists.c + +libsieve_ext_special_use_la_SOURCES = \ + $(tags) \ + $(tests) \ + ext-special-use-common.c \ + ext-special-use.c + +headers = \ + ext-special-use-common.h + +noinst_HEADERS = $(headers) diff --git a/src/lib-sieve/plugins/special-use/ext-special-use-common.c b/src/lib-sieve/plugins/special-use/ext-special-use-common.c new file mode 100644 index 0000000000000000000000000000000000000000..fcaf1b57d7b7e923fa81cef2b3b14d2f6c07b99e --- /dev/null +++ b/src/lib-sieve/plugins/special-use/ext-special-use-common.c @@ -0,0 +1,31 @@ +/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-arg.h" + +#include "ext-special-use-common.h" + +bool ext_special_use_flag_valid(const char *flag) +{ + const char *p = flag; + + /* RFC 6154, Section 6: + + use-attr = "\All" / "\Archive" / "\Drafts" / "\Flagged" / + "\Junk" / "\Sent" / "\Trash" / use-attr-ext + use-attr-ext = "\" atom + */ + + /* "\" */ + if (*p != '\\') + return FALSE; + p++; + + /* atom */ + for (; *p != '\0'; p++) { + if (!IS_ATOM_CHAR(*p)) + return FALSE; + } + + return TRUE; +} diff --git a/src/lib-sieve/plugins/special-use/ext-special-use-common.h b/src/lib-sieve/plugins/special-use/ext-special-use-common.h new file mode 100644 index 0000000000000000000000000000000000000000..44d0e1eb1e0b4e739916ede44f78f3496d327816 --- /dev/null +++ b/src/lib-sieve/plugins/special-use/ext-special-use-common.h @@ -0,0 +1,43 @@ +#ifndef EXT_SPECIAL_USE_COMMON_H +#define EXT_SPECIAL_USE_COMMON_H + +#include "sieve-common.h" + +/* + * Tagged arguments + */ + +extern const struct sieve_argument_def specialuse_tag; + +/* + * Commands + */ + +extern const struct sieve_command_def specialuse_exists_test; + +/* + * Operands + */ + +extern const struct sieve_operand_def specialuse_operand; + +/* + * Operations + */ + +extern const struct sieve_operation_def specialuse_exists_operation; + +/* + * Extension + */ + +extern const struct sieve_extension_def special_use_extension; + +/* + * Flag checking + */ + +bool ext_special_use_flag_valid(const char *flag); + +#endif /* __EXT_MAILBOX_COMMON_H */ + diff --git a/src/lib-sieve/plugins/special-use/ext-special-use.c b/src/lib-sieve/plugins/special-use/ext-special-use.c new file mode 100644 index 0000000000000000000000000000000000000000..7cf92a38bb1c6bc311fdb52172d033f67b4dc517 --- /dev/null +++ b/src/lib-sieve/plugins/special-use/ext-special-use.c @@ -0,0 +1,57 @@ +/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */ + +/* Extension special-use + * --------------------- + * + * Authors: Stephan Bosch + * Specification: RFC ---- + * Implementation: prototype + * Status: development + * + */ + +#include "sieve-common.h" + +#include "sieve-code.h" +#include "sieve-extensions.h" +#include "sieve-actions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-result.h" + +#include "ext-special-use-common.h" + +/* + * Extension + */ + +static bool +ext_special_use_validator_load(const struct sieve_extension *ext, + struct sieve_validator *valdtr); + +const struct sieve_extension_def special_use_extension = { + .name = "special-use", + .validator_load = ext_special_use_validator_load, + SIEVE_EXT_DEFINE_OPERATION(specialuse_exists_operation), + SIEVE_EXT_DEFINE_OPERAND(specialuse_operand) +}; + +static bool +ext_special_use_validator_load(const struct sieve_extension *ext, + struct sieve_validator *valdtr) +{ + /* Register :specialuse tag with fileinto command and we don't care + whether this command is registered or even whether it will be + registered at all. The validator handles either situation gracefully. + */ + sieve_validator_register_external_tag( + valdtr, "fileinto", ext, &specialuse_tag, + SIEVE_OPT_SIDE_EFFECT); + + /* Register new test */ + sieve_validator_register_command(valdtr, ext, &specialuse_exists_test); + + return TRUE; +} diff --git a/src/lib-sieve/plugins/special-use/tag-specialuse.c b/src/lib-sieve/plugins/special-use/tag-specialuse.c new file mode 100644 index 0000000000000000000000000000000000000000..ce9fbeec82bc4bae5e5b9b3cbfeb9fdc895f833e --- /dev/null +++ b/src/lib-sieve/plugins/special-use/tag-specialuse.c @@ -0,0 +1,313 @@ +/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */ + +#include "lib.h" +#include "str-sanitize.h" +#include "mail-storage.h" +#include "mail-namespace.h" + +#include "sieve-common.h" +#include "sieve-commands.h" +#include "sieve-actions.h" +#include "sieve-code.h" +#include "sieve-actions.h" +#include "sieve-result.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" + +#include "ext-special-use-common.h" + +/* + * Flags tagged argument + */ + +static bool +tag_specialuse_validate(struct sieve_validator *valdtr, + struct sieve_ast_argument **arg, + struct sieve_command *cmd); +static bool +tag_specialuse_generate(const struct sieve_codegen_env *cgenv, + struct sieve_ast_argument *arg, + struct sieve_command *cmd); + +const struct sieve_argument_def specialuse_tag = { + .identifier = "specialuse", + .validate = tag_specialuse_validate, + .generate = tag_specialuse_generate +}; + +/* + * Side effect + */ + +static bool +seff_specialuse_dump_context(const struct sieve_side_effect *seffect, + const struct sieve_dumptime_env *denv, + sieve_size_t *address); +static int +seff_specialuse_read_context(const struct sieve_side_effect *seffect, + const struct sieve_runtime_env *renv, + sieve_size_t *address, void **context); + +static int +seff_specialuse_merge(const struct sieve_runtime_env *renv, + const struct sieve_action *action, + const struct sieve_side_effect *old_seffect, + const struct sieve_side_effect *new_seffect, + void **old_context); + +static void +seff_specialuse_print(const struct sieve_side_effect *seffect, + const struct sieve_action *action, + const struct sieve_result_print_env *rpenv, bool *keep); +static int +seff_specialuse_pre_execute(const struct sieve_side_effect *seffect, + const struct sieve_action_exec_env *aenv, + void **context, void *tr_context); + +const struct sieve_side_effect_def specialuse_side_effect = { + SIEVE_OBJECT("specialuse", &specialuse_operand, 0), + .precedence = 200, + .to_action = &act_store, + .dump_context = seff_specialuse_dump_context, + .read_context = seff_specialuse_read_context, + .merge = seff_specialuse_merge, + .print = seff_specialuse_print, + .pre_execute = seff_specialuse_pre_execute +}; + +/* + * Operand + */ + +static const struct sieve_extension_objects ext_side_effects = + SIEVE_EXT_DEFINE_SIDE_EFFECT(specialuse_side_effect); + +const struct sieve_operand_def specialuse_operand = { + .name = "specialuse operand", + .ext_def = &special_use_extension, + .class = &sieve_side_effect_operand_class, + .interface = &ext_side_effects +}; + +/* + * Tag validation + */ + +static bool +tag_specialuse_validate(struct sieve_validator *valdtr, + struct sieve_ast_argument **arg, + struct sieve_command *cmd) +{ + struct sieve_ast_argument *tag = *arg; + const char *use_flag; + + /* Detach the tag itself */ + *arg = sieve_ast_argument_next(*arg); + + /* Check syntax: + * :specialuse <special-use-flag: string> + */ + if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0, + SAAT_STRING, FALSE)) + return FALSE; + use_flag = sieve_ast_argument_strc(*arg); + if (!ext_special_use_flag_valid(use_flag)) { + sieve_argument_validate_error( + valdtr, *arg, "special-use tag: " + "invalid special-use flag `%s' specified", + str_sanitize(use_flag, 64)); + return FALSE; + } + + tag->parameters = *arg; + + /* Detach parameter */ + *arg = sieve_ast_arguments_detach(*arg,1); + + return TRUE; +} + +/* + * Code generation + */ + +static bool +tag_specialuse_generate(const struct sieve_codegen_env *cgenv, + struct sieve_ast_argument *arg, + struct sieve_command *cmd) +{ + struct sieve_ast_argument *param; + + if (sieve_ast_argument_type(arg) != SAAT_TAG) + return FALSE; + + sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext, + &specialuse_side_effect); + + /* Explicit :specialuse tag */ + param = arg->parameters; + + /* Call the generation function for the argument */ + if (param->argument != NULL && param->argument->def != NULL && + param->argument->def->generate != NULL && + !param->argument->def->generate(cgenv, param, cmd)) + return FALSE; + + return TRUE; +} + +/* + * Side effect implementation + */ + +/* Context data */ + +struct seff_specialuse_context { + const char *special_use_flag; +}; + +/* Context coding */ + +static bool +seff_specialuse_dump_context( + const struct sieve_side_effect *seffect ATTR_UNUSED, + const struct sieve_dumptime_env *denv, sieve_size_t *address) +{ + return sieve_opr_stringlist_dump(denv, address, "specialuse"); +} + +static int +seff_specialuse_read_context( + const struct sieve_side_effect *seffect ATTR_UNUSED, + const struct sieve_runtime_env *renv, sieve_size_t *address, + void **se_context) +{ + pool_t pool = sieve_result_pool(renv->result); + struct seff_specialuse_context *ctx; + string_t *special_use_flag; + const char *use_flag; + int ret; + + if ((ret = sieve_opr_string_read(renv, address, "specialuse", + &special_use_flag)) <= 0) + return ret; + + use_flag = str_c(special_use_flag); + if (!ext_special_use_flag_valid(use_flag)) { + sieve_runtime_error( + renv, NULL, "special-use_tag: " + "invalid special-use flag `%s' specified", + str_sanitize(use_flag, 64)); + return SIEVE_EXEC_FAILURE; + } + + ctx = p_new(pool, struct seff_specialuse_context, 1); + ctx->special_use_flag = p_strdup(pool, use_flag); + + *se_context = (void *) ctx; + + return SIEVE_EXEC_OK; +} + +/* Result verification */ + +static int +seff_specialuse_merge(const struct sieve_runtime_env *renv ATTR_UNUSED, + const struct sieve_action *action ATTR_UNUSED, + const struct sieve_side_effect *old_seffect ATTR_UNUSED, + const struct sieve_side_effect *new_seffect, + void **old_context) +{ + if (new_seffect != NULL) + *old_context = new_seffect->context; + + return 1; +} + +/* Result printing */ + +static void +seff_specialuse_print(const struct sieve_side_effect *seffect, + const struct sieve_action *action ATTR_UNUSED, + const struct sieve_result_print_env *rpenv, + bool *keep ATTR_UNUSED) +{ + struct seff_specialuse_context *ctx = + (struct seff_specialuse_context *)seffect->context; + + sieve_result_seffect_printf( + rpenv, + "use mailbox with special-use flag `%s' instead if accessible", + ctx->special_use_flag); +} + +/* Result execution */ + +static int +seff_specialuse_pre_execute(const struct sieve_side_effect *seffect, + const struct sieve_action_exec_env *aenv, + void **context ATTR_UNUSED, void *tr_context) +{ + struct seff_specialuse_context *ctx = + (struct seff_specialuse_context *)seffect->context; + const struct sieve_execute_env *eenv = aenv->exec_env; + struct act_store_transaction *trans = + (struct act_store_transaction *)tr_context; + struct mailbox *box; + + if (trans->box == NULL || trans->disabled) + return SIEVE_EXEC_OK; + + /* Check whether something already failed */ + switch (trans->error_code) { + case MAIL_ERROR_NONE: + break; + case MAIL_ERROR_TEMP: + return SIEVE_EXEC_TEMP_FAILURE; + default: + return SIEVE_EXEC_FAILURE; + } + + trans->error = NULL; + trans->error_code = MAIL_ERROR_NONE; + + box = mailbox_alloc_for_user(eenv->scriptenv->user, + ctx->special_use_flag, + (MAILBOX_FLAG_POST_SESSION | + MAILBOX_FLAG_SPECIAL_USE)); + + /* We still override the allocate default mailbox with ours below even + when the default and special-use mailbox are identical. Choosing + either one is (currently) equal and setting trans->mailbox_identifier + for SPECIAL-USE needs to be done either way, so we use the same code + path. */ + + /* Try to open the mailbox */ + eenv->exec_status->last_storage = mailbox_get_storage(box); + if (mailbox_open(box) == 0) { + pool_t pool = sieve_result_pool(aenv->result); + + /* Success */ + mailbox_free(&trans->box); + trans->box = box; + trans->mailbox_identifier = p_strdup_printf(pool, + "[SPECIAL-USE %s]", ctx->special_use_flag); + + } else { + /* Failure */ + if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) { + /* Not found; revert to default */ + mailbox_free(&box); + } else { + /* Total failure */ + mailbox_free(&trans->box); + trans->box = box; + sieve_act_store_get_storage_error(aenv, trans); + return (trans->error_code == MAIL_ERROR_TEMP ? + SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE); + } + } + + return SIEVE_EXEC_OK; +} diff --git a/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c b/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c new file mode 100644 index 0000000000000000000000000000000000000000..fbed3058def0b81e20c0f81b7891c21bed84a205 --- /dev/null +++ b/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c @@ -0,0 +1,482 @@ +/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "str-sanitize.h" +#include "mail-storage.h" +#include "mail-namespace.h" + +#include "sieve-common.h" +#include "sieve-actions.h" +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-stringlist.h" +#include "sieve-code.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-dump.h" + +#include "ext-special-use-common.h" + +/* + * specialuse_exists command + * + * Syntax: + * specialuse_exists [<mailbox: string>] + * <special-use-flags: string-list> + */ + +static bool +tst_specialuse_exists_validate(struct sieve_validator *valdtr, + struct sieve_command *tst); +static bool +tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv, + struct sieve_command *ctx); + +const struct sieve_command_def specialuse_exists_test = { + .identifier = "specialuse_exists", + .type = SCT_TEST, + .positional_args = -1, /* We check positional arguments ourselves */ + .subtests = 0, + .block_allowed = FALSE, + .block_required = FALSE, + .validate = tst_specialuse_exists_validate, + .generate = tst_specialuse_exists_generate +}; + +/* + * Mailboxexists operation + */ + +static bool +tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv, + sieve_size_t *address); +static int +tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv, + sieve_size_t *address); + +const struct sieve_operation_def specialuse_exists_operation = { + .mnemonic = "SPECIALUSE_EXISTS", + .ext_def = &special_use_extension, + .dump = tst_specialuse_exists_operation_dump, + .execute = tst_specialuse_exists_operation_execute +}; + +/* + * Test validation + */ + +struct _validate_context { + struct sieve_validator *valdtr; + struct sieve_command *tst; +}; + +static int +tst_specialuse_exists_flag_validate(void *context, + struct sieve_ast_argument *arg) +{ + struct _validate_context *valctx = (struct _validate_context *)context; + + if (sieve_argument_is_string_literal(arg)) { + const char *flag = sieve_ast_argument_strc(arg); + + if (!ext_special_use_flag_valid(flag)) { + sieve_argument_validate_error( + valctx->valdtr, arg, "%s test: " + "invalid special-use flag `%s' specified", + sieve_command_identifier(valctx->tst), + str_sanitize(flag, 64)); + } + } + + return 1; +} + +static bool +tst_specialuse_exists_validate(struct sieve_validator *valdtr, + struct sieve_command *tst) +{ + struct sieve_ast_argument *arg = tst->first_positional; + struct sieve_ast_argument *arg2; + struct sieve_ast_argument *aarg; + struct _validate_context valctx; + + if (arg == NULL) { + sieve_command_validate_error( + valdtr, tst, "the %s %s expects at least one argument, " + "but none was found", + sieve_command_identifier(tst), + sieve_command_type_name(tst)); + return FALSE; + } + + if (sieve_ast_argument_type(arg) != SAAT_STRING && + sieve_ast_argument_type(arg) != SAAT_STRING_LIST) { + sieve_argument_validate_error( + valdtr, arg, + "the %s %s expects either a string (mailbox) or " + "a string-list (special-use flags) as first argument, " + "but %s was found", + sieve_command_identifier(tst), + sieve_command_type_name(tst), + sieve_ast_argument_name(arg)); + return FALSE; + } + + arg2 = sieve_ast_argument_next(arg); + if (arg2 != NULL) { + /* First, check syntax sanity */ + if (sieve_ast_argument_type(arg) != SAAT_STRING) { + sieve_argument_validate_error( + valdtr, arg, + "if a second argument is specified for the %s %s, the first " + "must be a string (mailbox), but %s was found", + sieve_command_identifier(tst), + sieve_command_type_name(tst), + sieve_ast_argument_name(arg)); + return FALSE; + } + if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE)) + return FALSE; + + if (sieve_ast_argument_type(arg2) != SAAT_STRING && + sieve_ast_argument_type(arg2) != SAAT_STRING_LIST) + { + sieve_argument_validate_error( + valdtr, arg2, + "the %s %s expects a string list (special-use flags) as " + "second argument when two arguments are specified, " + "but %s was found", + sieve_command_identifier(tst), + sieve_command_type_name(tst), + sieve_ast_argument_name(arg2)); + return FALSE; + } + } else + arg2 = arg; + + if (!sieve_validator_argument_activate(valdtr, tst, arg2, FALSE)) + return FALSE; + + aarg = arg2; + memset(&valctx, 0, sizeof(valctx)); + valctx.valdtr = valdtr; + valctx.tst = tst; + + return (sieve_ast_stringlist_map( + &aarg, (void*)&valctx, + tst_specialuse_exists_flag_validate) >= 0); +} + +/* + * Test generation + */ + +static bool +tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv, + struct sieve_command *tst) +{ + struct sieve_ast_argument *arg = tst->first_positional; + struct sieve_ast_argument *arg2; + + sieve_operation_emit(cgenv->sblock, + tst->ext, &specialuse_exists_operation); + + /* Generate arguments */ + arg2 = sieve_ast_argument_next(arg); + if (arg2 != NULL) { + if (!sieve_generate_argument(cgenv, arg, tst)) + return FALSE; + } else { + sieve_opr_omitted_emit(cgenv->sblock); + arg2 = arg; + } + return sieve_generate_argument(cgenv, arg2, tst); +} + +/* + * Code dump + */ + +static bool +tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv, + sieve_size_t *address) +{ + struct sieve_operand oprnd; + + sieve_code_dumpf(denv, "SPECIALUSE_EXISTS"); + sieve_code_descend(denv); + + sieve_code_mark(denv); + if (!sieve_operand_read(denv->sblock, address, NULL, &oprnd)) { + sieve_code_dumpf(denv, "ERROR: INVALID OPERAND"); + return FALSE; + } + + if (!sieve_operand_is_omitted(&oprnd)) { + return (sieve_opr_string_dump_data(denv, &oprnd, + address, "mailbox") && + sieve_opr_stringlist_dump(denv, address, + "special-use-flags")); + } + + return sieve_opr_stringlist_dump(denv, address, "special-use-flags"); +} + +/* + * Code execution + */ + +static int +tst_specialuse_find_mailbox(const struct sieve_runtime_env *renv, + const char *mailbox, struct mailbox **box_r) +{ + const struct sieve_execute_env *eenv = renv->exec_env; + struct mail_user *user = eenv->scriptenv->user; + struct mailbox *box; + bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING); + enum mail_error error_code; + const char *error; + + *box_r = NULL; + + if (user == NULL) + return 0; + + /* Open the box */ + box = mailbox_alloc_for_user(user, mailbox, MAILBOX_FLAG_POST_SESSION); + if (mailbox_open(box) < 0) { + error = mailbox_get_last_error(box, &error_code); + + if (trace) { + sieve_runtime_trace( + renv, 0, "mailbox `%s' cannot be opened: %s", + str_sanitize(mailbox, 256), error); + } + + mailbox_free(&box); + + if (error_code == MAIL_ERROR_TEMP) { + sieve_runtime_error( + renv, NULL, "specialuse_exists test: " + "failed to open mailbox `%s': %s", + str_sanitize(mailbox, 256), error); + return -1; + } + return 0; + } + + /* Also fail when it is readonly */ + if (mailbox_is_readonly(box)) { + if (trace) { + sieve_runtime_trace( + renv, 0, "mailbox `%s' is read-only", + str_sanitize(mailbox, 256)); + } + + mailbox_free(&box); + return 0; + } + + *box_r = box; + return 1; +} + +static int +tst_specialuse_find_specialuse(const struct sieve_runtime_env *renv, + const char *special_use) +{ + const struct sieve_execute_env *eenv = renv->exec_env; + struct mail_user *user = eenv->scriptenv->user; + struct mailbox *box; + bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING); + enum mail_error error_code; + const char *error; + + if (user == NULL) + return 0; + + /* Open the box */ + box = mailbox_alloc_for_user(user, special_use, + (MAILBOX_FLAG_POST_SESSION | + MAILBOX_FLAG_SPECIAL_USE)); + if (mailbox_open(box) < 0) { + error = mailbox_get_last_error(box, &error_code); + + if (trace) { + sieve_runtime_trace( + renv, 0, "mailbox with special-use flag `%s' " + "cannot be opened: %s", + str_sanitize(special_use, 64), error); + } + + mailbox_free(&box); + + if (error_code == MAIL_ERROR_TEMP) { + sieve_runtime_error( + renv, NULL, "specialuse_exists test: " + "failed to open mailbox with special-use flag`%s': %s", + str_sanitize(special_use, 64), error); + return -1; + } + return 0; + } + + /* Also fail when it is readonly */ + if (mailbox_is_readonly(box)) { + if (trace) { + sieve_runtime_trace( + renv, 0, + "mailbox with special-use flag `%s' is read-only", + str_sanitize(special_use, 64)); + } + + mailbox_free(&box); + return 0; + } + + mailbox_free(&box); + return 1; +} + +static int +tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv, + sieve_size_t *address) +{ + struct sieve_operand oprnd; + struct sieve_stringlist *special_use_flags; + string_t *mailbox; + struct mailbox *box = NULL; + bool trace = FALSE, all_exist = TRUE; + int ret; + + /* + * Read operands + */ + + /* Read bare operand (two types possible) */ + if ((ret = sieve_operand_runtime_read(renv, address, + NULL, &oprnd)) <= 0) + return ret; + + /* Mailbox operand (optional) */ + mailbox = NULL; + if (!sieve_operand_is_omitted(&oprnd)) { + /* Read the mailbox operand */ + if ((ret = sieve_opr_string_read_data( + renv, &oprnd, address, "mailbox", &mailbox)) <= 0) + return ret; + + /* Read flag list */ + if ((ret = sieve_opr_stringlist_read( + renv, address, "special-use-flags", + &special_use_flags)) <= 0) + return ret; + + /* Flag-list operand */ + } else { + /* Read flag list */ + if ((ret = sieve_opr_stringlist_read( + renv, address, "special-use-flags", + &special_use_flags)) <= 0) + return ret; + } + + /* + * Perform operation + */ + + if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) { + sieve_runtime_trace(renv, 0, "specialuse_exists test"); + sieve_runtime_trace_descend(renv); + + trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING); + } + + if (mailbox != NULL) { + if (tst_specialuse_find_mailbox(renv, str_c(mailbox), &box) < 0) + return SIEVE_EXEC_TEMP_FAILURE; + } + + ret = 0; + if (box == NULL && mailbox != NULL) { + all_exist = FALSE; + sieve_runtime_trace(renv, 0, + "mailbox `%s' is not accessible", + str_sanitize(str_c(mailbox), 80)); + } else { + string_t *special_use_flag; + + if (mailbox != NULL) { + sieve_runtime_trace( + renv, 0, "mailbox `%s' is accessible", + str_sanitize(str_c(mailbox), 80)); + } + + special_use_flag = NULL; + while ((ret = sieve_stringlist_next_item( + special_use_flags, &special_use_flag)) > 0) { + const char *use_flag = str_c(special_use_flag); + + if (!ext_special_use_flag_valid(use_flag)) { + sieve_runtime_error( + renv, NULL, "specialuse_exists test: " + "invalid special-use flag `%s' specified", + str_sanitize(use_flag, 64)); + if (box != NULL) { + /* Close mailbox */ + mailbox_free(&box); + } + return SIEVE_EXEC_FAILURE; + } + + if (box != NULL) { + /* Mailbox has this SPECIAL-USE flag? */ + if (!mailbox_has_special_use(box, use_flag)) { + all_exist = FALSE; + break; + } + } else { + /* Is there mailbox with this SPECIAL-USE flag? */ + if ((ret = tst_specialuse_find_specialuse( + renv, use_flag)) <= 0) { + if (ret < 0) + return SIEVE_EXEC_TEMP_FAILURE; + all_exist = FALSE; + break; + } + } + + if (trace) { + sieve_runtime_trace( + renv, 0, "special-use flag `%s' exists", + str_sanitize(use_flag, 80)); + } + } + } + + if (box != NULL) { + /* Close mailbox */ + mailbox_free(&box); + } + + if (ret < 0) { + sieve_runtime_trace_error( + renv, "invalid special-use flag item"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + if (trace) { + if (all_exist) { + sieve_runtime_trace( + renv, 0, "all special-use flags are set"); + } else { + sieve_runtime_trace( + renv, 0, "some special-use are not set"); + } + } + + sieve_interpreter_set_test_result(renv->interp, all_exist); + return SIEVE_EXEC_OK; +} diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c index 398e616261bd7c0c5adb5fa53bb9150068f2f69f..a1cb810560838277908f51c3e4a3e2a3155d9dd3 100644 --- a/src/lib-sieve/sieve-extensions.c +++ b/src/lib-sieve/sieve-extensions.c @@ -141,6 +141,7 @@ extern const struct sieve_extension_def spamtest_extension; extern const struct sieve_extension_def spamtestplus_extension; extern const struct sieve_extension_def virustest_extension; extern const struct sieve_extension_def editheader_extension; +extern const struct sieve_extension_def special_use_extension; extern const struct sieve_extension_def vnd_debug_extension; extern const struct sieve_extension_def vnd_environment_extension; @@ -150,6 +151,7 @@ const struct sieve_extension_def *sieve_extra_extensions[] = { &vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension, &virustest_extension, &editheader_extension, &mboxmetadata_extension, &servermetadata_extension, + &special_use_extension, /* vnd.dovecot. */ &vnd_debug_extension, &vnd_environment_extension, &vnd_report_extension diff --git a/tests/extensions/special-use/errors.svtest b/tests/extensions/special-use/errors.svtest new file mode 100644 index 0000000000000000000000000000000000000000..a0148a0ce1e7b36a01ef3d901614e45f73346cc5 --- /dev/null +++ b/tests/extensions/special-use/errors.svtest @@ -0,0 +1,20 @@ +require "vnd.dovecot.testsuite"; + +require "relational"; +require "comparator-i;ascii-numeric"; + +/* + * Invalid syntax + */ + +test "Invalid Syntax" { + if test_script_compile "errors/syntax.sieve" { + test_fail "compile should have failed"; + } + + if not test_error :count "eq" :comparator "i;ascii-numeric" "14" { + test_fail "wrong number of errors reported"; + } +} + + diff --git a/tests/extensions/special-use/errors/syntax.sieve b/tests/extensions/special-use/errors/syntax.sieve new file mode 100644 index 0000000000000000000000000000000000000000..fc28010096fc764c5c6a3167ed227849ae903106 --- /dev/null +++ b/tests/extensions/special-use/errors/syntax.sieve @@ -0,0 +1,32 @@ +require "special-use"; +require "fileinto"; + +# 1 +if specialuse_exists {} +# 2 +if specialuse_exists 3423 {} +# 3 +if specialuse_exists :frop {} +# 4 +if specialuse_exists 24234 "\\Sent" {} +# 5 +if specialuse_exists "frop" 32234 {} +# 6 +if specialuse_exists "frop" :friep {} + +# 7 +if specialuse_exists "frop" {} +# 8 +if specialuse_exists "frop" ["frop"] {} + +# 9 +fileinto :specialuse "\\frop"; +# 10 +fileinto :specialuse 343 "\\frop"; +# 11 +fileinto :specialuse :create "\\frop"; +# 12 +fileinto :specialuse "\\frop" 234234; + +# 13 +fileinto :specialuse "frop" "frop"; diff --git a/tests/extensions/special-use/execute.svtest b/tests/extensions/special-use/execute.svtest new file mode 100644 index 0000000000000000000000000000000000000000..746f1d3e6f33410330b83eca9b86f936a65ef44a --- /dev/null +++ b/tests/extensions/special-use/execute.svtest @@ -0,0 +1,35 @@ +require "vnd.dovecot.testsuite"; +require "special-use"; +require "fileinto"; + +test "Specialuse_exists - None exist" { + if specialuse_exists "\\Sent" { + test_fail "specialuse_exists confirms existence of unassigned special-use flag"; + } +} + +test "Specialuse_exists <MAILBOX> - None exist" { + if specialuse_exists "INBOX" "\\Sent" { + test_fail "specialuse_exists confirms existence of unassigned special-use flag"; + } +} + +test_mailbox_create "frop"; +test_mailbox_create "friep"; + +test ":specialuse" { + test_set "message" text: +From: stephan@example.org +To: nico@frop.example.org +Subject: Frop 1 + +Frop! +. + ; + + fileinto :specialuse "\\Junk" "frop"; + + if not test_result_execute { + test_fail "execution of result failed"; + } +}