From d65ee78efbaef2d1efe553117a80a53f733fd4fe Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Thu, 31 Jul 2008 10:44:17 +0200 Subject: [PATCH] Envelope: working towards proper RFC compliance of forward/return-path parsing. --- TODO | 2 + doc/rfc/RFC Controversy.txt | 3 - src/lib-sieve/ext-envelope.c | 245 ++++++++--- .../plugins/include/ext-include-binary.c | 3 + .../plugins/subaddress/ext-subaddress.c | 13 +- src/lib-sieve/sieve-address-parts.c | 17 +- src/lib-sieve/sieve-address-parts.h | 2 +- src/lib-sieve/sieve-address.c | 411 +++++++++++++++++- src/lib-sieve/sieve-address.h | 8 + src/lib-sieve/sieve-common.h | 3 + tests/extensions/envelope.svtest | 33 +- tests/testsuite.svtest | 16 +- 12 files changed, 654 insertions(+), 102 deletions(-) diff --git a/TODO b/TODO index c5a0c9550..20c6231d4 100644 --- a/TODO +++ b/TODO @@ -4,6 +4,8 @@ Next (in order of descending priority/precedence): - :matches : match values must only be changed when the test matches. - If an address is not syntactically valid, then it will not be matched by tests specifying ":localpart" or ":domain". + - Allow for the existance of dynamic comparators + (i.e. specified by variables). - Fix/Report issues listed in 'doc/rfc/RFC Controversy.txt' * Code cleanup * Full security review. Enforce limits on number of created objects, script diff --git a/doc/rfc/RFC Controversy.txt b/doc/rfc/RFC Controversy.txt index a499cb6b8..c53eb533c 100644 --- a/doc/rfc/RFC Controversy.txt +++ b/doc/rfc/RFC Controversy.txt @@ -26,9 +26,6 @@ these issues by my implementation is displayed inside [ ... ]. extensions; implementations SHOULD consider unknown envelope parts an error." - - Are envelope parts required to be addresses? And if not, what becomes the - meaning of the ADDRESS-PART modifiers? [not handled (FIXME); will first be: - if none specified full string match and trigger validation error otherwise] - Given the variables extension, sometimes the envelope parts aren't known until runtime. Will invalid ones abort the script? [not checked (FIXME); will first be: ignore envelope part] diff --git a/src/lib-sieve/ext-envelope.c b/src/lib-sieve/ext-envelope.c index 4e2e57ef9..05dd7089a 100644 --- a/src/lib-sieve/ext-envelope.c +++ b/src/lib-sieve/ext-envelope.c @@ -17,6 +17,7 @@ #include "sieve-extensions.h" #include "sieve-commands.h" #include "sieve-code.h" +#include "sieve-address.h" #include "sieve-comparators.h" #include "sieve-match-types.h" #include "sieve-address-parts.h" @@ -113,6 +114,71 @@ const struct sieve_operation envelope_operation = { ext_envelope_operation_execute }; +/* + * Envelope parts + * + * FIXME: not available to extensions + */ + +struct sieve_envelope_part { + const char *identifier; + + const struct sieve_address *const *(*get_addresses) + (const struct sieve_runtime_env *renv); + const char * const *(*get_values) + (const struct sieve_runtime_env *renv); +}; + +const struct sieve_address *const *_from_part_get_addresses + (const struct sieve_runtime_env *renv); +const struct sieve_address *const *_to_part_get_addresses + (const struct sieve_runtime_env *renv); +const char *const *_auth_part_get_values + (const struct sieve_runtime_env *renv); + +static const struct sieve_envelope_part _from_part = { + "from", + _from_part_get_addresses, + NULL +}; + +static const struct sieve_envelope_part _to_part = { + "to", + _to_part_get_addresses, + NULL +}; + +static const struct sieve_envelope_part _auth_part = { + "auth", + NULL, + _auth_part_get_values, +}; + +static const struct sieve_envelope_part *_envelope_parts[] = { + /* Required */ + &_from_part, &_to_part, + + /* Non-standard */ + &_auth_part +}; + +static unsigned int _envelope_part_count = N_ELEMENTS(_envelope_parts); + +static const struct sieve_envelope_part *_envelope_part_find +(const char *identifier) +{ + unsigned int i; + + for ( i = 0; i < _envelope_part_count; i++ ) { + if ( strcasecmp( _envelope_parts[i]->identifier, identifier ) == 0 ) { + return _envelope_parts[i]; + } + } + + return NULL; +} + + /* * Command Registration */ @@ -132,34 +198,28 @@ static bool tst_envelope_registered * Validation */ -static const char * const _supported_envelope_parts[] = { - /* Required */ - "from", "to", - - /* Non-standard */ - "auth", - - NULL -}; - static int _envelope_part_is_supported -(void *context ATTR_UNUSED, struct sieve_ast_argument *arg) +(void *context, struct sieve_ast_argument *arg) { - if ( sieve_argument_is_string_literal(arg) ) { - const char *epart = sieve_ast_strlist_strc(arg); + const struct sieve_envelope_part **not_address = + (const struct sieve_envelope_part **) context; - const char * const *epsp = _supported_envelope_parts; - while ( *epsp != NULL ) { - if ( strcasecmp( *epsp, epart ) == 0 ) - return TRUE; - - epsp++; + if ( sieve_argument_is_string_literal(arg) ) { + const struct sieve_envelope_part *epart; + + if ( (epart=_envelope_part_find(sieve_ast_strlist_strc(arg))) != NULL ) { + if ( epart->get_addresses == NULL ) { + if ( *not_address == NULL ) + *not_address = epart; + } + + return TRUE; } return FALSE; } - return TRUE; + return TRUE; /* Can't check at compile time */ } static bool tst_envelope_validate @@ -167,6 +227,7 @@ static bool tst_envelope_validate { struct sieve_ast_argument *arg = tst->first_positional; struct sieve_ast_argument *epart; + const struct sieve_envelope_part *not_address = NULL; if ( !sieve_validate_positional_argument (validator, tst, arg, "envelope part", 1, SAAT_STRING_LIST) ) { @@ -180,12 +241,27 @@ static bool tst_envelope_validate * FIXME: verify dynamic envelope parts at runtime */ epart = arg; - if ( !sieve_ast_stringlist_map(&epart, NULL, _envelope_part_is_supported) ) { + if ( !sieve_ast_stringlist_map(&epart, (void *) ¬_address, + _envelope_part_is_supported) ) { + sieve_command_validate_error(validator, tst, "specified envelope part '%s' is not supported by the envelope test", sieve_ast_strlist_strc(epart)); return FALSE; } + + if ( not_address != NULL ) { + struct sieve_ast_argument *addrp_arg = + sieve_command_find_argument(tst, &address_part_tag); + + if ( addrp_arg != NULL ) { + sieve_command_validate_error(validator, tst, + "address part ':%s' specified while non-address envelope part '%s' " + "is tested with the envelope test", + sieve_ast_argument_tag(addrp_arg), not_address->identifier); + return FALSE; + } + } arg = sieve_ast_argument_next(arg); @@ -241,29 +317,53 @@ static bool ext_envelope_operation_dump * Interpretation */ -static int ext_envelope_get_fields -(const struct sieve_message_data *msgdata, const char *field, - const char *const **value_r) +const struct sieve_address *const *_from_part_get_addresses +(const struct sieve_runtime_env *renv) { - const char *value; - ARRAY_DEFINE(envelope_values, const char *); + ARRAY_DEFINE(envelope_values, const struct sieve_address *); + const struct sieve_address *address = + sieve_address_parse_envelope_path(renv->msgdata->return_path); - t_array_init(&envelope_values, 2); - - if ( strcasecmp(field, "from") == 0 ) - value = msgdata->return_path; - else if ( strcasecmp(field, "to") == 0 ) - value = msgdata->to_address; - else if ( strcasecmp(field, "auth") == 0 ) /* Non-standard */ - value = msgdata->auth_user; - - if ( value != NULL ) - array_append(&envelope_values, &value, 1); + t_array_init(&envelope_values, 2); + + if ( address != NULL ) { + array_append(&envelope_values, &address, 1); + } + + (void)array_append_space(&envelope_values); + return array_idx(&envelope_values, 0); +} + +const struct sieve_address *const *_to_part_get_addresses +( const struct sieve_runtime_env *renv) +{ + ARRAY_DEFINE(envelope_values, const struct sieve_address *); + const struct sieve_address *address = + sieve_address_parse_envelope_path(renv->msgdata->to_address); + t_array_init(&envelope_values, 2); + + if ( address != NULL && address->local_part != NULL ) { + array_append(&envelope_values, &address, 1); + } + + (void)array_append_space(&envelope_values); + return array_idx(&envelope_values, 0); +} + +const char *const *_auth_part_get_values +( const struct sieve_runtime_env *renv) +{ + ARRAY_DEFINE(envelope_values, const char *); + + t_array_init(&envelope_values, 2); + + if ( renv->msgdata->auth_user != NULL ) + array_append(&envelope_values, &renv->msgdata->auth_user, 1); + (void)array_append_space(&envelope_values); - *value_r = array_idx(&envelope_values, 0); - return 0; + return array_idx(&envelope_values, 0); } static int ext_envelope_operation_execute @@ -312,34 +412,55 @@ static int ext_envelope_operation_execute while ( result && !matched && (result=sieve_coded_stringlist_next_item(envp_list, &envp_item)) && envp_item != NULL ) { - const char *epart = str_c(envp_item); - const char *const *fields; + const struct sieve_envelope_part *epart; - if ( ext_envelope_get_fields(renv->msgdata, epart, &fields) >= 0 ) { - /* From RFC 5228: 5.4. Test envelope : - * The null reverse-path is matched against as the empty string, - * regardless of the ADDRESS-PART argument specified. - */ - if ( strcmp(epart, "from") == 0 && - (fields[0] == NULL || **fields == '\0') ) { - - if ( (ret=sieve_match_value(mctx, "", 0)) < 0 ) { - result = FALSE; - break; - } - - matched = ret > 0; - } else { - int i; - for ( i = 0; !matched && fields[i] != NULL; i++ ) { - if ( (ret=sieve_address_match(addrp, mctx, fields[i])) < 0 ) { - result = FALSE; - break; + if ( (epart=_envelope_part_find(str_c(envp_item))) != NULL ) { + const struct sieve_address * const *addresses = NULL; + int i; + + if ( epart->get_addresses != NULL ) { + /* Field contains addresses */ + addresses = epart->get_addresses(renv); + + if ( addresses != NULL ) { + for ( i = 0; !matched && addresses[i] != NULL; i++ ) { + if ( addresses[i]->local_part == NULL ) { + /* Null path <> */ + ret = sieve_match_value(mctx, "", 0); + } else { + const char *part = addrp->extract_from(addresses[i]); + if ( part != NULL ) + ret = sieve_match_value(mctx, part, strlen(part)); + } + + if ( ret < 0 ) { + result = FALSE; + break; + } + + matched = ret > 0; } + } + } + + if ( epart->get_values != NULL && addresses == NULL && + addrp == &all_address_part ) { + /* Field contains something else */ + const char *const *values = epart->get_values(renv); + + if ( values == NULL ) continue; + + for ( i = 0; !matched && values[i] != NULL; i++ ) { + + if ( (ret=sieve_match_value + (mctx, values[i], strlen(values[i]))) < 0 ) { + result = FALSE; + break; + } matched = ret > 0; } - } + } } } diff --git a/src/lib-sieve/plugins/include/ext-include-binary.c b/src/lib-sieve/plugins/include/ext-include-binary.c index 00e012384..0126b5207 100644 --- a/src/lib-sieve/plugins/include/ext-include-binary.c +++ b/src/lib-sieve/plugins/include/ext-include-binary.c @@ -1,3 +1,6 @@ +#include "lib.h" +#include "str.h" + #include "sieve-common.h" #include "sieve-error.h" #include "sieve-script.h" diff --git a/src/lib-sieve/plugins/subaddress/ext-subaddress.c b/src/lib-sieve/plugins/subaddress/ext-subaddress.c index ab1b66ae4..6d6237dc6 100644 --- a/src/lib-sieve/plugins/subaddress/ext-subaddress.c +++ b/src/lib-sieve/plugins/subaddress/ext-subaddress.c @@ -14,6 +14,7 @@ #include "sieve-common.h" #include "sieve-code.h" +#include "sieve-address.h" #include "sieve-extensions.h" #include "sieve-commands.h" #include "sieve-address-parts.h" @@ -58,19 +59,19 @@ static bool ext_subaddress_load(int ext_id) /* Actual extension implementation */ static const char *subaddress_user_extract_from - (const struct message_address *address) + (const struct sieve_address *address) { - const char *sep = strchr(address->mailbox, SUBADDRESS_DEFAULT_SEP_CHAR); + const char *sep = strchr(address->local_part, SUBADDRESS_DEFAULT_SEP_CHAR); - if ( sep == NULL ) return address->mailbox; + if ( sep == NULL ) return address->local_part; - return t_strdup_until(address->mailbox, sep); + return t_strdup_until(address->local_part, sep); } static const char *subaddress_detail_extract_from - (const struct message_address *address) + (const struct sieve_address *address) { - const char *sep = strchr(address->mailbox, SUBADDRESS_DEFAULT_SEP_CHAR); + const char *sep = strchr(address->local_part, SUBADDRESS_DEFAULT_SEP_CHAR); if ( sep == NULL ) return NULL; diff --git a/src/lib-sieve/sieve-address-parts.c b/src/lib-sieve/sieve-address-parts.c index 21a400d5f..f0e6bfd0b 100644 --- a/src/lib-sieve/sieve-address-parts.c +++ b/src/lib-sieve/sieve-address-parts.c @@ -9,6 +9,7 @@ #include "sieve-extensions.h" #include "sieve-code.h" +#include "sieve-address.h" #include "sieve-commands.h" #include "sieve-binary.h" #include "sieve-comparators.h" @@ -198,11 +199,13 @@ int sieve_address_match while ( result == 0 && addr != NULL) { /* mailbox@domain */ + struct sieve_address address; const char *part; - i_assert(addr->mailbox != NULL); + address.local_part = addr->mailbox; + address.domain = addr->domain; - part = addrp->extract_from(addr); + part = addrp->extract_from(&address); if ( part != NULL ) result=sieve_match_value(mctx, part, strlen(part)); @@ -303,21 +306,21 @@ const struct sieve_argument address_part_tag = { }; static const char *addrp_all_extract_from - (const struct message_address *address) + (const struct sieve_address *address) { - return t_strconcat(address->mailbox, "@", address->domain, NULL); + return t_strconcat(address->local_part, "@", address->domain, NULL); } static const char *addrp_domain_extract_from - (const struct message_address *address) + (const struct sieve_address *address) { return address->domain; } static const char *addrp_localpart_extract_from - (const struct message_address *address) + (const struct sieve_address *address) { - return address->mailbox; + return address->local_part; } const struct sieve_address_part all_address_part = { diff --git a/src/lib-sieve/sieve-address-parts.h b/src/lib-sieve/sieve-address-parts.h index 1a29fb32e..5ec6da131 100644 --- a/src/lib-sieve/sieve-address-parts.h +++ b/src/lib-sieve/sieve-address-parts.h @@ -14,7 +14,7 @@ struct sieve_address_part { struct sieve_object object; - const char *(*extract_from)(const struct message_address *address); + const char *(*extract_from)(const struct sieve_address *address); }; /* diff --git a/src/lib-sieve/sieve-address.c b/src/lib-sieve/sieve-address.c index f7abac1c7..e2bcb8282 100644 --- a/src/lib-sieve/sieve-address.c +++ b/src/lib-sieve/sieve-address.c @@ -6,6 +6,8 @@ #include "sieve-address.h" +#include <ctype.h> + /* Mail message address according to RFC 2822 and implemented in the Dovecot * message address parser: * @@ -49,7 +51,7 @@ * / phrase "<" addr-spec ">" ; name & addr-spec */ -struct sieve_address_parser_context { +struct sieve_message_address_parser { struct rfc822_parser_context parser; string_t *address; @@ -62,11 +64,11 @@ struct sieve_address_parser_context { }; static inline void sieve_address_error - (struct sieve_address_parser_context *ctx, const char *fmt, ...) + (struct sieve_message_address_parser *ctx, const char *fmt, ...) ATTR_FORMAT(2, 3); static inline void sieve_address_error - (struct sieve_address_parser_context *ctx, const char *fmt, ...) + (struct sieve_message_address_parser *ctx, const char *fmt, ...) { va_list args; @@ -77,7 +79,7 @@ static inline void sieve_address_error } } -static int parse_local_part(struct sieve_address_parser_context *ctx) +static int parse_local_part(struct sieve_message_address_parser *ctx) { int ret; @@ -104,7 +106,7 @@ static int parse_local_part(struct sieve_address_parser_context *ctx) return ret; } -static int parse_domain(struct sieve_address_parser_context *ctx) +static int parse_domain(struct sieve_message_address_parser *ctx) { int ret; @@ -117,7 +119,7 @@ static int parse_domain(struct sieve_address_parser_context *ctx) return ret; } -static int parse_addr_spec(struct sieve_address_parser_context *ctx) +static int parse_addr_spec(struct sieve_message_address_parser *ctx) { /* addr-spec = local-part "@" domain */ int ret; @@ -134,7 +136,7 @@ static int parse_addr_spec(struct sieve_address_parser_context *ctx) return -1; } -static int parse_name_addr(struct sieve_address_parser_context *ctx) +static int parse_name_addr(struct sieve_message_address_parser *ctx) { int ret; const unsigned char *start; @@ -179,7 +181,7 @@ static int parse_name_addr(struct sieve_address_parser_context *ctx) return ret; } -static bool parse_sieve_address(struct sieve_address_parser_context *ctx) +static bool parse_sieve_address(struct sieve_message_address_parser *ctx) { int ret; @@ -217,7 +219,7 @@ static bool parse_sieve_address(struct sieve_address_parser_context *ctx) const char *sieve_address_normalize (string_t *address, const char **error_r) { - struct sieve_address_parser_context ctx; + struct sieve_message_address_parser ctx; memset(&ctx, 0, sizeof(ctx)); ctx.address = address; @@ -242,7 +244,7 @@ const char *sieve_address_normalize bool sieve_address_validate (string_t *address, const char **error_r) { - struct sieve_address_parser_context ctx; + struct sieve_message_address_parser ctx; memset(&ctx, 0, sizeof(ctx)); ctx.address = address; @@ -260,5 +262,394 @@ bool sieve_address_validate return TRUE; } +/* + * Envelope address parsing + * RFC 2821 + */ + +#define AB (1<<0) +#define DB (1<<1) +#define QB (1<<2) + +/* atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" + * / "&" / "'" / "*" / "+" / "-" / "/" / "=" + * / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~" + */ +//#define IS_ATEXT(C) ((rfc2821_chars[C] & AB) != 0) + +/* dtext = NO-WS-CTL / %d33-90 / %d94-126 + * NO-WS-CTL = %d1-8 / %d11 / %d12 / %d14-31 / %d127 + */ +#define IS_DTEXT(C) ((rfc2821_chars[C] & DB) != 0) + +/* qtext= NO-WS-CTL / %d33 / %d35-91 / %d93-126 */ +#define IS_QTEXT(C) ((rfc2821_chars[C] & QB) == 0) + +/* text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text*/ +#define IS_TEXT(C) ((C) != '\r' && (C) != '\n' && (C) < 128) + +static unsigned char rfc2821_chars[256] = { + DB, DB, DB, DB, DB, DB, DB, DB, // 0 + DB, QB, QB, DB, DB, QB, DB, DB, // 8 + DB, DB, DB, DB, DB, DB, DB, DB, // 16 + DB, DB, DB, DB, DB, DB, DB, DB, // 24 + QB, DB|AB, QB|DB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 32 + DB, DB, DB|AB, DB|AB, DB, DB|AB, DB, DB|AB, // 40 + DB, DB, DB, DB, DB, DB, DB, DB, // 48 + DB, DB, DB, DB, DB, DB|AB, DB, DB|AB, // 56 + DB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 64 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 72 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 80 + DB|AB, DB|AB, DB|AB, 0, QB, 0, DB|AB, DB|AB, // 88 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 96 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 104 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, // 112 + DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|AB, DB|QB, // 120 + + 0, 0, 0, 0, 0, 0, 0, 0, // 128 + 0, 0, 0, 0, 0, 0, 0, 0, // 136 + 0, 0, 0, 0, 0, 0, 0, 0, // 144 + 0, 0, 0, 0, 0, 0, 0, 0, // 152 + 0, 0, 0, 0, 0, 0, 0, 0, // 160 + 0, 0, 0, 0, 0, 0, 0, 0, // 168 + 0, 0, 0, 0, 0, 0, 0, 0, // 176 + 0, 0, 0, 0, 0, 0, 0, 0, // 184 + 0, 0, 0, 0, 0, 0, 0, 0, // 192 + 0, 0, 0, 0, 0, 0, 0, 0, // 200 + 0, 0, 0, 0, 0, 0, 0, 0, // 208 + 0, 0, 0, 0, 0, 0, 0, 0, // 216 + 0, 0, 0, 0, 0, 0, 0, 0, // 224 + 0, 0, 0, 0, 0, 0, 0, 0, // 232 + 0, 0, 0, 0, 0, 0, 0, 0, // 240 + 0, 0, 0, 0, 0, 0, 0, 0, // 248 + +}; + +struct sieve_envelope_address_parser { + const unsigned char *data; + const unsigned char *end; + + string_t *str; + + struct sieve_address *address; +}; + +static int path_skip_white_space(struct sieve_envelope_address_parser *parser) +{ + /* Not mentioned anywhere in the specification, but we do it any way + * (e.g. Exim does so too) + */ + while ( parser->data < parser->end && + (*parser->data == ' ' || *parser->data == '\t') ) + parser->data++; + + return parser->data < parser->end; +} + +static int path_skip_address_literal +(struct sieve_envelope_address_parser *parser) +{ + int count; + + /* Currently we are oblivious to address syntax: + * address-literal = "[" 1*dcontent "]" + * dcontent = dtext / quoted-pair + */ + + i_assert ( *parser->data == '[' ); + + str_append_c(parser->str, *parser->data); + parser->data++; + + while ( parser->data < parser->end ) { + if ( *parser->data == '\\' ) { + str_append_c(parser->str, *parser->data); + parser->data++; + + if ( parser->data < parser->end ) { + if ( !IS_TEXT(*parser->data) ) + return -1; + + str_append_c(parser->str, *parser->data); + parser->data++; + } else return -1; + } else { + if ( !IS_DTEXT(*parser->data) ) + break; + + str_append_c(parser->str, *parser->data); + parser->data++; + } + + count++; + } + + + if ( count == 0 || parser->data >= parser->end || *parser->data != ']' ) + return -1; + + str_append_c(parser->str, *parser->data); + parser->data++; + + return parser->data < parser->end; +} + +static int path_parse_domain +(struct sieve_envelope_address_parser *parser, bool skip) +{ + int ret; + + /* Domain = (sub-domain 1*("." sub-domain)) / address-literal + * sub-domain = Let-dig [Ldh-str] + * Let-dig = ALPHA / DIGIT + * Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + */ + + str_truncate(parser->str, 0); + if ( *parser->data == '[' ) { + ret = path_skip_address_literal(parser); + + if ( ret < 0 ) return ret; + } else { + for (;;) { + if ( !i_isalnum(*parser->data) ) + return -1; + + str_append_c(parser->str, *parser->data); + parser->data++; + + while ( parser->data < parser->end ) { + if ( !i_isalnum(*parser->data) && *parser->data != '-' ) + break; + + str_append_c(parser->str, *parser->data); + parser->data++; + } + + if ( !i_isalnum(*(parser->data-1)) ) + return -1; + + if ( (ret=path_skip_white_space(parser)) < 0 ) + return ret; + + if ( *parser->data != '.' ) + break; + + str_append_c(parser->str, *parser->data); + parser->data++; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + } + } + + if ( !skip ) + parser->address->domain = t_strdup(str_c(parser->str)); + + return path_skip_white_space(parser); +} + +static int path_skip_source_route(struct sieve_envelope_address_parser *parser) +{ + int ret; + + /* A-d-l = At-domain *( "," A-d-l ) + * At-domain = "@" domain + */ + + if ( *parser->data == '@' ) { + parser->data++; + + for (;;) { + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + + if ( (ret=path_parse_domain(parser, TRUE)) <= 0 ) + return -1; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return ret; + + /* Next? */ + if ( *parser->data != ',' ) + break; + parser->data++; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + + if ( *parser->data != '@' ) + return -1; + parser->data++; + } + } + + return parser->data < parser->end; +} + +static int path_parse_local_part(struct sieve_envelope_address_parser *parser) +{ + int ret; + /* Local-part = Dot-string / Quoted-string + * Dot-string = Atom *("." Atom) + * Atom = 1*atext + * Quoted-string = DQUOTE *qcontent DQUOTE + * qcontent = qtext / quoted-pair + * quoted-pair = ("\" text) + */ + + str_truncate(parser->str, 0); + if ( *parser->data == '"' ) { + str_append_c(parser->str, *parser->data); + parser->data++; + + while ( parser->data < parser->end ) { + if ( *parser->data == '\\' ) { + str_append_c(parser->str, *parser->data); + parser->data++; + + if ( parser->data < parser->end ) { + if ( !IS_TEXT(*parser->data) ) + return -1; + + str_append_c(parser->str, *parser->data); + parser->data++; + } else return -1; + } else { + if ( !IS_QTEXT(*parser->data) ) + break; + + str_append_c(parser->str, *parser->data); + parser->data++; + } + } + + if ( *parser->data != '"' ) + return -1; + + str_append_c(parser->str, *parser->data); + parser->data++; + + if ( (ret=path_skip_white_space(parser)) < 0 ) + return ret; + } else { + for (;;) { + if ( !IS_ATEXT(*parser->data) ) + return -1; + str_append_c(parser->str, *parser->data); + parser->data++; + + while ( parser->data < parser->end && IS_ATEXT(*parser->data)) { + str_append_c(parser->str, *parser->data); + parser->data++; + } + + if ( (ret=path_skip_white_space(parser)) < 0 ) + return ret; + + if ( *parser->data != '.' ) + break; + + str_append_c(parser->str, *parser->data); + parser->data++; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + } + } + + parser->address->local_part = t_strdup(str_c(parser->str)); + return parser->data < parser->end; +} + +static int path_parse_mailbox(struct sieve_envelope_address_parser *parser) +{ + int ret; + + /* Mailbox = Local-part "@" Domain */ + + if ( (ret=path_parse_local_part(parser)) <= 0 ) + return ret; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + + if ( *parser->data != '@' ) + return -1; + parser->data++; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + + return path_parse_domain(parser, FALSE); +} + +static int path_parse(struct sieve_envelope_address_parser *parser) +{ + int ret; + bool brackets = FALSE; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return ret; + + /* We allow angle brackets to be missing */ + if ( *parser->data == '<' ) { + parser->data++; + brackets = TRUE; + + if ( (ret=path_skip_white_space(parser)) <= 0 ) + return -1; + + /* Null path? */ + if ( *parser->data == '>' ) { + parser->data++; + return path_skip_white_space(parser); + } + } + + /* [ A-d-l ":" ] Mailbox */ + + if ( (ret=path_skip_source_route(parser)) <= 0 ) + return -1; + + if ( (ret=path_parse_mailbox(parser)) < 0 ) + return -1; + + if ( ret > 0 && (ret=path_skip_white_space(parser)) < 0 ) + return -1; + + if ( brackets ) { + if ( ret <= 0 ) return -1; + + if ( *parser->data != '>' ) + return -1; + parser->data++; + } + + return parser->data < parser->end; +} + +const struct sieve_address *sieve_address_parse_envelope_path +(const char *field_value) +{ + struct sieve_envelope_address_parser parser; + int ret; + + parser.data = (const unsigned char *) field_value; + parser.end = (const unsigned char *) field_value + strlen(field_value); + parser.address = t_new(struct sieve_address, 1); + parser.str = t_str_new(256); + + if ( (ret=path_parse(&parser)) < 0 ) + return NULL; + + if ( ret > 0 && path_skip_white_space(&parser) < 0 ) + return NULL; + + if ( parser.data != parser.end ) + return NULL; + + return parser.address; +} diff --git a/src/lib-sieve/sieve-address.h b/src/lib-sieve/sieve-address.h index ad4caba55..66a5c6b30 100644 --- a/src/lib-sieve/sieve-address.h +++ b/src/lib-sieve/sieve-address.h @@ -1,9 +1,17 @@ #ifndef __SIEVE_ADDRESS_H #define __SIEVE_ADDRESS_H +struct sieve_address { + const char *local_part; + const char *domain; +}; + const char *sieve_address_normalize (string_t *address, const char **error_r); bool sieve_address_validate (string_t *address, const char **error_r); +const struct sieve_address *sieve_address_parse_envelope_path + (const char *field_value); + #endif diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h index 4289081a6..a0ae3f547 100644 --- a/src/lib-sieve/sieve-common.h +++ b/src/lib-sieve/sieve-common.h @@ -86,6 +86,9 @@ struct sieve_match_type; /* sieve-match.h */ struct sieve_match_context; +/* sieve-address.h */ +struct sieve_address; + /* sieve-address-parts.h */ struct sieve_address_part; diff --git a/tests/extensions/envelope.svtest b/tests/extensions/envelope.svtest index 6dde173f5..9894c8e63 100644 --- a/tests/extensions/envelope.svtest +++ b/tests/extensions/envelope.svtest @@ -2,24 +2,47 @@ require "vnd.dovecot.testsuite"; require "envelope"; -test_set "envelope.from" ""; test_set "envelope.to" "stephan@rename-it.nl"; test "Envelope - from empty" { + /* Return_path: "" */ + + test_set "envelope.from" ""; + + if not envelope :all :is "from" "" { + test_fail "failed to (:all :is)-match a \"\" return path"; + } + + if not envelope :all :contains "from" "" { + test_fail "failed to (:all :contains)-match a \"\" return path"; + } + + if not envelope :domain :is "from" "" { + test_fail "failed to (:domain :is)-match a \"\" return path"; + } + + if not envelope :domain :contains "from" "" { + test_fail "failed to (:domain :contains)-match a \"\" return path"; + } + + /* Return path: <> */ + + test_set "envelope.from" "<>"; + if not envelope :all :is "from" "" { - test_fail "failed to :all :is match <> return path"; + test_fail "failed to (:all :is)-match a <> return path"; } if not envelope :all :contains "from" "" { - test_fail "failed to :all :contains match <> return path"; + test_fail "failed to (:all :contains)-match a <> return path"; } if not envelope :domain :is "from" "" { - test_fail "failed to :domain :is match <> return path"; + test_fail "failed to (:domain :is)-match a <> return path"; } if not envelope :domain :contains "from" "" { - test_fail "failed to :domain :contains match <> return path"; + test_fail "failed to (:domain :contains)-match a <> return path"; } if envelope :all :is "from" "nico@vestingbar.nl" { diff --git a/tests/testsuite.svtest b/tests/testsuite.svtest index 97bb5bcb7..5cf884ed1 100644 --- a/tests/testsuite.svtest +++ b/tests/testsuite.svtest @@ -35,36 +35,36 @@ test "Envelope Environment" { test_set "envelope.from" "stephan@hutsefluts.nl"; if not envelope :is "from" "stephan@hutsefluts.nl" { - test_fail "envelope.from data not set properly."; + test_fail "envelope.from data not set properly (1)."; } test_set "envelope.to" "news@rename-it.nl"; if not envelope :is "to" "news@rename-it.nl" { - test_fail "envelope.to data not set properly."; + test_fail "envelope.to data not set properly (1)."; } test_set "envelope.auth" "sirius"; - if not envelope :is "auth" "sirius@" { - test_fail "envelope.auth data not set properly."; + if not envelope :is "auth" "sirius" { + test_fail "envelope.auth data not set properly (1)."; } test_set "envelope.from" "stephan@rename-it.nl"; if not envelope :is "from" "stephan@rename-it.nl" { - test_fail "envelope.from data not reset properly."; + test_fail "envelope.from data not reset properly (2)."; } test_set "envelope.to" "past-news@rename-it.nl"; if not envelope :is "to" "past-news@rename-it.nl" { - test_fail "envelope.to data not reset properly."; + test_fail "envelope.to data not reset properly (2)."; } test_set "envelope.auth" "zilla"; - if not envelope :is "auth" "zilla@" { - test_fail "envelope.auth data not reset properly."; + if not envelope :is "auth" "zilla" { + test_fail "envelope.auth data not reset properly (2)."; } } -- GitLab