diff --git a/README b/README
index 605c65ed5e16eec2b44ebe3ebaddd0d7b97eab23..cb5c197d039c677d5101f755db43d839038df4fc 100644
--- a/README
+++ b/README
@@ -73,6 +73,8 @@ TODO
 ----
 
 * Implement match-type execution
+* Resolve code duplication amongst comparator, address-part and match-type 
+  support as much as possible. 
 * Produce a fully working interpreter that actually executes actions, currently
   tests are evaluated and actions just print their occurence.
 * Give the binary format some more thought, it is currently quite rough and
diff --git a/sieve/tests/match-type.sieve b/sieve/tests/match-type.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..10e9e659e58e0b02a5fe5e00729faf957fd50d5c
--- /dev/null
+++ b/sieve/tests/match-type.sieve
@@ -0,0 +1,10 @@
+if address :is :comparator "i;octet" :domain "from" "STEPHAN" {
+	discard;
+
+	if address :contains :domain :comparator "i;octet" "from" "drunksnipers.com" {
+		keep;
+	}
+	stop;
+}
+
+keep;
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 7cac3ae3db0c4e446c99d62a5e271699ac145ab3..d9b22f56af0f4ccab6af03cdb771e955f2ee4e55 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -38,6 +38,7 @@ libsieve_a_SOURCES = \
 	sieve-result.c \
 	sieve-error.c \
 	sieve-comparators.c \
+	sieve-match-types.c \
 	sieve-address-parts.c \
 	sieve-commands.c \
 	sieve-code.c \
@@ -58,6 +59,7 @@ noinst_HEADERS = \
 	sieve-result.h
 	sieve-error.h \
 	sieve-comparators.h \
+	sieve-match-types.h \
 	sieve-address-parts.h \
 	sieve-commands.h \
 	sieve-code.h \
diff --git a/src/lib-sieve/ext-envelope.c b/src/lib-sieve/ext-envelope.c
index 82ca3a89ba5feb2fb490e928e6011a2cf26a8f21..12bcae1bae9122df779e76d815479ada1e6ff68a 100644
--- a/src/lib-sieve/ext-envelope.c
+++ b/src/lib-sieve/ext-envelope.c
@@ -3,6 +3,7 @@
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
 #include "sieve-comparators.h"
+#include "sieve-match-types.h"
 #include "sieve-address-parts.h"
 
 #include "sieve-validator.h"
@@ -65,7 +66,7 @@ static bool tst_envelope_registered(struct sieve_validator *validator, struct si
 	/* The order of these is not significant */
 	sieve_comparators_link_tag(validator, cmd_reg, OPT_COMPARATOR);
 	sieve_address_parts_link_tags(validator, cmd_reg, OPT_ADDRESS_PART);
-	sieve_validator_link_match_type_tags(validator, cmd_reg, OPT_MATCH_TYPE);
+	sieve_match_types_link_tags(validator, cmd_reg, OPT_MATCH_TYPE);
 	
 	return TRUE;
 }
@@ -152,6 +153,7 @@ static bool ext_envelope_opcode_dump
                 sieve_opr_comparator_dump(interp, sbin, address);
                 break;
             case OPT_MATCH_TYPE:
+                sieve_opr_match_type_dump(interp, sbin, address);
                 break;
 			case OPT_ADDRESS_PART:
                 sieve_opr_address_part_dump(interp, sbin, address);
diff --git a/src/lib-sieve/sieve-address-parts.c b/src/lib-sieve/sieve-address-parts.c
index 2d0fe667fe81c4f233ea8835d70ce2aacdf1ecf9..9dde6c69fa46373276daf0c401d80c38ed290b5e 100644
--- a/src/lib-sieve/sieve-address-parts.c
+++ b/src/lib-sieve/sieve-address-parts.c
@@ -339,7 +339,7 @@ bool sieve_opr_address_part_dump
 	if ( addrp == NULL )
 		return FALSE;
 		
-	printf("%08x:   ADDRP: %s\n", pc, addrp->identifier);
+	printf("%08x:   ADDRESS-PART: %s\n", pc, addrp->identifier);
 	
 	return TRUE;
 }
