From f0a107a8426793f1d766e038fa6244ad8ec79ef3 Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan.bosch@open-xchange.com>
Date: Tue, 27 Aug 2024 02:55:08 +0200
Subject: [PATCH] plugins: sieve-extprograms: Migrate settings to new config
 structure

---
 src/plugins/settings/Makefile.am              |   1 +
 src/plugins/settings/settings-get.pl          |   1 +
 src/plugins/sieve-extprograms/Makefile.am     |   3 +
 src/plugins/sieve-extprograms/cmd-execute.c   |   4 +-
 .../sieve-extprograms-common.c                | 102 +++++++--------
 .../sieve-extprograms-common.h                |  15 +--
 .../sieve-extprograms-limits.h                |   9 ++
 .../sieve-extprograms-settings.c              | 121 ++++++++++++++++++
 .../sieve-extprograms-settings.h              |  29 +++++
 9 files changed, 216 insertions(+), 69 deletions(-)
 create mode 100644 src/plugins/sieve-extprograms/sieve-extprograms-limits.h
 create mode 100644 src/plugins/sieve-extprograms/sieve-extprograms-settings.c
 create mode 100644 src/plugins/sieve-extprograms/sieve-extprograms-settings.h

diff --git a/src/plugins/settings/Makefile.am b/src/plugins/settings/Makefile.am
index 9b41a2cd7..35b08c4b2 100644
--- a/src/plugins/settings/Makefile.am
+++ b/src/plugins/settings/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib-sieve/util \
 	-I$(top_srcdir)/src/lib-sieve/storage/ldap \
 	-I$(top_srcdir)/src/lib-managesieve \
+	-I$(top_srcdir)/src/plugins/sieve-extprograms \
 	-DSETTINGS_PLUGIN
 if LDAP_PLUGIN
 AM_CPPFLAGS += -DPLUGIN_BUILD -DLDAP_PLUGIN
diff --git a/src/plugins/settings/settings-get.pl b/src/plugins/settings/settings-get.pl
index 6398408d5..0cbc276c9 100755
--- a/src/plugins/settings/settings-get.pl
+++ b/src/plugins/settings/settings-get.pl
@@ -29,6 +29,7 @@ print '#include "sieve-common.h"'."\n";
 print '#include "sieve-limits.h"'."\n";
 print '#include "sieve-address-source.h"'."\n";
 print '#include "managesieve-url.h"'."\n";
+print '#include "sieve-extprograms-limits.h"'."\n";
 print '#include "pigeonhole-settings.h"'."\n";
 print '#include <unistd.h>'."\n";
 print "#ifdef LDAP_PLUGIN\n";
diff --git a/src/plugins/sieve-extprograms/Makefile.am b/src/plugins/sieve-extprograms/Makefile.am
index ab7844f38..b495ef677 100644
--- a/src/plugins/sieve-extprograms/Makefile.am
+++ b/src/plugins/sieve-extprograms/Makefile.am
@@ -25,10 +25,13 @@ extensions = \
 lib90_sieve_extprograms_plugin_la_SOURCES = \
 	$(commands) \
 	$(extensions) \
+	sieve-extprograms-settings.c \
 	sieve-extprograms-common.c \
 	sieve-extprograms-plugin.c
 
 noinst_HEADERS = \
+	sieve-extprograms-limits.h \
+	sieve-extprograms-settings.h \
 	sieve-extprograms-common.h \
 	sieve-extprograms-plugin.h
 
