diff --git a/Makefile.am b/Makefile.am index a1e1eb6e7ff06079fd21e86ed6868cf59856676d..a3daf563fa0291fb7d2e7f9dfb465ebf6ee5e9aa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,6 +130,7 @@ test_cases = \ tests/extensions/editheader/deleteheader.svtest \ tests/extensions/editheader/alternating.svtest \ tests/extensions/editheader/utf8.svtest \ + tests/extensions/editheader/protected.svtest \ tests/extensions/editheader/errors.svtest \ tests/extensions/vnd.dovecot/debug/execute.svtest \ tests/deprecated/notify/basic.svtest \ diff --git a/TODO b/TODO index 42517f433a0759e21cce43b8d91465e73e35682e..f32a61568806ba7892df357154369bc4ee6ae7e1 100644 --- a/TODO +++ b/TODO @@ -2,8 +2,6 @@ Current activities: * Implement editheader extension - Implement configurable limit on header value length - - Implement configurable list of protected headers, with Received: and - Auto-Submitted: headers always protected. - Add command syntax checks to the test suite. Parallel plugin-based efforts: diff --git a/src/lib-sieve/plugins/editheader/Makefile.am b/src/lib-sieve/plugins/editheader/Makefile.am index 25ca1c9940b5745ae6c69a83cc4c5965be4fda55..78c2fd449a9c71390e468cf9b9b6bbec50c45159 100644 --- a/src/lib-sieve/plugins/editheader/Makefile.am +++ b/src/lib-sieve/plugins/editheader/Makefile.am @@ -10,7 +10,8 @@ commands = \ libsieve_ext_editheader_la_SOURCES = \ $(commands) \ - ext-editheader.c + ext-editheader.c \ + ext-editheader-common.c noinst_HEADERS = \ ext-editheader-common.h diff --git a/src/lib-sieve/plugins/editheader/cmd-addheader.c b/src/lib-sieve/plugins/editheader/cmd-addheader.c index 8625684f076be298004f9362b6bcd3bd5f87ba7f..f7b001da374b624e139e3fcae3f5aebf8c90d1e5 100644 --- a/src/lib-sieve/plugins/editheader/cmd-addheader.c +++ b/src/lib-sieve/plugins/editheader/cmd-addheader.c @@ -88,18 +88,18 @@ const struct sieve_operation_def addheader_operation = { */ static bool cmd_addheader_validate -(struct sieve_validator *valdtr, struct sieve_command *tst) +(struct sieve_validator *valdtr, struct sieve_command *cmd) { - struct sieve_ast_argument *arg = tst->first_positional; + struct sieve_ast_argument *arg = cmd->first_positional; /* Check field-name syntax */ if ( !sieve_validate_positional_argument - (valdtr, tst, arg, "field-name", 1, SAAT_STRING) ) { + (valdtr, cmd, arg, "field-name", 1, SAAT_STRING) ) { return FALSE; } - if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) ) + if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) ) return FALSE; if ( sieve_argument_is_string_literal(arg) ) { @@ -111,6 +111,12 @@ static bool cmd_addheader_validate str_sanitize(str_c(fname), 80)); return FALSE; } + + if ( ext_editheader_header_is_protected(cmd->ext, str_c(fname)) ) { + sieve_argument_validate_warning(valdtr, arg, "addheader command: " + "specified header field `%s' is protected " + "(modification will be denied)", str_sanitize(str_c(fname), 80)); + } } /* Check value syntax */ @@ -118,11 +124,11 @@ static bool cmd_addheader_validate arg = sieve_ast_argument_next(arg); if ( !sieve_validate_positional_argument - (valdtr, tst, arg, "value", 2, SAAT_STRING) ) { + (valdtr, cmd, arg, "value", 2, SAAT_STRING) ) { return FALSE; } - if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) ) + if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) ) return FALSE; if ( sieve_argument_is_string_literal(arg) ) { @@ -207,6 +213,7 @@ static bool cmd_addheader_operation_dump static int cmd_addheader_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address) { + const struct sieve_extension *this_ext = renv->oprtn->ext; string_t *field_name; string_t *value; struct edit_mail *edmail; @@ -259,7 +266,14 @@ static int cmd_addheader_operation_execute str_sanitize(str_c(field_name), 80)); return SIEVE_EXEC_FAILURE; } - + + if ( ext_editheader_header_is_protected(this_ext, str_c(field_name)) ) { + sieve_runtime_warning(renv, NULL, "addheader action: " + "specified header field `%s' is protected (modification denied)", + str_sanitize(str_c(field_name), 80)); + return SIEVE_EXEC_OK; + } + if ( !rfc2822_header_field_body_verify (str_c(value), str_len(value), TRUE, TRUE) ) { sieve_runtime_error(renv, NULL, "addheader action: " diff --git a/src/lib-sieve/plugins/editheader/cmd-deleteheader.c b/src/lib-sieve/plugins/editheader/cmd-deleteheader.c index fa59314a59e548dfd415c391fa61f3a78dbef669..8f2bdccd222f8d9be33a89e8f6524cac6f288e9a 100644 --- a/src/lib-sieve/plugins/editheader/cmd-deleteheader.c +++ b/src/lib-sieve/plugins/editheader/cmd-deleteheader.c @@ -258,6 +258,12 @@ static bool cmd_deleteheader_validate str_sanitize(str_c(fname), 80)); return FALSE; } + + if ( ext_editheader_header_is_protected(cmd->ext, str_c(fname)) ) { + sieve_argument_validate_warning(valdtr, arg, "deleteheader command: " + "specified header field `%s' is protected " + "(modification will be denied)", str_sanitize(str_c(fname), 80)); + } } /* Value patterns argument */ @@ -363,6 +369,7 @@ static bool cmd_deleteheader_operation_dump static int cmd_deleteheader_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address) { + const struct sieve_extension *this_ext = renv->oprtn->ext; int opt_code = 0; struct sieve_operand oprnd; struct sieve_comparator cmp = @@ -437,6 +444,13 @@ static int cmd_deleteheader_operation_execute return SIEVE_EXEC_FAILURE; } + if ( ext_editheader_header_is_protected(this_ext, str_c(field_name)) ) { + sieve_runtime_warning(renv, NULL, "deleteheader action: " + "specified header field `%s' is protected (modification denied)", + str_sanitize(str_c(field_name), 80)); + return SIEVE_EXEC_OK; + } + /* * Execute command */ diff --git a/src/lib-sieve/plugins/editheader/ext-editheader-common.c b/src/lib-sieve/plugins/editheader/ext-editheader-common.c index 5ecd19674f88a49062a0d68a0cc2d8851101034d..c019ee12271ead082936f90478daeb592c11389a 100644 --- a/src/lib-sieve/plugins/editheader/ext-editheader-common.c +++ b/src/lib-sieve/plugins/editheader/ext-editheader-common.c @@ -1,28 +1,135 @@ /* Copyright (c) 2002-2011 Pigeonhole authors, see the included COPYING file */ -#ifndef __EXT_EDITHEADER_COMMON_H -#define __EXT_EDITHEADER_COMMON_H +#include "lib.h" +#include "mempool.h" +#include "array.h" -/* - * Extensions - */ +#include "rfc2822.h" + +#include "sieve-common.h" +#include "sieve-error.h" +#include "sieve-settings.h" +#include "sieve-extensions.h" -extern const struct sieve_extension_def editheader_extension; +#include "ext-editheader-common.h" /* - * Commands + * Extension configuration */ -extern const struct sieve_command_def addheader_command; -//extern const struct sieve_command_def deleteheader_command; +struct ext_editheader_header { + const char *name; + + /* may extend this later */ + unsigned int protected:1; +}; + +struct ext_editheader_config { + pool_t pool; + + ARRAY_DEFINE(headers, struct ext_editheader_header); +}; + +static struct ext_editheader_header *ext_editheader_config_header_find +(struct ext_editheader_config *ext_config, const char *hname) +{ + struct ext_editheader_header *headers; + unsigned int count, i; + + headers = array_get_modifiable(&ext_config->headers, &count); + for ( i = 0; i < count; i++ ) { + if ( strcasecmp(hname, headers[i].name) == 0 ) + return &headers[i]; + } + + return NULL; +} + +bool ext_editheader_load +(const struct sieve_extension *ext, void **context) +{ + struct ext_editheader_config *ext_config = + (struct ext_editheader_config *) *context; + struct sieve_instance *svinst = ext->svinst; + const char *protected; + pool_t pool; + + if ( *context != NULL ) { + ext_editheader_unload(ext); + *context = NULL; + } + + T_BEGIN { + pool = pool_alloconly_create("editheader_config", 512); + ext_config = p_new(pool, struct ext_editheader_config, 1); + ext_config->pool = pool; + + p_array_init(&ext_config->headers, pool, 16); + + protected = sieve_setting_get(svinst, "sieve_editheader_protected"); + if ( protected != NULL ) { + const char **headers = t_strsplit_spaces(protected, " \t"); + + while ( *headers != NULL ) { + struct ext_editheader_header *header; + + if ( !rfc2822_header_field_name_verify(*headers, strlen(*headers)) ) { + sieve_sys_warning(svinst, + "editheader: setting sieve_editheader_protected contains " + "invalid header field name `%s' (ignored)", *headers); + continue; + } + + header=ext_editheader_config_header_find(ext_config, *headers); + if ( header == NULL ) { + header = array_append_space(&ext_config->headers); + header->name = p_strdup(pool, *headers); + } + + header->protected = TRUE; + + headers++; + } + } + } T_END; + + *context = (void *) ext_config; + return TRUE; +} + +void ext_editheader_unload(const struct sieve_extension *ext) +{ + struct ext_editheader_config *ext_config = + (struct ext_editheader_config *) ext->context; + + if ( ext_config != NULL ) { + pool_unref(&ext_config->pool); + } +} /* - * Operations + * Protected headers */ -extern const struct sieve_operation_def addheader_operation; -//extern const struct sieve_operation_def deleteheader_operation; +bool ext_editheader_header_is_protected +(const struct sieve_extension *ext, const char *hname) +{ + struct ext_editheader_config *ext_config = + (struct ext_editheader_config *) ext->context; + const struct ext_editheader_header *header; + + if ( strcasecmp(hname, "received") == 0 + || strcasecmp(hname, "auto-submitted") == 0 ) { + return TRUE; + } + + if ( strcasecmp(hname, "subject") == 0 ) { + return FALSE; + } + if ( (header=ext_editheader_config_header_find(ext_config, hname)) == NULL ) + return FALSE; -#endif /* __EXT_EDITHEADER_COMMON_H */ + return header->protected; +} diff --git a/src/lib-sieve/plugins/editheader/ext-editheader-common.h b/src/lib-sieve/plugins/editheader/ext-editheader-common.h index 990ecb32a85ac51fdca7f5926ce9ad6ac73234b7..94e3d2698f68d4348b9e6d8c1a23ca4c165646d0 100644 --- a/src/lib-sieve/plugins/editheader/ext-editheader-common.h +++ b/src/lib-sieve/plugins/editheader/ext-editheader-common.h @@ -4,12 +4,6 @@ #ifndef __EXT_EDITHEADER_COMMON_H #define __EXT_EDITHEADER_COMMON_H -/* - * Extensions - */ - -extern const struct sieve_extension_def editheader_extension; - /* * Commands */ @@ -29,5 +23,21 @@ enum ext_imap4flags_opcode { extern const struct sieve_operation_def addheader_operation; extern const struct sieve_operation_def deleteheader_operation; +/* + * Extension + */ + +extern const struct sieve_extension_def editheader_extension; + +bool ext_editheader_load + (const struct sieve_extension *ext, void **context); +void ext_editheader_unload(const struct sieve_extension *ext); + +/* + * Protected headers + */ + +bool ext_editheader_header_is_protected + (const struct sieve_extension *ext, const char *header); #endif /* __EXT_EDITHEADER_COMMON_H */ diff --git a/src/lib-sieve/plugins/editheader/ext-editheader.c b/src/lib-sieve/plugins/editheader/ext-editheader.c index 884468f65e7f2581e67cd62fd5ceeb2c6eadd2f4..dab480e4b80433ef87713427fbe8457e021dc18e 100644 --- a/src/lib-sieve/plugins/editheader/ext-editheader.c +++ b/src/lib-sieve/plugins/editheader/ext-editheader.c @@ -46,7 +46,8 @@ static bool ext_editheader_validator_load const struct sieve_extension_def editheader_extension = { "editheader", - NULL, NULL, + ext_editheader_load, + ext_editheader_unload, ext_editheader_validator_load, NULL, NULL, NULL, NULL, NULL, SIEVE_EXT_DEFINE_OPERATIONS(editheader_operations), diff --git a/tests/extensions/editheader/protected.svtest b/tests/extensions/editheader/protected.svtest new file mode 100644 index 0000000000000000000000000000000000000000..70a0a6fa8675e95a0088a25dd4367bec6affa5fb --- /dev/null +++ b/tests/extensions/editheader/protected.svtest @@ -0,0 +1,74 @@ +require "vnd.dovecot.testsuite"; + +require "variables"; + +require "editheader"; + +set "message" text: +Received: by example.com (Postfix, from userid 202) + id 32A131WFW23QWE4; Mon, 21 Nov 2011 05:25:26 +0200 (EET) +Delivery-date: Mon, 21 Nov 2011 04:26:04 +0100 +Auto-Submitted: yes +Subject: Frop! +From: stephan@example.com +To: tss@example.com + +Frop! +. +; + +test_set "message" "${message}"; +test "Default protected" { + if not exists "received" { + test_fail "received header did not exist in the first place"; + } + + if not exists "auto-submitted" { + test_fail "auto-submitted header did not exist in the first place"; + } + + deleteheader "received"; + deleteheader "auto-submitted"; + + if not exists "received" { + test_fail "protected received header was deleted"; + } + + if not exists "auto-submitted" { + test_fail "protected auto-submitted header was deleted"; + } +} + +test_config_set "sieve_editheader_protected" "subject delivery-date x-frop"; +test_config_reload :extension "editheader"; + +test_set "message" "${message}"; +test "Configured protected" { + if not exists "delivery-date" { + test_fail "received header did not exist in the first place"; + } + + if not exists "subject" { + test_fail "received header did not exist in the first place"; + } + + if exists "x-frop" { + test_fail "x-frop header already present"; + } + + deleteheader "delivery-date"; + deleteheader "subject"; + addheader "x-frop" "Frop!"; + + if not exists "delivery-date" { + test_fail "protected delivery-date header was deleted"; + } + + if exists "subject" { + test_fail "subject header cannot be protected, but it was not deleted"; + } + + if exists "x-frop" { + test_fail "protected x-frop header was added"; + } +}