From 641e1c06be6c8b248fe742a2e25b2d9454b833fd Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Tue, 29 Dec 2009 23:14:28 +0100
Subject: [PATCH] Deprecated notify extension: implemented denotify command.

---
 Makefile.am                                   |   3 +-
 src/lib-sieve/plugins/notify/cmd-denotify.c   | 148 ++++++++--
 .../plugins/notify/ext-notify-common.h        |   6 +-
 src/lib-sieve/plugins/notify/ext-notify.c     |   9 +-
 src/lib-sieve/sieve-actions.h                 |   3 +
 src/lib-sieve/sieve-comparators.h             |   5 +-
 src/lib-sieve/sieve-result.c                  |  39 ++-
 src/lib-sieve/sieve-result.h                  |   2 +
 tests/deprecated/notify/denotify.svtest       | 279 ++++++++++++++++++
 9 files changed, 457 insertions(+), 37 deletions(-)
 create mode 100644 tests/deprecated/notify/denotify.svtest

diff --git a/Makefile.am b/Makefile.am
index 0a1c959a1..095b8d96d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -96,7 +96,8 @@ test_cases = \
 	tests/deprecated/notify/basic.svtest \
 	tests/deprecated/notify/mailto.svtest \
 	tests/deprecated/notify/errors.svtest \
-	tests/deprecated/notify/execute.svtest
+	tests/deprecated/notify/execute.svtest \
+	tests/deprecated/notify/denotify.svtest
 
 if HAVE_DOVECOT_LIBS
 
diff --git a/src/lib-sieve/plugins/notify/cmd-denotify.c b/src/lib-sieve/plugins/notify/cmd-denotify.c
index 2a9ccb80d..d3e879918 100644
--- a/src/lib-sieve/plugins/notify/cmd-denotify.c
+++ b/src/lib-sieve/plugins/notify/cmd-denotify.c
@@ -10,6 +10,7 @@
 #include "sieve-commands.h"
 #include "sieve-match-types.h"
 #include "sieve-comparators.h"
+#include "sieve-match.h"
 #include "sieve-actions.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
@@ -20,7 +21,7 @@
 #include "ext-notify-common.h"
  
 /* 
- * Denotify command (NOT IMPLEMENTED)
+ * Denotify command
  *
  * Syntax:
  *   denotify [MATCH-TYPE string] [<":low" / ":normal" / ":high">]
@@ -29,6 +30,10 @@
 static bool cmd_denotify_registered
 	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
 		struct sieve_command_registration *cmd_reg);
+static bool cmd_denotify_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_denotify_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
 static bool cmd_denotify_generate
 	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
 
@@ -37,8 +42,8 @@ const struct sieve_command_def cmd_denotify = {
 	SCT_COMMAND,
 	0, 0, FALSE, FALSE,
 	cmd_denotify_registered,
-	NULL,
-	NULL, 
+	cmd_denotify_pre_validate,
+	cmd_denotify_validate, 
 	cmd_denotify_generate, 
 	NULL
 };
@@ -68,9 +73,9 @@ const struct sieve_argument_def denotify_match_tag = {
 /* Codes for optional operands */
 
 enum cmd_denotify_optional {
-  OPT_END,
-  OPT_IMPORTANCE,
-  OPT_MATCH_TYPE,
+	OPT_END,
+	OPT_IMPORTANCE,
+	OPT_MATCH_TYPE,
 	OPT_MATCH_KEY
 };
 
@@ -91,6 +96,14 @@ const struct sieve_operation_def denotify_operation = {
 	cmd_denotify_operation_execute
 };
 
+/*
+ * Command validation context
+ */
+
+struct cmd_denotify_context_data {
+	struct sieve_ast_argument *match_key_arg;
+};
+
 /*
  * Tag validation
  */
