diff --git a/Makefile.am b/Makefile.am
index 128ea65443dc2af3667a07d5b0bfaa874a285ac3..62ea4386efe88ecc866ca420a998d82c09489801 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -162,6 +162,8 @@ test_cases = \
 	tests/extensions/metadata/execute.svtest \
 	tests/extensions/metadata/errors.svtest \
 	tests/extensions/vnd.dovecot/debug/execute.svtest \
+	tests/extensions/vnd.dovecot/environment/basic.svtest \
+	tests/extensions/vnd.dovecot/environment/variables.svtest \
 	tests/deprecated/notify/basic.svtest \
 	tests/deprecated/notify/mailto.svtest \
 	tests/deprecated/notify/errors.svtest \
diff --git a/configure.ac b/configure.ac
index 0d478385679573d56bb429e2a4911b88df4b675d..97a585c596afd71fe213aff57c545362f13870ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -211,6 +211,7 @@ src/lib-sieve/plugins/duplicate/Makefile
 src/lib-sieve/plugins/index/Makefile
 src/lib-sieve/plugins/vnd.dovecot/Makefile
 src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
+src/lib-sieve/plugins/vnd.dovecot/environment/Makefile
 src/lib-sieve-tool/Makefile
 src/lib-managesieve/Makefile
 src/plugins/Makefile
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index bdc35c80c4978c5a2d275aaf8ae076c07efa68d2..98ae11f178897b31dc4a646659179a3ae7796564 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -78,6 +78,7 @@ plugins = \
 	$(extdir)/index/libsieve_ext_index.la \
 	$(extdir)/metadata/libsieve_ext_metadata.la \
 	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+	$(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
 	$(unfinished_plugins)
 
 libdovecot_sieve_la_DEPENDENCIES = \
diff --git a/src/lib-sieve/plugins/environment/ext-environment-common.c b/src/lib-sieve/plugins/environment/ext-environment-common.c
index b8680c1f000bc91ddd7128b9eb5ac68ea3225ef4..9fd49ba6011b069013ff50e701c70b7707bd0e88 100644
--- a/src/lib-sieve/plugins/environment/ext-environment-common.c
+++ b/src/lib-sieve/plugins/environment/ext-environment-common.c
@@ -6,14 +6,10 @@
 
 #include "sieve-common.h"
 #include "sieve-extensions.h"
+#include "sieve-interpreter.h"
 
 #include "ext-environment-common.h"
 
-struct ext_environment_context {
-	HASH_TABLE(const char *,
-		   const struct sieve_environment_item *) environment_items;
-};
-
 /*
  * Core environment items
  */
@@ -30,71 +26,121 @@ static const struct sieve_environment_item *core_env_items[] = {
 static unsigned int core_env_items_count = N_ELEMENTS(core_env_items);
 
 /*
- * Registration
+ * Validator context
  */
 
-static void ext_environment_item_register
-(struct ext_environment_context *ectx,
-	const struct sieve_environment_item *item)
+struct ext_environment_interpreter_context {
+	HASH_TABLE(const char *,
+		   const struct sieve_environment_item *) environment_items;
+
+	unsigned int active:1;
+};
+
+static void ext_environment_interpreter_extension_free
+	(const struct sieve_extension *ext, struct sieve_interpreter *interp,
+		void *context);
+
+struct sieve_interpreter_extension environment_interpreter_extension = {
+	.ext_def = &environment_extension,
+	.free = ext_environment_interpreter_extension_free,
+};
+
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_create
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
 {
-	hash_table_insert(ectx->environment_items, item->name, item);
+	pool_t pool = sieve_interpreter_pool(interp);
+	struct ext_environment_interpreter_context *ctx;
+
+	ctx = p_new(pool, struct ext_environment_interpreter_context, 1);
+
+	hash_table_create
+		(&ctx->environment_items, default_pool, 0, str_hash, strcmp);
+
+	sieve_interpreter_extension_register
+		(interp, this_ext, &environment_interpreter_extension, (void *)ctx);
+	return ctx;
 }
 
-void sieve_ext_environment_item_register
-(const struct sieve_extension *ext, const struct sieve_environment_item *item)
+static void ext_environment_interpreter_extension_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_interpreter *interp ATTR_UNUSED, 	void *context)
 {
-	struct ext_environment_context *ectx =
-		(struct ext_environment_context *) ext->context;
+	struct ext_environment_interpreter_context *ctx =
+		(struct ext_environment_interpreter_context *)context;
 
-	ext_environment_item_register(ectx, item);
+	hash_table_destroy(&ctx->environment_items);
 }
 
-/*
- * Initialization
- */
-
-bool ext_environment_init
-(const struct sieve_extension *ext ATTR_UNUSED, void **context)
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_get
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
 {
-	struct ext_environment_context *ectx =
-		i_new(struct ext_environment_context, 1);
+	struct ext_environment_interpreter_context *ctx =
+		(struct ext_environment_interpreter_context *)
+		sieve_interpreter_extension_get_context(interp, this_ext);
 
+	if ( ctx == NULL )
+		ctx = ext_environment_interpreter_context_create(this_ext, interp);
+
+	return ctx;
+}
+
+void ext_environment_interpreter_init
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_environment_interpreter_context *ctx;
 	unsigned int i;
 
-	hash_table_create
-		(&ectx->environment_items, default_pool, 0, str_hash, strcmp);
+	/* Create our context */
+	ctx = ext_environment_interpreter_context_get(this_ext, interp);
 
 	for ( i = 0; i < core_env_items_count; i++ ) {
-		ext_environment_item_register(ectx, core_env_items[i]);
+		const struct sieve_environment_item *item = core_env_items[i];
+		hash_table_insert(ctx->environment_items, item->name, item);
 	}
 
-	*context = (void *) ectx;
-
-	return TRUE;
+	ctx->active = TRUE;
 }
 
