From f63b20cdab5a9ba4d7db1bd96e9236c2d39338d8 Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Sun, 16 Nov 2014 23:13:58 +0100
Subject: [PATCH] lib-sieve: Finished support for mboxmetadata and
 servermetadata extensions.

---
 src/lib-sieve/Makefile.am                     |   4 +-
 src/lib-sieve/plugins/Makefile.am             |   3 +-
 src/lib-sieve/plugins/metadata/Makefile.am    |   3 +-
 .../plugins/metadata/ext-metadata-common.h    |   4 +
 src/lib-sieve/plugins/metadata/tst-metadata.c | 147 +++++++++++++++---
 .../plugins/metadata/tst-metadataexists.c     | 137 +++++++++++++---
 src/lib-sieve/sieve-extensions.c              |   7 +-
 src/sieve-tools/sieve-test.c                  |   2 +-
 8 files changed, 256 insertions(+), 51 deletions(-)

diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 143287bbc..bdc35c80c 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -44,8 +44,7 @@ comparators = \
 
 if BUILD_UNFINISHED
 unfinished_storages =
-unfinished_plugins = \
-	$(extdir)/metadata/libsieve_ext_metadata.la
+unfinished_plugins =
 endif
 
 strgdir = $(top_builddir)/src/lib-sieve/storage
@@ -77,6 +76,7 @@ plugins = \
 	$(extdir)/editheader/libsieve_ext_editheader.la \
 	$(extdir)/duplicate/libsieve_ext_duplicate.la \
 	$(extdir)/index/libsieve_ext_index.la \
+	$(extdir)/metadata/libsieve_ext_metadata.la \
 	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
 	$(unfinished_plugins)
 
diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am
index ee69303f4..4e626582d 100644
--- a/src/lib-sieve/plugins/Makefile.am
+++ b/src/lib-sieve/plugins/Makefile.am
@@ -1,5 +1,5 @@
 if BUILD_UNFINISHED
-UNFINISHED = metadata
+UNFINISHED = 
 endif
 
 SUBDIRS = \
@@ -23,6 +23,7 @@ SUBDIRS = \
 	editheader \
 	duplicate \
 	index \
+	metadata \
 	vnd.dovecot \
 	$(UNFINISHED)
 
diff --git a/src/lib-sieve/plugins/metadata/Makefile.am b/src/lib-sieve/plugins/metadata/Makefile.am
index 7aeaf2e18..6d69ba822 100644
--- a/src/lib-sieve/plugins/metadata/Makefile.am
+++ b/src/lib-sieve/plugins/metadata/Makefile.am
@@ -5,7 +5,8 @@ libsieve_ext_metadata_la_LDFLAGS = -module -avoid-version
 AM_CPPFLAGS = \
 	-I$(srcdir)/../.. \
 	-I$(srcdir)/../variables \
-	$(LIBDOVECOT_INCLUDE)
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_STORAGE_INCLUDE)
 
 tests = \
 	tst-metadata.c \
diff --git a/src/lib-sieve/plugins/metadata/ext-metadata-common.h b/src/lib-sieve/plugins/metadata/ext-metadata-common.h
index c86da3dcf..d63c9f026 100644
--- a/src/lib-sieve/plugins/metadata/ext-metadata-common.h
+++ b/src/lib-sieve/plugins/metadata/ext-metadata-common.h
@@ -4,6 +4,10 @@
 #ifndef __EXT_METADATA_COMMON_H
 #define __EXT_METADATA_COMMON_H
 
+#include "lib.h"
+#include "mail-storage.h"
+#include "imap-metadata.h"
+
 #include "sieve-common.h"
 
 /*
diff --git a/src/lib-sieve/plugins/metadata/tst-metadata.c b/src/lib-sieve/plugins/metadata/tst-metadata.c
index 01b1296c1..7be3c70f4 100644
--- a/src/lib-sieve/plugins/metadata/tst-metadata.c
+++ b/src/lib-sieve/plugins/metadata/tst-metadata.c
@@ -1,7 +1,12 @@
 /* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file
  */
 
