common = \
config-connection.c \
+ config-dump-full.c \
config-filter.c \
config-parser.c \
config-request.c \
noinst_HEADERS = \
all-settings.h \
config-connection.h \
+ config-dump-full.h \
old-set-parser.h \
sysinfo-get.h
#include "config-request.h"
#include "config-parser.h"
#include "config-connection.h"
+#include "config-dump-full.h"
#include <unistd.h>
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;
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);
--- /dev/null
+/* 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, §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;
+}
--- /dev/null
+#ifndef CONFIG_DUMP_FULL
+#define CONFIG_DUMP_FULL
+
+int config_dump_full(struct ostream *output, const char **import_environment_r);
+
+#endif
#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"
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) {
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: ");
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;
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 */
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;
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"