@@ -106,11 +119,9 @@ static bool tag_match_type_validate
 (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
 	struct sieve_command *cmd)
 {
+	struct cmd_denotify_context_data *cmd_data =
+        (struct cmd_denotify_context_data *) cmd->data;
 	struct sieve_ast_argument *tag = *arg;
-	const struct sieve_match_type mcht_default = 
-		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
-	const struct sieve_comparator cmp_default = 
-		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
 
 	if ( !match_type_tag.validate(valdtr, arg, cmd) )
 		return FALSE;
@@ -118,7 +129,7 @@ static bool tag_match_type_validate
 	if ( *arg == NULL ) {
 		sieve_argument_validate_error(valdtr, tag, 
 			"the MATCH-TYPE argument (:%s) for the denotify command requires "
-			"an additional key-string paramterer, but no more arguments were found", 
+			"an additional key-string parameter, but no more arguments were found", 
 			sieve_ast_argument_tag(tag));
 		return FALSE;	
 	}
@@ -135,14 +146,11 @@ static bool tag_match_type_validate
 	if ( !sieve_validator_argument_activate(valdtr, cmd, *arg, FALSE) ) 
 		return FALSE;
 
-	if ( !sieve_match_type_validate
-		(valdtr, cmd, *arg, &mcht_default, &cmp_default) )
-		return FALSE;
-
 	tag->argument->def = &match_type_tag;
 	tag->argument->ext = NULL;
 
 	(*arg)->argument->id_code = OPT_MATCH_KEY;
+	cmd_data->match_key_arg = *arg;
 
 	*arg = sieve_ast_argument_next(*arg);
 
@@ -165,6 +173,44 @@ static bool cmd_denotify_registered
 	return TRUE;
 }
 
