diff --git a/TODO b/TODO index 853af4f271ec9c838d4db2e4a4b0f19f8f33ce76..4c81eb74f9572e2578e2fae5a9bf6b639d350518 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,8 @@ Next (in order of descending priority/precedence): * Full standards compliance review for the engine and all fully implemented sieve extensions. Issues discovered so far: + - Body: contains various bugs that need to be resolved for standards + compliance. - Include: import command must only trigger an error about unknown exported variables at runtime, otherwise managesieve upload is impossible. diff --git a/src/lib-sieve/plugins/body/tst-body.c b/src/lib-sieve/plugins/body/tst-body.c index b74107da1adec13c764c4130caa6966618e9e74a..1d58e30f1de0fa0313bab4c0be0c1322f2d01c82 100644 --- a/src/lib-sieve/plugins/body/tst-body.c +++ b/src/lib-sieve/plugins/body/tst-body.c @@ -305,7 +305,7 @@ static int ext_body_operation_execute int ret = SIEVE_EXEC_OK; int opt_code = 0; int mret; - const struct sieve_comparator *cmp = &i_octet_comparator; + const struct sieve_comparator *cmp = &i_ascii_casemap_comparator; const struct sieve_match_type *mtch = &is_match_type; enum tst_body_transform transform; struct sieve_coded_stringlist *key_list, *ctype_list = NULL; diff --git a/src/lib-sieve/plugins/include/cmd-include.c b/src/lib-sieve/plugins/include/cmd-include.c index 37bc997c1e83ccca8c9e4da98245aa7fb256ca1e..5500578d2aa0d80c3ceafadcd2cfffad169cb852 100644 --- a/src/lib-sieve/plugins/include/cmd-include.c +++ b/src/lib-sieve/plugins/include/cmd-include.c @@ -111,8 +111,7 @@ static bool cmd_include_validate_location_tag if ( ctx_data->location_assigned) { sieve_command_validate_error(validator, cmd, - "cannot use location tags ':personal' and ':global' multiple times " - "for the include command"); + "include: cannot use location tags ':personal' and ':global' multiple times"); return FALSE; } @@ -181,12 +180,19 @@ static bool cmd_include_validate(struct sieve_validator *validator, /* Find the script */ script_name = sieve_ast_argument_strc(arg); + + if ( strchr(script_name, '/') != NULL ) { + sieve_command_validate_error(validator, cmd, + "include: '/' not allowed in script name (%s)", + str_sanitize(script_name, 80)); + return FALSE; + } script_dir = ext_include_get_script_directory (ctx_data->location, script_name); if ( script_dir == NULL ) { - sieve_command_validate_error(validator, cmd, - "specified location for included script '%s' is unavalable " + sieve_command_validate_error(validator, cmd, + "include: specified location for included script '%s' is unavailable " "(system logs should provide more information)", str_sanitize(script_name, 80)); return FALSE; diff --git a/src/lib-sieve/sieve-message.c b/src/lib-sieve/sieve-message.c index bc623222f3c7a893f63e0fa84410c55a9c6f7872..810c9c74f00df6e48fbbed551fcae01321cf2296 100644 --- a/src/lib-sieve/sieve-message.c +++ b/src/lib-sieve/sieve-message.c @@ -20,16 +20,13 @@ struct sieve_message_context { struct sieve_message_context *sieve_message_context_create(void) { - pool_t pool; struct sieve_message_context *msgctx; - pool = pool_alloconly_create("sieve_message_context", 1024); - msgctx = p_new(pool, struct sieve_message_context, 1); - msgctx->pool = pool; + msgctx = i_new(struct sieve_message_context, 1); msgctx->refcount = 1; - - p_array_init(&msgctx->ext_contexts, pool, 4); - + + sieve_message_context_flush(msgctx); + return msgctx; } @@ -46,10 +43,25 @@ void sieve_message_context_unref(struct sieve_message_context **msgctx) return; pool_unref(&((*msgctx)->pool)); - + + i_free(*msgctx); *msgctx = NULL; } +void sieve_message_context_flush(struct sieve_message_context *msgctx) +{ + pool_t pool; + + if ( msgctx->pool != NULL ) { + pool_unref(&msgctx->pool); + } + + pool = pool_alloconly_create("sieve_message_context", 1024); + msgctx->pool = pool; + + p_array_init(&msgctx->ext_contexts, pool, sieve_extensions_get_count()); +} + void sieve_message_context_extension_set (struct sieve_message_context *msgctx, const struct sieve_extension *ext, void *context) diff --git a/src/lib-sieve/sieve-message.h b/src/lib-sieve/sieve-message.h index 4e6e848bf56c1476de21ab4a0ab702fd27c6d934..dff96e8d058738eafc0ac36a9449deb65eccd97c 100644 --- a/src/lib-sieve/sieve-message.h +++ b/src/lib-sieve/sieve-message.h @@ -9,6 +9,8 @@ struct sieve_message_context *sieve_message_context_create(void); void sieve_message_context_ref(struct sieve_message_context *msgctx); void sieve_message_context_unref(struct sieve_message_context **msgctx); +void sieve_message_context_flush(struct sieve_message_context *msgctx); + void sieve_message_context_extension_set (struct sieve_message_context *msgctx, const struct sieve_extension *ext, void *context); diff --git a/src/lib-sieve/sieve-script.c b/src/lib-sieve/sieve-script.c index f949155404698dae5af6d337da40e9a23b8b55fc..89d1cb986ddd14b1a3b565d577854a4f3d5ac084 100644 --- a/src/lib-sieve/sieve-script.c +++ b/src/lib-sieve/sieve-script.c @@ -49,7 +49,7 @@ struct sieve_script *sieve_script_init const char *filename, *dirpath, *basename; if ( exists_r != NULL ) - *exists_r = FALSE; + *exists_r = TRUE; T_BEGIN { /* Extract filename from path */ @@ -71,9 +71,11 @@ struct sieve_script *sieve_script_init /* First obtain stat data from the system */ if ( (ret=lstat(path, &st)) < 0 && (errno != ENOENT || exists_r == NULL) ) { - if ( errno == ENOENT ) + if ( errno == ENOENT ) { sieve_error(ehandler, basename, "sieve script does not exist"); - else + if ( exists_r != NULL ) + *exists_r = FALSE; + } else sieve_critical(ehandler, basename, "failed to lstat sieve script file '%s': %m", path); script = NULL; ret = 1; @@ -84,9 +86,11 @@ struct sieve_script *sieve_script_init /* Only create/init the object if it stat()s without problems */ if (S_ISLNK(st.st_mode)) { if ( (ret=stat(path, &st)) < 0 && (errno != ENOENT || exists_r == NULL) ) { - if ( errno == ENOENT ) + if ( errno == ENOENT ) { sieve_error(ehandler, basename, "sieve script does not exist"); - else + if ( exists_r != NULL ) + *exists_r = FALSE; + } else sieve_critical(ehandler, basename, "failed to stat sieve script file '%s': %m", path); script = NULL; @@ -102,9 +106,6 @@ struct sieve_script *sieve_script_init } if ( ret <= 0 ) { - if ( exists_r != NULL ) - *exists_r = ( ret == 0 ); - if ( script == NULL ) { pool = pool_alloconly_create("sieve_script", 1024); script = p_new(pool, struct sieve_script, 1); diff --git a/src/lib-sieve/sieve-script.h b/src/lib-sieve/sieve-script.h index 78c37e34df116f28d1fc1cf57c0a2d88daafb1d4..c484c46cd82086d8536deef3f45a18bde2e28ed9 100644 --- a/src/lib-sieve/sieve-script.h +++ b/src/lib-sieve/sieve-script.h @@ -16,7 +16,7 @@ struct sieve_script *sieve_script_create_in_directory void sieve_script_ref(struct sieve_script *script); void sieve_script_unref(struct sieve_script **script); -/* Stream manageement */ +/* Stream management */ struct istream *sieve_script_open(struct sieve_script *script, bool *deleted_r); void sieve_script_close(struct sieve_script *script); diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c index 7e330420619594282a2045d4337ba09c7db24b8d..8b46281551896d21a828288e5b9c37e52acaba9f 100644 --- a/src/lib-sieve/sieve.c +++ b/src/lib-sieve/sieve.c @@ -156,14 +156,14 @@ struct sieve_binary *sieve_compile */ struct sieve_binary *sieve_open -(const char *script_path, struct sieve_error_handler *ehandler) +(const char *script_path, struct sieve_error_handler *ehandler, bool *exists_r) { struct sieve_script *script; struct sieve_binary *sbin; const char *binpath; /* First open the scriptfile itself */ - script = sieve_script_create(script_path, NULL, ehandler, NULL); + script = sieve_script_create(script_path, NULL, ehandler, exists_r); if ( script == NULL ) { /* Failed */ diff --git a/src/lib-sieve/sieve.h b/src/lib-sieve/sieve.h index 819ad61235e86862c686680c3b2280e676db6503..84fdb1271251db092390436d11ef46f5744b0eb2 100644 --- a/src/lib-sieve/sieve.h +++ b/src/lib-sieve/sieve.h @@ -61,7 +61,8 @@ struct sieve_binary *sieve_compile * */ struct sieve_binary *sieve_open - (const char *scriptpath, struct sieve_error_handler *ehandler); + (const char *scriptpath, struct sieve_error_handler *ehandler, + bool *exists_r); /* sieve_save: * Saves the binary as the file indicated by the path parameter. diff --git a/src/plugins/lda-sieve/lda-sieve-plugin.c b/src/plugins/lda-sieve/lda-sieve-plugin.c index 6a74edb02144003abd923d91385aac6942b8e98b..85e0326747258c6488b614d4b999b0489746ba16 100644 --- a/src/plugins/lda-sieve/lda-sieve-plugin.c +++ b/src/plugins/lda-sieve/lda-sieve-plugin.c @@ -89,6 +89,7 @@ static int lda_sieve_run struct sieve_error_handler *ehandler; struct sieve_binary *sbin; const char *scriptlog; + bool exists = TRUE; int ret = 0; /* Create error handler */ @@ -100,12 +101,19 @@ static int lda_sieve_run if ( debug ) sieve_sys_info("opening script %s", script_path); - if ( (sbin=sieve_open(script_path, ehandler)) == NULL ) { - sieve_sys_error("failed to open script %s; " - "log should be available as %s", script_path, scriptlog); + if ( (sbin=sieve_open(script_path, ehandler, &exists)) == NULL ) { + + ret = sieve_get_errors(ehandler) > 0 ? -1 : 0; + + if ( debug ) { + if ( !exists && ret == 0 ) + sieve_sys_info("script file %s is missing; ending sieve processing", script_path); + else + sieve_sys_info("failed to openscript file %s; ending sieve processing", script_path); + } sieve_error_handler_unref(&ehandler); - return -1; + return ret; } /* Log the messages to the system error handlers as well from this moment @@ -179,6 +187,7 @@ static int lda_sieve_run case SIEVE_EXEC_FAILURE: sieve_sys_error("execution of script %s failed, but implicit keep was successful", script_path); + ret = SIEVE_EXEC_OK; break; case SIEVE_EXEC_BIN_CORRUPT: sieve_sys_error("!!BUG!!: binary compiled from %s is still corrupt; bailing out", diff --git a/src/sieve-bin/bin-common.c b/src/sieve-bin/bin-common.c index cfcf717bcbab57680816ee8d65d61dd3d36139ef..47f9622b485870bda0891453e8b7d601ff1deca7 100644 --- a/src/sieve-bin/bin-common.c +++ b/src/sieve-bin/bin-common.c @@ -93,7 +93,7 @@ struct sieve_binary *bin_open_sieve_script(const char *filename) ehandler = sieve_stderr_ehandler_create(0); sieve_error_handler_accept_infolog(ehandler, TRUE); - if ( (sbin = sieve_open(filename, ehandler)) == NULL ) { + if ( (sbin = sieve_open(filename, ehandler, NULL)) == NULL ) { sieve_error_handler_unref(&ehandler); i_fatal("Failed to compile sieve script\n"); } diff --git a/src/testsuite/cmd-test-set.c b/src/testsuite/cmd-test-set.c index a6abbcb8d10315b584cc4c510dcc347055c9ef09..63c8f96353374523c94fce0694348f34d724a60f 100644 --- a/src/testsuite/cmd-test-set.c +++ b/src/testsuite/cmd-test-set.c @@ -149,7 +149,7 @@ static int cmd_test_set_operation_execute return SIEVE_EXEC_FAILURE; } - object->set_member(member_id, value); + object->set_member(renv, member_id, value); return SIEVE_EXEC_OK; } diff --git a/src/testsuite/testsuite-common.c b/src/testsuite/testsuite-common.c index 297ebf6e5c5df7fb4b608cf2506a8dbc22be295e..bd1331944aa8bf87c329833823eaf1a131ea555f 100644 --- a/src/testsuite/testsuite-common.c +++ b/src/testsuite/testsuite-common.c @@ -11,10 +11,12 @@ #include "sieve.h" #include "sieve-error-private.h" #include "sieve-code.h" +#include "sieve-message.h" #include "sieve-commands.h" #include "sieve-extensions.h" #include "sieve-validator.h" #include "sieve-generator.h" +#include "sieve-interpreter.h" #include "sieve-dump.h" #include "testsuite-objects.h" @@ -114,11 +116,14 @@ void testsuite_message_init(pool_t namespaces_pool, const char *user) envelope_auth = str_new(message_pool, 256); } -void testsuite_message_set(string_t *message) +void testsuite_message_set +(const struct sieve_runtime_env *renv, string_t *message) { mail_raw_close(_raw_message); _testsuite_message_set(message); + + sieve_message_context_flush(renv->msgctx); } void testsuite_message_deinit(void) diff --git a/src/testsuite/testsuite-common.h b/src/testsuite/testsuite-common.h index 5385b26c894c484e2ff42cb1c4dc05ac4a207fdd..8bf37ab4752126bb325401ddc066ef86491e3aba 100644 --- a/src/testsuite/testsuite-common.h +++ b/src/testsuite/testsuite-common.h @@ -12,7 +12,8 @@ extern struct sieve_message_data testsuite_msgdata; void testsuite_message_init(pool_t namespaces_pool, const char *user); void testsuite_message_deinit(void); -void testsuite_message_set(string_t *message); +void testsuite_message_set + (const struct sieve_runtime_env *renv, string_t *message); void testsuite_envelope_set_sender(const char *value); void testsuite_envelope_set_recipient(const char *value); diff --git a/src/testsuite/testsuite-objects.c b/src/testsuite/testsuite-objects.c index 88142303a63e63c432f3e053887d34fd4b90d863..4e002603fec2cf02e782d8de765e4371d6668bbb 100644 --- a/src/testsuite/testsuite-objects.c +++ b/src/testsuite/testsuite-objects.c @@ -249,11 +249,13 @@ static bool arg_testsuite_object_generate * Testsuite core object implementation */ -static bool tsto_message_set_member(int id, string_t *value); +static bool tsto_message_set_member + (const struct sieve_runtime_env *renv, int id, string_t *value); static int tsto_envelope_get_member_id(const char *identifier); static const char *tsto_envelope_get_member_name(int id); -static bool tsto_envelope_set_member(int id, string_t *value); +static bool tsto_envelope_set_member + (const struct sieve_runtime_env *renv, int id, string_t *value); const struct testsuite_object message_testsuite_object = { SIEVE_OBJECT("message", &testsuite_object_operand, TESTSUITE_OBJECT_MESSAGE), @@ -276,11 +278,12 @@ enum testsuite_object_envelope_field { TESTSUITE_OBJECT_ENVELOPE_AUTH_USER }; -static bool tsto_message_set_member(int id, string_t *value) +static bool tsto_message_set_member +(const struct sieve_runtime_env *renv, int id, string_t *value) { if ( id != -1 ) return FALSE; - testsuite_message_set(value); + testsuite_message_set(renv, value); return TRUE; } @@ -311,7 +314,8 @@ static const char *tsto_envelope_get_member_name(int id) return NULL; } -static bool tsto_envelope_set_member(int id, string_t *value) +static bool tsto_envelope_set_member +(const struct sieve_runtime_env *renv ATTR_UNUSED, int id, string_t *value) { switch ( id ) { case TESTSUITE_OBJECT_ENVELOPE_FROM: diff --git a/src/testsuite/testsuite-objects.h b/src/testsuite/testsuite-objects.h index 999a0c7fc6619e9e7f725f66d860923ebfdf2aa2..0512ea370c2037d5089f1c0554108dac4b6a84fb 100644 --- a/src/testsuite/testsuite-objects.h +++ b/src/testsuite/testsuite-objects.h @@ -21,8 +21,9 @@ struct testsuite_object { int (*get_member_id)(const char *identifier); const char *(*get_member_name)(int id); - bool (*set_member)(int id, string_t *value); - string_t *(*get_member)(int id); + + bool (*set_member)(const struct sieve_runtime_env *renv, int id, string_t *value); + string_t *(*get_member)(const struct sieve_runtime_env *renv, int id); }; /* Testsuite object registration */ diff --git a/tests/extensions/body/basic.svtest b/tests/extensions/body/basic.svtest index 786ab5b2a59120b0cad93cd00ef1f619e10a4e45..042b88a9671a6a024012098fc55a1a957d86f406 100644 --- a/tests/extensions/body/basic.svtest +++ b/tests/extensions/body/basic.svtest @@ -1,4 +1,6 @@ require "vnd.dovecot.testsuite"; +require "relational"; +require "comparator-i;ascii-numeric"; require "body"; @@ -12,6 +14,14 @@ Test! . ; +/* Empty line + * + * RFC 5173: + * 'The body test matches content in the body of an email message, that + * is, anything following the first empty line after the header. (The + * empty line itself, if present, is not considered to be part of the + * body.)' + */ test "The empty line" { if not body :raw :is text: @@ -36,3 +46,143 @@ Test! test_fail "body test matches nonsense"; } } + +/* Default comparator and match type + * + * RFC 5173: + * 'The COMPARATOR and MATCH-TYPE keyword parameters are defined in + * [SIEVE]. As specified in Sections 2.7.1 and 2.7.3 of [SIEVE], the + * default COMPARATOR is "i;ascii-casemap" and the default MATCH-TYPE is + * ":is".' + */ + +test "Defaults" { + if anyof ( body :raw "Test", body :raw "*Test*" ) { + test_fail "default match type is not :is as is required"; + } + + if allof( not body :raw :contains "tesT", body :raw :contains "Test" ) { + test_fail "default comparator is not i;ascii-casemap as is required"; + } +} + +/* No body + * + * RFC 5173: + * 'If a message consists of a header only, not followed by an empty line, + * then that set is empty and all "body" tests return false, including + * those that test for an empty string. (This is similar to how the + * "header" test always fails when the named header fields aren't present.)' + */ + +test_set "message" text: +From: stephan@rename-it.nl +To: tss@iki.fi +Subject: No body is here! +. +; + +test "No body" { + if body :raw :contains "" { + test_fail "matched against non-existant body (:contains \"\")"; + } + + if body :raw :is "" { + test_fail "matched against non-existant body (:is \"\")"; + } + + if body :raw :matches "*" { + test_fail "matched against non-existant body (:matches \"*\")"; + } +} + +/* + * + */ + +test_set "message" text: +From: Whomever <whomever@domain.dom> +To: Someone <someone@domain.com> +Date: Fri, 08 Aug 2008 10:14:34 -0700 +Subject: whatever +Content-Type: multipart/mixed; boundary=outer + +This is a multi-part message in MIME format. + +--outer +Content-Type: multipart/alternative; boundary=inner + +This is a nested multi-part message in MIME format. + +--inner +Content-Type: text/plain; charset="us-ascii" + +Hello + +--inner +Content-Type: text/html; charset="us-ascii" + +<html><body>Hello</body></html> + +--inner-- + +This is the end of the inner MIME multipart. + +--outer +Content-Type: message/rfc822 +From: Someone Else <someone.else@domain.dom> +Subject: hello request + +Please say Hello + +--outer-- + +This is the end of the outer MIME multipart. +. +; + +test "RFC nested example - :content \"text\"" { + if not body :content "text" :contains "html" { + test_fail "failed to acquire nested MIME body part (1)"; + } + + if not body :content "text/html" :contains "hello" { + test_fail "failed to acquire nested MIME body part (2)"; + } + + if not body :content "text/plain" :contains "hello" { + test_fail "failed to acquire nested MIME body part (3)"; + } + + if not body :content "text" :contains "hello" { + test_fail "failed to acquire nested MIME body part (4)"; + } + +/* FIXME: fails + if not body :content "text" :count "eq" :comparator "i;ascii-numeric" "2" { + test_fail "matched wrong number of \"text/*\" body parts"; + }*/ +} + +/* FIXME: fails +test "RFC nested example - :content \"multipart\"" { + if not body :content "multipart" :contains + "This is a multi-part message in MIME format" { + test_fail "missed first multipart body part"; + } + + if not body :content "multipart" :contains + "This is a nested multi-part message in MIME format" { + test_fail "missed second multipart body part"; + } + + if not body :content "multipart" :contains + "This is the end of the inner MIME multipart" { + test_fail "missed third multipart body part"; + } + + if not body :content "multipart" :contains + "This is the end of the outer MIME multipart." { + test_fail "missed fourth multipart body part"; + } +}*/ diff --git a/tests/extensions/include/errors.svtest b/tests/extensions/include/errors.svtest index de8d22b7eca7193f1ebe244a0399fd8fda613d63..964e1f2302be5e3dfd7ce5e054dd1cde8e2b5425 100644 --- a/tests/extensions/include/errors.svtest +++ b/tests/extensions/include/errors.svtest @@ -12,7 +12,7 @@ test "Generic" { test_fail "compile should have failed"; } - if not test_error :count "eq" :comparator "i;ascii-numeric" "2" { + if not test_error :count "eq" :comparator "i;ascii-numeric" "3" { test_fail "wrong number of errors reported"; } } diff --git a/tests/extensions/include/errors/generic.sieve b/tests/extensions/include/errors/generic.sieve index 48c8006eba4efe3e5ed492a948b894a20f9901ea..56d0f97da3d291caa898d1fffc13529c490433ea 100644 --- a/tests/extensions/include/errors/generic.sieve +++ b/tests/extensions/include/errors/generic.sieve @@ -2,3 +2,6 @@ require "include"; # Non-existant sieve script include "frop.sieve"; + +# Use of / in script names +include "../frop.sieve";