]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
config: Add support for dumping machine-readable "full config"
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 28 Nov 2022 17:39:01 +0000 (19:39 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Fri, 27 Jan 2023 13:01:47 +0000 (13:01 +0000)
This includes the main config and all different filters. It can be
requested from config service with "full" parameter or with doveconf
-F [<command>] parameter. If <command> is not given, output is written
to stdout. Otherwise the <command> is executed and the config is
provided via file descriptor in DOVECOT_CONFIG_FD environment.

The format will change in the following commits.

src/config/Makefile.am
src/config/config-connection.c
src/config/config-dump-full.c [new file with mode: 0644]
src/config/config-dump-full.h [new file with mode: 0644]
src/config/doveconf.c
src/lib-master/master-interface.h

index 41c36eb1d3e473771a016f39b5c17214ad4f2b21..91a648dbee61c4eae1bd496893dc5d27e522f456 100644 (file)
@@ -48,6 +48,7 @@ doveconf_DEPENDENCIES = $(LIBDOVECOT_DEPS) $(noinst_LTLIBRARIES)
 
 common = \
        config-connection.c \
+       config-dump-full.c \
        config-filter.c \
        config-parser.c \
        config-request.c \
@@ -67,6 +68,7 @@ doveconf_SOURCES = \
 noinst_HEADERS = \
        all-settings.h \
        config-connection.h \
+       config-dump-full.h \
        old-set-parser.h \
        sysinfo-get.h
 
index 321a798ceded9d860938849e1955a8eb77718c85..18d8640ef66c1083f82a900413549be5a9f1d899 100644 (file)
@@ -12,6 +12,7 @@
 #include "config-request.h"
 #include "config-parser.h"
 #include "config-connection.h"
+#include "config-dump-full.h"
 
 #include <unistd.h>
 
@@ -72,6 +73,7 @@ static int config_connection_request(struct config_connection *conn,
        struct config_filter filter;
        unsigned int section_idx = 0;
        const char *path, *value, *error, *module, *const *wanted_modules;
+       const char *import_environment;
        ARRAY(const char *) modules;
        ARRAY(const char *) exclude_settings;
        bool is_master = FALSE;
@@ -103,6 +105,12 @@ static int config_connection_request(struct config_connection *conn,
                                        IPADDR_IS_V4(&filter.remote_net) ?
                                        32 : 128;
                        }
+               } else if (strcmp(*args, "full") == 0) {
+                       if (config_dump_full(conn->output, &import_environment) < 0) {
+                               config_connection_destroy(conn);
+                               return -1;
+                       }
+                       return 0;
                }
        }
        array_append_zero(&modules);
