From f4511d79b53c961148077970955e6015c092bddd Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Mon, 10 Mar 2025 14:52:10 +0200
Subject: [PATCH] managesieve-login: Settings plugin - Get capabilities without
 executing managesieve

---
 src/managesieve-login/Makefile.am             |   3 +
 .../managesieve-login-settings-plugin.c       | 275 ++++--------------
 2 files changed, 58 insertions(+), 220 deletions(-)

diff --git a/src/managesieve-login/Makefile.am b/src/managesieve-login/Makefile.am
index 19ac1a5a3..d556da6a9 100644
--- a/src/managesieve-login/Makefile.am
+++ b/src/managesieve-login/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS = \
 	$(LIBDOVECOT_SERVICE_INCLUDE) \
 	$(LIBDOVECOT_LOGIN_INCLUDE) \
 	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve \
 	-I$(top_srcdir)/src/lib-managesieve
 
 libmanagesieve_login_settings_la_LDFLAGS = -module -avoid-version
@@ -20,6 +21,8 @@ libmanagesieve_login_settings_la_SOURCES = \
 
 libmanagesieve_login_settings_la_CFLAGS = \
 	$(AM_CFLAGS) $(LIBDOVECOT_CONFIG_INCLUDE) -DPKG_LIBEXECDIR=\""$(dovecot_pkglibexecdir)"\"
+libmanagesieve_login_settings_la_LIBADD = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
 
 libs = \
 	$(top_builddir)/src/lib-managesieve/libdovecot-managesieve.la
diff --git a/src/managesieve-login/managesieve-login-settings-plugin.c b/src/managesieve-login/managesieve-login-settings-plugin.c
index 692b9a3f4..14eaed0d3 100644
--- a/src/managesieve-login/managesieve-login-settings-plugin.c
+++ b/src/managesieve-login/managesieve-login-settings-plugin.c
@@ -2,229 +2,31 @@
  */
 
 #include "lib.h"
-#include "str.h"
-#include "buffer.h"
-#include "env-util.h"
-#include "execv-const.h"
-#include "master-service.h"
-#include "settings-parser.h"
 #include "config-parser-private.h"
+#include "sieve.h"
 #include "managesieve-login-settings-plugin.h"
 
-#include <stddef.h>
-#include <unistd.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <sysexits.h>
-#include <signal.h>
+static int
+(*next_hook_config_parser_end)(struct config_parser_context *ctx,
+			       struct config_parsed *new_config,
+			       struct event *event, const char **error_r) = NULL;
 