+#include "lib.h"
+#include "str-sanitize.h"
+#include "istream.h"
+
 #include "sieve-common.h"
+#include "sieve-limits.h"
 #include "sieve-commands.h"
 #include "sieve-stringlist.h"
 #include "sieve-code.h"
@@ -15,6 +20,10 @@
 
 #include "ext-metadata-common.h"
 
+#include <ctype.h>
+
+#define TST_METADATA_MAX_MATCH_SIZE SIEVE_MAX_STRING_LEN
+
 /*
  * Test definitions
  */
@@ -102,7 +111,8 @@ const struct sieve_operation_def servermetadata_operation = {
  */
 
 static bool tst_metadata_registered
-(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+(struct sieve_validator *valdtr,
+	const struct sieve_extension *ext ATTR_UNUSED,
 	struct sieve_command_registration *cmd_reg)
 {
 	/* The order of these is not significant */
@@ -125,7 +135,9 @@ static bool tst_metadata_validate
 	const struct sieve_comparator cmp_default =
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	unsigned int arg_index = 1;
+	const char *error;
 
+	/* mailbox */
 	if ( sieve_command_is(tst, metadata_test) ) {
 		if ( !sieve_validate_positional_argument
 			(valdtr, tst, arg, "mailbox", arg_index++, SAAT_STRING) ) {
@@ -138,6 +150,7 @@ static bool tst_metadata_validate
 		arg = sieve_ast_argument_next(arg);
 	}
 
+	/* annotation-name */
 	if ( !sieve_validate_positional_argument
 		(valdtr, tst, arg, "annotation-name", arg_index++, SAAT_STRING) ) {
 		return FALSE;
@@ -146,10 +159,25 @@ static bool tst_metadata_validate
 	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
 		return FALSE;
 
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *aname = sieve_ast_argument_str(arg);
+
+		if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+			char *lcerror = t_strdup_noconst(error);
+			lcerror[0] = i_tolower(lcerror[0]);
+			sieve_argument_validate_warning
+				(valdtr, arg, "%s test: "
+					"specified annotation name `%s' is invalid: %s",
+					sieve_command_identifier(tst),
+					str_sanitize(str_c(aname), 256), lcerror);
+		}
+	}
+
 	arg = sieve_ast_argument_next(arg);
 
+	/* key-list */
 	if ( !sieve_validate_positional_argument
-		(valdtr, tst, arg, "key list", arg_index++, SAAT_STRING_LIST) ) {
+		(valdtr, tst, arg, "key-list", arg_index++, SAAT_STRING_LIST) ) {
 		return FALSE;
 	}
 
@@ -217,6 +245,63 @@ static bool tst_metadata_operation_dump
  * Code execution
  */
 
+static inline const char *_lc_error(const char *error)
+{
+	char *lcerror = t_strdup_noconst(error);
+	lcerror[0] = i_tolower(lcerror[0]);
+
+	return lcerror;
+}
+
+static int tst_metadata_get_annotation
+(const struct sieve_runtime_env *renv, const char *mailbox,
+	const char *aname, const char **annotation_r)
+{
+	struct mail_user *user = renv->scriptenv->user;
+	struct mailbox *box;
+	struct imap_metadata_transaction *imtrans;
+	struct mail_attribute_value avalue;
+	int status, ret;
+
+	*annotation_r = NULL;
+
+	if ( user == NULL )
+		return SIEVE_EXEC_OK;
+
+	if ( mailbox != NULL ) {
+		struct mail_namespace *ns;
+		ns = mail_namespace_find(user->namespaces, mailbox);
+		box = mailbox_alloc(ns->list, mailbox, 0);
+		imtrans = imap_metadata_transaction_begin(box);
+	} else {
+		imtrans = imap_metadata_transaction_begin_server(user);
+	}
+
+	status = SIEVE_EXEC_OK;
+	ret = imap_metadata_get(imtrans, aname, &avalue);
+	if (ret < 0) {
+		enum mail_error error_code;
+		const char *error;
+
+		error = imap_metadata_transaction_get_last_error
+			(imtrans, &error_code);
+
+		sieve_runtime_error(renv, NULL, "%s test: "
+			"failed to retrieve annotation `%s': %s%s",
+			(mailbox != NULL ? "metadata" : "servermetadata"),
+			str_sanitize(aname, 256), _lc_error(error),
+			(error_code == MAIL_ERROR_TEMP ? " (temporary failure)" : ""));
+
+		status = ( error_code == MAIL_ERROR_TEMP ?
+			SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+
+	} else if (avalue.value != NULL) {
+		*annotation_r = avalue.value;
+	}
+	(void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+	return status;
+}
+
 static int tst_metadata_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
@@ -225,9 +310,9 @@ static int tst_metadata_operation_execute
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp =
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
-	string_t *mailbox, *annotation_name;
+	string_t *mailbox, *aname;
 	struct sieve_stringlist *value_list, *key_list;
-	const char *annotation = NULL;
+	const char *annotation = NULL, *error;
 	int match, ret;
 
 	/*
@@ -247,7 +332,7 @@ static int tst_metadata_operation_execute
 
 	/* Read annotation-name */
 	if ( (ret=sieve_opr_string_read
-		(renv, address, "annotation-name", &annotation_name)) <= 0 )
+		(renv, address, "annotation-name", &aname)) <= 0 )
 		return ret;
 
 	/* Read key-list */
@@ -263,24 +348,48 @@ static int tst_metadata_operation_execute
 		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "metadata test");
 	else
 		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "servermetadata test");
+	sieve_runtime_trace_descend(renv);
+
+	if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+		sieve_runtime_warning(renv, NULL, "%s test: "
+			"specified annotation name `%s' is invalid: %s",
+			(metadata ? "metadata" : "servermetadata"),
+			str_sanitize(str_c(aname), 256), _lc_error(error));
+		sieve_interpreter_set_test_result(renv->interp, FALSE);
+		return SIEVE_EXEC_OK;
+	}
 