diff --git a/src/lib-sieve/sieve-code.c b/src/lib-sieve/sieve-code.c
index af601d01a9091e13b9fb5d832087c61f2777e508..8764bba6f9ecbec46c341798cf37506dce0adecb 100644
--- a/src/lib-sieve/sieve-code.c
+++ b/src/lib-sieve/sieve-code.c
@@ -219,6 +219,7 @@ const struct sieve_operand stringlist_operand =
 /* Core operands */
 
 extern struct sieve_operand comparator_operand;
+extern struct sieve_operand match_type_operand;
 extern struct sieve_operand address_part_operand;
 
 const struct sieve_operand *sieve_operands[] = {
@@ -227,7 +228,7 @@ const struct sieve_operand *sieve_operands[] = {
 	&string_operand,
 	&stringlist_operand,
 	&comparator_operand,
-	NULL,
+	&match_type_operand,
 	&address_part_operand
 }; 
 
diff --git a/src/lib-sieve/sieve-comparators.c b/src/lib-sieve/sieve-comparators.c
index c153243f29d604fb77be6f730f11177113220353..be3f751e309aa2782ce10648f7c6c9eea948bcc1 100644
--- a/src/lib-sieve/sieve-comparators.c
+++ b/src/lib-sieve/sieve-comparators.c
@@ -119,7 +119,7 @@ const struct sieve_comparator *sieve_comparator_find
 
 	if ( ext_id != NULL ) *ext_id = reg->ext_id;
 
-  return reg->address_part;
+  return reg->comparator;
 }
 
 bool cmp_validator_load(struct sieve_validator *validator)
@@ -347,7 +347,7 @@ bool sieve_opr_comparator_dump
 	if ( cmp == NULL )
 		return FALSE;
 		
-	printf("%08x:   CMP: %s\n", pc, cmp->identifier);
+	printf("%08x:   COMPARATOR: %s\n", pc, cmp->identifier);
 	
 	return TRUE;
 }
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index 40491cbc77f3922da1c97ddad593a5c95bddc7d8..4f4638a75176880d635d43839d552d20350ef68f 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -10,11 +10,19 @@
 static void sieve_extensions_init_registry(void);
 static void sieve_extensions_deinit_registry(void);
 
-/* Core extensions */
+/* Pre-loaded extensions */
 
 extern const struct sieve_extension comparator_extension;
+extern const struct sieve_extension match_type_extension;
 extern const struct sieve_extension address_part_extension;
 