-typedef enum {
-	CAP_SIEVE,
-	CAP_NOTIFY,
-} capability_type_t;
-
-bool capability_dumped = FALSE;
-static char *capability_sieve = NULL;
-static char *capability_notify = NULL;
-
-static void
-(*next_hook_config_parser_begin)(struct config_parser_context *ctx) = NULL;
-
-static void
-managesieve_login_config_parser_begin(struct config_parser_context *ctx);
+static int
+managesieve_login_config_parser_end(struct config_parser_context *ctx,
+				    struct config_parsed *new_config,
+				    struct event *event, const char **error_r);
 
 const char *managesieve_login_settings_version = DOVECOT_ABI_VERSION;
 
 void managesieve_login_settings_init(struct module *module ATTR_UNUSED)
 {
-	next_hook_config_parser_begin = hook_config_parser_begin;
-	hook_config_parser_begin = managesieve_login_config_parser_begin;
+	next_hook_config_parser_end = hook_config_parser_end;
+	hook_config_parser_end = managesieve_login_config_parser_end;
 }
 
 void managesieve_login_settings_deinit(void)
 {
-	hook_config_parser_begin = next_hook_config_parser_begin;
-
-	if (capability_sieve != NULL)
-		i_free(capability_sieve);
-	if (capability_notify != NULL)
-		i_free(capability_notify);
-}
-
-static void capability_store(capability_type_t cap_type, const char *value)
-{
-	switch (cap_type) {
-	case CAP_SIEVE:
-		capability_sieve = i_strdup(value);
-		break;
-	case CAP_NOTIFY:
-		capability_notify = i_strdup(value);
-		break;
-	}
-}
-
-static void capability_parse(const char *cap_string)
-{
-	capability_type_t cap_type = CAP_SIEVE;
-	const char *p = cap_string;
-	string_t *part = t_str_new(256);
-
-	if (cap_string == NULL || *cap_string == '\0') {
-		i_warning("managesieve-login: capability string is empty.");
-		return;
-	}
-
-	while (*p != '\0') {
-		if (*p == '\\') {
-			p++;
-			if (*p != '\0') {
-				str_append_c(part, *p);
-				p++;
-			} else break;
-		} else if (*p == ':') {
-			if (strcasecmp(str_c(part), "SIEVE") == 0)
-				cap_type = CAP_SIEVE;
-			else if (strcasecmp(str_c(part), "NOTIFY") == 0)
-				cap_type = CAP_NOTIFY;
-			else {
-				i_warning("managesieve-login: unknown capability '%s' listed in "
-					  "capability string (ignored).", str_c(part));
-			}
-			str_truncate(part, 0);
-		} else if (*p == ',') {
-			capability_store(cap_type, str_c(part));
-			str_truncate(part, 0);
-		} else {
-			/* Append character, but omit leading spaces */
-			if (str_len(part) > 0 || *p != ' ')
-				str_append_c(part, *p);
-		}
-		p++;
-	}
-
-	if (str_len(part) > 0)
-		capability_store(cap_type, str_c(part));
-}
-
-static bool capability_dump(bool dump_defaults)
-{
-	char buf[4096];
-	int fd[2], status = 0;
-	ssize_t ret;
-	unsigned int pos;
-	pid_t pid;
-
-	if (getenv("DUMP_CAPABILITY") != NULL)
-		return TRUE;
-
-	/* We want to dump capability only when doing the main config parsing
-	   (config and doveconf processes) and managesieve process (started
-	   from command line). We especially don't want to dump capability
-	   every time when running doveadm. */
-	const char *protocol = getenv("DOVECONF_PROTOCOL");
-	if (protocol != NULL && strcmp(protocol, "sieve") != 0)
-		return TRUE;
-	if (pipe(fd) < 0) {
-		i_error("managesieve-login: dump-capability pipe() failed: %m");
-		return FALSE;
-	}
-	fd_close_on_exec(fd[0], TRUE);
-	fd_close_on_exec(fd[1], TRUE);
-
-	pid = fork();
-	if (pid == (pid_t)-1) {
-		i_close_fd(&fd[0]);
-		i_close_fd(&fd[1]);
-		i_error("managesieve-login: dump-capability fork() failed: %m");
-		return FALSE;
-	}
-	if (pid == 0) {
-		const char *argv[5];
-
-		/* Child */
-		i_close_fd(&fd[0]);
-
-		if (dup2(fd[1], STDOUT_FILENO) < 0) {
-			i_fatal("managesieve-login: "
-				"dump-capability dup2() failed: %m");
-		}
-
-		env_put("DUMP_CAPABILITY", "1");
-
-		argv[0] = PKG_LIBEXECDIR"/managesieve";
-		argv[1] = "-k";
-		if (!dump_defaults) {
-			argv[2] = "-c";
-			argv[3] = master_service_get_config_path(master_service);
-			argv[4] = NULL;
-		} else {
-			argv[2] = "-O";
-			argv[3] = NULL;
-		}
-		execv_const(argv[0], argv);
-
-		i_fatal("managesieve-login: "
-			"dump-capability execv(%s) failed: %m", argv[0]);
-	}
-	i_close_fd(&fd[1]);
-
-	time_t start_time = time(NULL);
-	alarm(60);
-	pid_t wait_ret = wait(&status);
-	alarm(0);
-
-	if (wait_ret >= 0)
-		; /* success */
-	else if (errno != ECHILD) {
-		i_error("managesieve-login: dump-capability failed: "
-			"wait() failed: %m");
-		return FALSE;
-	} else {
-		i_error("managesieve-login: dump-capability failed: "
-			"process %d got stuck (waited %"PRIdTIME_T" seconds) - "
-			"killing sig SIGABRT",
-			(int)pid, (time(NULL) - start_time));
-		if (kill(pid, SIGABRT) < 0)
-			i_error("kill(%d) failed: %m", (int)pid);
-		return FALSE;
-	}
-
-	if (status != 0) {
-		i_close_fd(&fd[0]);
-		if (WIFSIGNALED(status)) {
-			i_error("managesieve-login: dump-capability process "
-				"killed with signal %d", WTERMSIG(status));
-		} else {
-			i_error("managesieve-login: "
-				"dump-capability process returned %d",
-				(WIFEXITED(status) ?
-				 WEXITSTATUS(status) : status));
-		}
-		return FALSE;
-	}
-
-	pos = 0;
-	while ((ret = read(fd[0], buf + pos, sizeof(buf) - pos)) > 0)
-		pos += ret;
-
-	if (ret < 0) {
-		i_error("managesieve-login: "
-			"read(dump-capability process) failed: %m");
-		i_close_fd(&fd[0]);
-		return FALSE;
-	}
-	i_close_fd(&fd[0]);
-
-	if (pos == 0 || buf[pos-1] != '\n') {
-		i_error("managesieve-login: "
-			"dump-capability: Couldn't read capability "
-			"(got %u bytes)", pos);
-		return FALSE;
-	}
-	buf[pos-1] = '\0';
-
-	capability_parse(buf);
-	return TRUE;
+	hook_config_parser_end = next_hook_config_parser_end;
 }
 
 static void
