diff --git a/sieve/tests/matches.sieve b/sieve/tests/matches.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..913d33b348a566b97dffcf6dd1416f210f08fad0
--- /dev/null
+++ b/sieve/tests/matches.sieve
@@ -0,0 +1,54 @@
+require "fileinto";
+
+fileinto "SHOULD MATCH";
+if address :matches "from" "*@d*ksn*ers.com" {
+	fileinto "A";
+}
+if address :matches "from" "stephan+sieve@drunksnipers.*" {
+	fileinto "B";
+}
+if address :matches "from" "*+sieve@drunksnipers.com" {
+	fileinto "C";
+}
+if address :matches "from" "stephan+sieve?drunksnipers.com" {
+	fileinto "D";
+}
+if address :matches "from" "?tephan+sieve@drunksnipers.com" {
+	fileinto "E";
+}
+if address :matches "from" "stephan+sieve@drunksnipers.co?" {
+	fileinto "F";
+}
+if address :matches "from" "?t?phan?sieve?drunksnip?rs.co?" {
+	fileinto "G";
+}
+if address :matches "x-bullshit" "33333\\?\\?\\??" {
+	fileinto "H";
+}
+
+fileinto "SHOULD NOT MATCH";
+if address :matches "from" "*@d*kn*ers.com" {
+	fileinto "A";
+}
+if address :matches "from" "stepan+sieve@drunksnipers.*" {
+	fileinto "B";
+}
+if address :matches "from" "*+sieve@drunksnipers.om" {
+	fileinto "C";
+}
+if address :matches "from" "stephan+sieve?drunksipers.com" {
+	fileinto "D";
+}
+if address :matches "from" "?tephan+sievedrunksnipers.com" {
+	fileinto "E";
+}
+if address :matches "from" "sephan+sieve@drunksnipers.co?" {
+	fileinto "F";
+}
+if address :matches "from" "?t?phan?sieve?dunksnip?rs.co?" {
+	fileinto "G";
+}
+if address :matches "x-bullshit" "33333\\?\\??" {
+	fileinto "H";
+}
+
diff --git a/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c b/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
index 229307dddc00f7c21d89fb172512612f43e11e94..f51dccbf745f2a57c09c193e64340de5a6504632 100644
--- a/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
+++ b/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
@@ -64,6 +64,7 @@ const struct sieve_comparator i_ascii_numeric_comparator = {
 	&i_ascii_numeric_comparator_ext,
 	0,
 	cmp_i_ascii_numeric_compare,
+	NULL,
 	NULL
 };
 
diff --git a/src/lib-sieve/sieve-comparators.c b/src/lib-sieve/sieve-comparators.c
index e9798840d10499a91dc60d7f034b3a37b6838645..e861b1680405a15c060f469b0969d54c55fd1e76 100644
--- a/src/lib-sieve/sieve-comparators.c
+++ b/src/lib-sieve/sieve-comparators.c
@@ -33,7 +33,10 @@ static int cmp_i_octet_compare
 		const char *val1, size_t val1_size, const char *val2, size_t val2_size);
 static bool cmp_i_octet_char_match
 	(const struct sieve_comparator *cmp, const char **val1, const char *val1_end, 
-		const char **val2, const char *val2_end);	
+		const char **val2, const char *val2_end);
+static bool cmp_i_octet_char_skip
+	(const struct sieve_comparator *cmp, const char **val, const char *val_end);
+	
 static int cmp_i_ascii_casemap_compare
 	(const struct sieve_comparator *cmp,
 		const char *val1, size_t val1_size, const char *val2, size_t val2_size);