+/*
+ * Command validation
+ */
+
+static bool cmd_denotify_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *cmd)
+{
+	struct cmd_denotify_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(cmd),
+		struct cmd_denotify_context_data, 1);
+	cmd->data = (void *) ctx_data;
+
+	return TRUE;
+}
+
+static bool cmd_denotify_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+    struct cmd_denotify_context_data *ctx_data =
+        (struct cmd_denotify_context_data *) cmd->data;
+	struct sieve_ast_argument *key_arg = ctx_data->match_key_arg;
+	const struct sieve_match_type mcht_default = 
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default = 
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+
+	if ( key_arg != NULL ) {
+		if ( !sieve_match_type_validate
+			(valdtr, cmd, key_arg, &mcht_default, &cmp_default) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
 /*
  * Code generation
  */
@@ -237,14 +283,19 @@ static bool cmd_denotify_operation_dump
 static int cmd_denotify_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
+	bool result = TRUE;
 	int opt_code = 1;
-	sieve_number_t importance = 1;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
-/*	const struct sieve_comparator cmp = 
-		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);*/
-	string_t *match_key = NULL; 
+	const struct sieve_comparator cmp = 
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+    struct sieve_coded_stringlist *match_key = NULL;
+	sieve_number_t importance = 0;
+	struct sieve_match_context *mctx;
+	struct sieve_result_iterate_context *rictx;
+	const struct sieve_action *action;
 	unsigned int source_line;
+	int ret;
 
 	/*
 	 * Read operands
@@ -274,8 +325,8 @@ static int cmd_denotify_operation_execute
 				}
 				break;
 			case OPT_MATCH_KEY:
-				if ( !sieve_opr_string_read(renv, address, &match_key) ) {
-					sieve_runtime_trace_error(renv, "invalid from operand");
+				if ( (match_key=sieve_opr_stringlist_read(renv, address)) == NULL ) {
+					sieve_runtime_trace_error(renv, "invalid match key operand");
 					return SIEVE_EXEC_BIN_CORRUPT;
 				}
 				break;
@@ -303,7 +354,60 @@ static int cmd_denotify_operation_execute
 	 * Perform operation
 	 */
 
-	sieve_runtime_trace(renv, "DENOTIFY action");	
+	sieve_runtime_trace(renv, "DENOTIFY action");
+
+	/* Either do string matching or just kill all notify actions */
+	if ( match_key != NULL ) { 	
+
+		/* Initialize match */
+    	mctx = sieve_match_begin(renv->interp, &mcht, &cmp, NULL, match_key);
+
+		/* Iterate through all actions */
+		rictx = sieve_result_iterate_init(renv->result);
+	
+		while ( result &&
+			(action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+			if ( sieve_action_is(action, act_notify_old) ) {		
+				struct ext_notify_action *nact =
+					(struct ext_notify_action *) action->context;
+	
+				if ( importance == 0 || nact->importance == importance ) {
+					if ( (ret=sieve_match_value(mctx, nact->id, strlen(nact->id)))
+						< 0 ) {
+						result = FALSE;
+						break;
+					}
+	
+					if ( ret > 0 )
+						sieve_result_iterate_delete(rictx);
+				}
+			}
+		}
+	
+		/* Finish match */
+		if ( sieve_match_end(&mctx) < 0 )
+			result = FALSE;
+
+	    if ( !result ) {
+		    sieve_runtime_trace_error(renv, "invalid string-list item");
+    		return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	} else {
+		/* Iterate through all actions */
+		rictx = sieve_result_iterate_init(renv->result);
+
+		while ( result &&
+			(action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+
+			if ( sieve_action_is(action, act_notify_old) ) {
+				struct ext_notify_action *nact =
+					(struct ext_notify_action *) action->context;
+
+				if ( importance == 0 || nact->importance == importance )
+					sieve_result_iterate_delete(rictx);
+			}
+		}	
+	}
 
 	return SIEVE_EXEC_OK;
 }
diff --git a/src/lib-sieve/plugins/notify/ext-notify-common.h b/src/lib-sieve/plugins/notify/ext-notify-common.h
index 87deb188d..dd2f07cf5 100644
--- a/src/lib-sieve/plugins/notify/ext-notify-common.h
+++ b/src/lib-sieve/plugins/notify/ext-notify-common.h
@@ -37,7 +37,11 @@ enum ext_notify_opcode {
 	EXT_NOTIFY_OPERATION_DENOTIFY,
 };
 
-/* Action context */
+/* 
+ * Actions 
+ */
+
+const struct sieve_action_def act_notify_old;
 
 struct ext_notify_recipient {
 	const char *full;
diff --git a/src/lib-sieve/plugins/notify/ext-notify.c b/src/lib-sieve/plugins/notify/ext-notify.c
index 1fd110f5f..5251f6cf0 100644
--- a/src/lib-sieve/plugins/notify/ext-notify.c
+++ b/src/lib-sieve/plugins/notify/ext-notify.c
@@ -6,16 +6,11 @@
  *
  * Authors: Stephan Bosch
  * Specification: draft-ietf-sieve-notify-00.txt
- * Implementation: deprecated; provided for backwards compatibility
- * Status: deprecated
+ * Implementation: fully backwards compatible
+ * Status: testing (deprecated)
  * 
  */
 
-/* FIXME: Currently the following CMUSieve features are not supported:
- * 
- * (*) The $text$ substitution is not available for the :message argument.
- */
-	
 #include <stdio.h>
 
 #include "sieve-common.h"
diff --git a/src/lib-sieve/sieve-actions.h b/src/lib-sieve/sieve-actions.h
index 48e6eecf5..411dac845 100644
--- a/src/lib-sieve/sieve-actions.h
+++ b/src/lib-sieve/sieve-actions.h
@@ -92,6 +92,9 @@ struct sieve_action {
 	bool executed;
 };
 
+#define sieve_action_is(act, definition) \
+	( (act)->def == &(definition) )
+
 /* 
  * Action side effects 
  */
diff --git a/src/lib-sieve/sieve-comparators.h b/src/lib-sieve/sieve-comparators.h
index 501ee395f..1eee4cfa7 100644
--- a/src/lib-sieve/sieve-comparators.h
+++ b/src/lib-sieve/sieve-comparators.h
@@ -91,9 +91,10 @@ static inline const struct sieve_comparator *sieve_comparator_copy
 extern const struct sieve_argument_def comparator_tag;
 
 static inline bool sieve_argument_is_comparator
-	(struct sieve_ast_argument *arg) 
+(struct sieve_ast_argument *arg) 
 {
-	return arg->argument->def == &comparator_tag;
+	return ( arg->argument != NULL && 
+		(arg->argument->def == &comparator_tag) );
 }
 
 void sieve_comparators_link_tag
diff --git a/src/lib-sieve/sieve-result.c b/src/lib-sieve/sieve-result.c
index ff875591c..6cc0515e5 100644
--- a/src/lib-sieve/sieve-result.c
+++ b/src/lib-sieve/sieve-result.c
@@ -1237,7 +1237,8 @@ int sieve_result_execute
 
 struct sieve_result_iterate_context {
 	struct sieve_result *result;
-	struct sieve_result_action *action;
+	struct sieve_result_action *current_action;
+	struct sieve_result_action *next_action;
 };
 
 struct sieve_result_iterate_context *sieve_result_iterate_init
@@ -1247,7 +1248,8 @@ struct sieve_result_iterate_context *sieve_result_iterate_init
 		t_new(struct sieve_result_iterate_context, 1);
 	
 	rictx->result = result;
-	rictx->action = result->first_action;
+	rictx->current_action = NULL;
+	rictx->next_action = result->first_action;
 
 	return rictx;
 }
@@ -1260,9 +1262,9 @@ const struct sieve_action *sieve_result_iterate_next
 	if ( rictx == NULL )
 		return  NULL;
 
-	rac = rictx->action;
+	rac = rictx->current_action = rictx->next_action;
 	if ( rac != NULL ) {
-		rictx->action = rac->next;
+		rictx->next_action = rac->next;
 		
 		if ( keep != NULL )
 			*keep = rac->keep;
@@ -1273,6 +1275,35 @@ const struct sieve_action *sieve_result_iterate_next
 	return NULL;
 }
 
+void sieve_result_iterate_delete
+(struct sieve_result_iterate_context *rictx)
+{
+	struct sieve_result *result;
+	struct sieve_result_action *rac;
+
+	if ( rictx == NULL || rictx->current_action == NULL )
+		return;
+
+	result = rictx->result;
+	rac = rictx->current_action;
+
+	/* Delete action */
+
+	if ( rac->prev == NULL )
+		result->first_action = rac->next;
+	else
+		rac->prev->next = rac->next;
+
+	if ( rac->next == NULL )
+		result->last_action = rac->prev;
+	else
+		rac->next->prev = rac->prev;
+
+	/* Skip to next action in iteration */
+
+	rictx->current_action = NULL;
+}
+
 /*
  * Side effects list
  */
diff --git a/src/lib-sieve/sieve-result.h b/src/lib-sieve/sieve-result.h
index 1414a80e1..d2ebac946 100644
--- a/src/lib-sieve/sieve-result.h
+++ b/src/lib-sieve/sieve-result.h
@@ -148,6 +148,8 @@ struct sieve_result_iterate_context *sieve_result_iterate_init
 	(struct sieve_result *result);
 const struct sieve_action *sieve_result_iterate_next
 	(struct sieve_result_iterate_context *rictx, bool *keep);
+void sieve_result_iterate_delete
+	(struct sieve_result_iterate_context *rictx);
 	
 /*
  * Side effects list
diff --git a/tests/deprecated/notify/denotify.svtest b/tests/deprecated/notify/denotify.svtest
new file mode 100644
index 000000000..7b2ff619d
--- /dev/null
+++ b/tests/deprecated/notify/denotify.svtest
@@ -0,0 +1,279 @@
+require "vnd.dovecot.testsuite";
+require "notify";
+require "envelope";
+
+/*
+ * Denotify all
+ */
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify All" {
+    notify :options "timo@example.com";
+	notify :options "stephan@dovecot.org";
+	notify :options "postmaster@vestingbar.nl";
+	denotify;
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if test_message :smtp 0 {
+        test_fail "no notifications should have been sent";
+	}
+}
+
+/*
+ * Denotify First
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID First" {
+	/* #1 */
+    notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.org" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@vestingbar.nl" :id "mies";
+
+	denotify :is "aap";
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if not test_message :smtp 0 {
+        test_fail "two notifications should have been sent (#2 missing)";
+	}
+
+	if not envelope "to" "stephan@dovecot.org" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+    if not test_message :smtp 1 {
+        test_fail "two notifications should have been sent (#3 missing)";
+	}
+
+	if not envelope "to" "postmaster@vestingbar.nl" {
+		test_fail "message #3 unexpectedly missing from output";
+	}
+
+    if test_message :smtp 2 {
+        test_fail "too many notifications sent";
+	}
+}
+
+/*
+ * Denotify Middle
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID Middle" {
+	/* #1 */
+    notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.org" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@vestingbar.nl" :id "mies";
+
+	denotify :is "noot";
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if not test_message :smtp 0 {
+        test_fail "two notifications should have been sent (#1 missing)";
+	}
+
+	if not envelope "to" "timo@example.com" {
+		test_fail "message #1 unexpectedly missing from output";
+	}
+
+    if not test_message :smtp 1 {
+        test_fail "two notifications should have been sent (#3 missing)";
+	}
+
+	if not envelope "to" "postmaster@vestingbar.nl" {
+		test_fail "message #3 unexpectedly missing from output";
+	}
+
+    if test_message :smtp 2 {
+        test_fail "too many notifications sent";
+	}
+}
+
+/*
+ * Denotify Last
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID Last" {
+	/* #1 */
+    notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.org" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@vestingbar.nl" :id "mies";
+
+	denotify :is "mies";
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if not test_message :smtp 0 {
+        test_fail "two notifications should have been sent (#1 missing)";
+	}
+
+	if not envelope "to" "timo@example.com" {
+		test_fail "message #1 unexpectedly missing from output";
+	}
+
+    if not test_message :smtp 1 {
+        test_fail "two notifications should have been sent (#2 missing)";
+	}
+
+	if not envelope "to" "stephan@dovecot.org" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+    if test_message :smtp 2 {
+        test_fail "too many notifications sent";
+	}
+}
+
+
+/*
+ * Denotify Matching
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify Matching" {
+	/* #1 */
+    notify :options "timo@example.com" :id "frop";
+
+	/* #2 */
+	notify :options "stephan@dovecot.org" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@vestingbar.nl" :id "friep";
+
+	denotify :matches "fr*";
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if not test_message :smtp 0 {
+        test_fail "one notification should have been sent";
+	}
+
+	if not envelope "to" "stephan@dovecot.org" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+    if test_message :smtp 1 {
+        test_fail "too many notifications sent";
+	}
+}
+
+
+/*
+ * Denotify Matching
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@rename-it.nl
+To: nico@vestingbar.nl
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify Matching Importance" {
+	/* #1 */
+    notify :options "timo@example.com" :id "frop" :low;
+
+	/* #2 */
+	notify :options "stephan@dovecot.org" :id "frml" :high;
+
+	/* #3 */
+	notify :options "postmaster@vestingbar.nl" :id "friep" :low;
+
+	denotify :matches "fr*" :low;
+
+    if not test_result_execute {
+        test_fail "failed to execute notify";
+    }
+
+    if not test_message :smtp 0 {
+        test_fail "one notification should have been sent";
+	}
+
+	if not envelope "to" "stephan@dovecot.org" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+    if test_message :smtp 1 {
+        test_fail "too many notifications sent";
+	}
+}
+
+
-- 
GitLab