+const struct sieve_extension *sieve_preloaded_extensions[] = {
+	&comparator_extension, &match_type_extension, &address_part_extension 
+};
+
+const unsigned int sieve_preloaded_extensions_count = 
+	N_ELEMENTS(sieve_preloaded_extensions);
+
 /* Dummy extensions */
 
 static const struct sieve_extension comparator_i_octet_extension = {
@@ -40,7 +48,7 @@ extern const struct sieve_extension subaddress_extension;
 extern const struct sieve_extension comparator_i_ascii_numeric_extension;
 
 const struct sieve_extension *sieve_core_extensions[] = {
-	&comparator_extension, &address_part_extension, 
+	&comparator_extension, &match_type_extension, &address_part_extension, 
 	&comparator_i_octet_extension, &comparator_i_ascii_casemap_extension, 
 	&fileinto_extension, &reject_extension, &envelope_extension, 
 	
diff --git a/src/lib-sieve/sieve-extensions.h b/src/lib-sieve/sieve-extensions.h
index 9334747ed625c2e3140d0e1221a3b5075b3a6abb..cd4e155a542e4342ec3a5daee60b3e22aad9ac17 100644
--- a/src/lib-sieve/sieve-extensions.h
+++ b/src/lib-sieve/sieve-extensions.h
@@ -17,6 +17,9 @@ struct sieve_extension {
 	const struct sieve_operand *operand;
 };
 
+extern const struct sieve_extension *sieve_preloaded_extensions[];
+extern const unsigned int sieve_preloaded_extensions_count;
+
 const struct sieve_extension *sieve_extension_acquire(const char *extension);
 
 /* Extensions state */
diff --git a/src/lib-sieve/sieve-interpreter.c b/src/lib-sieve/sieve-interpreter.c
index 68035454f7c340ecc17ff6b3924849a1e577fdc5..6a5d4b8a5c076c4e362ef4ad6961820b8e1c76d5 100644
--- a/src/lib-sieve/sieve-interpreter.c
+++ b/src/lib-sieve/sieve-interpreter.c
@@ -33,12 +33,10 @@ struct sieve_interpreter {
 	struct mail *mail;	
 };
 
-extern struct sieve_extension comparator_extension;
-extern struct sieve_extension address_part_extension;
-
 struct sieve_interpreter *sieve_interpreter_create(struct sieve_binary *binary) 
 {
-	int i;
+	unsigned int i;
+	int idx;
 	pool_t pool;
 	struct sieve_interpreter *interp;
 	
@@ -53,15 +51,19 @@ struct sieve_interpreter *sieve_interpreter_create(struct sieve_binary *binary)
 	interp->pc = 0;
 
 	p_array_init(&interp->ext_contexts, pool, 4);
-	//array_create(&interp->ext_contexts, pool, sizeof(void *), 
-	//		sieve_extensions_get_count());
-	
-	(void)comparator_extension.interpreter_load(interp);	
-	(void)address_part_extension.interpreter_load(interp);	
 
-	for ( i = 0; i < sieve_binary_extensions_count(binary); i++ ) {
+	/* Pre-load core language features implemented as 'extensions' */
+	for ( i = 0; i < sieve_preloaded_extensions_count; i++ ) {
+		const struct sieve_extension *ext = sieve_preloaded_extensions[i];
+		
+		if ( ext->interpreter_load != NULL )
+			(void)ext->interpreter_load(interp);		
+	}
+
+	/* Load other extensions listed in the binary */
+	for ( idx = 0; idx < sieve_binary_extensions_count(binary); idx++ ) {
 		const struct sieve_extension *ext = 
-			sieve_binary_extension_get_by_index(binary, i, NULL);
+			sieve_binary_extension_get_by_index(binary, idx, NULL);
 		
 		if ( ext->interpreter_load != NULL )
 			ext->interpreter_load(interp);
diff --git a/src/lib-sieve/sieve-match-types.c b/src/lib-sieve/sieve-match-types.c
new file mode 100644
index 0000000000000000000000000000000000000000..48e16c3ca421a90f86c2339b36acc29b86d44bfa
--- /dev/null
+++ b/src/lib-sieve/sieve-match-types.c
@@ -0,0 +1,412 @@
+#include <stdio.h>
+
+#include "lib.h"
+#include "compat.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-match-types.h"
+
+#include <string.h>
+
+/* 
+ * Predeclarations 
+ */
+ 
+static void opr_match_type_emit
+	(struct sieve_binary *sbin, struct sieve_match_type *addrp);
+static void opr_match_type_emit_ext
+	(struct sieve_binary *sbin, struct sieve_match_type *addrp, int ext_id);
+
+/* 
+ * Address-part 'extension' 
+ */
+
+static int ext_my_id = -1;
+
+static bool mtch_extension_load(int ext_id);
+static bool mtch_validator_load(struct sieve_validator *validator);
+static bool mtch_interpreter_load(struct sieve_interpreter *interp);
+
+const struct sieve_extension match_type_extension = {
+	"@match-type",
+	mtch_extension_load,
+	mtch_validator_load,
+	NULL,
+	mtch_interpreter_load,
+	NULL,
+	NULL
+};
+	
+static bool mtch_extension_load(int ext_id) 
+{
+	ext_my_id = ext_id;
+	return TRUE;
+}
+
+/* 
+ * Validator context:
+ *   name-based match-type registry. 
+ *
+ * FIXME: This code will be duplicated across all extensions that introduce 
+ * a registry of some kind in the validator. 
+ */
+ 
+struct mtch_validator_registration {
+	int ext_id;
+	const struct sieve_match_type *match_type;
+};
+ 
+struct mtch_validator_context {
+	struct hash_table *registrations;
+};
+
+static inline struct mtch_validator_context *
+	get_validator_context(struct sieve_validator *validator)
+{
+	return (struct mtch_validator_context *) 
+		sieve_validator_extension_get_context(validator, ext_my_id);
+}
+
+static void _sieve_match_type_register
+	(pool_t pool, struct mtch_validator_context *ctx, 
+	const struct sieve_match_type *addrp, int ext_id) 
+{
+	struct mtch_validator_registration *reg;
+	
+	reg = p_new(pool, struct mtch_validator_registration, 1);
+	reg->match_type = addrp;
+	reg->ext_id = ext_id;
+	
+	hash_insert(ctx->registrations, (void *) addrp->identifier, (void *) reg);
+}
+ 
+void sieve_match_type_register
+	(struct sieve_validator *validator, 
+	const struct sieve_match_type *addrp, int ext_id) 
+{
+	pool_t pool = sieve_validator_pool(validator);
+	struct mtch_validator_context *ctx = get_validator_context(validator);
+
+	_sieve_match_type_register(pool, ctx, addrp, ext_id);
+}
+
+const struct sieve_match_type *sieve_match_type_find
+	(struct sieve_validator *validator, const char *identifier,
+		int *ext_id) 
+{
+	struct mtch_validator_context *ctx = get_validator_context(validator);
+	struct mtch_validator_registration *reg =
+		(struct mtch_validator_registration *) 
+			hash_lookup(ctx->registrations, identifier);
+			
+	if ( reg == NULL ) return NULL;
+
+	if ( ext_id != NULL ) *ext_id = reg->ext_id;
+
+  return reg->match_type;
+}
+
+bool mtch_validator_load(struct sieve_validator *validator)
+{
+	unsigned int i;
+	pool_t pool = sieve_validator_pool(validator);
+	
+	struct mtch_validator_context *ctx = 
+		p_new(pool, struct mtch_validator_context, 1);
+	
+	/* Setup match-type registry */
+	ctx->registrations = hash_create
+		(pool, pool, 0, str_hash, (hash_cmp_callback_t *)strcmp);
+
+	/* Register core match-types */
+	for ( i = 0; i < sieve_core_match_types_count; i++ ) {
+		const struct sieve_match_type *addrp = sieve_core_match_types[i];
+		
+		_sieve_match_type_register(pool, ctx, addrp, -1);
+	}
+
+	sieve_validator_extension_set_context(validator, ext_my_id, ctx);
+
+	return TRUE;
+}
+
+void sieve_match_types_link_tags
+	(struct sieve_validator *validator, 
+		struct sieve_command_registration *cmd_reg, unsigned int id_code) 
+{	
+	sieve_validator_register_tag
+		(validator, cmd_reg, &match_type_tag, id_code); 	
+}
+
+/*
+ * Interpreter context:
+ *
+ * FIXME: This code will be duplicated across all extensions that introduce 
+ * a registry of some kind in the interpreter. 
+ */
+
+struct mtch_interpreter_context {
+	ARRAY_DEFINE(mtch_extensions, 
+		const struct sieve_match_type_extension *); 
+};
+
+static inline struct mtch_interpreter_context *
+	get_interpreter_context(struct sieve_interpreter *interpreter)
+{
+	return (struct mtch_interpreter_context *) 
+		sieve_interpreter_extension_get_context(interpreter, ext_my_id);
+}
+
+static const struct sieve_match_type_extension *sieve_match_type_extension_get
+	(struct sieve_interpreter *interpreter, int ext_id)
+{
+	struct mtch_interpreter_context *ctx = get_interpreter_context(interpreter);
+	
+	if ( (ctx != NULL) && (ext_id > 0) && (ext_id < (int) array_count(&ctx->mtch_extensions)) ) {
+		const struct sieve_match_type_extension * const *ext;
+
+		ext = array_idx(&ctx->mtch_extensions, (unsigned int) ext_id);
+
+		return *ext;
+	}
+	
+	return NULL;
+}
+
+void sieve_match_type_extension_set
+	(struct sieve_interpreter *interpreter, int ext_id,
+		const struct sieve_match_type_extension *ext)
+{
+	struct mtch_interpreter_context *ctx = get_interpreter_context(interpreter);
+
+	array_idx_set(&ctx->mtch_extensions, (unsigned int) ext_id, &ext);
+}
+
+static bool mtch_interpreter_load(struct sieve_interpreter *interpreter)
+{
+	pool_t pool = sieve_interpreter_pool(interpreter);
+	
+	struct mtch_interpreter_context *ctx = 
+		p_new(pool, struct mtch_interpreter_context, 1);
+	
+	/* Setup comparator registry */
+	p_array_init(&ctx->mtch_extensions, pool, 4);
+
+	sieve_interpreter_extension_set_context(interpreter, ext_my_id, ctx);
+	
+	return TRUE;
+}
+
+/*
+ * Address-part operand
+ */
+ 
+struct sieve_operand_class match_type_class = 
+	{ "match-type", NULL };
+struct sieve_operand match_type_operand = 
+	{ "match-type", &match_type_class, FALSE };
+
+/* 
+ * Address-part tag 
+ */
+  
+static bool tag_match_type_is_instance_of
+	(struct sieve_validator *validator, const char *tag)
+{
+	return sieve_match_type_find(validator, tag, NULL) != NULL;
+}
+ 
+static bool tag_match_type_validate
+	(struct sieve_validator *validator, 
+	struct sieve_ast_argument **arg, 
+	struct sieve_command_context *cmd)
+{
+	int ext_id;
+	const struct sieve_match_type *addrp;
+
+	/* Syntax:   
+	 *   ":localpart" / ":domain" / ":all" (subject to extension)
+   */
+	
+	/* Get match_type from registry */
+	addrp = sieve_match_type_find
+		(validator, sieve_ast_argument_tag(*arg), &ext_id);
+	
+	/* In theory, addrp can never be NULL, because we must have found it earlier
+	 * to get here.
+	 */
+	if ( addrp == NULL ) {
+		sieve_command_validate_error(validator, cmd, 
+			"unknown match-type modifier '%s' "
+			"(this error should not occur and is probably a bug)", 
+			sieve_ast_argument_strc(*arg));
+
+		return FALSE;
+	}
+
+	/* Store match-type in context */
+	(*arg)->context = (void *) addrp;
+	(*arg)->ext_id = ext_id;
+	
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/* Code generation */
+
+static void opr_match_type_emit
+	(struct sieve_binary *sbin, struct sieve_match_type *addrp)
+{ 
+	(void) sieve_operand_emit_code(sbin, SIEVE_OPERAND_MATCH_TYPE);
+	(void) sieve_binary_emit_byte(sbin, addrp->code);
+}
+
+static void opr_match_type_emit_ext
+	(struct sieve_binary *sbin, struct sieve_match_type *addrp, int ext_id)
+{ 
+	unsigned char mtch_code = SIEVE_MATCH_TYPE_CUSTOM + 
+		sieve_binary_extension_get_index(sbin, ext_id);
+	
+	(void) sieve_operand_emit_code(sbin, SIEVE_OPERAND_MATCH_TYPE);	
+	(void) sieve_binary_emit_byte(sbin, mtch_code);
+	if ( addrp->extension->match_type == NULL )
+		(void) sieve_binary_emit_byte(sbin, addrp->ext_code);
+}
+
+const struct sieve_match_type *sieve_opr_match_type_read
+  (struct sieve_interpreter *interpreter, 
+		struct sieve_binary *sbin, sieve_size_t *address)
+{
+	unsigned int mtch_code;
+	const struct sieve_operand *operand = sieve_operand_read(sbin, address);
+	
+	if ( operand == NULL || operand->class != &match_type_class ) 
+		return NULL;
+	
+	if ( sieve_binary_read_byte(sbin, address, &mtch_code) ) {
+		if ( mtch_code < SIEVE_MATCH_TYPE_CUSTOM ) {
+			if ( mtch_code < sieve_core_match_types_count )
+				return sieve_core_match_types[mtch_code];
+			else
+				return NULL;
+		} else {
+			int ext_id = -1;
+			const struct sieve_match_type_extension *ap_ext;
+
+			if ( sieve_binary_extension_get_by_index(sbin,
+				mtch_code - SIEVE_MATCH_TYPE_CUSTOM, &ext_id) == NULL )
+				return NULL; 
+
+			ap_ext = sieve_match_type_extension_get(interpreter, ext_id); 
+ 
+			if ( ap_ext != NULL ) {  	
+				unsigned int code;
+				if ( ap_ext->match_type != NULL )
+					return ap_ext->match_type;
+		  	
+				if ( sieve_binary_read_byte(sbin, address, &code) &&
+					ap_ext->get_part != NULL )
+				return ap_ext->get_part(code);
+			} else {
+				i_info("Unknown match-type modifier %d.", mtch_code); 
+			}
+		}
+	}		
+		
+	return NULL; 
+}
+
+bool sieve_opr_match_type_dump
+	(struct sieve_interpreter *interpreter,
+		struct sieve_binary *sbin, sieve_size_t *address)
+{
+	sieve_size_t pc = *address;
+	const struct sieve_match_type *addrp = 
+		sieve_opr_match_type_read(interpreter, sbin, address);
+	
+	if ( addrp == NULL )
+		return FALSE;
+		
+	printf("%08x:   MATCH-TYPE: %s\n", pc, addrp->identifier);
+	
+	return TRUE;
+}
+
+static bool tag_match_type_generate
+	(struct sieve_generator *generator, struct sieve_ast_argument **arg, 
+	struct sieve_command_context *cmd ATTR_UNUSED)
+{
+	struct sieve_binary *sbin = sieve_generator_get_binary(generator);
+	struct sieve_match_type *addrp =
+		(struct sieve_match_type *) (*arg)->context;
+	
+	if ( addrp->extension == NULL ) {
+		if ( addrp->code < SIEVE_MATCH_TYPE_CUSTOM )
+			opr_match_type_emit(sbin, addrp);
+		else
+			return FALSE;
+	} else {
+		opr_match_type_emit_ext(sbin, addrp, (*arg)->ext_id);
+	} 
+		
+	*arg = sieve_ast_argument_next(*arg);
+	
+	return TRUE;
+}
+
+/*
+ * Matching
+ */
+ 
+
+/* 
+ * Core match-type modifiers
+ */
+
+const struct sieve_argument match_type_tag = { 
+	NULL,
+	tag_match_type_is_instance_of, 
+	tag_match_type_validate, 
+	tag_match_type_generate 
+};
+ 
+const struct sieve_match_type is_match_type = {
+	"is",
+	SIEVE_MATCH_TYPE_IS,
+	NULL,
+	0,
+};
+
+const struct sieve_match_type contains_match_type = {
+	"contains",
+	SIEVE_MATCH_TYPE_CONTAINS,
+	NULL,
+	0,
+};
+
+const struct sieve_match_type matches_match_type = {
+	"matches",
+	SIEVE_MATCH_TYPE_MATCHES,
+	NULL,
+	0,
+};
+
+const struct sieve_match_type *sieve_core_match_types[] = {
+	&is_match_type, &contains_match_type, &matches_match_type
+};
+
+const unsigned int sieve_core_match_types_count = 
+	N_ELEMENTS(sieve_core_match_types);
+
+
diff --git a/src/lib-sieve/sieve-match-types.h b/src/lib-sieve/sieve-match-types.h
new file mode 100644
index 0000000000000000000000000000000000000000..7af63242d94df1a0e49a0078144ea94a3a972253
--- /dev/null
+++ b/src/lib-sieve/sieve-match-types.h
@@ -0,0 +1,66 @@
+#ifndef __SIEVE_MATCH_TYPES_H
+#define __SIEVE_MATCH_TYPES_H
+
+#include "sieve-common.h"
+
+enum sieve_match_type_code {
+	SIEVE_MATCH_TYPE_IS,
+	SIEVE_MATCH_TYPE_CONTAINS,
+	SIEVE_MATCH_TYPE_MATCHES,
+	SIEVE_MATCH_TYPE_CUSTOM
+};
+
+struct sieve_match_type_extension;
+
+struct sieve_match_type {
+	const char *identifier;
+	
+	enum sieve_match_type_code code;
+	
+	const struct sieve_match_type_extension *extension;
+	unsigned int ext_code;
+};
+
+struct sieve_match_type_extension {
+	const struct sieve_extension *extension;
+
+	/* Either a single match-type in this extension ... */
+	const struct sieve_match_type *match_type;
+	
+	/* ... or multiple: then the extension must handle emit/read */
+	const struct sieve_match_type *(*get_part)
+		(unsigned int code);
+};
+
+void sieve_match_types_link_tags
+	(struct sieve_validator *validator, 
+		struct sieve_command_registration *cmd_reg,
+		unsigned int id_code);
+		
+void sieve_match_type_register
+	(struct sieve_validator *validator, 
+	const struct sieve_match_type *addrp, int ext_id);
+const struct sieve_match_type *sieve_match_type_find
+	(struct sieve_validator *validator, const char *identifier,
+		int *ext_id);
+void sieve_match_type_extension_set
+	(struct sieve_interpreter *interpreter, int ext_id,
+		const struct sieve_match_type_extension *ext);
+
+extern const struct sieve_argument match_type_tag;
+
+extern const struct sieve_match_type is_match_type;
+extern const struct sieve_match_type contains_match_type;
+extern const struct sieve_match_type matches_match_type;
+
+extern const struct sieve_match_type *sieve_core_match_types[];
+extern const unsigned int sieve_core_match_types_count;
+
+const struct sieve_match_type *sieve_opr_match_type_read
+  (struct sieve_interpreter *interpreter, 
+  	struct sieve_binary *sbin, sieve_size_t *address);
+bool sieve_opr_match_type_dump
+	(struct sieve_interpreter *interpreter,
+		struct sieve_binary *sbin, sieve_size_t *address);
+
+#endif /* __SIEVE_MATCH_TYPES_H */
diff --git a/src/lib-sieve/sieve-validator.c b/src/lib-sieve/sieve-validator.c
index cada9ae852aa1669ca74a365b4469fc30a0b6f25..2c2cd3f39c212608ae8cb0f3cf55af95bd021b13 100644
--- a/src/lib-sieve/sieve-validator.c
+++ b/src/lib-sieve/sieve-validator.c
@@ -52,11 +52,9 @@ void sieve_validator_error(struct sieve_validator *validator, struct sieve_ast_n
 	va_end(args);
 }
 
-extern struct sieve_extension comparator_extension;
-extern struct sieve_extension address_part_extension;
-
 struct sieve_validator *sieve_validator_create(struct sieve_ast *ast, struct sieve_error_handler *ehandler) 
 {
+	unsigned int i;
 	pool_t pool;
 	struct sieve_validator *validator;
 	
@@ -73,8 +71,12 @@ struct sieve_validator *sieve_validator_create(struct sieve_ast *ast, struct sie
 		sieve_extensions_get_count());
 		
 	/* Pre-load core language features implemented as 'extensions' */
-	(void)comparator_extension.validator_load(validator);	
-	(void)address_part_extension.validator_load(validator);	
+	for ( i = 0; i < sieve_preloaded_extensions_count; i++ ) {
+		const struct sieve_extension *ext = sieve_preloaded_extensions[i];
+		
+		if ( ext->validator_load != NULL )
+			(void)ext->validator_load(validator);		
+	}
 
 	/* Setup command registry */
 	validator->commands = hash_create
@@ -327,39 +329,6 @@ inline const void *sieve_validator_extension_get_context(struct sieve_validator
 	return array_idx(&validator->ext_contexts, (unsigned int) ext_id);		
 }
 
-/* Match type validation */
-
-static bool sieve_validate_match_type_tag
-	(struct sieve_validator *validator ATTR_UNUSED, 
-	struct sieve_ast_argument **arg, 
-	struct sieve_command_context *cmd ATTR_UNUSED)
-{
-	/* Syntax:   
-	 *   ":is" / ":contains" / ":matches"
-   */
-	
-	/* Not implemented, so delete it */
-	*arg = sieve_ast_arguments_delete(*arg, 1);
-		
-	return TRUE;
-}
-
-static const struct sieve_argument match_is_tag = 
-	{ "is", NULL, sieve_validate_match_type_tag, NULL };
-static const struct sieve_argument match_contains_tag = 
-	{ "contains", NULL, sieve_validate_match_type_tag, NULL };
-static const struct sieve_argument match_matches_tag = 
-	{ "matches", NULL, sieve_validate_match_type_tag, NULL };
-
-void sieve_validator_link_match_type_tags
-	(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg,
-		unsigned int id_code) 
-{
-	sieve_validator_register_tag(validator, cmd_reg, &match_is_tag, id_code); 	
-	sieve_validator_register_tag(validator, cmd_reg, &match_contains_tag, id_code); 	
-	sieve_validator_register_tag(validator, cmd_reg, &match_matches_tag, id_code); 	
-}
-
 /* Tag Validation API */
 
 /* Test validation API */
diff --git a/src/lib-sieve/tst-address.c b/src/lib-sieve/tst-address.c
index fcff7543eebfe34534a3229b91b7aadde0a722e4..91b085810bf8d694d37e6a33cfde3508e57d912c 100644
--- a/src/lib-sieve/tst-address.c
+++ b/src/lib-sieve/tst-address.c
@@ -4,6 +4,7 @@
 #include "sieve-commands-private.h"
 
 #include "sieve-comparators.h"
+#include "sieve-match-types.h"
 #include "sieve-address-parts.h"
 
 #include "sieve-validator.h"
@@ -36,7 +37,7 @@ bool tst_address_registered(struct sieve_validator *validator, struct sieve_comm
 	/* The order of these is not significant */
 	sieve_comparators_link_tag(validator, cmd_reg, OPT_COMPARATOR );
 	sieve_address_parts_link_tags(validator, cmd_reg, OPT_ADDRESS_PART);
-	sieve_validator_link_match_type_tags(validator, cmd_reg, OPT_MATCH_TYPE);
+	sieve_match_types_link_tags(validator, cmd_reg, OPT_MATCH_TYPE);
 
 	return TRUE;
 }
@@ -111,6 +112,8 @@ static bool tst_address_opcode_dump
 					return FALSE;
                 break;
             case OPT_MATCH_TYPE:
+				if ( !sieve_opr_match_type_dump(interp, sbin, address) )
+                    return FALSE;
                 break;
 			case OPT_ADDRESS_PART:
 				if ( !sieve_opr_address_part_dump(interp, sbin, address) )
@@ -135,6 +138,7 @@ static bool tst_address_opcode_execute
 	struct mail *mail = sieve_interpreter_get_mail(interp);
 	
 	const struct sieve_comparator *cmp = &i_octet_comparator;
+	const struct sieve_match_type *mtch = &is_match_type;
 	const struct sieve_address_part *addrp = &all_address_part;
 	unsigned int opt_code;
 	struct sieve_coded_stringlist *hdr_list;
@@ -153,6 +157,8 @@ static bool tst_address_opcode_execute
 					return FALSE;
                 break;
             case OPT_MATCH_TYPE:
+                if ( (mtch = sieve_opr_match_type_read(interp, sbin, address)) == NULL )
+					return FALSE;
                 break;
 			case OPT_ADDRESS_PART:
 				if ( (addrp = sieve_opr_address_part_read(interp, sbin, address)) == NULL )
diff --git a/src/lib-sieve/tst-header.c b/src/lib-sieve/tst-header.c
index e06ddb2a26d7c0ea0aa3d51f241fad788b45ee96..a7eb0a950197da13c641ee2a5e18906c0c00ecad 100644
--- a/src/lib-sieve/tst-header.c
+++ b/src/lib-sieve/tst-header.c
@@ -3,6 +3,7 @@
 #include "sieve-commands.h"
 #include "sieve-commands-private.h"
 #include "sieve-comparators.h"
+#include "sieve-match-types.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
 #include "sieve-interpreter.h"
@@ -31,7 +32,7 @@ bool tst_header_registered(struct sieve_validator *validator, struct sieve_comma
 {
 	/* The order of these is not significant */
 	sieve_comparators_link_tag(validator, cmd_reg, OPT_COMPARATOR);
-	sieve_validator_link_match_type_tags(validator, cmd_reg, OPT_MATCH_TYPE);
+	sieve_match_types_link_tags(validator, cmd_reg, OPT_MATCH_TYPE);
 
 	return TRUE;
 }
@@ -102,6 +103,7 @@ static bool tst_header_opcode_dump
 				sieve_opr_comparator_dump(interp, sbin, address);
 				break;
 			case OPT_MATCH_TYPE:
+				sieve_opr_match_type_dump(interp, sbin, address);
 				break;
 			default: 
 				return FALSE;
@@ -123,6 +125,7 @@ static bool tst_header_opcode_execute
 
 	unsigned int opt_code;
 	const struct sieve_comparator *cmp = &i_octet_comparator;
+	const struct sieve_match_type *mtch = &is_match_type;
 	struct sieve_coded_stringlist *hdr_list;
 	struct sieve_coded_stringlist *key_list;
 	string_t *hdr_item;
@@ -138,6 +141,7 @@ static bool tst_header_opcode_execute
                 cmp = sieve_opr_comparator_read(interp, sbin, address);
                 break;
             case OPT_MATCH_TYPE:
+                mtch = sieve_opr_match_type_read(interp, sbin, address);
                 break;
             default:
                 return FALSE;