-	/* Get annotation */
-	annotation = "FIXME";
-
-	/* Perform match */
-	if ( annotation != NULL ) {
-		/* Create value stringlist */
-		value_list = sieve_single_stringlist_create_cstr(renv, annotation, FALSE);
+	if ( metadata ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"retrieving annotation `%s' from mailbox `%s'",
+			str_sanitize(str_c(aname), 256),
+			str_sanitize(str_c(mailbox), 80));
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"retrieving server annotation `%s'",
+			str_sanitize(str_c(aname), 256));
+	}
 
+	/* Get annotation */
+	if ( (ret=tst_metadata_get_annotation
+		(renv, (metadata ? str_c(mailbox) : NULL), str_c(aname), &annotation))
+			== SIEVE_EXEC_OK ) {
 		/* Perform match */
-		if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret))
-			< 0 )
-			return ret;
-	} else {
-		match = 0;
+		if ( annotation != NULL ) {
+			/* Create value stringlist */
+			value_list = sieve_single_stringlist_create_cstr(renv, annotation, FALSE);
+
+			/* Perform match */
+			if ( (match=sieve_match
+				(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+				return ret;
+		} else {
+			match = 0;
+		}
 	}
 
 	/* Set test result for subsequent conditional jump */
-	sieve_interpreter_set_test_result(renv->interp, match > 0);
-	return SIEVE_EXEC_OK;
+	if (ret == SIEVE_EXEC_OK)
+		sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return ret;
 }
diff --git a/src/lib-sieve/plugins/metadata/tst-metadataexists.c b/src/lib-sieve/plugins/metadata/tst-metadataexists.c
index ba1a67d7b..58808354a 100644
--- a/src/lib-sieve/plugins/metadata/tst-metadataexists.c
+++ b/src/lib-sieve/plugins/metadata/tst-metadataexists.c
@@ -17,6 +17,9 @@
 
 #include "ext-metadata-common.h"
 
+#include <ctype.h>
+
+
 /*
  * Command definitions
  */
@@ -175,14 +178,114 @@ static bool tst_metadataexists_operation_dump
  * Code execution
  */
 
+static inline const char *_lc_error(const char *error)
+{
+	char *lcerror = t_strdup_noconst(error);
+	lcerror[0] = i_tolower(lcerror[0]);
+
+	return lcerror;
+}
+
+static int tst_metadataexists_check_annotations
+(const struct sieve_runtime_env *renv, const char *mailbox,
+	struct sieve_stringlist *anames, bool *all_exist_r)
+{
+	struct mail_user *user = renv->scriptenv->user;
+	struct mailbox *box = NULL;
+	struct imap_metadata_transaction *imtrans;
+	string_t *aname;
+	bool all_exist = TRUE;
+	int ret, sret, status;
+
+	*all_exist_r = FALSE;
+
+	if ( user == NULL )
+		return SIEVE_EXEC_OK;
+
+	if ( mailbox != NULL ) {
+		struct mail_namespace *ns;
+		ns = mail_namespace_find(user->namespaces, mailbox);
+		box = mailbox_alloc(ns->list, mailbox, 0);
+		imtrans = imap_metadata_transaction_begin(box);
+	} else {
+		imtrans = imap_metadata_transaction_begin_server(user);
+	}
+
+	if ( mailbox != NULL ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"checking annotations of mailbox `%s':",
+			str_sanitize(mailbox, 80));
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"checking server annotations");
+	}
+
+	aname = NULL;
+	status = SIEVE_EXEC_OK;
+	while ( all_exist &&
+		(sret=sieve_stringlist_next_item(anames, &aname)) > 0 ) {
+		struct mail_attribute_value avalue;
+		const char *error;
+
+		if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+			sieve_runtime_warning(renv, NULL, "%s test: "
+				"specified annotation name `%s' is invalid: %s",
+				(mailbox != NULL ? "metadataexists" : "servermetadataexists"),
+				str_sanitize(str_c(aname), 256), _lc_error(error));
+			continue;
+		}
+
+		ret = imap_metadata_get(imtrans, str_c(aname), &avalue);
+		if (ret < 0) {
+			enum mail_error error_code;
+			const char *error;
+
+			error = imap_metadata_transaction_get_last_error
+				(imtrans, &error_code);
+			sieve_runtime_error(renv, NULL, "%s test: "
+				"failed to retrieve annotation `%s': %s%s",
+				(mailbox != NULL ? "metadataexists" : "servermetadataexists"),
+				str_sanitize(str_c(aname), 256), _lc_error(error),
+				(error_code == MAIL_ERROR_TEMP ? " (temporary failure)" : ""));
+
+			all_exist = FALSE;
+			status = ( error_code == MAIL_ERROR_TEMP ?
+				SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+			break;
+
+		} else if (avalue.value == NULL && avalue.value_stream == NULL) {
+			all_exist = FALSE;
+			sieve_runtime_trace(renv, 0,
+				"annotation `%s': not found", str_c(aname));
+			break;
+
+		} else {
+			sieve_runtime_trace(renv, 0,
+				"annotation `%s': found", str_c(aname));
+		}
+	}
+
+	if ( sret < 0 ) {
+		sieve_runtime_trace_error
+			(renv, "invalid annotation name stringlist item");
+		status = SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	(void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+	if ( box != NULL )
+		mailbox_free(&box);
+
+	*all_exist_r = all_exist;
+	return status;
+}
+
 static int tst_metadataexists_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
 	bool metadata = sieve_operation_is(renv->oprtn, metadataexists_operation);
-	struct sieve_stringlist *annotation_names;
-	string_t *mailbox, *annotation_item;
-	bool trace = FALSE;
-	bool all_exist = TRUE;
+	struct sieve_stringlist *anames;
+	string_t *mailbox;
+	bool trace = FALSE, all_exist = TRUE;
 	int ret;
 
 	/*
@@ -197,7 +300,7 @@ static int tst_metadataexists_operation_execute
 
 	/* Read annotation names */
 	if ( (ret=sieve_opr_stringlist_read
-		(renv, address, "annotation-names", &annotation_names)) <= 0 )
+		(renv, address, "annotation-names", &anames)) <= 0 )
 		return ret;
 
 	/*
@@ -215,30 +318,16 @@ static int tst_metadataexists_operation_execute
 		trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
 	}
 
-	if ( renv->scriptenv->user != NULL ) {
-		int ret;
-
-		annotation_item = NULL;
-		while ( (ret=sieve_stringlist_next_item(annotation_names, &annotation_item))
-			> 0 ) {
-			//const char *annotation = str_c(annotation_item);
-
-			/* IMPLEMENT ... */
-			all_exist = FALSE;
-		}
-
-		if ( ret < 0 ) {
-			sieve_runtime_trace_error
-				(renv, "invalid annotation name stringlist item");
-			return SIEVE_EXEC_BIN_CORRUPT;
-		}
-	}
+	if ( (ret=tst_metadataexists_check_annotations
+		(renv, (metadata ? str_c(mailbox) : NULL), anames,
+			&all_exist)) <= 0 )
+		return ret;
 
 	if ( trace ) {
 		if ( all_exist )
 			sieve_runtime_trace(renv, 0, "all annotations exist");
 		else
-			sieve_runtime_trace(renv, 0, "some mailboxes no not exist");
+			sieve_runtime_trace(renv, 0, "some annotations do not exist");
 	}
 
 	sieve_interpreter_set_test_result(renv->interp, all_exist);
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index ff887afa4..32a13b5a4 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -106,6 +106,8 @@ extern const struct sieve_extension_def date_extension;
 extern const struct sieve_extension_def index_extension;
 extern const struct sieve_extension_def ihave_extension;
 extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def mboxmetadata_extension;