@@ -412,7 +415,8 @@ const struct sieve_comparator i_octet_comparator = {
 	NULL,
 	0,
 	cmp_i_octet_compare,
-	cmp_i_octet_char_match	
+	cmp_i_octet_char_match,
+	cmp_i_octet_char_skip	
 };
 
 const struct sieve_comparator i_ascii_casemap_comparator = {
@@ -423,7 +427,8 @@ const struct sieve_comparator i_ascii_casemap_comparator = {
 	NULL,
 	0,
 	cmp_i_ascii_casemap_compare,
-	cmp_i_ascii_casemap_char_match
+	cmp_i_ascii_casemap_char_match,
+	cmp_i_octet_char_skip
 };
 
 const struct sieve_comparator *sieve_core_comparators[] = {
@@ -460,13 +465,34 @@ static int cmp_i_octet_compare(
 
 static bool cmp_i_octet_char_match
 	(const struct sieve_comparator *cmp ATTR_UNUSED, 
-		const char **val1, const char *val1_end ATTR_UNUSED, 
-		const char **val2, const char *val2_end ATTR_UNUSED)
+		const char **val, const char *val_end, 
+		const char **key, const char *key_end)
 {
-	if ( **val1 == **val2 ) {
-		(*val1)++;
-		(*val2)++;
-		
+	const char *val_begin = *val;
+	const char *key_begin = *key;
+	
+	while ( **val == **key && *val < val_end && *key < key_end ) {
+		(*val)++;
+		(*key)++;
+	}
+	
+	if ( *key < key_end ) {
+		/* Reset */
+		*val = val_begin;
+		*key = key_begin;	
+	
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+static bool cmp_i_octet_char_skip
+	(const struct sieve_comparator *cmp ATTR_UNUSED, 
+		const char **val, const char *val_end)
+{
+	if ( *val < val_end ) {
+		(*val)++;
 		return TRUE;
 	}
 	
@@ -500,17 +526,27 @@ static int cmp_i_ascii_casemap_compare(
 
 static bool cmp_i_ascii_casemap_char_match
 	(const struct sieve_comparator *cmp ATTR_UNUSED, 
-		const char **val1, const char *val1_end ATTR_UNUSED, 
-		const char **val2, const char *val2_end ATTR_UNUSED)
+		const char **val, const char *val_end, 
+		const char **key, const char *key_end)
 {
-	if ( tolower(**val1) == tolower(**val2) ) {
-		(*val1)++;
-		(*val2)++;
+	const char *val_begin = *val;
+	const char *key_begin = *key;
+	
+	while ( tolower(**val) == tolower(**key) &&
+		*val < val_end && *key < key_end ) {
+		(*val)++;
+		(*key)++;
+	}
+	
+	if ( *key < key_end ) {
+		/* Reset */
+		*val = val_begin;
+		*key = key_begin;	
 		
-		return TRUE;
+		return FALSE;
 	}
 	
-	return FALSE;
+	return TRUE;
 }
 
 
diff --git a/src/lib-sieve/sieve-comparators.h b/src/lib-sieve/sieve-comparators.h
index 39207e2c06f24956981031596a78fed35aebe84c..10b7c829037ae1bd803f2e64aca36820a064b702 100644
--- a/src/lib-sieve/sieve-comparators.h
+++ b/src/lib-sieve/sieve-comparators.h
@@ -32,8 +32,10 @@ struct sieve_comparator {
 	/* Prefix and substring match */
 	
 	bool (*char_match)(const struct sieve_comparator *cmp, 
-		const char **val1, const char *val1_end, 
-		const char **val2, const char *val2_end);
+		const char **val, const char *val_end,
+		const char **key, const char *key_end);
+	bool (*char_skip)(const struct sieve_comparator *cmp, 
+		const char **val, const char *val_end);
 };
 
 struct sieve_comparator_extension {
diff --git a/src/lib-sieve/sieve-match-types.c b/src/lib-sieve/sieve-match-types.c
index 1605ee226b5d2717287e5a659198af00ce935e20..fe586c4bab2f4b7945800f5b76be47554d3d92d6 100644
--- a/src/lib-sieve/sieve-match-types.c
+++ b/src/lib-sieve/sieve-match-types.c
@@ -536,6 +536,7 @@ static bool mtch_contains_match
 (struct sieve_match_context *mctx, const char *val, size_t val_size, 
 	const char *key, size_t key_size, int key_index ATTR_UNUSED)
 {
+	const struct sieve_comparator *cmp = mctx->comparator;
 	const char *vend = (const char *) val + val_size;
 	const char *kend = (const char *) key + key_size;
 	const char *vp = val;
@@ -545,20 +546,118 @@ static bool mtch_contains_match
 		return FALSE;
 
 	while ( (vp < vend) && (kp < kend) ) {
-		if ( !mctx->comparator->char_match(mctx->comparator, &vp, vend, &kp, kend) ) {
-			vp = vp - (kp - key) + 1;
-			kp = key;
-		}
+		if ( !cmp->char_match(cmp, &vp, vend, &kp, kend) )
+			vp++;
 	}
     
-  	return (kp == kend);
+	return (kp == kend);
+}
+
+static bool _matches_section
+	(const struct sieve_comparator *cmp, 
+		const char **val, const char *vend, 
+		const char **key, const char *kend)
+{
+	const char *val_begin = *val;
+	const char *key_begin = *key;
+	const char *wp = *key;
+
+	while ( *key < kend ) {
+		char wildcard;
+		
+		/* Find next ? wildcard, \ escape or end of key pattern */
+		while ( wp < kend && *wp != '?' && *wp != '\\' ) {
+			wp++;
+		}
+		
+		wildcard = *wp;
+		
+		/* Match text */
+		if ( wp > *key && !cmp->char_match(cmp, val, vend, key, wp) ) 		
+			break;
+		
+		if ( *key == kend ) return TRUE;
+
+		wp++;			
+		*key = wp;			
+		
+		if ( wildcard == '\\' )
+			wp++;
+		else if ( !cmp->char_skip(cmp, val, vend) ) 
+				break;
+	}
+		
+	if ( *key < kend ) {
+		/* Reset */
+		*val = val_begin;
+		*key = key_begin;	
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 static bool mtch_matches_match
 (struct sieve_match_context *mctx, const char *val, size_t val_size, 
 	const char *key, size_t key_size, int key_index ATTR_UNUSED)
 {
-	return FALSE;
+	const struct sieve_comparator *cmp = mctx->comparator;
+	const char *vend = (const char *) val + val_size;
+	const char *kend = (const char *) key + key_size;
+	const char *vp = val;
+	const char *kp = key;
+	const char *wp = key;
+	
+	/* Match the pattern as a two-level structure: 
+	 *   <pattern> = <section>*<section>*<section>....
+	 *   <section> = [text]?[text]?[text].... 
+	 */
+	 
+	while (kp < kend) {
+		char wildcard;
+		
+		/* Find next * wildcard or end of key pattern */
+		
+		while ( wp < kend && *wp != '*' ) {
+			if ( *wp == '\\' ) {
+				/* Defer handling of escaping for ? to the _matches_section function */
+				if ( wp < kend - 1 && *(wp+1) == '?' )
+					wp++;
+				else 
+					break;
+			}
+			
+			wp++;
+		}
+		
+		wildcard = *wp;
+		
+		/* Find this section */
+		if ( wp > kp ) {
+			while ( (vp < vend) && (kp < wp) ) {
+				
+				if ( !_matches_section(cmp, &vp, vend, &kp, wp) ) {
+					/* First section must match without offset */
+					if ( kp == key ) return FALSE; 
+					
+					vp++;
+				}
+			}
+		}
+		
+		/* Check whether we matched up to the * wildcard or the end */
+		if ( wp > kp ) return FALSE; 
+		
+		if ( kp == kend ) return TRUE;
+
+		wp++;						
+		kp = wp;
+		
+		if ( wildcard == '\\' )
+			wp++;
+	}
+	
+	return (kp == kend);
 }
 			 
 /*