/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "hash.h" #include "ostream.h" #include "settings-parser.h" #include "master-service-settings.h" #include "all-settings.h" #include "config-parser.h" #include "config-request.h" #include "old-set-parser.h" struct config_export_context { pool_t pool; string_t *value; string_t *prefix; HASH_TABLE(char *, char *) keys; enum config_dump_scope scope; config_request_callback_t *callback; void *context; const char *const *modules; const char *const *exclude_settings; enum config_dump_flags flags; const struct config_module_parser *parsers; struct config_module_parser *dup_parsers; struct master_service_settings_output output; bool failed; }; static void config_export_size(string_t *str, uoff_t size) { static const char suffixes[] = { 'B', 'k', 'M', 'G', 'T' }; char suffix = suffixes[0]; unsigned int i; if (size == 0) { str_append_c(str, '0'); return; } for (i = 1; i < N_ELEMENTS(suffixes) && (size % 1024) == 0; i++) { suffix = suffixes[i]; size /= 1024; } str_printfa(str, "%"PRIuUOFF_T" %c", size, suffix); } static void config_export_time(string_t *str, unsigned int stamp) { const char *suffix = "secs"; if (stamp == 0) { str_append_c(str, '0'); return; } if (stamp % 60 == 0) { stamp /= 60; suffix = "mins"; if (stamp % 60 == 0) { stamp /= 60; suffix = "hours"; if (stamp % 24 == 0) { stamp /= 24; suffix = "days"; if (stamp % 7 == 0) { stamp /= 7; suffix = "weeks"; } } } } str_printfa(str, "%u %s", stamp, suffix); } static void config_export_time_msecs(string_t *str, unsigned int stamp_msecs) { if ((stamp_msecs % 1000) == 0) config_export_time(str, stamp_msecs/1000); else str_printfa(str, "%u ms", stamp_msecs); } bool config_export_type(string_t *str, const void *value, const void *default_value, enum setting_type type, bool dump_default, bool *dump_r) { switch (type) { case SET_BOOL: { const bool *val = value, *dval = default_value; if (dump_default || dval == NULL || *val != *dval) str_append(str, *val ? "yes" : "no"); break; } case SET_SIZE: { const uoff_t *val = value, *dval = default_value; if (dump_default || dval == NULL || *val != *dval) config_export_size(str, *val); break; } case SET_UINT: case SET_UINT_OCT: case SET_TIME: case SET_TIME_MSECS: { const unsigned int *val = value, *dval = default_value; if (dump_default || dval == NULL || *val != *dval) { switch (type) { case SET_UINT_OCT: str_printfa(str, "0%o", *val); break; case SET_TIME: config_export_time(str, *val); break; case SET_TIME_MSECS: config_export_time_msecs(str, *val); break; default: str_printfa(str, "%u", *val); break; } } break; } case SET_IN_PORT: { const in_port_t *val = value, *dval = default_value; if (dump_default || dval == NULL || *val != *dval) str_printfa(str, "%u", *val); break; } case SET_STR_VARS: { const char *const *val = value, *sval; const char *const *_dval = default_value; const char *dval = _dval == NULL ? NULL : *_dval; i_assert(*val == NULL || **val == SETTING_STRVAR_UNEXPANDED[0]); sval = *val == NULL ? NULL : (*val + 1); if ((dump_default || null_strcmp(sval, dval) != 0) && sval != NULL) { str_append(str, sval); *dump_r = TRUE; } break; } case SET_STR: { const char *const *val = value; const char *const *_dval = default_value; const char *dval = _dval == NULL ? NULL : *_dval; if ((dump_default || null_strcmp(*val, dval) != 0) && *val != NULL) { str_append(str, *val); *dump_r = TRUE; } break; } case SET_ENUM: { const char *const *val = value; size_t len = strlen(*val); if (dump_default) str_append(str, *val); else { const char *const *_dval = default_value; const char *dval = _dval == NULL ? NULL : *_dval; i_assert(dval != NULL); if (strncmp(*val, dval, len) != 0 || ((*val)[len] != ':' && (*val)[len] != '\0')) str_append(str, *val); } break; } default: return FALSE; } return TRUE; } static void setting_export_section_name(string_t *str, const struct setting_define *def, const void *set, unsigned int idx) { const char *const *name; size_t name_offset; if (def->type != SET_DEFLIST_UNIQUE) { /* not unique, use the index */ str_printfa(str, "%u", idx); return; } name_offset = def->list_info->type_offset; i_assert(name_offset != SIZE_MAX); name = CONST_PTR_OFFSET(set, name_offset); if (*name == NULL || **name == '\0') { /* no name, this one isn't unique. use the index. */ str_printfa(str, "%u", idx); } else T_BEGIN { str_append(str, settings_section_escape(*name)); } T_END; } static void settings_export(struct config_export_context *ctx, const struct setting_parser_info *info, bool parent_unique_deflist, const void *set, const void *change_set) { const struct setting_define *def; const void *value, *default_value, *change_value; void *const *children, *const *change_children = NULL; unsigned int i, count, count2; size_t prefix_len; const char *str; char *key; bool dump, dump_default = FALSE; for (def = info->defines; def->key != NULL; def++) { if (ctx->exclude_settings != NULL && str_array_find(ctx->exclude_settings, def->key)) continue; value = CONST_PTR_OFFSET(set, def->offset); default_value = info->defaults == NULL ? NULL : CONST_PTR_OFFSET(info->defaults, def->offset); change_value = CONST_PTR_OFFSET(change_set, def->offset); switch (ctx->scope) { case CONFIG_DUMP_SCOPE_ALL_WITH_HIDDEN: dump_default = TRUE; break; case CONFIG_DUMP_SCOPE_ALL_WITHOUT_HIDDEN: if ((def->flags & SET_FLAG_HIDDEN) == 0) { /* not hidden - dump it */ dump_default = TRUE; break; } /* hidden - dump default only if it's explicitly set */ /* fall through */ case CONFIG_DUMP_SCOPE_SET: dump_default = *((const char *)change_value) != 0; break; case CONFIG_DUMP_SCOPE_CHANGED: dump_default = FALSE; break; } if (!parent_unique_deflist || (ctx->flags & CONFIG_DUMP_FLAG_HIDE_LIST_DEFAULTS) == 0) { /* .. */ } else if (*((const char *)change_value) == 0 && def->offset != info->type_offset) { /* this is mainly for service {} blocks. if value hasn't changed, it's the default. even if info->defaults has a different value. */ default_value = value; } else { /* value is set explicitly, but we don't know the default here. assume it's not the default. */ dump_default = TRUE; } dump = FALSE; count = 0; children = NULL; str_truncate(ctx->value, 0); switch (def->type) { case SET_BOOL: case SET_SIZE: case SET_UINT: case SET_UINT_OCT: case SET_TIME: case SET_TIME_MSECS: case SET_IN_PORT: case SET_STR_VARS: case SET_STR: case SET_ENUM: if (!config_export_type(ctx->value, value, default_value, def->type, dump_default, &dump)) i_unreached(); break; case SET_DEFLIST: case SET_DEFLIST_UNIQUE: { const ARRAY_TYPE(void_array) *val = value; const ARRAY_TYPE(void_array) *change_val = change_value; if (!array_is_created(val)) break; children = array_get(val, &count); for (i = 0; i < count; i++) { if (i > 0) str_append_c(ctx->value, ' '); setting_export_section_name(ctx->value, def, children[i], i); } change_children = array_get(change_val, &count2); i_assert(count == count2); break; } case SET_STRLIST: { const ARRAY_TYPE(const_string) *val = value; const char *const *strings; if (!array_is_created(val)) break; key = p_strconcat(ctx->pool, str_c(ctx->prefix), def->key, NULL); if (hash_table_lookup(ctx->keys, key) != NULL) { /* already added all of these */ break; } hash_table_insert(ctx->keys, key, key); /* for doveconf -n to see this KEY_LIST */ ctx->callback(key, "", CONFIG_KEY_LIST, ctx->context); strings = array_get(val, &count); i_assert(count % 2 == 0); for (i = 0; i < count; i += 2) { str = p_strdup_printf(ctx->pool, "%s%s%c%s", str_c(ctx->prefix), def->key, SETTINGS_SEPARATOR, strings[i]); ctx->callback(str, strings[i+1], CONFIG_KEY_NORMAL, ctx->context); } count = 0; break; } case SET_ALIAS: break; } if (str_len(ctx->value) > 0 || dump) { key = p_strconcat(ctx->pool, str_c(ctx->prefix), def->key, NULL); if (hash_table_lookup(ctx->keys, key) == NULL) { enum config_key_type type; if (def->offset == info->type_offset && parent_unique_deflist) type = CONFIG_KEY_UNIQUE_KEY; else if (SETTING_TYPE_IS_DEFLIST(def->type)) type = CONFIG_KEY_LIST; else type = CONFIG_KEY_NORMAL; ctx->callback(key, str_c(ctx->value), type, ctx->context); hash_table_insert(ctx->keys, key, key); } } i_assert(count == 0 || children != NULL); prefix_len = str_len(ctx->prefix); for (i = 0; i < count; i++) { str_append(ctx->prefix, def->key); str_append_c(ctx->prefix, SETTINGS_SEPARATOR); setting_export_section_name(ctx->prefix, def, children[i], i); str_append_c(ctx->prefix, SETTINGS_SEPARATOR); settings_export(ctx, def->list_info, def->type == SET_DEFLIST_UNIQUE, children[i], change_children[i]); str_truncate(ctx->prefix, prefix_len); } } } struct config_export_context * config_export_init(const char *const *modules, const char *const *exclude_settings, enum config_dump_scope scope, enum config_dump_flags flags, config_request_callback_t *callback, void *context) { struct config_export_context *ctx; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"config export", 1024*64); ctx = p_new(pool, struct config_export_context, 1); ctx->pool = pool; ctx->modules = modules == NULL ? NULL : p_strarray_dup(pool, modules); ctx->exclude_settings = exclude_settings == NULL ? NULL : p_strarray_dup(pool, exclude_settings); ctx->flags = flags; ctx->callback = callback; ctx->context = context; ctx->scope = scope; ctx->value = str_new(pool, 256); ctx->prefix = str_new(pool, 64); hash_table_create(&ctx->keys, ctx->pool, 0, str_hash, strcmp); return ctx; } void config_export_by_filter(struct config_export_context *ctx, const struct config_filter *filter) { const char *error; if (config_filter_parsers_get(config_filter, ctx->pool, ctx->modules, filter, &ctx->dup_parsers, &ctx->output, &error) < 0) { i_error("%s", error); ctx->failed = TRUE; } ctx->parsers = ctx->dup_parsers; } void config_export_parsers(struct config_export_context *ctx, const struct config_module_parser *parsers) { ctx->parsers = parsers; } void config_export_get_output(struct config_export_context *ctx, struct master_service_settings_output *output_r) { *output_r = ctx->output; } const char * config_export_get_import_environment(struct config_export_context *ctx) { enum setting_type stype; unsigned int i; for (i = 0; ctx->parsers[i].root != NULL; i++) { if (ctx->parsers[i].root == &master_service_setting_parser_info) { const char *const *value = settings_parse_get_value(ctx->parsers[i].parser, "import_environment", &stype); i_assert(value != NULL); return *value; } } i_unreached(); } static void config_export_free(struct config_export_context *ctx) { if (ctx->dup_parsers != NULL) config_filter_parsers_free(ctx->dup_parsers); hash_table_destroy(&ctx->keys); pool_unref(&ctx->pool); } int config_export_finish(struct config_export_context **_ctx) { struct config_export_context *ctx = *_ctx; const struct config_module_parser *parser; const char *error; unsigned int i; int ret = 0; *_ctx = NULL; if (ctx->failed) { config_export_free(ctx); return -1; } for (i = 0; ctx->parsers[i].root != NULL; i++) { parser = &ctx->parsers[i]; if (!config_module_want_parser(config_module_parsers, ctx->modules, parser->root)) continue; T_BEGIN { enum setting_type stype; const char *const *value = settings_parse_get_value(parser->parser, "ssl", &stype); if ((ctx->flags & CONFIG_DUMP_FLAG_IN_SECTION) == 0 && value != NULL && strcmp(*value, "no") != 0 && settings_parse_is_valid_key(parser->parser, "ssl_dh")) { value = settings_parse_get_value(parser->parser, "ssl_dh", &stype); if (value == NULL || **value == '\0') { const char *newval; if (old_settings_ssl_dh_load(&newval, &error)) { if (newval != NULL) settings_parse_line(parser->parser, t_strdup_printf("%s=%s", "ssl_dh", newval)); } else { i_error("%s", error); ret = -1; } } } settings_export(ctx, parser->root, FALSE, settings_parser_get(parser->parser), settings_parser_get_changes(parser->parser)); } T_END; if ((ctx->flags & CONFIG_DUMP_FLAG_CHECK_SETTINGS) != 0) { settings_parse_var_skip(parser->parser); if (!settings_parser_check(parser->parser, ctx->pool, &error)) { if ((ctx->flags & CONFIG_DUMP_FLAG_CALLBACK_ERRORS) != 0) { ctx->callback(NULL, error, CONFIG_KEY_ERROR, ctx->context); } else { i_error("%s", error); ret = -1; break; } } } } config_export_free(ctx); return ret; }