diff --git a/src/config/config-dump-full.c b/src/config/config-dump-full.c
new file mode 100644 (file)
index 0000000..afda098
--- /dev/null
@@ -0,0 +1,145 @@
+/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "config-parser.h"
+#include "config-request.h"
+#include "config-dump-full.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+struct dump_context {
+       struct ostream *output;
+       string_t *delayed_output;
+};
+
+static void config_dump_full_callback(const char *key, const char *value,
+                                     enum config_key_type type ATTR_UNUSED,
+                                     void *context)
+{
+       struct dump_context *ctx = context;
+       const char *suffix;
+
+       if (ctx->delayed_output != NULL &&
+           ((str_begins(key, "passdb", &suffix) &&
+             (suffix[0] == '\0' || suffix[0] == '/')) ||
+            (str_begins(key, "userdb", &suffix) &&
+             (suffix[0] == '\0' || suffix[0] == '/')))) {
+               /* For backwards compatibility: global passdbs and userdbs are
+                  added after per-protocol ones, not before. */
+               str_printfa(ctx->delayed_output, "%s=%s\n", key, value);
+       } else T_BEGIN {
+               o_stream_nsend_str(ctx->output, t_strdup_printf(
+                       "%s=%s\n", key, str_tabescape(value)));
+       } T_END;
+}
+
+static void
+config_dump_full_write_filter(struct ostream *output,
+                             const struct config_filter *filter)
+{
+       string_t *str = t_str_new(128);
+       str_append(str, ":FILTER ");
+       unsigned int prefix_len = str_len(str);
+
+       if (filter->service != NULL) {
+               if (filter->service[0] != '!')
+                       str_printfa(str, "protocol=\"%s\" AND ", str_escape(filter->service));
+               else
+                       str_printfa(str, "NOT protocol=\"%s\" AND ", str_escape(filter->service + 1));
+       }
+       if (filter->local_name != NULL)
+               str_printfa(str, "local_name=\"%s\" AND ", str_escape(filter->local_name));
+       if (filter->local_bits > 0) {
+               str_printfa(str, "local_ip=\"%s/%u\" AND ",
+                           net_ip2addr(&filter->local_net),
+                           filter->local_bits);
+       }
+       if (filter->remote_bits > 0) {
+               str_printfa(str, "remote_ip=\"%s/%u\" AND ",
+                           net_ip2addr(&filter->remote_net),
+                           filter->remote_bits);
+       }
+
+       i_assert(str_len(str) > prefix_len);
+       str_delete(str, str_len(str) - 4, 4);
+       str_append_c(str, '\n');
+       o_stream_nsend(output, str_data(str), str_len(str));
+}
+
+static int
+config_dump_full_sections(struct ostream *output, unsigned int section_idx)
+{
+       struct config_filter_parser *const *filters;
+       struct config_export_context *export_ctx;
+       int ret = 0;
+
+       struct config_filter empty_filter;
+       i_zero(&empty_filter);
+       filters = config_filter_find_subset(config_filter, &empty_filter);
+
+       /* first filter should be the global one */
+       i_assert(filters[0] != NULL && filters[0]->filter.service == NULL);
+       filters++;
+
+       struct dump_context dump_ctx = {
+               .output = output,
+       };
+
+       for (; *filters != NULL && ret == 0; filters++) T_BEGIN {
+               config_dump_full_write_filter(output, &(*filters)->filter);
+               export_ctx = config_export_init(NULL, NULL,
+                       CONFIG_DUMP_SCOPE_SET,
+                       CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS,
+                       config_dump_full_callback, &dump_ctx);
+               config_export_parsers(export_ctx, (*filters)->parsers);
+               ret = config_export_finish(&export_ctx, &section_idx);
+       } T_END;
+       return 0;
+}
+
+int config_dump_full(struct ostream *output, const char **import_environment_r)
+{
+       struct config_export_context *export_ctx;
+       struct config_filter empty_filter;
+       enum config_dump_flags flags;
+       unsigned int section_idx = 0;
+       int ret;
+
+       i_zero(&empty_filter);
+       o_stream_cork(output);
+
+       struct dump_context dump_ctx = {
+               .output = output,
+               .delayed_output = str_new(default_pool, 256),
+       };
+
+       flags = CONFIG_DUMP_FLAG_CHECK_SETTINGS;
+       export_ctx = config_export_init(NULL, NULL,
+                                       CONFIG_DUMP_SCOPE_CHANGED, flags,
+                                       config_dump_full_callback, &dump_ctx);
+       i_zero(&empty_filter);
+       config_export_by_filter(export_ctx, &empty_filter);
+
+       *import_environment_r =
+               t_strdup(config_export_get_import_environment(export_ctx));
+       if (config_export_finish(&export_ctx, &section_idx) < 0)
+               ret = -1;
+       else
+               ret = config_dump_full_sections(output, section_idx);
+
+       if (dump_ctx.delayed_output != NULL &&
+           str_len(dump_ctx.delayed_output) > 0) {
+               o_stream_nsend_str(output, ":FILTER \n");
+               o_stream_nsend(output, str_data(dump_ctx.delayed_output),
+                              str_len(dump_ctx.delayed_output));
+       }
+       str_free(&dump_ctx.delayed_output);
+
+       o_stream_nsend_str(output, "\n");
+       o_stream_uncork(output);
+       return ret;
+}
diff --git a/src/config/config-dump-full.h b/src/config/config-dump-full.h
new file mode 100644 (file)
index 0000000..b10ef8b
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef CONFIG_DUMP_FULL
+#define CONFIG_DUMP_FULL
+
+int config_dump_full(struct ostream *output, const char **import_environment_r);
+
+#endif
index 1e294a54027680a9e8804341f5a76db915a8318d..e55b54eafb9fbdd3c2d2d75ad4b9814dea8bf34e 100644 (file)
 #include "ostream.h"
 #include "str.h"
 #include "strescape.h"
+#include "safe-mkstemp.h"
 #include "settings-parser.h"
 #include "master-interface.h"
 #include "master-service.h"
 #include "all-settings.h"
 #include "sysinfo-get.h"
 #include "old-set-parser.h"
+#include "config-dump-full.h"
 #include "config-connection.h"
 #include "config-parser.h"
 #include "config-request.h"
@@ -871,13 +873,13 @@ int main(int argc, char *argv[])
        const char *orig_config_path, *config_path, *module;
        ARRAY(const char *) module_names;
        struct config_filter filter;
-       const char *const *wanted_modules, *error;
+       const char *const *wanted_modules, *import_environment, *error;
        char **exec_args = NULL, **setting_name_filters = NULL;
        unsigned int i;
        int c, ret, ret2;
        bool config_path_specified, expand_vars = FALSE, hide_key = FALSE;
        bool parse_full_config = FALSE, simple_output = FALSE;
