diff options
Diffstat (limited to 'src/config/doveconf.c')
-rw-r--r-- | src/config/doveconf.c | 1072 |
1 files changed, 1072 insertions, 0 deletions
diff --git a/src/config/doveconf.c b/src/config/doveconf.c new file mode 100644 index 0000000..79ea9e8 --- /dev/null +++ b/src/config/doveconf.c @@ -0,0 +1,1072 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "path-util.h" +#include "module-dir.h" +#include "env-util.h" +#include "guid.h" +#include "hash.h" +#include "hostpid.h" +#include "ostream.h" +#include "str.h" +#include "strescape.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-connection.h" +#include "config-parser.h" +#include "config-request.h" +#include "dovecot-version.h" + +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <sysexits.h> + +struct prefix_stack { + unsigned int prefix_idx; + unsigned int str_pos; +}; +ARRAY_DEFINE_TYPE(prefix_stack, struct prefix_stack); + +struct config_dump_human_context { + pool_t pool; + string_t *list_prefix; + ARRAY_TYPE(const_string) strings; + ARRAY_TYPE(const_string) errors; + struct config_export_context *export_ctx; + + bool list_prefix_sent:1; +}; + +#define LIST_KEY_PREFIX "\001" +#define UNIQUE_KEY_SUFFIX "\xff" + +static const char *indent_str = " !!!!"; + +static const char *const secrets[] = { + "key", + "secret", + "pass", + "http://", + "https://", + "ftp://", + NULL +}; + + +static void +config_request_get_strings(const char *key, const char *value, + enum config_key_type type, void *context) +{ + struct config_dump_human_context *ctx = context; + const char *p; + + switch (type) { + case CONFIG_KEY_NORMAL: + value = p_strdup_printf(ctx->pool, "%s=%s", key, value); + break; + case CONFIG_KEY_LIST: + value = p_strdup_printf(ctx->pool, LIST_KEY_PREFIX"%s=%s", + key, value); + break; + case CONFIG_KEY_UNIQUE_KEY: + p = strrchr(key, '/'); + i_assert(p != NULL); + value = p_strdup_printf(ctx->pool, "%.*s/"UNIQUE_KEY_SUFFIX"%s=%s", + (int)(p - key), key, p + 1, value); + break; + case CONFIG_KEY_ERROR: + value = p_strdup(ctx->pool, value); + array_push_back(&ctx->errors, &value); + return; + } + array_push_back(&ctx->strings, &value); +} + +static int config_string_cmp(const char *const *p1, const char *const *p2) +{ + const char *s1 = *p1, *s2 = *p2; + unsigned int i = 0; + + while (s1[i] == s2[i]) { + if (s1[i] == '\0' || s1[i] == '=') + return 0; + i++; + } + + if (s1[i] == '=') + return -1; + if (s2[i] == '=') + return 1; + + return s1[i] - s2[i]; +} + +static struct prefix_stack prefix_stack_pop(ARRAY_TYPE(prefix_stack) *stack) +{ + const struct prefix_stack *s; + struct prefix_stack sc; + unsigned int count; + + s = array_get(stack, &count); + i_assert(count > 0); + if (count == 1) { + sc.prefix_idx = UINT_MAX; + } else { + sc.prefix_idx = s[count-2].prefix_idx; + } + sc.str_pos = s[count-1].str_pos; + array_delete(stack, count-1, 1); + return sc; +} + +static void prefix_stack_reset_str(ARRAY_TYPE(prefix_stack) *stack) +{ + struct prefix_stack *s; + + array_foreach_modifiable(stack, s) + s->str_pos = UINT_MAX; +} + +static struct config_dump_human_context * +config_dump_human_init(const char *const *modules, enum config_dump_scope scope, + bool check_settings, bool in_section) +{ + struct config_dump_human_context *ctx; + enum config_dump_flags flags; + pool_t pool; + + pool = pool_alloconly_create(MEMPOOL_GROWING"config human strings", 1024*32); + ctx = p_new(pool, struct config_dump_human_context, 1); + ctx->pool = pool; + ctx->list_prefix = str_new(ctx->pool, 128); + i_array_init(&ctx->strings, 256); + i_array_init(&ctx->errors, 256); + + flags = CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS | + CONFIG_DUMP_FLAG_CALLBACK_ERRORS; + if (check_settings) + flags |= CONFIG_DUMP_FLAG_CHECK_SETTINGS; + if (in_section) + flags |= CONFIG_DUMP_FLAG_IN_SECTION; + ctx->export_ctx = config_export_init(modules, NULL, scope, flags, + config_request_get_strings, ctx); + return ctx; +} + +static void config_dump_human_deinit(struct config_dump_human_context *ctx) +{ + array_free(&ctx->strings); + array_free(&ctx->errors); + pool_unref(&ctx->pool); +} + +static bool value_need_quote(const char *value) +{ + size_t len = strlen(value); + + if (len == 0) + return FALSE; + + if (strchr(value, '#') != NULL) + return TRUE; + if (IS_WHITE(value[0]) || IS_WHITE(value[len-1])) + return TRUE; + return FALSE; +} + +static const char *find_next_secret(const char *input, const char **secret_r) +{ + const char *const *secret; + const char *ptr = NULL; + *secret_r = NULL; + for(secret = secrets; *secret != NULL; secret++) { + const char *cptr; + if ((cptr = strstr(input, *secret)) != NULL) { + if (ptr == NULL || cptr < ptr) { + *secret_r = *secret; + ptr = cptr; + } + } + } + i_assert(*secret_r != NULL || ptr == NULL); + return ptr; +} + +static bool +hide_url_userpart_from_value(struct ostream *output, const char **_ptr, + const char **optr, bool quote) +{ + const char *ptr = *_ptr; + const char *start_of_user = ptr; + const char *start_of_host = NULL; + string_t *quoted = NULL; + + if (quote) + quoted = t_str_new(256); + + /* it's a URL, see if there is a userpart */ + while(*ptr != '\0' && !i_isspace(*ptr) && *ptr != '/') { + if (*ptr == '@') { + start_of_host = ptr; + break; + } + ptr++; + } + + if (quote) { + str_truncate(quoted, 0); + str_append_escaped(quoted, *optr, start_of_user - (*optr)); + o_stream_nsend(output, quoted->data, quoted->used); + } else { + o_stream_nsend(output, *optr, start_of_user - (*optr)); + } + + if (start_of_host != NULL && start_of_host != start_of_user) { + o_stream_nsend_str(output, "#hidden_use-P_to_show#"); + } else if (quote) { + str_truncate(quoted, 0); + str_append_escaped(quoted, start_of_user, ptr - start_of_user); + o_stream_nsend(output, quoted->data, quoted->used); + } else { + o_stream_nsend(output, start_of_user, ptr - start_of_user); + } + + *optr = ptr; + *_ptr = ptr; + return TRUE; +} + +static inline bool key_ends_with(const char *key, const char *eptr, + const char *suffix) +{ + /* take = into account */ + size_t n = strlen(suffix)+1; + return (eptr-key > (ptrdiff_t)n && str_begins(eptr-n, suffix)); +} + +static bool +hide_secrets_from_value(struct ostream *output, const char *key, + const char *value) +{ + bool ret = FALSE, quote = value_need_quote(value); + const char *ptr, *optr, *secret; + if (*value != '\0' && + (key_ends_with(key, value, "_password") || + key_ends_with(key, value, "_key") || + key_ends_with(key, value, "_nonce") || + str_begins(key, "ssl_dh"))) { + o_stream_nsend_str(output, "# hidden, use -P to show it"); + return TRUE; + } + + /* Check if we can find anything that has prefix of any of the + secrets. It should match things like secret_api_key or pass or password, + etc. but not something like nonsecret. */ + optr = ptr = value; + while((ptr = find_next_secret(ptr, &secret)) != NULL) { + if (strstr(secret, "://") != NULL) { + ptr += strlen(secret); + if ((ret = hide_url_userpart_from_value(output, &ptr, &optr, quote))) + continue; + } + /* we have found something that we hide, and will deal with output + here. */ + ret = TRUE; + if (ptr == value || + (ptr > value && !i_isalnum(ptr[-1]))) { + size_t len; + while(*ptr != '\0') { + if (*ptr == '=' || i_isspace(*ptr)) + break; + ptr++; + } + while(i_isspace(*ptr)) + ptr++; + len = (size_t)(ptr-optr); + if (quote) { + string_t *quoted = t_str_new(len*2); + str_append_escaped(quoted, optr, len); + o_stream_nsend(output, + quoted->data, quoted->used); + } else { + o_stream_nsend(output, optr, len); + } + if (*ptr == '=') { + o_stream_nsend(output, ptr, 1); + o_stream_nsend_str(output, "#hidden_use-P_to_show#"); + while(*ptr != '\0' && !i_isspace(*ptr) && + *ptr != ';' && *ptr != ':') + ptr++; + } + optr = ptr; + } else { + /* "secret" is prefixed with alphanumeric character, + e.g. "nopassword". So it's not really a secret. + Skip forward to avoid infinite loop. */ + ptr++; + } + }; + /* if we are dealing with output, send rest here */ + if (ret) { + if (quote) + o_stream_nsend_str(output, str_escape(optr)); + else + o_stream_nsend_str(output, optr); + } + return ret; +} + +static int ATTR_NULL(4) +config_dump_human_output(struct config_dump_human_context *ctx, + struct ostream *output, unsigned int indent, + const char *setting_name_filter, bool hide_passwords) +{ + ARRAY_TYPE(const_string) prefixes_arr; + ARRAY_TYPE(prefix_stack) prefix_stack; + struct prefix_stack prefix; + const char *const *strings, *const *args, *p, *str, *const *prefixes; + const char *key, *key2, *value; + unsigned int i, j, count, prefix_count; + unsigned int prefix_idx = UINT_MAX; + size_t len, skip_len, setting_name_filter_len; + bool unique_key; + int ret = 0; + + setting_name_filter_len = setting_name_filter == NULL ? 0 : + strlen(setting_name_filter); + if (config_export_finish(&ctx->export_ctx) < 0) + return -1; + + array_sort(&ctx->strings, config_string_cmp); + strings = array_get(&ctx->strings, &count); + + /* strings are sorted so that all lists come first */ + p_array_init(&prefixes_arr, ctx->pool, 32); + for (i = 0; i < count && strings[i][0] == LIST_KEY_PREFIX[0]; i++) T_BEGIN { + p = strchr(strings[i], '='); + i_assert(p != NULL); + if (p[1] == '\0') { + /* "strlist=" */ + str = p_strdup_printf(ctx->pool, "%s/", + t_strcut(strings[i]+1, '=')); + array_push_back(&prefixes_arr, &str); + } else { + /* string is in format: "list=0 1 2" */ + for (args = t_strsplit(p + 1, " "); *args != NULL; args++) { + str = p_strdup_printf(ctx->pool, "%s/%s/", + t_strcut(strings[i]+1, '='), + *args); + array_push_back(&prefixes_arr, &str); + } + } + } T_END; + prefixes = array_get(&prefixes_arr, &prefix_count); + + p_array_init(&prefix_stack, ctx->pool, 8); + for (; i < count; i++) T_BEGIN { + value = strchr(strings[i], '='); + i_assert(value != NULL); + + key = t_strdup_until(strings[i], value++); + unique_key = FALSE; + + p = strrchr(key, '/'); + if (p != NULL && p[1] == UNIQUE_KEY_SUFFIX[0]) { + key = t_strconcat(t_strdup_until(key, p + 1), + p + 2, NULL); + unique_key = TRUE; + } + if (setting_name_filter_len > 0) { + /* see if this setting matches the name filter */ + if (!(strncmp(setting_name_filter, key, + setting_name_filter_len) == 0 && + (key[setting_name_filter_len] == '/' || + key[setting_name_filter_len] == '\0'))) + goto end; + } + again: + j = 0; + /* if there are open sections and this key isn't in it, + close the sections */ + while (prefix_idx != UINT_MAX) { + len = strlen(prefixes[prefix_idx]); + if (strncmp(prefixes[prefix_idx], key, len) != 0) { + prefix = prefix_stack_pop(&prefix_stack); + indent--; + if (prefix.str_pos != UINT_MAX) + str_truncate(ctx->list_prefix, prefix.str_pos); + else { + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend_str(output, "}\n"); + } + prefix_idx = prefix.prefix_idx; + } else { + /* keep the prefix */ + j = prefix_idx + 1; + break; + } + } + /* see if this key is in some section */ + for (; j < prefix_count; j++) { + len = strlen(prefixes[j]); + if (strncmp(prefixes[j], key, len) == 0) { + key2 = key + (prefix_idx == UINT_MAX ? 0 : + strlen(prefixes[prefix_idx])); + prefix.str_pos = !unique_key ? UINT_MAX : + str_len(ctx->list_prefix); + prefix_idx = j; + prefix.prefix_idx = prefix_idx; + array_push_back(&prefix_stack, &prefix); + + str_append_max(ctx->list_prefix, indent_str, indent*2); + p = strchr(key2, '/'); + if (p != NULL) + str_append_data(ctx->list_prefix, key2, p - key2); + else + str_append(ctx->list_prefix, key2); + if (unique_key && *value != '\0') { + if (strchr(value, ' ') == NULL) + str_printfa(ctx->list_prefix, " %s", value); + else + str_printfa(ctx->list_prefix, " \"%s\"", str_escape(value)); + } + str_append(ctx->list_prefix, " {\n"); + indent++; + + if (unique_key) + goto end; + else + goto again; + } + } + o_stream_nsend(output, str_data(ctx->list_prefix), str_len(ctx->list_prefix)); + str_truncate(ctx->list_prefix, 0); + prefix_stack_reset_str(&prefix_stack); + ctx->list_prefix_sent = TRUE; + + skip_len = prefix_idx == UINT_MAX ? 0 : strlen(prefixes[prefix_idx]); + i_assert(skip_len == 0 || + strncmp(prefixes[prefix_idx], strings[i], skip_len) == 0); + o_stream_nsend(output, indent_str, indent*2); + key = strings[i] + skip_len; + if (unique_key) key++; + value = strchr(key, '='); + i_assert(value != NULL); + o_stream_nsend(output, key, value-key); + o_stream_nsend_str(output, " = "); + if (hide_passwords && + hide_secrets_from_value(output, key, value+1)) + /* sent */ + ; + else if (!value_need_quote(value+1)) + o_stream_nsend_str(output, value+1); + else { + o_stream_nsend(output, "\"", 1); + o_stream_nsend_str(output, str_escape(value+1)); + o_stream_nsend(output, "\"", 1); + } + o_stream_nsend(output, "\n", 1); + end: ; + } T_END; + + while (prefix_idx != UINT_MAX) { + prefix = prefix_stack_pop(&prefix_stack); + if (prefix.str_pos != UINT_MAX) + break; + prefix_idx = prefix.prefix_idx; + indent--; + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend_str(output, "}\n"); + } + + /* flush output before writing errors */ + o_stream_uncork(output); + array_foreach_elem(&ctx->errors, str) { + i_error("%s", str); + ret = -1; + } + return ret; +} + +static unsigned int +config_dump_filter_begin(string_t *str, + const struct config_filter *filter) +{ + unsigned int indent = 0; + + if (filter->local_bits > 0) { + str_printfa(str, "local %s", net_ip2addr(&filter->local_net)); + + if (IPADDR_IS_V4(&filter->local_net)) { + if (filter->local_bits != 32) + str_printfa(str, "/%u", filter->local_bits); + } else { + if (filter->local_bits != 128) + str_printfa(str, "/%u", filter->local_bits); + } + str_append(str, " {\n"); + indent++; + } + + if (filter->local_name != NULL) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "local_name %s {\n", filter->local_name); + indent++; + } + + if (filter->remote_bits > 0) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "remote %s", net_ip2addr(&filter->remote_net)); + + if (IPADDR_IS_V4(&filter->remote_net)) { + if (filter->remote_bits != 32) + str_printfa(str, "/%u", filter->remote_bits); + } else { + if (filter->remote_bits != 128) + str_printfa(str, "/%u", filter->remote_bits); + } + str_append(str, " {\n"); + indent++; + } + if (filter->service != NULL) { + str_append_max(str, indent_str, indent*2); + str_printfa(str, "protocol %s {\n", filter->service); + indent++; + } + return indent; +} + +static void +config_dump_filter_end(struct ostream *output, unsigned int indent) +{ + while (indent > 0) { + indent--; + o_stream_nsend(output, indent_str, indent*2); + o_stream_nsend(output, "}\n", 2); + } +} + +static int +config_dump_human_sections(struct ostream *output, + const struct config_filter *filter, + const char *const *modules, bool hide_passwords) +{ + struct config_filter_parser *const *filters; + static struct config_dump_human_context *ctx; + unsigned int indent; + int ret = 0; + + filters = config_filter_find_subset(config_filter, filter); + + /* first filter should be the global one */ + i_assert(filters[0] != NULL && filters[0]->filter.service == NULL); + filters++; + + for (; *filters != NULL; filters++) { + ctx = config_dump_human_init(modules, CONFIG_DUMP_SCOPE_SET, + FALSE, TRUE); + indent = config_dump_filter_begin(ctx->list_prefix, + &(*filters)->filter); + config_export_parsers(ctx->export_ctx, (*filters)->parsers); + if (config_dump_human_output(ctx, output, indent, NULL, hide_passwords) < 0) + ret = -1; + if (ctx->list_prefix_sent) + config_dump_filter_end(output, indent); + config_dump_human_deinit(ctx); + } + return ret; +} + +static int ATTR_NULL(4) +config_dump_human(const struct config_filter *filter, const char *const *modules, + enum config_dump_scope scope, const char *setting_name_filter, + bool hide_passwords) +{ + static struct config_dump_human_context *ctx; + struct ostream *output; + int ret; + + output = o_stream_create_fd(STDOUT_FILENO, 0); + o_stream_set_no_error_handling(output, TRUE); + o_stream_cork(output); + + ctx = config_dump_human_init(modules, scope, TRUE, FALSE); + config_export_by_filter(ctx->export_ctx, filter); + ret = config_dump_human_output(ctx, output, 0, setting_name_filter, hide_passwords); + config_dump_human_deinit(ctx); + + if (setting_name_filter == NULL) + ret = config_dump_human_sections(output, filter, modules, hide_passwords); + + o_stream_uncork(output); + o_stream_destroy(&output); + return ret; +} + +static int +config_dump_one(const struct config_filter *filter, bool hide_key, + enum config_dump_scope scope, const char *setting_name_filter, + bool hide_passwords) +{ + static struct config_dump_human_context *ctx; + const char *str; + size_t len; + bool dump_section = FALSE; + + ctx = config_dump_human_init(NULL, scope, FALSE, FALSE); + config_export_by_filter(ctx->export_ctx, filter); + if (config_export_finish(&ctx->export_ctx) < 0) + return -1; + + len = strlen(setting_name_filter); + array_foreach_elem(&ctx->strings, str) { + if (strncmp(str, setting_name_filter, len) != 0) + continue; + + if (str[len] == '=') { + if (hide_key) + printf("%s\n", str + len+1); + else { + printf("%s = %s\n", setting_name_filter, + str + len+1); + } + dump_section = FALSE; + break; + } else if (str[len] == '/') { + dump_section = TRUE; + } + } + config_dump_human_deinit(ctx); + + if (dump_section) + (void)config_dump_human(filter, NULL, scope, setting_name_filter, hide_passwords); + return 0; +} + +static void config_request_simple_stdout(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, + void *context) +{ + char **setting_name_filters = context; + unsigned int i; + size_t filter_len; + + if (setting_name_filters == NULL) { + printf("%s=%s\n", key, value); + return; + } + + for (i = 0; setting_name_filters[i] != NULL; i++) { + filter_len = strlen(setting_name_filters[i]); + if (strncmp(setting_name_filters[i], key, filter_len) == 0 && + (key[filter_len] == '\0' || key[filter_len] == '/')) + printf("%s=%s\n", key, value); + } +} + +static void config_request_putenv(const char *key, const char *value, + enum config_key_type type ATTR_UNUSED, + void *context ATTR_UNUSED) +{ + T_BEGIN { + env_put(t_str_ucase(key), value); + } T_END; +} + +static const char *get_setting(const char *module, const char *name) +{ + struct config_module_parser *l; + const struct setting_define *def; + const char *const *value; + const void *set; + + for (l = config_module_parsers; l->root != NULL; l++) { + if (strcmp(l->root->module_name, module) != 0) + continue; + + set = settings_parser_get(l->parser); + for (def = l->root->defines; def->key != NULL; def++) { + if (strcmp(def->key, name) == 0) { + value = CONST_PTR_OFFSET(set, def->offset); + return *value; + } + } + } + return ""; +} + +static void filter_parse_arg(struct config_filter *filter, const char *arg) +{ + const char *key, *value, *error; + + value = strchr(arg, '='); + if (value != NULL) + key = t_strdup_until(arg, value++); + else { + key = arg; + value = ""; + } + + if (strcmp(key, "service") == 0) + filter->service = value; + else if (strcmp(key, "protocol") == 0) + filter->service = value; + else if (strcmp(key, "lname") == 0) + filter->local_name = value; + else if (strcmp(key, "local") == 0) { + if (config_parse_net(value, &filter->local_net, + &filter->local_bits, &error) < 0) + i_fatal("local filter: %s", error); + } else if (strcmp(key, "remote") == 0) { + if (config_parse_net(value, &filter->remote_net, + &filter->remote_bits, &error) < 0) + i_fatal("remote filter: %s", error); + } else { + i_fatal("Unknown filter argument: %s", arg); + } +} + +struct hostname_format { + const char *prefix, *suffix; + unsigned int numcount; + bool zeropadding; +}; + +static void +hostname_format_write(string_t *str, const struct hostname_format *fmt, + unsigned int num) +{ + str_truncate(str, 0); + str_append(str, fmt->prefix); + if (!fmt->zeropadding) + str_printfa(str, "%d", num); + else + str_printfa(str, "%0*d", fmt->numcount, num); + str_append(str, fmt->suffix); +} + +static void hostname_verify_format(const char *arg) +{ + struct hostname_format fmt; + const char *p; + unsigned char hash[GUID_128_HOST_HASH_SIZE]; + unsigned int n, limit; + HASH_TABLE(void *, void *) hosts; + void *key, *value; + string_t *host; + const char *host2; + bool duplicates = FALSE; + + i_zero(&fmt); + if (arg != NULL) { + /* host%d, host%2d, host%02d */ + p = strchr(arg, '%'); + if (p == NULL) + i_fatal("Host parameter missing %%d"); + fmt.prefix = t_strdup_until(arg, p++); + if (*p == '0') { + fmt.zeropadding = TRUE; + p++; + } + if (!i_isdigit(*p)) + fmt.numcount = 1; + else + fmt.numcount = *p++ - '0'; + if (*p++ != 'd') + i_fatal("Host parameter missing %%d"); + fmt.suffix = p; + } else { + /* detect host1[suffix] vs host01[suffix] */ + size_t len = strlen(my_hostname); + while (len > 0 && !i_isdigit(my_hostname[len-1])) + len--; + fmt.suffix = my_hostname + len; + fmt.numcount = 0; + while (len > 0 && i_isdigit(my_hostname[len-1])) { + len--; + fmt.numcount++; + } + if (my_hostname[len] == '0') + fmt.zeropadding = TRUE; + fmt.prefix = t_strndup(my_hostname, len); + if (fmt.numcount == 0) { + i_fatal("Hostname '%s' has no digits, can't verify", + my_hostname); + } + } + for (n = 0, limit = 1; n < fmt.numcount; n++) + limit *= 10; + host = t_str_new(128); + hash_table_create_direct(&hosts, default_pool, limit); + for (n = 0; n < limit; n++) { + hostname_format_write(host, &fmt, n); + + guid_128_host_hash_get(str_c(host), hash); + i_assert(sizeof(key) >= sizeof(hash)); + key = NULL; memcpy(&key, hash, sizeof(hash)); + + value = hash_table_lookup(hosts, key); + if (value != NULL) { + host2 = t_strdup(str_c(host)); + hostname_format_write(host, &fmt, + POINTER_CAST_TO(value, unsigned int)-1); + i_error("Duplicate host hashes: %s and %s", + str_c(host), host2); + duplicates = TRUE; + } else { + hash_table_insert(hosts, key, POINTER_CAST(n+1)); + } + } + hash_table_destroy(&hosts); + + if (duplicates) + lib_exit(EX_CONFIG); + else { + host2 = t_strdup(str_c(host)); + hostname_format_write(host, &fmt, 0); + printf("No duplicate host hashes in %s .. %s\n", + str_c(host), host2); + lib_exit(0); + } +} + +static void check_wrong_config(const char *config_path) +{ + const char *base_dir, *symlink_path, *prev_path, *error; + + base_dir = get_setting("master", "base_dir"); + symlink_path = t_strconcat(base_dir, "/"PACKAGE".conf", NULL); + if (t_readlink(symlink_path, &prev_path, &error) < 0) { + if (errno != ENOENT) + i_error("t_readlink(%s) failed: %s", symlink_path, error); + return; + } + + if (strcmp(prev_path, config_path) != 0) { + i_warning("Dovecot was last started using %s, " + "but this config is %s", prev_path, config_path); + } +} + +static void failure_exit_callback(int *status) +{ + /* don't use EX_CONFIG, because it often causes MTAs to bounce + the mails back. */ + *status = EX_TEMPFAIL; +} + +int main(int argc, char *argv[]) +{ + enum master_service_flags master_service_flags = + MASTER_SERVICE_FLAG_DONT_SEND_STATS | + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME; + enum config_dump_scope scope = CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN; + const char *orig_config_path, *config_path, *module; + ARRAY(const char *) module_names; + struct config_filter filter; + const char *const *wanted_modules, *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 print_plugin_banner = FALSE, hide_passwords = TRUE; + + if (getenv("USE_SYSEXITS") != NULL) { + /* we're coming from (e.g.) LDA */ + i_set_failure_exit_callback(failure_exit_callback); + } + + i_zero(&filter); + master_service = master_service_init("config", master_service_flags, + &argc, &argv, "adf:hHm:nNpPexsS"); + orig_config_path = t_strdup(master_service_get_config_path(master_service)); + + i_set_failure_prefix("doveconf: "); + t_array_init(&module_names, 4); + while ((c = master_getopt(master_service)) > 0) { + if (c == 'e') { + expand_vars = TRUE; + break; + } + switch (c) { + case 'a': + break; + case 'd': + dump_defaults = TRUE; + break; + case 'f': + filter_parse_arg(&filter, optarg); + break; + case 'h': + hide_key = TRUE; + break; + case 'H': + host_verify = TRUE; + break; + case 'm': + module = t_strdup(optarg); + array_push_back(&module_names, &module); + break; + case 'n': + scope = CONFIG_DUMP_SCOPE_CHANGED; + break; + case 'N': + scope = CONFIG_DUMP_SCOPE_SET; + break; + case 'p': + parse_full_config = TRUE; + break; + case 'P': + hide_passwords = FALSE; + break; + case 's': + scope = CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN; + break; + case 'S': + simple_output = TRUE; + break; + case 'x': + expand_vars = TRUE; + break; + default: + return FATAL_DEFAULT; + } + } + array_append_zero(&module_names); + wanted_modules = array_count(&module_names) == 1 ? NULL : + array_front(&module_names); + + config_path = master_service_get_config_path(master_service); + /* use strcmp() instead of !=, because dovecot -n always gives us + -c parameter */ + config_path_specified = strcmp(config_path, orig_config_path) != 0; + + if (host_verify) + hostname_verify_format(argv[optind]); + + if (c == 'e') { + if (argv[optind] == NULL) + i_fatal("Missing command for -e"); + exec_args = &argv[optind]; + } else if (argv[optind] != NULL) { + /* print only a single config setting */ + setting_name_filters = argv+optind; + if (scope == CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN) + scope = CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN; + } else if (!simple_output) { + /* print the config file path before parsing it, so in case + of errors it's still shown */ + printf("# "DOVECOT_VERSION_FULL": %s\n", config_path); + print_plugin_banner = TRUE; + fflush(stdout); + } + master_service_init_finish(master_service); + config_parse_load_modules(); + + if (print_plugin_banner) { + struct module *m; + + for (m = modules; m != NULL; m = m->next) { + const char **str = module_get_symbol_quiet(m, + t_strdup_printf("%s_doveconf_banner", m->name)); + if (str != NULL) + printf("# %s\n", *str); + } + } + + if ((ret = config_parse_file(dump_defaults ? NULL : config_path, + expand_vars, + parse_full_config ? NULL : wanted_modules, + &error)) == 0 && + access(EXAMPLE_CONFIG_DIR, X_OK) == 0) { + i_fatal("%s (copy example configs from "EXAMPLE_CONFIG_DIR"/)", + error); + } + + if ((ret == -1 && exec_args != NULL) || ret == 0 || ret == -2) + i_fatal("%s", error); + + if (simple_output) { + struct config_export_context *ctx; + + ctx = config_export_init(wanted_modules, NULL, scope, + CONFIG_DUMP_FLAG_CHECK_SETTINGS, + config_request_simple_stdout, + setting_name_filters); + config_export_by_filter(ctx, &filter); + ret2 = config_export_finish(&ctx); + } else if (setting_name_filters != NULL) { + ret2 = 0; + /* ignore settings-check failures in configuration. this allows + using doveconf to lookup settings for things like install or + uninstall scripts where the configuration might + (temporarily) not be fully usable */ + ret = 0; + for (i = 0; setting_name_filters[i] != NULL; i++) { + if (config_dump_one(&filter, hide_key, scope, + setting_name_filters[i], hide_passwords) < 0) + ret2 = -1; + } + } else if (exec_args == NULL) { + const char *info; + + info = sysinfo_get(get_setting("mail", "mail_location")); + if (*info != '\0') + printf("# %s\n", info); + printf("# Hostname: %s\n", my_hostdomain()); + if (!config_path_specified) + check_wrong_config(config_path); + if (scope == CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN) + printf("# NOTE: Send doveconf -n output instead when asking for help.\n"); + fflush(stdout); + ret2 = config_dump_human(&filter, wanted_modules, scope, NULL, hide_passwords); + } else { + struct config_export_context *ctx; + + ctx = config_export_init(wanted_modules, NULL, CONFIG_DUMP_SCOPE_SET, + CONFIG_DUMP_FLAG_CHECK_SETTINGS, + config_request_putenv, NULL); + config_export_by_filter(ctx, &filter); + + 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). */ + const char *import_environment = + config_export_get_import_environment(ctx); + master_service_import_environment(import_environment); + master_service_env_clean(); + } + + env_put("DOVECONF_ENV", "1"); + if (config_export_finish(&ctx) < 0) + i_fatal("Invalid configuration"); + execvp(exec_args[0], exec_args); + i_fatal("execvp(%s) failed: %m", exec_args[0]); + } + + if (ret < 0) { + /* delayed error */ + i_fatal("%s", error); + } + if (ret2 < 0) + i_fatal("Errors in configuration"); + + config_filter_deinit(&config_filter); + old_settings_deinit_global(); + module_dir_unload(&modules); + config_parser_deinit(); + master_service_deinit(&master_service); + return 0; +} |