@@ -236,22 +38,55 @@ managesieve_login_config_set(struct config_parser_context *ctx,
 	config_parser_set_change_counter(ctx, CONFIG_PARSER_CHANGE_EXPLICIT);
 }
 
-static void
-managesieve_login_config_parser_begin(struct config_parser_context *ctx)
+static int
+dump_capability(struct config_parser_context *ctx,
+		struct config_parsed *new_config,
+		struct event *event, const char **error_r)
 {
-	if (!capability_dumped) {
-		(void)capability_dump(ctx->dump_defaults);
-		capability_dumped = TRUE;
+	struct sieve_instance *svinst;
+
+	/* If all capabilities are explicitly set, we don't need to
+	   generate them. */
+	if (config_parsed_get_setting_change_counter(new_config,
+		"managesieve_login", "managesieve_sieve_capability") ==
+			CONFIG_PARSER_CHANGE_EXPLICIT &&
+	    config_parsed_get_setting_change_counter(new_config,
+		"managesieve_login", "managesieve_notify_capability") ==
+			CONFIG_PARSER_CHANGE_EXPLICIT)
+		return 0;
+
+	/* Initialize Sieve engine */
+	struct sieve_environment svenv = {
+		.home_dir = "/tmp",
+		.event_parent = event,
+	};
+	if (sieve_init(&svenv, NULL, NULL, FALSE, &svinst) < 0) {
+		*error_r = "Failed to initialize Sieve";
+		return -1;
 	}
 
-	if (capability_sieve != NULL) {
-		managesieve_login_config_set(
-			ctx, "managesieve_sieve_capability",
-			capability_sieve);
-	}
+	/* Dump capabilities */
+	managesieve_login_config_set(ctx, "managesieve_sieve_capability",
+				     sieve_get_capabilities(svinst, NULL));
+	const char *capability_notify = sieve_get_capabilities(svinst, "notify");
 	if (capability_notify != NULL) {
 		managesieve_login_config_set(
 			ctx, "managesieve_notify_capability",
 			capability_notify);
 	}
+
+	sieve_deinit(&svinst);
+	return 0;
+}
+
+static int
+managesieve_login_config_parser_end(struct config_parser_context *ctx,
+				    struct config_parsed *new_config,
+				    struct event *event, const char **error_r)
+{
+	if (dump_capability(ctx, new_config, event, error_r) < 0)
+		return -1;
+	if (next_hook_config_parser_end != NULL)
+		return next_hook_config_parser_end(ctx, new_config, event, error_r);
+	return 0;
 }
-- 
GitLab