diff --git a/src/plugins/sieve-extprograms/cmd-execute.c b/src/plugins/sieve-extprograms/cmd-execute.c
index c105087e7..1d77f6a7e 100644
--- a/src/plugins/sieve-extprograms/cmd-execute.c
+++ b/src/plugins/sieve-extprograms/cmd-execute.c
@@ -167,7 +167,9 @@ cmd_execute_validate_output_tag(struct sieve_validator *valdtr,
 	struct sieve_ast_argument *tag = *arg;
 	struct sieve_extprograms_ext_context *extctx = cmd->ext->context;
 
-	if (extctx == NULL || extctx->var_ext == NULL ||
+	if (extctx == NULL)
+		return FALSE;
+	if (extctx->var_ext == NULL ||
 	    !sieve_ext_variables_is_active(extctx->var_ext, valdtr)) {
 		sieve_argument_validate_error(
 			valdtr,*arg,
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.c b/src/plugins/sieve-extprograms/sieve-extprograms-common.c
index 401fb2ef0..a4f7b33c7 100644
--- a/src/plugins/sieve-extprograms/sieve-extprograms-common.c
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.c
@@ -14,6 +14,7 @@
 #include "istream-crlf.h"
 #include "istream-header-filter.h"
 #include "ostream.h"
+#include "settings.h"
 #include "mail-user.h"
 #include "mail-storage.h"
 
@@ -35,6 +36,8 @@
 #include "sieve-ext-copy.h"
 #include "sieve-ext-variables.h"
 
+#include "sieve-extprograms-limits.h"
+#include "sieve-extprograms-settings.h"
 #include "sieve-extprograms-common.h"
 
 #include <unistd.h>
@@ -42,16 +45,6 @@
 #include <sys/wait.h>
 #include <sys/socket.h>
 
-/*
- * Limits
- */
-
-#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN 128
-#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN  1024
-
-#define SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS 10
-#define SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS 5
-
 /*
  * Context
  */
@@ -62,21 +55,19 @@ int sieve_extprograms_ext_load(const struct sieve_extension *ext,
 	struct sieve_instance *svinst = ext->svinst;
 	const struct sieve_extension *copy_ext = NULL;
 	const struct sieve_extension *var_ext = NULL;
+	const struct sieve_extprograms_settings *set;
 	struct sieve_extprograms_ext_context *extctx;
-	const char *extname = sieve_extension_name(ext);
-	const char *bin_dir, *socket_dir, *input_eol;
-	sieve_number_t execute_timeout;
-
-	extname = strrchr(extname, '.');
-	i_assert(extname != NULL);
-	extname++;
-
-	bin_dir = sieve_setting_get(
-		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));
+	const char *extname = sieve_extension_name(ext), *error;
+	const struct setting_parser_info *set_info;
+
+	if (sieve_extension_is(ext, sieve_ext_vnd_pipe))
+		set_info = &sieve_ext_vnd_pipe_setting_parser_info;
+	else if (sieve_extension_is(ext, sieve_ext_vnd_filter))
+		set_info = &sieve_ext_vnd_filter_setting_parser_info;
+	else if (sieve_extension_is(ext, sieve_ext_vnd_execute))
+		set_info = &sieve_ext_vnd_execute_setting_parser_info;
+	else
+		i_unreached();
 
 	if (sieve_extension_is(ext, sieve_ext_vnd_pipe)) {
 		if (sieve_ext_copy_get_extension(ext->svinst, &copy_ext) < 0)
@@ -88,32 +79,23 @@ int sieve_extprograms_ext_load(const struct sieve_extension *ext,
 			return -1;
 	}
 
-	extctx = i_new(struct sieve_extprograms_ext_context, 1);
-	extctx->execute_timeout =
-		SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS;
-	extctx->copy_ext = copy_ext;
-	extctx->var_ext = var_ext;
-
-	if (bin_dir == NULL && socket_dir == NULL) {
+	if (settings_get(svinst->event, set_info, 0, &set, &error) < 0) {
+		e_error(svinst->event, "%s", error);
+		return -1;
+	}
+	if (*set->bin_dir == '\0' && *set->socket_dir == '\0') {
 		e_debug(svinst->event, "%s extension: "
-			"no bin or socket directory specified; extension is unconfigured "
+			"No bin or socket directory specified; "
+			"extension is unconfigured "
 			"(both sieve_%s_bin_dir and sieve_%s_socket_dir are not set)",
-			sieve_extension_name(ext), extname, extname);
-	} else {
-		extctx->bin_dir = i_strdup(bin_dir);
-		extctx->socket_dir = i_strdup(socket_dir);
-
-		if (sieve_setting_get_duration_value(
-			svinst, t_strdup_printf("sieve_%s_exec_timeout",
-						extname), &execute_timeout))
-			extctx->execute_timeout = execute_timeout;
-
-		extctx->default_input_eol = SIEVE_EXTPROGRAMS_EOL_CRLF;
-		if (input_eol != NULL && strcasecmp(input_eol, "lf") == 0) {
-			extctx->default_input_eol = SIEVE_EXTPROGRAMS_EOL_LF;
-		}
+			extname, extname, extname);
 	}
 
+	extctx = i_new(struct sieve_extprograms_ext_context, 1);
+	extctx->copy_ext = copy_ext;
+	extctx->var_ext = var_ext;
+	extctx->set = set;
+
 	*context_r = extctx;
 	return 0;
 }
@@ -124,9 +106,7 @@ void sieve_extprograms_ext_unload(const struct sieve_extension *ext)
 
 	if (extctx == NULL)
 		return;
-
-	i_free(extctx->bin_dir);
-	i_free(extctx->socket_dir);
+	settings_free(extctx->set);
 	i_free(extctx);
 }
 
@@ -247,11 +227,20 @@ _arg_validate(void *context, struct sieve_ast_argument *item)
 bool sieve_extprogram_command_validate(struct sieve_validator *valdtr,
 				       struct sieve_command *cmd)
 {
+	struct sieve_extprograms_ext_context *extctx = cmd->ext->context;
 	struct sieve_ast_argument *arg = cmd->first_positional;
 	struct sieve_ast_argument *stritem;
 	struct _arg_validate_context actx;
 	string_t *program_name;
 
+	if (extctx == NULL) {
+		sieve_command_validate_error(
+			valdtr, cmd, "the %s extension is not configured "
+			"(refer to server log for more information)",
+			sieve_extension_name(cmd->ext));
+		return FALSE;
+	}
+
 	if (arg == NULL) {
 		sieve_command_validate_error(
 			valdtr, cmd,
@@ -426,7 +415,8 @@ sieve_extprogram_create(const struct sieve_extension *ext,
 		"running program: %s", action, program_name);
 
 	if (extctx == NULL ||
-	    (extctx->bin_dir == NULL && extctx->socket_dir == NULL)) {
+	    (*extctx->set->bin_dir == '\0' &&
+	     *extctx->set->socket_dir == '\0')) {
 		e_error(svinst->event, "action %s: "
 			"failed to execute program '%s': "
 			"vnd.dovecot.%s extension is unconfigured",
@@ -436,9 +426,9 @@ sieve_extprogram_create(const struct sieve_extension *ext,
 	}
 
 	/* Try socket first */
-	if (extctx->socket_dir != NULL) {
+	if (*extctx->set->socket_dir != '\0') {
 		path = t_strconcat(senv->user->set->base_dir, "/",
-				   extctx->socket_dir, "/", program_name,
+				   extctx->set->socket_dir, "/", program_name,
 				   NULL);
 		if (stat(path, &st) < 0) {
 			switch (errno) {
@@ -471,9 +461,9 @@ sieve_extprogram_create(const struct sieve_extension *ext,
 	}
 
 	/* Try executable next */
-	if (path == NULL && extctx->bin_dir != NULL) {
+	if (path == NULL && *extctx->set->bin_dir != '\0') {
 		fork = TRUE;
-		path = t_strconcat(extctx->bin_dir, "/", program_name, NULL);
+		path = t_strconcat(extctx->set->bin_dir, "/", program_name, NULL);
 		if (stat(path, &st) < 0) {
 			switch (errno) {
 			case ENOENT:
@@ -528,7 +518,7 @@ sieve_extprogram_create(const struct sieve_extension *ext,
 	struct program_client_parameters pc_params = {
 		.client_connect_timeout_msecs =
 			SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS,
-		.input_idle_timeout_msecs = extctx->execute_timeout * 1000,
+		.input_idle_timeout_msecs = extctx->set->exec_timeout * 1000,
 	};
 
 	if (fork) {
@@ -592,7 +582,7 @@ void sieve_extprogram_set_output(struct sieve_extprogram *sprog,
 void sieve_extprogram_set_input(struct sieve_extprogram *sprog,
 				struct istream *input)
 {
-	switch (sprog->extctx->default_input_eol) {
+	switch (sprog->extctx->set->parsed.input_eol) {
 	case SIEVE_EXTPROGRAMS_EOL_LF:
 		input = i_stream_create_lf(input);
 		break;
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.h b/src/plugins/sieve-extprograms/sieve-extprograms-common.h
index 881b7d467..bd71c0f44 100644
--- a/src/plugins/sieve-extprograms/sieve-extprograms-common.h
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.h
@@ -2,26 +2,17 @@
 #define SIEVE_EXTPROGRAMS_COMMON_H
 
 #include "sieve-common.h"
+#include "sieve-extprograms-settings.h"
 
 /*
  * Extension configuration
  */
 
-enum sieve_extprograms_eol {
-	SIEVE_EXTPROGRAMS_EOL_CRLF = 0,
-	SIEVE_EXTPROGRAMS_EOL_LF
-};
-
 struct sieve_extprograms_ext_context {
+	const struct sieve_extprograms_settings *set;
+
 	const struct sieve_extension *copy_ext;
 	const struct sieve_extension *var_ext;
-
-	char *socket_dir;
-	char *bin_dir;
-
-	enum sieve_extprograms_eol default_input_eol;
-
-	unsigned int execute_timeout;
 };
 
 int sieve_extprograms_ext_load(const struct sieve_extension *ext,
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-limits.h b/src/plugins/sieve-extprograms/sieve-extprograms-limits.h
new file mode 100644
index 000000000..c77d2466d
--- /dev/null
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-limits.h
@@ -0,0 +1,9 @@
+#ifndef SIEVE_EXTPROGRAMS_LIMITS_H
+#define SIEVE_EXTPROGRAMS_LIMITS_H
+
+#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN 128
+#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN  1024
+
+#define SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS 5
+
+#endif
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-settings.c b/src/plugins/sieve-extprograms/sieve-extprograms-settings.c
new file mode 100644
index 000000000..0d33f13b4
--- /dev/null
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-settings.c
@@ -0,0 +1,121 @@
+/* Copyright (c) 2024 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "settings.h"
+#include "settings-parser.h"
+
+#include "sieve-extprograms-limits.h"
+#include "sieve-extprograms-settings.h"
+
+static bool
+sieve_extprograms_settings_check(void *_set, pool_t pool, const char **error_r);
+
+#undef DEF
+#define DEF(type, name) \
+	SETTING_DEFINE_STRUCT_##type("sieve_pipe_"#name, name, \
+				     struct sieve_extprograms_settings)
+
+static const struct setting_define ext_pipe_setting_defines[] = {
+	DEF(STR, bin_dir),
+	DEF(STR, socket_dir),
+	DEF(ENUM, input_eol),
+
+	DEF(TIME, exec_timeout),
+
+	SETTING_DEFINE_LIST_END,
+};
+
+#undef DEF
+#define DEF(type, name) \
+	SETTING_DEFINE_STRUCT_##type("sieve_filter_"#name, name, \
+				     struct sieve_extprograms_settings)
+
+static const struct setting_define ext_filter_setting_defines[] = {
+	DEF(STR, bin_dir),
+	DEF(STR, socket_dir),
+	DEF(ENUM, input_eol),
+
+	DEF(TIME, exec_timeout),
+
+	SETTING_DEFINE_LIST_END,
+};
+
+#undef DEF
+#define DEF(type, name) \
+	SETTING_DEFINE_STRUCT_##type("sieve_execute_"#name, name, \
+				     struct sieve_extprograms_settings)
+
+static const struct setting_define ext_execute_setting_defines[] = {
+	DEF(STR, bin_dir),
+	DEF(STR, socket_dir),
+	DEF(ENUM, input_eol),
+
+	DEF(TIME, exec_timeout),
+
+	SETTING_DEFINE_LIST_END,
+};
+
+static const struct sieve_extprograms_settings sieve_extprograms_default_settings = {
+	.bin_dir = "",
+	.socket_dir = "",
+	.input_eol = "crlf:lf",
+	.exec_timeout = 10,
+};
+
+const struct setting_parser_info sieve_ext_vnd_pipe_setting_parser_info = {
+	.name = "sieve_ext_pipe",
+
+	.defines = ext_pipe_setting_defines,
+	.defaults = &sieve_extprograms_default_settings,
+
+	.struct_size = sizeof(struct sieve_extprograms_settings),
+
+	.check_func = sieve_extprograms_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct sieve_extprograms_settings, pool),
+};
+
+const struct setting_parser_info sieve_ext_vnd_filter_setting_parser_info = {
+	.name = "sieve_ext_filter",
+
+	.defines = ext_filter_setting_defines,
+	.defaults = &sieve_extprograms_default_settings,
+
+	.struct_size = sizeof(struct sieve_extprograms_settings),
+
+	.check_func = sieve_extprograms_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct sieve_extprograms_settings, pool),
+};
+
+const struct setting_parser_info sieve_ext_vnd_execute_setting_parser_info = {
+	.name = "sieve_ext_execute",
+
+	.defines = ext_execute_setting_defines,
+	.defaults = &sieve_extprograms_default_settings,
+
+	.struct_size = sizeof(struct sieve_extprograms_settings),
+
+	.check_func = sieve_extprograms_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct sieve_extprograms_settings, pool),
+};
+
+/* <settings checks> */
+static bool
+sieve_extprograms_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+				 const char **error_r ATTR_UNUSED)
+{
+	struct sieve_extprograms_settings *set = _set;
+
+	if (strcasecmp(set->input_eol, "crlf") == 0)
+		set->parsed.input_eol = SIEVE_EXTPROGRAMS_EOL_CRLF;
+	else if (strcasecmp(set->input_eol, "lf") == 0)
+		set->parsed.input_eol = SIEVE_EXTPROGRAMS_EOL_LF;
+	else
+		i_unreached();
+	return TRUE;
+}
+/* </settings checks> */
diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-settings.h b/src/plugins/sieve-extprograms/sieve-extprograms-settings.h
new file mode 100644
index 000000000..3f2db7a31
--- /dev/null
+++ b/src/plugins/sieve-extprograms/sieve-extprograms-settings.h
@@ -0,0 +1,29 @@
+#ifndef SIEVE_EXTPROGRAMS_SETTINGS_H
+#define SIEVE_EXTPROGRAMS_SETTINGS_H
+
+/* <settings checks> */
+enum sieve_extprograms_eol {
+	SIEVE_EXTPROGRAMS_EOL_CRLF = 0,
+	SIEVE_EXTPROGRAMS_EOL_LF
+};
+/* </settings checks> */
+
+struct sieve_extprograms_settings {
+	pool_t pool;
+
+	const char *bin_dir;
+	const char *socket_dir;
+	const char *input_eol;
+
+	unsigned int exec_timeout;
+
+	struct {
+		enum sieve_extprograms_eol input_eol;
+	} parsed;
+};
+
+extern const struct setting_parser_info sieve_ext_vnd_pipe_setting_parser_info;
+extern const struct setting_parser_info sieve_ext_vnd_filter_setting_parser_info;
+extern const struct setting_parser_info sieve_ext_vnd_execute_setting_parser_info;
+
+#endif
-- 
GitLab