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 *) &not_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