From 1ce4c883b64ba8acfb0f5d08dbeda4b8214db309 Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Wed, 23 Sep 2015 20:06:15 +0200
Subject: [PATCH] Sieve extprograms plugin: Made line endings configurable for
 the input passed to the external program.

---
 doc/plugins/sieve_extprograms.txt             |  9 ++++-
 .../sieve-extprograms-common.c                | 31 +++++++++++++----
 .../sieve-extprograms-common.h                |  7 ++++
 tests/plugins/extprograms/bin/crlf            |  3 ++
 .../extprograms/execute/execute.svtest        | 33 ++++++++++++++++++-
 5 files changed, 74 insertions(+), 9 deletions(-)
 create mode 100755 tests/plugins/extprograms/bin/crlf

diff --git a/doc/plugins/sieve_extprograms.txt b/doc/plugins/sieve_extprograms.txt
index d2b59a668..4110fce85 100644
--- a/doc/plugins/sieve_extprograms.txt
+++ b/doc/plugins/sieve_extprograms.txt
@@ -106,9 +106,16 @@ sieve_<extension>_bin_dir =
   execute directly and pipe messages to.
 
 sieve_<extension>_exec_timeout = 10s
-  Configures the maximum execution time after which the program is forcefully
+  Configures the maximum execution time after which the program is forcibly
   terminated.
 
+sieve_<extension>_input_eol = crlf
+  Determines the end-of-line character sequence used for the data piped to
+  external programs. The default is currently "crlf", which represents a
+  sequence of the carriage return (CR) and line feed (LF) characters. This
+  matches the Internet Message Format (RFC5322) and what Sieve itself uses as a
+  line ending. Set this setting to "lf" to use a single LF character instead.
+
 Examples
 --------
 
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.c b/src/plugins/sieve-extprograms/sieve-extprograms-common.c
index 1619f095e..cf52b80a9 100644
--- a/src/plugins/sieve-extprograms/sieve-extprograms-common.c
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.c
@@ -62,7 +62,7 @@ struct sieve_extprograms_config *sieve_extprograms_config_init
 	struct sieve_instance *svinst = ext->svinst;
 	struct sieve_extprograms_config *ext_config;
 	const char *extname = sieve_extension_name(ext);
-	const char *bin_dir, *socket_dir;
+	const char *bin_dir, *socket_dir, *input_eol;
 	sieve_number_t execute_timeout;
 
 	extname = strrchr(extname, '.');
@@ -73,6 +73,8 @@ struct sieve_extprograms_config *sieve_extprograms_config_init
 		(svinst, t_strdup_printf("sieve_%s_bin_dir", extname));
 	socket_dir = sieve_setting_get
 		(svinst, t_strdup_printf("sieve_%s_socket_dir", extname));
+	input_eol = sieve_setting_get
+		(svinst, t_strdup_printf("sieve_%s_input_eol", extname));
 	
 	ext_config = i_new(struct sieve_extprograms_config, 1);
 	ext_config->execute_timeout = 
@@ -94,6 +96,10 @@ struct sieve_extprograms_config *sieve_extprograms_config_init
 				&execute_timeout)) {
 			ext_config->execute_timeout = execute_timeout;
 		}
+
+		ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_CRLF;
+		if (input_eol != NULL && strcasecmp(input_eol, "lf") == 0)
+			ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_LF;
 	}
 
 	if ( sieve_extension_is(ext, vnd_pipe_extension) ) 