-void ext_environment_deinit(const struct sieve_extension *ext)
+bool sieve_ext_environment_is_active
+(const struct sieve_extension *env_ext, struct sieve_interpreter *interp)
 {
-	struct ext_environment_context *ectx =
-		(struct ext_environment_context *) ext->context;
+	struct ext_environment_interpreter_context *ctx =
+		ext_environment_interpreter_context_get(env_ext, interp);
 
-	hash_table_destroy(&ectx->environment_items);
-	i_free(ectx);
+	return ( ctx != NULL && ctx->active );
 }
 
+/*
+ * Registration
+ */
+
+void sieve_environment_item_register
+(const struct sieve_extension *env_ext, struct sieve_interpreter *interp,
+	const struct sieve_environment_item *item)
+{
+	struct ext_environment_interpreter_context *ctx;
+
+	i_assert( sieve_extension_is(env_ext, environment_extension) );
+	ctx = ext_environment_interpreter_context_get(env_ext, interp);
+	hash_table_insert(ctx->environment_items, item->name, item);
+}
 
 /*
  * Retrieval
  */
 
 const char *ext_environment_item_get_value
-(const struct sieve_extension *ext, const char *name,
-	const struct sieve_script_env *senv)
+(const struct sieve_extension *env_ext,
+	const struct sieve_runtime_env *renv, const char *name)
 {
-	struct ext_environment_context *ectx =
-		(struct ext_environment_context *) ext->context;
+	struct ext_environment_interpreter_context *ctx =
+		ext_environment_interpreter_context_get(env_ext, renv->interp);
 	const struct sieve_environment_item *item =
-		hash_table_lookup(ectx->environment_items, name);
+		hash_table_lookup(ctx->environment_items, name);
+
+	i_assert( sieve_extension_is(env_ext, environment_extension) );
 
 	if ( item == NULL )
 		return NULL;
@@ -103,7 +149,7 @@ const char *ext_environment_item_get_value
 		return item->value;
 
 	if ( item->get_value != NULL )
-		return item->get_value(ext->svinst, senv);
+		return item->get_value(renv);
 
 	return NULL;
 }
@@ -119,10 +165,9 @@ const char *ext_environment_item_get_value
  */
 
 static const char *envit_domain_get_value
