diff options
Diffstat (limited to 'src/config/config-parser.c')
-rw-r--r-- | src/config/config-parser.c | 1212 |
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/config/config-parser.c b/src/config/config-parser.c new file mode 100644 index 0000000..9d36796 --- /dev/null +++ b/src/config/config-parser.c @@ -0,0 +1,1212 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hash.h" +#include "strescape.h" +#include "istream.h" +#include "module-dir.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "master-service-ssl-settings.h" +#include "all-settings.h" +#include "old-set-parser.h" +#include "config-request.h" +#include "config-parser-private.h" + +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#ifdef HAVE_GLOB_H +# include <glob.h> +#endif + +#ifndef GLOB_BRACE +# define GLOB_BRACE 0 +#endif + +#define DNS_LOOKUP_TIMEOUT_SECS 30 +#define DNS_LOOKUP_WARN_SECS 5 + +ARRAY_DEFINE_TYPE(setting_parser_info_p, const struct setting_parser_info *); + +static const enum settings_parser_flags settings_parser_flags = + SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS | + SETTINGS_PARSER_FLAG_TRACK_CHANGES; + +struct config_module_parser *config_module_parsers; +struct config_filter_context *config_filter; +struct module *modules; +void (*hook_config_parser_begin)(struct config_parser_context *ctx); +int (*hook_config_parser_end)(struct config_parser_context *ctx, + const char **error_r); + +static ARRAY_TYPE(service_settings) services_free_at_deinit = ARRAY_INIT; +static ARRAY_TYPE(setting_parser_info_p) roots_free_at_deinit = ARRAY_INIT; + +static const char *info_type_name_find(const struct setting_parser_info *info) +{ + unsigned int i; + + for (i = 0; info->defines[i].key != NULL; i++) { + if (info->defines[i].offset == info->type_offset) + return info->defines[i].key; + } + i_panic("setting parser: Invalid type_offset value"); + return NULL; +} + +static int config_add_type(struct setting_parser_context *parser, + const char *line, const char *section_name) +{ + const struct setting_parser_info *info; + const char *p; + string_t *str; + int ret; + + info = settings_parse_get_prev_info(parser); + if (info == NULL) { + /* section inside strlist */ + return -1; + } + if (info->type_offset == SIZE_MAX) + return 0; + + str = t_str_new(256); + p = strchr(line, '='); + str_append_data(str, line, p-line); + str_append_c(str, SETTINGS_SEPARATOR); + str_append(str, p+1); + if (info != NULL) { + str_append_c(str, SETTINGS_SEPARATOR); + str_append(str, info_type_name_find(info)); + } + + ret = settings_parse_keyvalue(parser, str_c(str), section_name); + i_assert(ret > 0); + return 0; +} + +static bool +config_parser_is_in_localremote(struct config_section_stack *section) +{ + const struct config_filter *filter = §ion->filter; + + return filter->local_name != NULL || filter->local_bits > 0 || + filter->remote_bits > 0; +} + +static void +section_stack_write(string_t *str, struct config_section_stack *section) +{ + if (section == NULL) + return; + + section_stack_write(str, section->prev); + if (!section->is_filter && section->key != NULL) + str_printfa(str, "%s { ", section->key); +} + +static const char * +get_setting_full_path(struct config_parser_context *ctx, const char *key) +{ + string_t *str = t_str_new(128); + + section_stack_write(str, ctx->cur_section); + str_append(str, key); + return str_c(str); +} + +int config_apply_line(struct config_parser_context *ctx, const char *key, + const char *line, const char *section_name) +{ + struct config_module_parser *l; + bool found = FALSE; + int ret; + + for (l = ctx->cur_section->parsers; l->root != NULL; l++) { + ret = settings_parse_line(l->parser, line); + if (ret > 0) { + found = TRUE; + /* FIXME: remove once auth does support these. */ + if (strcmp(l->root->module_name, "auth") == 0 && + config_parser_is_in_localremote(ctx->cur_section)) { + ctx->error = p_strconcat(ctx->pool, + "Auth settings not supported inside local/remote blocks: ", + key, NULL); + return -1; + } + if (section_name != NULL) { + if (config_add_type(l->parser, line, section_name) < 0) { + ctx->error = "Section not allowed here"; + return -1; + } + } + } else if (ret < 0) { + ctx->error = settings_parser_get_error(l->parser); + return -1; + } + } + if (!found) { + ctx->error = p_strconcat(ctx->pool, "Unknown setting: ", + get_setting_full_path(ctx, key), NULL); + return -1; + } + return 0; +} + +static const char * +fix_relative_path(const char *path, struct input_stack *input) +{ + const char *p; + + if (*path == '/') + return path; + + p = strrchr(input->path, '/'); + if (p == NULL) + return path; + + return t_strconcat(t_strdup_until(input->path, p+1), path, NULL); +} + +static struct config_module_parser * +config_module_parsers_init(pool_t pool) +{ + struct config_module_parser *dest; + unsigned int i, count; + + for (count = 0; all_roots[count] != NULL; count++) ; + + dest = p_new(pool, struct config_module_parser, count + 1); + for (i = 0; i < count; i++) { + dest[i].root = all_roots[i]; + dest[i].parser = settings_parser_init(pool, all_roots[i], + settings_parser_flags); + } + return dest; +} + +static void +config_add_new_parser(struct config_parser_context *ctx) +{ + struct config_section_stack *cur_section = ctx->cur_section; + struct config_filter_parser *parser; + + parser = p_new(ctx->pool, struct config_filter_parser, 1); + parser->filter = cur_section->filter; + if (ctx->cur_input->linenum == 0) { + parser->file_and_line = + p_strdup(ctx->pool, ctx->cur_input->path); + } else { + parser->file_and_line = + p_strdup_printf(ctx->pool, "%s:%d", + ctx->cur_input->path, + ctx->cur_input->linenum); + } + parser->parsers = cur_section->prev == NULL ? ctx->root_parsers : + config_module_parsers_init(ctx->pool); + array_push_back(&ctx->all_parsers, &parser); + + cur_section->parsers = parser->parsers; +} + +static struct config_section_stack * +config_add_new_section(struct config_parser_context *ctx) +{ + struct config_section_stack *section; + + section = p_new(ctx->pool, struct config_section_stack, 1); + section->prev = ctx->cur_section; + section->filter = ctx->cur_section->filter; + section->parsers = ctx->cur_section->parsers; + + section->open_path = p_strdup(ctx->pool, ctx->cur_input->path); + section->open_linenum = ctx->cur_input->linenum; + return section; +} + +static struct config_filter_parser * +config_filter_parser_find(struct config_parser_context *ctx, + const struct config_filter *filter) +{ + struct config_filter_parser *parser; + + array_foreach_elem(&ctx->all_parsers, parser) { + if (config_filters_equal(&parser->filter, filter)) + return parser; + } + return NULL; +} + +int config_parse_net(const char *value, struct ip_addr *ip_r, + unsigned int *bits_r, const char **error_r) +{ + struct ip_addr *ips; + const char *p; + unsigned int ip_count, bits, max_bits; + time_t t1, t2; + int ret; + + if (net_parse_range(value, ip_r, bits_r) == 0) + return 0; + + p = strchr(value, '/'); + if (p != NULL) { + value = t_strdup_until(value, p); + p++; + } + + t1 = time(NULL); + alarm(DNS_LOOKUP_TIMEOUT_SECS); + ret = net_gethostbyname(value, &ips, &ip_count); + alarm(0); + t2 = time(NULL); + if (ret != 0) { + *error_r = t_strdup_printf("gethostbyname(%s) failed: %s", + value, net_gethosterror(ret)); + return -1; + } + *ip_r = ips[0]; + + if (t2 - t1 >= DNS_LOOKUP_WARN_SECS) { + i_warning("gethostbyname(%s) took %d seconds", + value, (int)(t2-t1)); + } + + max_bits = IPADDR_IS_V4(&ips[0]) ? 32 : 128; + if (p == NULL) + *bits_r = max_bits; + else if (str_to_uint(p, &bits) == 0 && bits <= max_bits) + *bits_r = bits; + else { + *error_r = t_strdup_printf("Invalid network mask: %s", p); + return -1; + } + return 0; +} + +static bool +config_filter_add_new_filter(struct config_parser_context *ctx, + const char *key, const char *value) +{ + struct config_filter *filter = &ctx->cur_section->filter; + struct config_filter *parent = &ctx->cur_section->prev->filter; + struct config_filter_parser *parser; + const char *error; + + if (strcmp(key, "protocol") == 0) { + if (parent->service != NULL) + ctx->error = "Nested protocol { protocol { .. } } block not allowed"; + else + filter->service = p_strdup(ctx->pool, value); + } else if (strcmp(key, "local") == 0) { + if (parent->remote_bits > 0) + ctx->error = "remote { local { .. } } not allowed (use local { remote { .. } } instead)"; + else if (parent->service != NULL) + ctx->error = "protocol { local { .. } } not allowed (use local { protocol { .. } } instead)"; + else if (parent->local_name != NULL) + ctx->error = "local_name { local { .. } } not allowed (use local { local_name { .. } } instead)"; + else if (config_parse_net(value, &filter->local_net, + &filter->local_bits, &error) < 0) + ctx->error = p_strdup(ctx->pool, error); + else if (parent->local_bits > filter->local_bits || + (parent->local_bits > 0 && + !net_is_in_network(&filter->local_net, + &parent->local_net, + parent->local_bits))) + ctx->error = "local net1 { local net2 { .. } } requires net2 to be inside net1"; + else + filter->local_host = p_strdup(ctx->pool, value); + } else if (strcmp(key, "local_name") == 0) { + if (parent->remote_bits > 0) + ctx->error = "remote { local_name { .. } } not allowed (use local_name { remote { .. } } instead)"; + else if (parent->service != NULL) + ctx->error = "protocol { local_name { .. } } not allowed (use local_name { protocol { .. } } instead)"; + else + filter->local_name = p_strdup(ctx->pool, value); + } else if (strcmp(key, "remote") == 0) { + if (parent->service != NULL) + ctx->error = "protocol { remote { .. } } not allowed (use remote { protocol { .. } } instead)"; + else if (config_parse_net(value, &filter->remote_net, + &filter->remote_bits, &error) < 0) + ctx->error = p_strdup(ctx->pool, error); + else if (parent->remote_bits > filter->remote_bits || + (parent->remote_bits > 0 && + !net_is_in_network(&filter->remote_net, + &parent->remote_net, + parent->remote_bits))) + ctx->error = "remote net1 { remote net2 { .. } } requires net2 to be inside net1"; + else + filter->remote_host = p_strdup(ctx->pool, value); + } else { + return FALSE; + } + + parser = config_filter_parser_find(ctx, filter); + if (parser != NULL) + ctx->cur_section->parsers = parser->parsers; + else + config_add_new_parser(ctx); + ctx->cur_section->is_filter = TRUE; + return TRUE; +} + +static int +config_filter_parser_check(struct config_parser_context *ctx, + const struct config_module_parser *p, + const char **error_r) +{ + const char *error = NULL; + bool ok; + + for (; p->root != NULL; p++) { + /* skip checking settings we don't care about */ + if (!config_module_want_parser(ctx->root_parsers, + ctx->modules, p->root)) + continue; + + settings_parse_var_skip(p->parser); + T_BEGIN { + ok = settings_parser_check(p->parser, ctx->pool, &error); + } T_END_PASS_STR_IF(!ok, &error); + if (!ok) { + /* be sure to assert-crash early if error is missing */ + i_assert(error != NULL); + *error_r = error; + return -1; + } + } + return 0; +} + +static const char * +get_str_setting(struct config_filter_parser *parser, const char *key, + const char *default_value) +{ + struct config_module_parser *module_parser; + const char *const *set_value; + enum setting_type set_type; + + module_parser = parser->parsers; + for (; module_parser->parser != NULL; module_parser++) { + set_value = settings_parse_get_value(module_parser->parser, + key, &set_type); + if (set_value != NULL && + settings_parse_is_changed(module_parser->parser, key)) { + i_assert(set_type == SET_STR || set_type == SET_ENUM); + return *set_value; + } + } + return default_value; +} + +static int +config_all_parsers_check(struct config_parser_context *ctx, + struct config_filter_context *new_filter, + const char **error_r) +{ + struct config_filter_parser *const *parsers; + struct config_module_parser *tmp_parsers; + struct master_service_settings_output output; + unsigned int i, count; + const char *ssl_set, *global_ssl_set; + pool_t tmp_pool; + bool ssl_warned = FALSE; + int ret = 0; + + if (ctx->cur_section->prev != NULL) { + *error_r = t_strdup_printf( + "Missing '}' (section started at %s:%u)", + ctx->cur_section->open_path, + ctx->cur_section->open_linenum); + return -1; + } + + tmp_pool = pool_alloconly_create(MEMPOOL_GROWING"config parsers check", 1024*64); + parsers = array_get(&ctx->all_parsers, &count); + i_assert(count > 0 && parsers[count-1] == NULL); + count--; + + global_ssl_set = get_str_setting(parsers[0], "ssl", ""); + for (i = 0; i < count && ret == 0; i++) { + if (config_filter_parsers_get(new_filter, tmp_pool, NULL, + &parsers[i]->filter, + &tmp_parsers, &output, + error_r) < 0) { + ret = -1; + break; + } + + ssl_set = get_str_setting(parsers[i], "ssl", global_ssl_set); + if (strcmp(ssl_set, "no") != 0 && + strcmp(global_ssl_set, "no") == 0 && !ssl_warned) { + i_warning("SSL is disabled because global ssl=no, " + "ignoring ssl=%s for subsection", ssl_set); + ssl_warned = TRUE; + } + + ret = config_filter_parser_check(ctx, tmp_parsers, error_r); + config_filter_parsers_free(tmp_parsers); + p_clear(tmp_pool); + } + pool_unref(&tmp_pool); + return ret; +} + +static int +str_append_file(string_t *str, const char *key, const char *path, + const char **error_r) +{ + unsigned char buf[1024]; + int fd; + ssize_t ret; + + *error_r = NULL; + + fd = open(path, O_RDONLY); + if (fd == -1) { + *error_r = t_strdup_printf("%s: Can't open file %s: %m", + key, path); + return -1; + } + while ((ret = read(fd, buf, sizeof(buf))) > 0) + str_append_data(str, buf, ret); + if (ret < 0) { + *error_r = t_strdup_printf("%s: read(%s) failed: %m", + key, path); + } + i_close_fd(&fd); + return ret < 0 ? -1 : 0; +} + +static int settings_add_include(struct config_parser_context *ctx, const char *path, + bool ignore_errors, const char **error_r) +{ + struct input_stack *tmp, *new_input; + int fd; + + for (tmp = ctx->cur_input; tmp != NULL; tmp = tmp->prev) { + if (strcmp(tmp->path, path) == 0) + break; + } + if (tmp != NULL) { + *error_r = t_strdup_printf("Recursive include file: %s", path); + return -1; + } + + if ((fd = open(path, O_RDONLY)) == -1) { + if (ignore_errors) + return 0; + + *error_r = t_strdup_printf("Couldn't open include file %s: %m", + path); + return -1; + } + + new_input = p_new(ctx->pool, struct input_stack, 1); + new_input->prev = ctx->cur_input; + new_input->path = p_strdup(ctx->pool, path); + new_input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + i_stream_set_return_partial_line(new_input->input, TRUE); + ctx->cur_input = new_input; + return 0; +} + +static int +settings_include(struct config_parser_context *ctx, const char *pattern, + bool ignore_errors) +{ + const char *error; +#ifdef HAVE_GLOB + glob_t globbers; + unsigned int i; + + switch (glob(pattern, GLOB_BRACE, NULL, &globbers)) { + case 0: + break; + case GLOB_NOSPACE: + ctx->error = "glob() failed: Not enough memory"; + return -1; + case GLOB_ABORTED: + ctx->error = "glob() failed: Read error"; + return -1; + case GLOB_NOMATCH: + if (ignore_errors) + return 0; + ctx->error = "No matches"; + return -1; + default: + ctx->error = "glob() failed: Unknown error"; + return -1; + } + + /* iterate through the different files matching the globbing */ + for (i = globbers.gl_pathc; i > 0; i--) { + if (settings_add_include(ctx, globbers.gl_pathv[i-1], + ignore_errors, &error) < 0) { + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + } + globfree(&globbers); + return 0; +#else + if (settings_add_include(ctx, pattern, ignore_errors, &error) < 0) { + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + return 0; +#endif +} + +static enum config_line_type +config_parse_line(struct config_parser_context *ctx, + char *line, string_t *full_line, + const char **key_r, const char **value_r) +{ + const char *key; + size_t len; + char *p; + + *key_r = NULL; + *value_r = NULL; + + /* @UNSAFE: line is modified */ + + /* skip whitespace */ + while (IS_WHITE(*line)) + line++; + + /* ignore comments or empty lines */ + if (*line == '#' || *line == '\0') + return CONFIG_LINE_TYPE_SKIP; + + /* strip away comments. pretty kludgy way really.. */ + for (p = line; *p != '\0'; p++) { + if (*p == '\'' || *p == '"') { + char quote = *p; + for (p++; *p != quote && *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + } + if (*p == '\0') + break; + } else if (*p == '#') { + if (!IS_WHITE(p[-1])) { + i_warning("Configuration file %s line %u: " + "Ambiguous '#' character in line, treating it as comment. " + "Add a space before it to remove this warning.", + ctx->cur_input->path, + ctx->cur_input->linenum); + } + *p = '\0'; + break; + } + } + + /* remove whitespace from end of line */ + len = strlen(line); + while (len >= 1) { + if(!IS_WHITE(line[len-1])) + break; + len--; + } + line[len] = '\0'; + + if (len >= 1 && line[len-1] == '\\') { + /* continues in next line */ + len--; + while (len >= 1) { + if(!IS_WHITE(line[len-1])) + break; + len--; + } + if(len >= 1) { + str_append_data(full_line, line, len); + str_append_c(full_line, ' '); + } + return CONFIG_LINE_TYPE_CONTINUE; + } + if (str_len(full_line) > 0) { + str_append(full_line, line); + line = str_c_modifiable(full_line); + } + + /* a) key = value + b) section_type [section_name] { + c) } */ + key = line; + while (!IS_WHITE(*line) && *line != '\0' && *line != '=') + line++; + if (IS_WHITE(*line)) { + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + } + *key_r = key; + *value_r = line; + + if (strcmp(key, "!include") == 0) + return CONFIG_LINE_TYPE_INCLUDE; + if (strcmp(key, "!include_try") == 0) + return CONFIG_LINE_TYPE_INCLUDE_TRY; + + if (*line == '=') { + /* a) */ + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + + if (*line == '<') { + while (IS_WHITE(line[1])) line++; + *value_r = line + 1; + return CONFIG_LINE_TYPE_KEYFILE; + } + if (*line != '\'' && *line != '"' && strchr(line, '$') != NULL) { + *value_r = line; + return CONFIG_LINE_TYPE_KEYVARIABLE; + } + + len = strlen(line); + if (len > 0 && + ((*line == '"' && line[len-1] == '"') || + (*line == '\'' && line[len-1] == '\''))) { + line[len-1] = '\0'; + line = str_unescape(line+1); + } + *value_r = line; + return CONFIG_LINE_TYPE_KEYVALUE; + } + + if (strcmp(key, "}") == 0 && *line == '\0') + return CONFIG_LINE_TYPE_SECTION_END; + + /* b) + errors */ + line[-1] = '\0'; + + if (*line == '{') + *value_r = ""; + else { + /* get section name */ + if (*line != '"') { + *value_r = line; + while (!IS_WHITE(*line) && *line != '\0') + line++; + if (*line != '\0') { + *line++ = '\0'; + while (IS_WHITE(*line)) + line++; + } + } else { + char *value = ++line; + while (*line != '"' && *line != '\0') + line++; + if (*line == '"') { + *line++ = '\0'; + while (IS_WHITE(*line)) + line++; + *value_r = str_unescape(value); + } + } + if (*line != '{') { + *value_r = "Expecting '{'"; + return CONFIG_LINE_TYPE_ERROR; + } + } + if (line[1] != '\0') { + *value_r = "Garbage after '{'"; + return CONFIG_LINE_TYPE_ERROR; + } + return CONFIG_LINE_TYPE_SECTION_BEGIN; +} + +static int config_parse_finish(struct config_parser_context *ctx, const char **error_r) +{ + struct config_filter_context *new_filter; + const char *error; + int ret = 0; + + if (hook_config_parser_end != NULL) + ret = hook_config_parser_end(ctx, error_r); + + new_filter = config_filter_init(ctx->pool); + array_append_zero(&ctx->all_parsers); + config_filter_add_all(new_filter, array_front(&ctx->all_parsers)); + + if (ret < 0) + ; + else if (ctx->hide_errors) + ret = 0; + else if ((ret = config_all_parsers_check(ctx, new_filter, &error)) < 0) { + *error_r = t_strdup_printf("Error in configuration file %s: %s", + ctx->path, error); + } + + if (config_filter != NULL) + config_filter_deinit(&config_filter); + config_module_parsers = ctx->root_parsers; + config_filter = new_filter; + return ret; +} + +static const void * +config_get_value(struct config_section_stack *section, const char *key, + bool expand_parent, enum setting_type *type_r) +{ + struct config_module_parser *l; + const void *value; + + for (l = section->parsers; l->root != NULL; l++) { + value = settings_parse_get_value(l->parser, key, type_r); + if (value != NULL) { + if (!expand_parent || section->prev == NULL || + settings_parse_is_changed(l->parser, key)) + return value; + + /* not changed by this parser. maybe parent has. */ + return config_get_value(section->prev, + key, TRUE, type_r); + } + } + return NULL; +} + +static bool +config_require_key(struct config_parser_context *ctx, const char *key) +{ + struct config_module_parser *l; + + if (ctx->modules == NULL) + return TRUE; + + for (l = ctx->cur_section->parsers; l->root != NULL; l++) { + if (config_module_want_parser(ctx->root_parsers, + ctx->modules, l->root) && + settings_parse_is_valid_key(l->parser, key)) + return TRUE; + } + return FALSE; +} + +static int config_write_keyvariable(struct config_parser_context *ctx, + const char *key, const char *value, + string_t *str) +{ + const char *var_end, *p_start = value; + bool dump; + while (value != NULL) { + const char *var_name; + bool expand_parent; + var_end = strchr(value, ' '); + + /* expand_parent=TRUE for "key = $key stuff". + we'll always expand it so that doveconf -n can give + usable output */ + if (var_end == NULL) + var_name = value; + else + var_name = t_strdup_until(value, var_end); + expand_parent = strcmp(key, var_name + + (*var_name == '$' ? 1 : 0)) == 0; + + if (!str_begins(var_name, "$") || + (value > p_start && !IS_WHITE(value[-1]))) { + str_append(str, var_name); + } else if (!ctx->expand_values && !expand_parent) { + str_append(str, var_name); + } else if (str_begins(var_name, "$ENV:")) { + /* use environment variable */ + const char *envval = getenv(var_name+5); + if (envval != NULL) + str_append(str, envval); + } else { + const char *var_value; + enum setting_type var_type; + + i_assert(var_name[0] == '$'); + var_name++; + + var_value = config_get_value(ctx->cur_section, var_name, + expand_parent, &var_type); + if (var_value == NULL) { + ctx->error = p_strconcat(ctx->pool, + "Unknown variable: $", + var_name, NULL); + return -1; + } + if (!config_export_type(str, var_value, NULL, + var_type, TRUE, &dump)) { + ctx->error = p_strconcat(ctx->pool, + "Invalid variable: $", + var_name, NULL); + return -1; + } + } + + if (var_end == NULL) + break; + + str_append_c(str, ' '); + + /* find next token */ + while (*var_end != '\0' && IS_WHITE(*var_end)) var_end++; + value = var_end; + while (*var_end != '\0' && !IS_WHITE(*var_end)) var_end++; + } + + return 0; +} + +static int config_write_value(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value) +{ + string_t *str = ctx->str; + const char *error, *path, *full_key; + + switch (type) { + case CONFIG_LINE_TYPE_KEYVALUE: + str_append(str, value); + break; + case CONFIG_LINE_TYPE_KEYFILE: + full_key = t_strndup(str_data(ctx->str), str_len(str)-1); + if (!ctx->expand_values) { + str_append_c(str, '<'); + str_append(str, value); + } else { + if (!config_require_key(ctx, full_key)) { + /* don't even try to open the file */ + } else { + path = fix_relative_path(value, ctx->cur_input); + if (str_append_file(str, full_key, path, &error) < 0) { + /* file reading failed */ + ctx->error = p_strdup(ctx->pool, error); + return -1; + } + } + } + break; + case CONFIG_LINE_TYPE_KEYVARIABLE: + if (config_write_keyvariable(ctx, key, value, str) < 0) + return -1; + break; + default: + i_unreached(); + } + return 0; +} + +static void +config_parser_check_warnings(struct config_parser_context *ctx, const char *key) +{ + const char *path, *first_pos; + + first_pos = hash_table_lookup(ctx->seen_settings, str_c(ctx->str)); + if (ctx->cur_section->prev == NULL) { + /* changing a root setting. if we've already seen it inside + filters, log a warning. */ + if (first_pos == NULL) + return; + i_warning("%s line %u: Global setting %s won't change the setting inside an earlier filter at %s " + "(if this is intentional, avoid this warning by moving the global setting before %s)", + ctx->cur_input->path, ctx->cur_input->linenum, + key, first_pos, first_pos); + return; + } + if (first_pos != NULL) + return; + first_pos = p_strdup_printf(ctx->pool, "%s line %u", + ctx->cur_input->path, ctx->cur_input->linenum); + path = p_strdup(ctx->pool, str_c(ctx->str)); + hash_table_insert(ctx->seen_settings, path, first_pos); +} + +void config_parser_apply_line(struct config_parser_context *ctx, + enum config_line_type type, + const char *key, const char *value) +{ + const char *section_name; + + str_truncate(ctx->str, ctx->pathlen); + switch (type) { + case CONFIG_LINE_TYPE_SKIP: + break; + case CONFIG_LINE_TYPE_CONTINUE: + i_unreached(); + case CONFIG_LINE_TYPE_ERROR: + ctx->error = p_strdup(ctx->pool, value); + break; + case CONFIG_LINE_TYPE_KEYVALUE: + case CONFIG_LINE_TYPE_KEYFILE: + case CONFIG_LINE_TYPE_KEYVARIABLE: + str_append(ctx->str, key); + config_parser_check_warnings(ctx, key); + str_append_c(ctx->str, '='); + + if (config_write_value(ctx, type, key, value) < 0) + break; + (void)config_apply_line(ctx, key, str_c(ctx->str), NULL); + break; + case CONFIG_LINE_TYPE_SECTION_BEGIN: + ctx->cur_section = config_add_new_section(ctx); + ctx->cur_section->pathlen = ctx->pathlen; + ctx->cur_section->key = p_strdup(ctx->pool, key); + + if (config_filter_add_new_filter(ctx, key, value)) { + /* new filter */ + break; + } + + /* new config section */ + if (*value == '\0') { + /* no section name, use a counter */ + section_name = dec2str(ctx->section_counter++); + } else { + section_name = settings_section_escape(value); + } + str_append(ctx->str, key); + ctx->pathlen = str_len(ctx->str); + + str_append_c(ctx->str, '='); + str_append(ctx->str, section_name); + + if (config_apply_line(ctx, key, str_c(ctx->str), value) < 0) + break; + + str_truncate(ctx->str, ctx->pathlen); + str_append_c(ctx->str, SETTINGS_SEPARATOR); + str_append(ctx->str, section_name); + str_append_c(ctx->str, SETTINGS_SEPARATOR); + ctx->pathlen = str_len(ctx->str); + break; + case CONFIG_LINE_TYPE_SECTION_END: + if (ctx->cur_section->prev == NULL) + ctx->error = "Unexpected '}'"; + else { + ctx->pathlen = ctx->cur_section->pathlen; + ctx->cur_section = ctx->cur_section->prev; + } + break; + case CONFIG_LINE_TYPE_INCLUDE: + case CONFIG_LINE_TYPE_INCLUDE_TRY: + (void)settings_include(ctx, fix_relative_path(value, ctx->cur_input), + type == CONFIG_LINE_TYPE_INCLUDE_TRY); + break; + } +} + +int config_parse_file(const char *path, bool expand_values, + const char *const *modules, const char **error_r) +{ + struct input_stack root; + struct config_parser_context ctx; + unsigned int i, count; + const char *key, *value; + string_t *full_line; + enum config_line_type type; + char *line; + int fd, ret = 0; + bool handled; + + if (path == NULL) { + path = "<defaults>"; + fd = -1; + } else { + fd = open(path, O_RDONLY); + if (fd < 0) { + *error_r = t_strdup_printf("open(%s) failed: %m", path); + return 0; + } + } + + i_zero(&ctx); + ctx.pool = pool_alloconly_create(MEMPOOL_GROWING"config file parser", 1024*256); + ctx.path = path; + ctx.hide_errors = fd == -1; + + for (count = 0; all_roots[count] != NULL; count++) ; + ctx.root_parsers = + p_new(ctx.pool, struct config_module_parser, count+1); + for (i = 0; i < count; i++) { + ctx.root_parsers[i].root = all_roots[i]; + ctx.root_parsers[i].parser = + settings_parser_init(ctx.pool, all_roots[i], + settings_parser_flags); + } + + i_zero(&root); + root.path = path; + ctx.cur_input = &root; + ctx.expand_values = expand_values; + ctx.modules = modules; + hash_table_create(&ctx.seen_settings, ctx.pool, 0, str_hash, strcmp); + + p_array_init(&ctx.all_parsers, ctx.pool, 128); + ctx.cur_section = p_new(ctx.pool, struct config_section_stack, 1); + config_add_new_parser(&ctx); + + ctx.str = str_new(ctx.pool, 256); + full_line = str_new(default_pool, 512); + ctx.cur_input->input = fd != -1 ? + i_stream_create_fd_autoclose(&fd, SIZE_MAX) : + i_stream_create_from_data("", 0); + i_stream_set_return_partial_line(ctx.cur_input->input, TRUE); + old_settings_init(&ctx); + if (hook_config_parser_begin != NULL) T_BEGIN { + hook_config_parser_begin(&ctx); + } T_END; + +prevfile: + while ((line = i_stream_read_next_line(ctx.cur_input->input)) != NULL) { + ctx.cur_input->linenum++; + type = config_parse_line(&ctx, line, full_line, + &key, &value); + str_truncate(ctx.str, ctx.pathlen); + if (type == CONFIG_LINE_TYPE_CONTINUE) + continue; + + T_BEGIN { + handled = old_settings_handle(&ctx, type, key, value); + if (!handled) + config_parser_apply_line(&ctx, type, key, value); + } T_END; + + if (ctx.error != NULL) { + *error_r = t_strdup_printf( + "Error in configuration file %s line %d: %s", + ctx.cur_input->path, ctx.cur_input->linenum, + ctx.error); + ret = -2; + break; + } + str_truncate(full_line, 0); + } + + i_stream_destroy(&ctx.cur_input->input); + ctx.cur_input = ctx.cur_input->prev; + if (line == NULL && ctx.cur_input != NULL) + goto prevfile; + + hash_table_destroy(&ctx.seen_settings); + str_free(&full_line); + if (ret == 0) + ret = config_parse_finish(&ctx, error_r); + return ret < 0 ? ret : 1; +} + +void config_parse_load_modules(void) +{ + struct module_dir_load_settings mod_set; + struct module *m; + const struct setting_parser_info **roots; + ARRAY_TYPE(setting_parser_info_p) new_roots; + ARRAY_TYPE(service_settings) new_services; + struct service_settings *const *services, *service_set; + unsigned int i, count; + + i_zero(&mod_set); + mod_set.abi_version = DOVECOT_ABI_VERSION; + modules = module_dir_load(CONFIG_MODULE_DIR, NULL, &mod_set); + module_dir_init(modules); + + i_array_init(&new_roots, 64); + i_array_init(&new_services, 64); + for (m = modules; m != NULL; m = m->next) { + roots = module_get_symbol_quiet(m, + t_strdup_printf("%s_set_roots", m->name)); + if (roots != NULL) { + for (i = 0; roots[i] != NULL; i++) + array_push_back(&new_roots, &roots[i]); + } + + services = module_get_symbol_quiet(m, + t_strdup_printf("%s_service_settings_array", m->name)); + if (services != NULL) { + for (count = 0; services[count] != NULL; count++) ; + array_append(&new_services, services, count); + } else { + service_set = module_get_symbol_quiet(m, + t_strdup_printf("%s_service_settings", m->name)); + if (service_set != NULL) + array_push_back(&new_services, &service_set); + } + } + if (array_count(&new_roots) > 0) { + /* modules added new settings. add the defaults and start + using the new list. */ + for (i = 0; all_roots[i] != NULL; i++) + array_push_back(&new_roots, &all_roots[i]); + array_append_zero(&new_roots); + all_roots = array_front(&new_roots); + roots_free_at_deinit = new_roots; + } else { + array_free(&new_roots); + } + if (array_count(&new_services) > 0) { + /* module added new services. update the defaults. */ + services = array_get(default_services, &count); + for (i = 0; i < count; i++) + array_push_back(&new_services, &services[i]); + *default_services = new_services; + services_free_at_deinit = new_services; + } else { + array_free(&new_services); + } +} + +static bool parsers_are_connected(const struct setting_parser_info *root, + const struct setting_parser_info *info) +{ + const struct setting_parser_info *p; + const struct setting_parser_info *const *dep; + + /* we're trying to find info or its parents from root's dependencies. */ + + for (p = info; p != NULL; p = p->parent) { + if (p == root) + return TRUE; + } + + if (root->dependencies != NULL) { + for (dep = root->dependencies; *dep != NULL; dep++) { + if (parsers_are_connected(*dep, info)) + return TRUE; + } + } + return FALSE; +} + +bool config_module_want_parser(struct config_module_parser *parsers, + const char *const *modules, + const struct setting_parser_info *root) +{ + struct config_module_parser *l; + + if (modules == NULL) + return TRUE; + if (root == &master_service_setting_parser_info) { + /* everyone wants master service settings */ + return TRUE; + } + + for (l = parsers; l->root != NULL; l++) { + if (!str_array_find(modules, l->root->module_name)) + continue; + + /* see if we can find a way to get from the original parser + to this parser */ + if (parsers_are_connected(l->root, root)) + return TRUE; + } + return FALSE; +} + +void config_parser_deinit(void) +{ + if (array_is_created(&services_free_at_deinit)) + array_free(&services_free_at_deinit); + if (array_is_created(&roots_free_at_deinit)) + array_free(&roots_free_at_deinit); +} |