@@ -366,6 +372,8 @@ int sieve_extprogram_command_read_operands
 
 struct sieve_extprogram {
 	struct sieve_instance *svinst;
+	const struct sieve_extprograms_config *ext_config;
+
 	const struct sieve_script_env *scriptenv;
 	struct program_client_settings set;
 	struct program_client *program_client;
@@ -515,6 +523,7 @@ struct sieve_extprogram *sieve_extprogram_create
 
 	sprog = i_new(struct sieve_extprogram, 1);
 	sprog->svinst = ext->svinst;
+	sprog->ext_config = ext_config;
 	sprog->scriptenv = senv;
 
 	sprog->set.client_connect_timeout_msecs =
@@ -574,7 +583,20 @@ void sieve_extprogram_set_output
 void sieve_extprogram_set_input
 (struct sieve_extprogram *sprog, struct istream *input)
 {
+	switch (sprog->ext_config->default_input_eol) {
+	case SIEVE_EXTPROGRAMS_EOL_LF:
+		input = i_stream_create_lf(input);
+		break;
+	case SIEVE_EXTPROGRAMS_EOL_CRLF:
+		input = i_stream_create_crlf(input);
+		break;
+	default:
+		i_unreached();
+	}
+
 	program_client_set_input(sprog->program_client, input);
+
+	i_stream_unref(&input);
 }
 
 void sieve_extprogram_set_output_seekable
@@ -601,12 +623,7 @@ int sieve_extprogram_set_input_mail
 	if (mail_get_stream(mail, NULL, NULL, &input) < 0)
 		return -1;
 
-	/* Make sure the message contains CRLF consistently */
-	input = i_stream_create_crlf(input);
-
-	program_client_set_input(sprog->program_client, input);
-	i_stream_unref(&input);
-
+	sieve_extprogram_set_input(sprog, input);
 	return 1;
 }
 
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.h b/src/plugins/sieve-extprograms/sieve-extprograms-common.h
index d769e3607..1c09d96ee 100644
--- a/src/plugins/sieve-extprograms/sieve-extprograms-common.h
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.h
@@ -10,6 +10,11 @@
  * Extension configuration
  */
 
+enum sieve_extprograms_eol {
+	SIEVE_EXTPROGRAMS_EOL_CRLF = 0,
+	SIEVE_EXTPROGRAMS_EOL_LF
+};
+
 struct sieve_extprograms_config {
 	const struct sieve_extension *copy_ext;
 	const struct sieve_extension *var_ext;
@@ -17,6 +22,8 @@ struct sieve_extprograms_config {
 	char *socket_dir;
 	char *bin_dir;
 
+	enum sieve_extprograms_eol default_input_eol;
+
 	unsigned int execute_timeout;
 };
 
diff --git a/tests/plugins/extprograms/bin/crlf b/tests/plugins/extprograms/bin/crlf
new file mode 100755
index 000000000..a0028cf55
--- /dev/null
+++ b/tests/plugins/extprograms/bin/crlf
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+tr -s '\r' '#'
diff --git a/tests/plugins/extprograms/execute/execute.svtest b/tests/plugins/extprograms/execute/execute.svtest
index f8fde2698..5e671c571 100644
--- a/tests/plugins/extprograms/execute/execute.svtest
+++ b/tests/plugins/extprograms/execute/execute.svtest
@@ -4,6 +4,7 @@ require "vnd.dovecot.debug";
 require "variables";
 require "relational";
 require "environment";
+require "encoded-character";
 
 test_set "message" text:
 From: stephan@example.com
@@ -115,4 +116,34 @@ test "Execute - used as test" {
 	}
 }
 	
-	
+test_config_set "sieve_execute_input_eol" "crlf";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+set "out" "";
+
+test "Execute - CRLF" {
+	execute
+		:input "FROP${hex:0A}FRIEP${hex:0a}"
+		:output "out"
+		"crlf";
+
+	if not string "${out}" "FROP#${hex:0A}FRIEP#${hex:0a}" {
+		test_fail "wrong string returned: '${out}'";
+	}
+}
+
+test_config_set "sieve_execute_input_eol" "lf";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+set "out" "";
+
+test "Execute - LF" {
+	execute
+		:input "FROP${hex:0D 0A}FRIEP${hex:0d 0a}"
+		:output "out"
+		"crlf";
+
+	if not string "${out}" "FROP${hex:0A}FRIEP${hex:0a}" {
+		test_fail "wrong string returned: '${out}'";
+	}
+}
-- 
GitLab