From: Timo Sirainen Date: Mon, 28 Nov 2022 17:39:01 +0000 (+0200) Subject: config: Add support for dumping machine-readable "full config" X-Git-Tag: 2.4.0~3089 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=396b0c3caefd1b98a6fc20722d4197e9bed91b04;p=thirdparty%2Fdovecot%2Fcore.git config: Add support for dumping machine-readable "full config" This includes the main config and all different filters. It can be requested from config service with "full" parameter or with doveconf -F [] parameter. If is not given, output is written to stdout. Otherwise the is executed and the config is provided via file descriptor in DOVECOT_CONFIG_FD environment. The format will change in the following commits. --- diff --git a/src/config/Makefile.am b/src/config/Makefile.am index 41c36eb1d3..91a648dbee 100644 --- a/src/config/Makefile.am +++ b/src/config/Makefile.am @@ -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 diff --git a/src/config/config-connection.c b/src/config/config-connection.c index 321a798ced..18d8640ef6 100644 --- a/src/config/config-connection.c +++ b/src/config/config-connection.c @@ -12,6 +12,7 @@ #include "config-request.h" #include "config-parser.h" #include "config-connection.h" +#include "config-dump-full.h" #include @@ -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 index 0000000000..afda098c36 --- /dev/null +++ b/src/config/config-dump-full.c @@ -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 +#include + +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, §ion_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, §ion_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 index 0000000000..b10ef8bdeb --- /dev/null +++ b/src/config/config-dump-full.h @@ -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 diff --git a/src/config/doveconf.c b/src/config/doveconf.c index 1e294a5402..e55b54eafb 100644 --- a/src/config/doveconf.c +++ b/src/config/doveconf.c @@ -11,12 +11,14 @@ #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; diff --git a/src/lib-master/master-interface.h b/src/lib-master/master-interface.h index e5db7ce720..9966bec405 100644 --- a/src/lib-master/master-interface.h +++ b/src/lib-master/master-interface.h @@ -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"