/* 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;
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_t)-1);
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 {
str_append(str, settings_section_escape(*name));
}
}
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++) {
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:
dump_default = TRUE;
break;
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, 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->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;
}