-(struct sieve_instance *svinst,
-	const struct sieve_script_env *senv ATTR_UNUSED)
+(const struct sieve_runtime_env *renv)
 {
-	return svinst->domainname;
+	return renv->svinst->domainname;
 }
 
 const struct sieve_environment_item domain_env_item = {
@@ -137,10 +182,9 @@ const struct sieve_environment_item domain_env_item = {
  */
 
 static const char *envit_host_get_value
-(struct sieve_instance *svinst,
-	const struct sieve_script_env *senv ATTR_UNUSED)
+(const struct sieve_runtime_env *renv)
 {
-	return svinst->hostname;
+	return renv->svinst->hostname;
 }
 
 const struct sieve_environment_item host_env_item = {
@@ -160,10 +204,9 @@ const struct sieve_environment_item host_env_item = {
  */
 
 static const char *envit_location_get_value
-(struct sieve_instance *svinst,
-	const struct sieve_script_env *senv ATTR_UNUSED)
+(const struct sieve_runtime_env *renv)
 {
-	switch ( svinst->env_location ) {
+	switch ( renv->svinst->env_location ) {
 	case SIEVE_ENV_LOCATION_MDA:
 		return "MDA";
 	case SIEVE_ENV_LOCATION_MTA:
@@ -190,10 +233,9 @@ const struct sieve_environment_item location_env_item = {
  */
 
 static const char *envit_phase_get_value
-(struct sieve_instance *svinst,
-	const struct sieve_script_env *senv ATTR_UNUSED)
+(const struct sieve_runtime_env *renv)
 {
-	switch ( svinst->delivery_phase ) {
+	switch ( renv->svinst->delivery_phase ) {
 	case SIEVE_DELIVERY_PHASE_PRE:
 		return "pre";
 	case SIEVE_DELIVERY_PHASE_DURING:
diff --git a/src/lib-sieve/plugins/environment/ext-environment-common.h b/src/lib-sieve/plugins/environment/ext-environment-common.h
index eedb6ee7a0ea9dad479bfa2ada027ac90f10e1d6..9c8fec73a6494d6e4a43f24cccc1baf7074f0f2f 100644
--- a/src/lib-sieve/plugins/environment/ext-environment-common.h
+++ b/src/lib-sieve/plugins/environment/ext-environment-common.h
@@ -47,11 +47,10 @@ bool ext_environment_init(const struct sieve_extension *ext, void **context);
 void ext_environment_deinit(const struct sieve_extension *ext);
 
 /*
- * Environment item retrieval
+ * Validator context
  */
 
-const char *ext_environment_item_get_value
-	(const struct sieve_extension *ext, const char *name,
-		const struct sieve_script_env *senv);
+void ext_environment_interpreter_init
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp);
 
 #endif /* __EXT_VARIABLES_COMMON_H */
diff --git a/src/lib-sieve/plugins/environment/ext-environment.c b/src/lib-sieve/plugins/environment/ext-environment.c
index 8abc659032565417cedb29fb5edb68036f6cfd2c..e536864c43dcc212c990f039d97f417c74406fef 100644
--- a/src/lib-sieve/plugins/environment/ext-environment.c
+++ b/src/lib-sieve/plugins/environment/ext-environment.c
@@ -30,12 +30,14 @@
 
 static bool ext_environment_validator_load
 	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv, sieve_size_t *address);
 
 const struct sieve_extension_def environment_extension = {
 	.name = "environment",
-	.load = ext_environment_init,
-	.unload = ext_environment_deinit,
 	.validator_load = ext_environment_validator_load,
+	.interpreter_load = ext_environment_interpreter_load,
 	SIEVE_EXT_DEFINE_OPERATION(tst_environment_operation)
 };
 
@@ -43,7 +45,15 @@ static bool ext_environment_validator_load
 (const struct sieve_extension *ext, struct sieve_validator *valdtr)
 {
 	sieve_validator_register_command(valdtr, ext, &tst_environment);
+	return TRUE;
+}
 
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	ext_environment_interpreter_init(ext, renv->interp);
 	return TRUE;
 }
 
diff --git a/src/lib-sieve/plugins/environment/sieve-ext-environment.h b/src/lib-sieve/plugins/environment/sieve-ext-environment.h
index a0c3259fe5340d97fb09ffd2ab6811cbd30c00fb..f5a52e2460bd211ed6cb75d365c75da2f741efd6 100644
--- a/src/lib-sieve/plugins/environment/sieve-ext-environment.h
+++ b/src/lib-sieve/plugins/environment/sieve-ext-environment.h
@@ -6,16 +6,43 @@
 
 #include "sieve-common.h"
 
+/*
+ * Environment extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def environment_extension;
+
+static inline const struct sieve_extension *
+sieve_ext_environment_get_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_register
+		(svinst, &environment_extension, FALSE);
+}
+
+bool sieve_ext_environment_is_active
+	(const struct sieve_extension *env_ext,
+		struct sieve_interpreter *interp);
+
+/*
+ * Environment item
+ */
+
 struct sieve_environment_item {
 	const char *name;
 
 	const char *value;
 	const char *(*get_value)
-		(struct sieve_instance *svinst, const struct sieve_script_env *senv);
+		(const struct sieve_runtime_env *renv);
 };
 
-void sieve_ext_environment_item_register
-	(const struct sieve_extension *ext,
+void sieve_environment_item_register
+	(const struct sieve_extension *env_ext, struct sieve_interpreter *interp,
 		const struct sieve_environment_item *item);
+const char *ext_environment_item_get_value
+	(const struct sieve_extension *env_ext,
+		const struct sieve_runtime_env *renv, const char *name);
 
 #endif
diff --git a/src/lib-sieve/plugins/environment/tst-environment.c b/src/lib-sieve/plugins/environment/tst-environment.c
index bb3fef34f42e7846c0e7e1bf0d78e78fe70e3450..68040291bcc5883301044f6c532c18369810a771 100644
--- a/src/lib-sieve/plugins/environment/tst-environment.c
+++ b/src/lib-sieve/plugins/environment/tst-environment.c
@@ -188,7 +188,7 @@ static int tst_environment_operation_execute
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "environment test");
 
 	env_item = ext_environment_item_get_value
-		(this_ext, str_c(name), renv->scriptenv);
+		(this_ext, renv, str_c(name));
 
 	if ( env_item != NULL ) {
 		/* Construct value list */
diff --git a/src/lib-sieve/plugins/variables/ext-variables-common.c b/src/lib-sieve/plugins/variables/ext-variables-common.c
index 2564efefffa136b2d023baad9419c40b195e5221..2309cd90c864da4cd448a63502637eed4e741689 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-common.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-common.c
@@ -608,8 +608,10 @@ ext_variables_validator_context_create
 struct ext_variables_validator_context *ext_variables_validator_context_get
 (const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
 {
-	struct ext_variables_validator_context *ctx =
-		(struct ext_variables_validator_context *)
+	struct ext_variables_validator_context *ctx;
+
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	ctx = (struct ext_variables_validator_context *)
 		sieve_validator_extension_get_context(valdtr, this_ext);
 
 	if ( ctx == NULL ) {
@@ -768,8 +770,13 @@ static inline struct ext_variables_interpreter_context *
 ext_variables_interpreter_context_get
 	(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
 {
-	return (struct ext_variables_interpreter_context *)
+	struct ext_variables_interpreter_context *ctx;
+
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	ctx = (struct ext_variables_interpreter_context *)
 		sieve_interpreter_extension_get_context(interp, this_ext);
+
+	return ctx;
 }
 
 struct sieve_variable_storage *sieve_ext_variables_runtime_get_storage
diff --git a/src/lib-sieve/plugins/variables/ext-variables-dump.c b/src/lib-sieve/plugins/variables/ext-variables-dump.c
index db4aa886adc6a496c856e07e2cf0abec48760bac..b574ea38bc084d92577164b38276f12418cb6487 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-dump.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-dump.c
@@ -49,10 +49,12 @@ static struct ext_variables_dump_context *ext_variables_dump_get_context
 (const struct sieve_extension *this_ext, const struct sieve_dumptime_env *denv)
 {
 	struct sieve_code_dumper *dumper = denv->cdumper;
-	struct ext_variables_dump_context *dctx = sieve_dump_extension_get_context
-		(dumper, this_ext);
+	struct ext_variables_dump_context *dctx;
 	pool_t pool;
 
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	dctx = sieve_dump_extension_get_context(dumper, this_ext);
+
 	if ( dctx == NULL ) {
 		/* Create dumper context */
 		pool = sieve_code_dumper_pool(dumper);
diff --git a/src/lib-sieve/plugins/variables/ext-variables-namespaces.c b/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
index 8f62a78d99951159336f78e554c919d82a552889..5bd9e9c4b8d1fa4d74341b71951b5c80a633b4a2 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
@@ -182,6 +182,7 @@ void sieve_variables_opr_namespace_variable_emit
 	const struct sieve_extension *ext,
 	const struct sieve_variables_namespace_def *nspc_def)
 {
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
 	sieve_operand_emit(sblock, var_ext, &namespace_variable_operand);
 	sieve_opr_object_emit(sblock, ext, &nspc_def->obj_def);
 }
diff --git a/src/lib-sieve/plugins/variables/ext-variables-operands.c b/src/lib-sieve/plugins/variables/ext-variables-operands.c
index 476e7279c8123cf00ae528727d3cdcaee4d1e8c7..a4930fcd0bdc5af7ecab1379afcd902fadaf5bb1 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-operands.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-operands.c
@@ -52,6 +52,8 @@ void sieve_variables_opr_variable_emit
 (struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
 	struct sieve_variable *var)
 {
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
+
 	if ( var->ext == NULL ) {
 		/* Default variable storage */
 		(void) sieve_operand_emit(sblock, var_ext, &variable_operand);
@@ -222,6 +224,7 @@ void sieve_variables_opr_match_value_emit
 (struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
 	unsigned int index)
 {
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
 	(void) sieve_operand_emit(sblock, var_ext, &match_value_operand);
 	(void) sieve_binary_emit_unsigned(sblock, index);
 }
diff --git a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
index 3c46867ff1f7b519e0efb9b24c05204041061084..65d394e41964b56fc01ac882b6c1d5fc8f01c4a8 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
+++ b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = debug
+SUBDIRS = debug environment
 
diff --git a/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am b/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..5d4e438835c9d834538cd3913ee99c33960a45de
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_vnd_environment.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../../.. \
+	-I$(srcdir)/../../environment \
+	-I$(srcdir)/../../variables \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_vnd_environment_la_SOURCES = \
+	ext-vnd-environment.c \
+	ext-vnd-environment-items.c \
+	ext-vnd-environment-variables.c
+
+noinst_HEADERS =
+	ext-vnd-environment-common.h
+
diff --git a/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h
new file mode 100644
index 0000000000000000000000000000000000000000..97b795e85ebfcb8ea941eeca4284f28e54dee8cb
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#ifndef __EXT_VND_ENVIRONMENT_COMMON_H
+#define __EXT_VND_ENVIRONMENT_COMMON_H
+
+#include "sieve-ext-environment.h"
+
+/*
+ * Extension
+ */
+
+struct ext_vnd_environment_context {
+	const struct sieve_extension *env_ext;
+	const struct sieve_extension *var_ext;
+};
+
+const struct sieve_extension_def vnd_environment_extension;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def environment_namespace_operand;
+
+/*
+ * Environment items
+ */
+
+void ext_vnd_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv);
+
+/*
+ * Variables
+ */
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
diff --git a/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c
new file mode 100644
index 0000000000000000000000000000000000000000..eab13c3ebb691e01126899abfd238d9d3eb8c137
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#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"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Environment items
+ */
+
+/* default_mailbox */
+
+static const char *envit_default_mailbox_get_value
+(const struct sieve_runtime_env *renv)
+{
+	i_assert(renv->scriptenv->default_mailbox != NULL);
+	return renv->scriptenv->default_mailbox;
+}
+
+const struct sieve_environment_item default_mailbox_env_item = {
+	.name = "vnd.dovecot.default-mailbox",
+	.get_value = envit_default_mailbox_get_value
+};
+
+/* username */
+
+static const char *envit_username_get_value
+(const struct sieve_runtime_env *renv)
+{
+	return renv->svinst->username;
+}
+
+const struct sieve_environment_item username_env_item = {
+	.name = "vnd.dovecot.username",
+	.get_value = envit_username_get_value
+};
+
+/*
+ * Register
+ */
+
+void ext_vnd_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) ext->context;
+
+	sieve_environment_item_register
+		(ectx->env_ext, renv->interp, &default_mailbox_env_item);
+	sieve_environment_item_register
+		(ectx->env_ext, renv->interp, &username_env_item);
+}
diff --git a/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c
new file mode 100644
index 0000000000000000000000000000000000000000..4f7082676404a1e817a7eb0bba1d247d5e57600a
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c
@@ -0,0 +1,207 @@
+/* Copyright (c) 2015 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+static bool vnspc_vnd_environment_validate
+	(struct sieve_validator *valdtr,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+		bool assignment);
+static bool vnspc_vnd_environment_generate
+	(const struct sieve_codegen_env *cgenv,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg,
+		struct sieve_command *cmd, void *var_data);
+static bool vnspc_vnd_environment_dump_variable
+	(const struct sieve_dumptime_env *denv,
+		const struct sieve_variables_namespace *nspc, 
+		const struct sieve_operand *oprnd, sieve_size_t *address);
+static int vnspc_vnd_environment_read_variable
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_variables_namespace *nspc,
+		const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_variables_namespace_def
+environment_namespace = {
+	SIEVE_OBJECT("env", &environment_namespace_operand, 0),
+	vnspc_vnd_environment_validate,
+	vnspc_vnd_environment_generate,
+	vnspc_vnd_environment_dump_variable,
+	vnspc_vnd_environment_read_variable
+};
+
+static bool vnspc_vnd_environment_validate
+(struct sieve_validator *valdtr, 
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	struct sieve_ast_argument *arg, struct sieve_command *cmd ATTR_UNUSED,
+	ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+	bool assignment)
+{
+	struct sieve_ast *ast = arg->ast;
+	const struct sieve_variable_name *name_elements;
+	unsigned int i, count;
+	const char *variable;
+	string_t *name;
+
+	/* Compose environment name from parsed variable name */
+	name = t_str_new(64);
+	name_elements = array_get(var_name, &count);
+	i_assert(count > 1);
+	for (i = 1; i < count; i++) {
+		if ( name_elements[i].num_variable >= 0 ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"vnd.dovecot.environment: invalid variable name within "
+				"env namespace `env.%d': "
+				"encountered numeric variable name",
+				name_elements[i].num_variable);
+			return FALSE;
+		}
+		if (str_len(name) > 0)
+			str_append_c(name, '.');
+		str_append_str(name, name_elements[i].identifier);
+	}
+
+	variable = str_c(name);
+
+	if ( assignment ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"vnd.dovecot.environment: cannot assign to environment "
+			"variable `env.%s'", variable);
+		return FALSE;
+	}
+
+	*var_data = (void *) p_strdup(sieve_ast_pool(ast), variable);
+	return TRUE;
+}
+
+static bool vnspc_vnd_environment_generate
+(const struct sieve_codegen_env *cgenv,
+	const struct sieve_variables_namespace *nspc,
+	struct sieve_ast_argument *arg ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);	
+	const char *variable = (const char *) var_data;
+	struct ext_vnd_environment_context *ext_data;
+
+	if ( this_ext == NULL )
+		return FALSE;
+
+	ext_data = (struct ext_vnd_environment_context *) this_ext->context;
+
+	sieve_variables_opr_namespace_variable_emit
+		(cgenv->sblock, ext_data->var_ext, this_ext, &environment_namespace);
+	sieve_binary_emit_cstring(cgenv->sblock, variable);
+
+	return TRUE;
+}
+
+static bool vnspc_vnd_environment_dump_variable
+(const struct sieve_dumptime_env *denv,
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	const struct sieve_operand *oprnd, sieve_size_t *address)
+{
+	string_t *var_name;
+
+	if ( !sieve_binary_read_string(denv->sblock, address, &var_name) )
+		return FALSE;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: VAR ${env.%s}",
+			oprnd->field_name, str_c(var_name));
+	else
+		sieve_code_dumpf(denv, "VAR ${env.%s}",
+			str_c(var_name));
+
+	return TRUE;
+}
+
+static int vnspc_vnd_environment_read_variable
+(const struct sieve_runtime_env *renv,
+	const struct sieve_variables_namespace *nspc,
+	const struct sieve_operand *oprnd, sieve_size_t *address,
+	string_t **str_r)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);	
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) this_ext->context;
+	string_t *var_name;
+	const char *ext_value;
+
+	if ( !sieve_binary_read_string(renv->sblock, address, &var_name) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"environment variable operand corrupt: invalid name");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( str_r !=  NULL ) {
+		const char *vname = str_c(var_name);
+
+		ext_value = ext_environment_item_get_value
+			(ectx->env_ext, renv, vname);
+
+		if ( ext_value == NULL && strchr(vname, '_') != NULL) {
+			char *p, *aname;
+
+			/* Try again with '_' replaced with '-' */
+			aname = t_strdup_noconst(vname);
+			for (p = aname; *p != '\0'; p++) {
+				if (*p == '_')
+					*p = '-';
+			}
+			ext_value = ext_environment_item_get_value
+				(ectx->env_ext, renv, aname);
+		}
+
+		if ( ext_value == NULL ) {
+			*str_r = t_str_new_const("", 0);
+			return SIEVE_EXEC_OK;
+		}
+
+		*str_r = t_str_new_const(ext_value, strlen(ext_value));
+	}
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Namespace registration
+ */
+
+static const struct sieve_extension_objects environment_namespaces =
+	SIEVE_VARIABLES_DEFINE_NAMESPACE(environment_namespace);
+
+const struct sieve_operand_def environment_namespace_operand = {
+	"env-namespace",
+	&vnd_environment_extension,
+	0,
+	&sieve_variables_namespace_operand_class,
+	&environment_namespaces
+};
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_vnd_environment_context *ext_data =
+		(struct ext_vnd_environment_context *) this_ext->context;
+
+	sieve_variables_namespace_register
+		(ext_data->var_ext, valdtr, this_ext, &environment_namespace);
+}
diff --git a/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c
new file mode 100644
index 0000000000000000000000000000000000000000..849d7d7b6e8353c006d373fc6b27ec70ec49d3a7
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.environment
+ * ---------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined
+ * Implementation: preliminary
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#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"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_vnd_environment_load
+	(const struct sieve_extension *ext, void **context);
+static void ext_vnd_environment_unload
+	(const struct sieve_extension *ext);
+static bool ext_vnd_environment_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_vnd_environment_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+const struct sieve_extension_def vnd_environment_extension = {
+	.name = "vnd.dovecot.environment",
+	.load = ext_vnd_environment_load,
+	.unload = ext_vnd_environment_unload,
+	.validator_load = ext_vnd_environment_validator_load,
+	.interpreter_load = ext_vnd_environment_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERAND(environment_namespace_operand)
+};
+
+static bool ext_vnd_environment_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct ext_vnd_environment_context *ectx;
+
+	if ( *context != NULL )
+		ext_vnd_environment_unload(ext);
+
+	ectx = i_new(struct ext_vnd_environment_context, 1);
+	ectx->env_ext = sieve_ext_environment_get_extension(ext->svinst);
+	ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+	*context = (void *) ectx;
+
+	return TRUE;
+}
+
+static void ext_vnd_environment_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) ext->context;
+
+	i_free(ectx);
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_vnd_environment_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	ext_environment_variables_init(ext, valdtr);
+	return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static bool ext_vnd_environment_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	ext_vnd_environment_items_register(ext, renv);
+	return TRUE;
+}
diff --git a/src/lib-sieve/sieve-code.c b/src/lib-sieve/sieve-code.c
index 9af09c0c5fdf381fc45e8b452ac1733c0825bf5d..8c8303ab15aab41b09967e4e782b0199d53076c5 100644
--- a/src/lib-sieve/sieve-code.c
+++ b/src/lib-sieve/sieve-code.c
@@ -800,12 +800,17 @@ int sieve_opr_stringlist_read_data
 			return SIEVE_EXEC_FAILURE;
 		}
 
-		if ( (ret=intf->read(renv, oprnd, address, NULL)) <= 0 )
-			return ret;
+		if ( strlist_r == NULL ) {
+			if ( (ret=intf->read(renv, oprnd, address, NULL)) <= 0 )
+				return ret;
+		} else {
+			string_t *stritem;
+			if ( (ret=intf->read(renv, oprnd, address, &stritem)) <= 0 )
+				return ret;
 
-		if ( strlist_r != NULL )
-			*strlist_r = sieve_code_stringlist_create
-				(renv, oprnd->address, 1, *address);
+			*strlist_r = sieve_single_stringlist_create
+				(renv, stritem, FALSE);
+		}
 		return SIEVE_EXEC_OK;
 	}
 
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index 2f3cc8df9906954ff18c097b747ccf4ef91506c2..c910a4db940a51deaf80023fcb5583da16b7511d 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -139,6 +139,7 @@ extern const struct sieve_extension_def virustest_extension;
 extern const struct sieve_extension_def editheader_extension;
 
 extern const struct sieve_extension_def vnd_debug_extension;