+extern const struct sieve_extension_def servermetadata_extension;
 
 const struct sieve_extension_def *sieve_core_extensions[] = {
 	/* Core extensions */
@@ -141,6 +143,7 @@ extern const struct sieve_extension_def vnd_debug_extension;
 const struct sieve_extension_def *sieve_extra_extensions[] = {
 	&vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension,
 	&virustest_extension, &editheader_extension,
+	&mboxmetadata_extension, &servermetadata_extension,
 
 	/* vnd.dovecot. */
 	&vnd_debug_extension
@@ -173,11 +176,9 @@ const unsigned int sieve_deprecated_extensions_count =
 #ifdef HAVE_SIEVE_UNFINISHED
 
 extern const struct sieve_extension_def ereject_extension;
-extern const struct sieve_extension_def mboxmetadata_extension;
-extern const struct sieve_extension_def servermetadata_extension;
 
 const struct sieve_extension_def *sieve_unfinished_extensions[] = {
-	&ereject_extension, &mboxmetadata_extension, &servermetadata_extension
+	&ereject_extension
 };
 
 const unsigned int sieve_unfinished_extensions_count =
diff --git a/src/sieve-tools/sieve-test.c b/src/sieve-tools/sieve-test.c
index 3de246aeb..8b4c833e1 100644
--- a/src/sieve-tools/sieve-test.c
+++ b/src/sieve-tools/sieve-test.c
@@ -223,7 +223,7 @@ int main(int argc, char **argv)
 	}
 
 	/* Finish tool initialization */
-	svinst = sieve_tool_init_finish(sieve_tool, execute && mailloc == NULL, FALSE);
+	svinst = sieve_tool_init_finish(sieve_tool, mailloc == NULL, FALSE);
 
 	/* Enable debug extension */
 	sieve_enable_debug_extension(svinst);
-- 
GitLab