From a195bd824fd60f06812e3b2161a7a8af748cae2d Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan.bosch@dovecot.fi>
Date: Tue, 11 Oct 2016 16:40:47 +0200
Subject: [PATCH] lib-sieve: vnd.dovecot.report extension: Added support for
 configuring the "From:" address used in the report.

---
 doc/extensions/vnd.dovecot.report.txt         |  54 ++++++
 .../plugins/vnd.dovecot/report/cmd-report.c   |  31 +++-
 .../report/ext-vnd-report-common.c            |  16 ++
 .../report/ext-vnd-report-common.h            |  11 ++
 .../vnd.dovecot/report/ext-vnd-report.c       |   1 +
 .../vnd.dovecot/report/execute.svtest         | 166 ++++++++++++++++++
 6 files changed, 271 insertions(+), 8 deletions(-)
 create mode 100644 doc/extensions/vnd.dovecot.report.txt

diff --git a/doc/extensions/vnd.dovecot.report.txt b/doc/extensions/vnd.dovecot.report.txt
new file mode 100644
index 000000000..c5126071a
--- /dev/null
+++ b/doc/extensions/vnd.dovecot.report.txt
@@ -0,0 +1,54 @@
+Vnd.dovecot.report Extension
+
+Relevant specifications
+=======================
+
+  doc/rfc/spec-bosch-sieve-report.txt
+
+Description
+===========
+
+The "vnd.dovecot.report" extension provides the means to send Messaging Abuse
+Reporting Format (MARF) reports (RFC 5965). This format is intended for
+communications regarding email abuse and related issues.  The "report" command
+allows (partially) automating the exchange of these reports, which is
+particularly useful when the Sieve script is executed for an IMAP event
+(RFC 6785) that is triggered by direct user action.
+
+Configuration
+=============
+
+The "vnd.dovecot.report" extension is not available by default; it needs
+to be added to the sieve_extensions setting (or any of the alternatives).
+
+The "vnd.dovecot.report" extension has its own specific settings. The following
+settings can be configured for the vacation extension (default values are
+indicated):
+
+ sieve_report_from = postmaster
+   Specifies what address is used for the "From:" header field in reports.
+   The following values are supported for this setting:
+   
+     "postmaster"     - The postmaster_address configured for the LDA (default).
+     "sender"         - The sender address is used.
+     "recipient"      - The final recipient address is used.
+     "orig_recipient" - The original recipient is used.
+     "user_email"     - The user's primary address is used. This is
+                        configured with the "sieve_user_email" setting. If
+                        that setting is unconfigured, "user_mail" is equal to
+                        "recipient".
+     "<user@domain>"  - Redirected messages are always sent from user@domain.
+                        The angle brackets are mandatory. The null "<>" address
+                        not supported and interpreted as "postmaster".
+
+Invalid values for the settings above will make the Sieve interpreter log a
+warning and revert to the default values.
+
+Example
+=======
+
+plugin {
+  sieve = file:~/sieve;active=~/.dovecot.sieve
+
+  sieve_report_from = <reporter@example.com>
+}
diff --git a/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c b/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
index 7ce5eafd7..4eaba3e77 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
+++ b/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
@@ -291,6 +291,7 @@ static bool cmd_report_operation_dump
 static int cmd_report_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
 	struct act_report_data *act;
 	string_t *fbtype, *message, *to_address;
 	const char *norm_address, *feedback_type, *error;
@@ -378,7 +379,7 @@ static int cmd_report_operation_execute
 	act->to_address = p_strdup(pool, norm_address);
 
 	if ( sieve_result_add_action(renv,
-		NULL, &act_report, NULL, (void *) act, 0, TRUE) < 0 )
+		this_ext, &act_report, NULL, (void *) act, 0, TRUE) < 0 )
 		return SIEVE_EXEC_FAILURE;
 
 	return SIEVE_EXEC_OK;
@@ -445,18 +446,20 @@ static bool _contains_8bit(const char *msg)
 
 static int act_report_send
 (const struct sieve_action_exec_env *aenv,
+	const struct ext_report_config *config,
 	const struct act_report_data *act)
 {
 	struct sieve_instance *svinst = aenv->svinst;
 	struct sieve_message_context *msgctx = aenv->msgctx;
 	const struct sieve_script_env *senv = aenv->scriptenv;
 	const struct sieve_message_data *msgdata = aenv->msgdata;
+	struct sieve_address_source report_from = config->report_from;
 	struct sieve_smtp_context *sctx;
 	struct istream *input;
 	struct ostream *output;
 	string_t *msg;
 	const char *const *headers;
-	const char *outmsgid, *boundary, *error, *subject;
+	const char *outmsgid, *boundary, *error, *subject, *from;
 	int ret;
 
 	/* Just to be sure */
@@ -479,6 +482,18 @@ static int act_report_send
 		subject = "Report: (message without subject)";
 	}
 