+extern const struct sieve_extension_def vnd_environment_extension;
 
 const struct sieve_extension_def *sieve_extra_extensions[] = {
 	&vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension,
@@ -146,7 +147,7 @@ const struct sieve_extension_def *sieve_extra_extensions[] = {
 	&mboxmetadata_extension, &servermetadata_extension,
 
 	/* vnd.dovecot. */
-	&vnd_debug_extension
+	&vnd_debug_extension, &vnd_environment_extension
 };
 
 const unsigned int sieve_extra_extensions_count =
diff --git a/src/sieve-tools/sieve-filter.c b/src/sieve-tools/sieve-filter.c
index 8e0f4e063af990d42c2f3bd8597b41319defffab..9790966820a1f590d1c977cceaa771b0a4e517d1 100644
--- a/src/sieve-tools/sieve-filter.c
+++ b/src/sieve-tools/sieve-filter.c
@@ -58,6 +58,7 @@ struct sieve_filter_data {
 
 	unsigned int execute:1;
 	unsigned int source_write:1;
+	unsigned int default_move:1;
 };
 
 struct sieve_filter_context {
@@ -227,6 +228,17 @@ static int filter_message
 		return -1;
 	case SIEVE_EXEC_FAILURE:
 	case SIEVE_EXEC_TEMP_FAILURE:
+		if ( source_write && execute && sfctx->data->default_move &&
+			!estatus.keep_original &&	estatus.message_saved ) {
+			/* The implicit keep action moved message to default mailbox, so
+			   the source message still needs to be expunged */
+			sieve_error(ehandler, NULL,
+				"sieve script execution failed for this message; "
+				"message moved to default mailbox");
+			mail_expunge(mail);
+			return 0;
+		}
+		/* Fall through */
 	case SIEVE_EXEC_KEEP_FAILED:
 		sieve_error(ehandler, NULL,
 			"sieve script execution failed for this message; "
@@ -360,7 +372,7 @@ int main(int argc, char **argv)
 	struct sieve_binary *main_sbin;
 	struct sieve_script_env scriptenv;
 	struct sieve_error_handler *ehandler;
-	bool force_compile, execute, source_write, verbose;
+	bool force_compile, execute, source_write, verbose, default_move;
 	struct mail_namespace *ns;
 	struct mailbox *src_box = NULL, *move_box = NULL;
 	enum mailbox_flags open_flags = MAILBOX_FLAG_IGNORE_ACLS;
@@ -374,7 +386,8 @@ int main(int argc, char **argv)
 
 	/* Parse arguments */
 	dst_mailbox = move_mailbox = NULL;
-	force_compile = execute = source_write = verbose = FALSE;
+	force_compile = execute = source_write = default_move = FALSE;
+	verbose = FALSE;	
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
 		case 'm':
@@ -475,6 +488,9 @@ int main(int argc, char **argv)
 
 	if ( dst_mailbox == NULL ) {
 		dst_mailbox = src_mailbox;
+	} else {
+		/* The (implicit) keep action will move the message */
+		default_move = TRUE;
 	}
 
 	/* Finish tool initialization */
@@ -552,6 +568,7 @@ int main(int argc, char **argv)
 	sfdata.ehandler = ehandler;
 	sfdata.execute = execute;
 	sfdata.source_write = source_write;
+	sfdata.default_move = default_move;
 
 	/* Apply Sieve filter to all messages found */
 	(void) filter_mailbox(&sfdata, src_box);
diff --git a/tests/extensions/vnd.dovecot/environment/basic.svtest b/tests/extensions/vnd.dovecot/environment/basic.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..a966d8954134ded4ee5f8204256b30f9d46b18e3
--- /dev/null
+++ b/tests/extensions/vnd.dovecot/environment/basic.svtest
@@ -0,0 +1,19 @@
+require "vnd.dovecot.testsuite";
+require "environment";
+require "vnd.dovecot.environment";
+require "variables";
+
+test "default-mailbox" {
+	if not environment :is "vnd.dovecot.default-mailbox" "INBOX" {
+		if environment :matches "vnd.dovecot.default-mailbox" "*" { set "env" "${1}"; }
+
+		test_fail "vnd.dovecot.default-mailbox environment returned invalid value(1): `${env}'";
+	}
+}
+
+test "username" {
+	if not environment :contains "vnd.dovecot.username" "" {
+		test_fail "vnd.dovecot.username environment does not exist";
+	}
+}
+
diff --git a/tests/extensions/vnd.dovecot/environment/variables.svtest b/tests/extensions/vnd.dovecot/environment/variables.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..7339bba576ba999b617d8c17e4837cdf69067512
--- /dev/null
+++ b/tests/extensions/vnd.dovecot/environment/variables.svtest
@@ -0,0 +1,19 @@
+require "vnd.dovecot.testsuite";
+require "environment";
+require "vnd.dovecot.environment";
+require "variables";
+require "relational";
+
+test "default_mailbox" {
+	if not string "${env.vnd.dovecot.default_mailbox}" "INBOX" {
+		test_fail "The env.vnd.dovecot.default_mailbox variable returned invalid value: `${env.vnd.dovecot.default_mailbox}'";
+	}
+}
+
+test "username" {
+	set :length "userlen" "${env.vnd.dovecot.username}";
+	if not string :value "ge" "${userlen}" "1" {
+		test_fail "The env.vnd.dovecot.username variable is empty or does not exist";
+	}
+}
+