diff options
Diffstat (limited to 'src/lib-settings/settings.c')
-rw-r--r-- | src/lib-settings/settings.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/src/lib-settings/settings.c b/src/lib-settings/settings.c new file mode 100644 index 0000000..228fab7 --- /dev/null +++ b/src/lib-settings/settings.c @@ -0,0 +1,434 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "strescape.h" +#include "settings.h" + +#include <stdio.h> +#include <fcntl.h> +#ifdef HAVE_GLOB_H +# include <glob.h> +#endif + +#ifndef GLOB_BRACE +# define GLOB_BRACE 0 +#endif + +#define SECTION_ERRORMSG "%s (section changed in %s at line %d)" + +struct input_stack { + struct input_stack *prev; + + struct istream *input; + const char *path; + unsigned int linenum; +}; + +settings_section_callback_t *null_settings_section_callback = NULL; + +static const char *get_bool(const char *value, bool *result) +{ + if (strcasecmp(value, "yes") == 0) + *result = TRUE; + else if (strcasecmp(value, "no") == 0) + *result = FALSE; + else + return t_strconcat("Invalid boolean: ", value, NULL); + + return NULL; +} + +static const char *get_uint(const char *value, unsigned int *result) +{ + int num; + + if (sscanf(value, "%i", &num) != 1 || num < 0) + return t_strconcat("Invalid number: ", value, NULL); + *result = num; + return NULL; +} + +#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') + +static const char *expand_environment_vars(const char *value) +{ + const char *pvalue = value, *p; + + /* Fast path when there are no candidates */ + if ((pvalue = strchr(pvalue, '$')) == NULL) + return value; + + string_t *expanded_value = t_str_new(strlen(value)); + str_append_data(expanded_value, value, pvalue - value); + + while (pvalue != NULL && (p = strchr(pvalue, '$')) != NULL) { + const char *var_end; + str_append_data(expanded_value, pvalue, p - pvalue); + if ((p == value || IS_WHITE(p[-1])) && + str_begins(p, "$ENV:")) { + const char *var_name, *envval; + var_end = strchr(p, ' '); + if (var_end == NULL) + var_name = p + 5; + else + var_name = t_strdup_until(p + 5, var_end); + if ((envval = getenv(var_name)) != NULL) + str_append(expanded_value, envval); + } else { + str_append_c(expanded_value, '$'); + var_end = p + 1; + } + pvalue = var_end; + } + + if (pvalue != NULL) + str_append(expanded_value, pvalue); + + return str_c(expanded_value); +} + +const char * +parse_setting_from_defs(pool_t pool, const struct setting_def *defs, void *base, + const char *key, const char *value) +{ + const struct setting_def *def; + + for (def = defs; def->name != NULL; def++) { + if (strcmp(def->name, key) == 0) { + void *ptr = STRUCT_MEMBER_P(base, def->offset); + + switch (def->type) { + case SET_STR: + *((char **)ptr) = p_strdup(pool, value); + return NULL; + case SET_INT: + /* use %i so we can handle eg. 0600 + as octal value with umasks */ + return get_uint(value, (unsigned int *) ptr); + case SET_BOOL: + return get_bool(value, (bool *) ptr); + } + } + } + + return t_strconcat("Unknown setting: ", key, NULL); +} + +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 int settings_add_include(const char *path, struct input_stack **inputp, + bool ignore_errors, const char **error_r) +{ + struct input_stack *tmp, *new_input; + int fd; + + for (tmp = *inputp; 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 = t_new(struct input_stack, 1); + new_input->prev = *inputp; + new_input->path = t_strdup(path); + new_input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + i_stream_set_return_partial_line(new_input->input, TRUE); + *inputp = new_input; + return 0; +} + +static int +settings_include(const char *pattern, struct input_stack **inputp, + bool ignore_errors, const char **error_r) +{ +#ifdef HAVE_GLOB + glob_t globbers; + unsigned int i; + + switch (glob(pattern, GLOB_BRACE, NULL, &globbers)) { + case 0: + break; + case GLOB_NOSPACE: + *error_r = "glob() failed: Not enough memory"; + return -1; + case GLOB_ABORTED: + *error_r = "glob() failed: Read error"; + return -1; + case GLOB_NOMATCH: + if (ignore_errors) + return 0; + *error_r = "No matches"; + return -1; + default: + *error_r = "glob() failed: Unknown error"; + return -1; + } + + /* iterate through the different files matching the globbing */ + for (i = 0; i < globbers.gl_pathc; i++) { + if (settings_add_include(globbers.gl_pathv[i], inputp, + ignore_errors, error_r) < 0) + return -1; + } + globfree(&globbers); + return 0; +#else + return settings_add_include(pattern, inputp, ignore_errors, error_r); +#endif +} + +bool settings_read_i(const char *path, const char *section, + settings_callback_t *callback, + settings_section_callback_t *sect_callback, void *context, + const char **error_r) +{ + /* pretty horrible code, but v2.0 will have this rewritten anyway.. */ + struct input_stack root, *input; + const char *errormsg, *next_section, *name, *last_section_path = NULL; + char *line, *key, *p, quote; + string_t *full_line; + size_t len; + int fd, last_section_line = 0, skip, sections, root_section; + + fd = open(path, O_RDONLY); + if (fd < 0) { + *error_r = t_strdup_printf( + "Can't open configuration file %s: %m", path); + return FALSE; + } + + if (section == NULL) { + skip = 0; + next_section = NULL; + } else { + skip = 1; + next_section = t_strcut(section, '/'); + } + + i_zero(&root); + root.path = path; + input = &root; + + full_line = t_str_new(512); + sections = 0; root_section = 0; errormsg = NULL; + input->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + i_stream_set_return_partial_line(input->input, TRUE); +prevfile: + while ((line = i_stream_read_next_line(input->input)) != NULL) { + input->linenum++; + + /* @UNSAFE: line is modified */ + + /* skip whitespace */ + while (IS_WHITE(*line)) + line++; + + /* ignore comments or empty lines */ + if (*line == '#' || *line == '\0') + continue; + + /* strip away comments. pretty kludgy way really.. */ + for (p = line; *p != '\0'; p++) { + if (*p == '\'' || *p == '"') { + 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.", + input->path, input->linenum); + } + *p = '\0'; + break; + } + } + + /* remove whitespace from end of line */ + len = strlen(line); + while (IS_WHITE(line[len-1])) + len--; + line[len] = '\0'; + + if (len > 0 && line[len-1] == '\\') { + /* continues in next line */ + len--; + while (IS_WHITE(line[len-1])) + len--; + str_append_data(full_line, line, len); + str_append_c(full_line, ' '); + continue; + } + if (str_len(full_line) > 0) { + str_append(full_line, line); + line = str_c_modifiable(full_line); + } + + bool quoted = FALSE; + /* 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++; + } + + if (strcmp(key, "!include_try") == 0 || + strcmp(key, "!include") == 0) { + if (settings_include(fix_relative_path(line, input), + &input, + strcmp(key, "!include_try") == 0, + &errormsg) == 0) + goto prevfile; + } else if (*line == '=') { + /* a) */ + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + + len = strlen(line); + if (len > 0 && + ((*line == '"' && line[len-1] == '"') || + (*line == '\'' && line[len-1] == '\''))) { + line[len-1] = '\0'; + line = str_unescape(line+1); + quoted = TRUE; + } + + /* @UNSAFE: Cast to modifiable datastack value, + but it will not be actually modified after this. */ + if (!quoted) + line = (char *)expand_environment_vars(line); + + errormsg = skip > 0 ? NULL : + callback(key, line, context); + } else if (strcmp(key, "}") != 0 || *line != '\0') { + /* b) + errors */ + line[-1] = '\0'; + + if (*line == '{') + name = ""; + else { + name = line; + while (!IS_WHITE(*line) && *line != '\0') + line++; + + if (*line != '\0') { + *line++ = '\0'; + while (IS_WHITE(*line)) + line++; + } + } + + if (*line != '{') + errormsg = "Expecting '='"; + else { + sections++; + if (next_section != NULL && + strcmp(next_section, name) == 0) { + section += strlen(next_section); + if (*section == '\0') { + skip = 0; + next_section = NULL; + root_section = sections; + } else { + i_assert(*section == '/'); + section++; + next_section = + t_strcut(section, '/'); + } + } + + if (skip > 0) + skip++; + else { + skip = sect_callback == NULL ? 1 : + !sect_callback(key, name, + context, + &errormsg); + if (errormsg != NULL && + last_section_line != 0) { + errormsg = t_strdup_printf( + SECTION_ERRORMSG, + errormsg, + last_section_path, + last_section_line); + } + } + last_section_path = input->path; + last_section_line = input->linenum; + } + } else { + /* c) */ + if (sections == 0) + errormsg = "Unexpected '}'"; + else { + if (skip > 0) + skip--; + else { + i_assert(sect_callback != NULL); + sect_callback(NULL, NULL, context, + &errormsg); + if (root_section == sections && + errormsg == NULL) { + /* we found the section, + now quit */ + break; + } + } + last_section_path = input->path; + last_section_line = input->linenum; + sections--; + } + } + + if (errormsg != NULL) { + *error_r = t_strdup_printf( + "Error in configuration file %s line %d: %s", + input->path, input->linenum, errormsg); + break; + } + str_truncate(full_line, 0); + } + + i_stream_destroy(&input->input); + input = input->prev; + if (line == NULL && input != NULL) + goto prevfile; + + return errormsg == NULL; +} |