+	/* Determine from address */
+	if ( report_from.type == SIEVE_ADDRESS_SOURCE_POSTMASTER ) {
+		report_from.type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+		report_from.address = NULL;
+	}
+	if ( (ret=sieve_address_source_get_address
+		(&report_from, svinst, senv, msgctx,
+			aenv->flags, &from)) <= 0 || from == NULL || *from == '\0') {
+		from = t_strdup_printf
+			("Postmaster <%s>", senv->postmaster_address);
+	}
+
 	/* Start message */
 	sctx = sieve_smtp_start_single
 		(senv, act->to_address, NULL, &output);
@@ -492,11 +507,8 @@ static int act_report_send
 	rfc2822_header_write(msg, "Message-ID", outmsgid);
 	rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
 
-	rfc2822_header_printf(msg, "From",
-		"Postmaster <%s>", senv->postmaster_address);
-
-	rfc2822_header_printf(msg, "To",
-		"<%s>", act->to_address);
+	rfc2822_header_write(msg, "From", from);
+	rfc2822_header_printf(msg, "To", "<%s>", act->to_address);
 
 	if ( _contains_8bit(subject) )
 		rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
@@ -647,12 +659,15 @@ static int act_report_commit
 	void *tr_context ATTR_UNUSED,
 	bool *keep ATTR_UNUSED)
 {
+	const struct sieve_extension *ext = action->ext;
+	const struct ext_report_config *config =
+		(const struct ext_report_config *) ext->context;
 	const struct act_report_data *act =
 		(const struct act_report_data *) action->context;
 	int ret;
 
 	T_BEGIN {
-		ret = act_report_send(aenv, act);
+		ret = act_report_send(aenv, config, act);
 	} T_END;
 
 	if ( ret == SIEVE_EXEC_TEMP_FAILURE )
diff --git a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
index 93827cb58..911a8c036 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
+++ b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
@@ -6,9 +6,25 @@
 #include "rfc822-parser.h"
 
 #include "sieve-common.h"
+#include "sieve-extensions.h"
 
 #include "ext-vnd-report-common.h"
 
+bool ext_report_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_report_config *config;
+
+	config = p_new(svinst->pool, struct ext_report_config, 1);
+
+	(void)sieve_address_source_parse_from_setting(svinst,
+		svinst->pool, "sieve_report_from", &config->report_from);
+
+	*context = (void *) config;
+	return TRUE;
+}
+
 const char *
 ext_vnd_report_parse_feedback_type(const char *feedback_type)
 {
diff --git a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
index 67b499bca..6292b1a09 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
+++ b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
@@ -4,12 +4,23 @@
 #ifndef __EXT_REPORT_COMMON_H
 #define __EXT_REPORT_COMMON_H
 
+/*
+ * Extension configuration
+ */
+
+struct ext_report_config {
+	struct sieve_address_source report_from;
+};
+
 /*
  * Extension
  */
 
 extern const struct sieve_extension_def vnd_report_extension;
 
+bool ext_report_load
+	(const struct sieve_extension *ext, void **context);
+
 /*
  * Commands
  */
diff --git a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
index 6db8189c5..45714fcf9 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
+++ b/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
@@ -33,6 +33,7 @@ static bool ext_report_validator_load
 
 const struct sieve_extension_def vnd_report_extension = {
 	.name = "vnd.dovecot.report",
+	.load = ext_report_load,
 	.validator_load = ext_report_validator_load,
 	SIEVE_EXT_DEFINE_OPERATION(report_operation)
 };
diff --git a/tests/extensions/vnd.dovecot/report/execute.svtest b/tests/extensions/vnd.dovecot/report/execute.svtest
index fdb80433a..3df6032f6 100644
--- a/tests/extensions/vnd.dovecot/report/execute.svtest
+++ b/tests/extensions/vnd.dovecot/report/execute.svtest
@@ -3,6 +3,7 @@ require "vnd.dovecot.report";
 require "relational";
 require "comparator-i;ascii-numeric";
 require "body";
+require "variables";
 
 /*
  * Simple test
@@ -68,3 +69,168 @@ test "Simple - :headers_only" {
 		test_fail "report contains message body";
 	}
 }
+
+/*
+ * Configuration
+ */
+
+set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+/* default */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_result_reset;
+
+test "Configuration - from default" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "postmaster" {
+		test_fail "not sent from postmaster";
+	}
+}
+
+/* from sender */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "sender";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from sender" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "from" {
+		test_fail "not sent from sender";
+	}
+}
+
+/* from recipient */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "recipient";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from recipient" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "to" {
+		test_fail "not sent from recipient";
+	}
+}
+
+/* from original recipient */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "orig_recipient";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from original recipient" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "orig_to" {
+		test_fail "not sent from original recipient";
+	}
+}
+
+/* from user email */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "user_email";
+test_config_set "sieve_user_email" "user@example.com";
+test_config_reload;
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from user email" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "user" {
+		test_fail "not sent from user email";
+	}
+}
+
+/* explicit */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "<frop@example.com>";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - explicit" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "frop" {
+		test_fail "not sent from explicit address";
+	}
+}
+
+
-- 
GitLab