-       bool dump_defaults = FALSE, host_verify = FALSE;
+       bool dump_defaults = FALSE, host_verify = FALSE, dump_full = FALSE;
        bool print_plugin_banner = FALSE, hide_passwords = TRUE;
 
        if (getenv("USE_SYSEXITS") != NULL) {
@@ -887,7 +889,7 @@ int main(int argc, char *argv[])
 
        i_zero(&filter);
        master_service = master_service_init("config", master_service_flags,
-                                            &argc, &argv, "adf:hHm:nNpPexsS");
+                                            &argc, &argv, "adf:FhHm:nNpPexsS");
        orig_config_path = t_strdup(master_service_get_config_path(master_service));
 
        i_set_failure_prefix("doveconf: ");
@@ -906,6 +908,11 @@ int main(int argc, char *argv[])
                case 'f':
                        filter_parse_arg(&filter, optarg);
                        break;
+               case 'F':
+                       dump_full = TRUE;
+                       simple_output = TRUE;
+                       expand_vars = TRUE;
+                       break;
                case 'h':
                        hide_key = TRUE;
                        break;
@@ -953,9 +960,9 @@ int main(int argc, char *argv[])
        if (host_verify)
                hostname_verify_format(argv[optind]);
 
-       if (c == 'e') {
+       if (c == 'e' || (dump_full && argv[optind] != NULL)) {
                if (argv[optind] == NULL)
-                       i_fatal("Missing command for -e");
+                       i_fatal("Missing command for -%c", c == 'e' ? 'e' : 'F');
                exec_args = &argv[optind];
        } else if (argv[optind] != NULL) {
                /* print only a single config setting */
@@ -995,7 +1002,42 @@ int main(int argc, char *argv[])
        if ((ret == -1 && exec_args != NULL) || ret == 0 || ret == -2)
                i_fatal("%s", error);
 
-       if (simple_output) {
+       if (dump_full && exec_args == NULL) {
+               struct ostream *output = o_stream_create_fd(STDOUT_FILENO, 0);
+               o_stream_set_no_error_handling(output, TRUE);
+               ret2 = config_dump_full(output, &import_environment);
+               o_stream_destroy(&output);
+       } else if (dump_full) {
+               /* create an unlinked file to /tmp */
+               string_t *path = t_str_new(128);
+               str_append(path, "/tmp/doveconf.");
+               int temp_fd = safe_mkstemp(path, 0700, (uid_t)-1, (gid_t)-1);
+               if (temp_fd == -1)
+                       i_fatal("safe_mkstemp(%s) failed: %m", str_c(path));
+               i_unlink(str_c(path));
+               struct ostream *output =
+                       o_stream_create_fd(temp_fd, IO_BLOCK_SIZE);
+               if (config_dump_full(output, &import_environment) < 0)
+                       i_fatal("Invalid configuration");
+               if (o_stream_finish(output) < 0) {
+                       i_fatal("write(%s) failed: %s",
+                               str_c(path), o_stream_get_error(output));
+               }
+               o_stream_destroy(&output);
+
+               if (getenv(DOVECOT_PRESERVE_ENVS_ENV) != NULL) {
+                       /* Standalone binary is getting its configuration via
+                          doveconf. Clean the environment before calling it.
+                          Do this only if the environment exists, because
+                          lib-master doesn't set it if it doesn't want the
+                          environment to be cleaned (e.g. -k parameter). */
+                       master_service_import_environment(import_environment);
+                       master_service_env_clean();
+               }
+               env_put(DOVECOT_CONFIG_FD_ENV, dec2str(temp_fd));
+               execvp(exec_args[0], exec_args);
+               i_fatal("execvp(%s) failed: %m", exec_args[0]);
+       } else if (simple_output) {
                struct config_export_context *ctx;
                unsigned int section_idx = 0;
 
index e5db7ce720c42e8d9f7ac92564e4143d64ab59cd..9966bec4056b1871dfde032ba4113e7ffb34cff9 100644 (file)
@@ -82,6 +82,10 @@ enum master_login_state {
    be used to initialize debug logging immediately at startup. */
 #define DOVECOT_LOG_DEBUG_ENV "LOG_DEBUG"
 
+/* getenv(DOVECOT_CONFIG_FD_ENV) returns the configuration fd provided by
+   doveconf. */
+#define DOVECOT_CONFIG_FD_ENV "DOVECOT_CONFIG_FD"
+
 /* getenv(DOVECOT_STATS_WRITER_SOCKET_PATH) returns path to the stats-writer
    socket. */
 #define DOVECOT_STATS_WRITER_SOCKET_PATH "STATS_WRITER_SOCKET_PATH"