+++ /dev/null
-/* 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-legacy.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_with(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 = PTR_OFFSET(base, def->offset);
-
- switch (def->type) {
- case SET_LEGACY_STR:
- *((char **)ptr) = p_strdup(pool, value);
- return NULL;
- case SET_LEGACY_INT:
- /* use %i so we can handle eg. 0600
- as octal value with umasks */
- return get_uint(value, (unsigned int *) ptr);
- case SET_LEGACY_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;
-}
+++ /dev/null
-#ifndef SETTINGS_LEGACY_H
-#define SETTINGS_LEGACY_H
-
-/*
- * Note:
- *
- * The definitions in this file are used for parsing of external config
- * files and *not* for parsing of dovecot.conf. Unfortunately, the types
- * here (e.g., enum settings_type) collide with those in settings-parser.h.
- *
- * We should remove the need for this file in v3.0.
- */
-
-enum setting_legacy_type {
- SET_LEGACY_STR,
- SET_LEGACY_INT,
- SET_LEGACY_BOOL
-};
-
-struct setting_def {
- enum setting_legacy_type type;
- const char *name;
- size_t offset;
-};
-
-#define DEF_STRUCT_STR(name, struct_name) \
- { SET_LEGACY_STR + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
- ((struct struct_name *)0)->name, const char *), \
- #name, offsetof(struct struct_name, name) }
-#define DEF_STRUCT_INT(name, struct_name) \
- { SET_LEGACY_INT + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
- ((struct struct_name *)0)->name, unsigned int), \
- #name, offsetof(struct struct_name, name) }
-#define DEF_STRUCT_BOOL(name, struct_name) \
- { SET_LEGACY_BOOL + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \
- ((struct struct_name *)0)->name, bool), \
- #name, offsetof(struct struct_name, name) }
-
-/* Return error message. When closing section, key = NULL, value = NULL. */
-typedef const char *settings_callback_t(const char *key, const char *value,
- void *context);
-
-/* Return TRUE if we want to go inside the section */
-typedef bool settings_section_callback_t(const char *type, const char *name,
- void *context, const char **errormsg);
-
-extern settings_section_callback_t *null_settings_section_callback;
-
-const char *
-parse_setting_from_defs(pool_t pool, const struct setting_def *defs, void *base,
- const char *key, const char *value);
-
-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)
- ATTR_NULL(2, 4, 5);
-#define settings_read(path, section, callback, sect_callback, context, error_r) \
- settings_read_i(path - \
- CALLBACK_TYPECHECK(callback, const char *(*)( \
- const char *, const char *, typeof(context))) - \
- CALLBACK_TYPECHECK(sect_callback, bool (*)( \
- const char *, const char *, typeof(context), \
- const char **)), \
- section, (settings_callback_t *)callback, \
- (settings_section_callback_t *)sect_callback, context, error_r)
-#define settings_read_nosection(path, callback, context, error_r) \
- settings_read_i(path - \
- CALLBACK_TYPECHECK(callback, const char *(*)( \
- const char *, const char *, typeof(context))), \
- NULL, (settings_callback_t *)callback, NULL, context, error_r)
-
-#endif
#include "lib.h"
#include "array.h"
-#include "net.h"
#include "settings.h"
-#include "settings-legacy.h"
-#include "istream.h"
-#include "ostream.h"
#include "test-common.h"
-/*
- * settings_read_nosection()
- */
-
-#define TEST_SETTING_FILE ".test_settings.conf"
-
-static const char *config_contents =
-"# this is a comment\n"
-"str = value\n"
-"str2 = some other value # and this should be ignored\n"
-"str3 = $ENV:test\n"
-"str4 = $ENV:test %{second}\n"
-"str5 = Hello $ENV:test\n"
-"str6 = foo$ENV:test bar\n"
-"str7 = \"this is $ENV:test string literal\"\n"
-"str8 = \\$ENV:test escaped\n"
-"str9 = $ENV:FOO$ENV:FOO bar\n"
-"str10 = \\$escape \\escape \\\"escape\\\"\n"
-"str11 = 'this is $ENV:test string literal'\n"
-"str12 = $ENV:test $ENV:test\n"
-"b_true = yes\n"
-"b_false = no\n"
-"number = 1234\n";
-
-struct test_settings {
- const char *str;
- const char *str2;
- const char *str3;
- const char *str4;
- const char *str5;
- const char *str6;
- const char *str7;
- const char *str8;
- const char *str9;
- const char *str10;
- const char *str11;
- const char *str12;
-
- bool b_true;
- bool b_false;
- unsigned int number;
-};
-
-#undef DEF_STR
-#undef DEF_BOOL
-#undef DEF_INT
-
-#define DEF_STR(name) DEF_STRUCT_STR(name, test_settings)
-#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, test_settings)
-#define DEF_INT(name) DEF_STRUCT_INT(name, test_settings)
-
-static struct setting_def setting_defs[] = {
- DEF_STR(str),
- DEF_STR(str2),
- DEF_STR(str3),
- DEF_STR(str4),
- DEF_STR(str5),
- DEF_STR(str6),
- DEF_STR(str7),
- DEF_STR(str8),
- DEF_STR(str9),
- DEF_STR(str10),
- DEF_STR(str11),
- DEF_STR(str12),
- DEF_BOOL(b_true),
- DEF_BOOL(b_false),
- DEF_INT(number),
- { 0, NULL, 0 }
-};
-
-static struct test_settings default_settings = {
- .str = "",
- .str2 = "",
- .str3 = "",
- .str4 = "",
- .str5 = "",
- .str6 = "",
- .str7 = "",
- .str8 = "",
- .str9 = "",
- .str10 = "",
- .str11 = "",
- .str12 = "",
-
- .b_true = FALSE,
- .b_false = TRUE,
- .number = 0,
-};
-
-struct test_settings_context {
- pool_t pool;
- struct test_settings set;
-};
-
-static const char *parse_setting(const char *key, const char *value,
- struct test_settings_context *ctx)
-{
- return parse_setting_from_defs(ctx->pool, setting_defs,
- &ctx->set, key, value);
-}
-
-static void test_settings_read_nosection(void)
-{
- test_begin("settings_read_nosection");
-
- const char *error = NULL;
- /* write a simple config file */
- struct ostream *os = o_stream_create_file(TEST_SETTING_FILE, 0, 0600, 0);
- o_stream_nsend_str(os, config_contents);
- test_assert(o_stream_finish(os) == 1);
- o_stream_unref(&os);
-
- putenv("test=first");
- putenv("FOO$ENV:FOO=works");
- /* try parse it */
- pool_t pool = pool_alloconly_create("test settings", 1024);
- struct test_settings_context *ctx =
- p_new(pool, struct test_settings_context, 1);
- ctx->pool = pool;
- ctx->set = default_settings;
-
- test_assert(settings_read_nosection(TEST_SETTING_FILE, parse_setting,
- ctx, &error));
- test_assert(error == NULL);
- if (error != NULL)
- i_error("%s", error);
-
- /* see what we got */
- test_assert_strcmp(ctx->set.str, "value");
- test_assert_strcmp(ctx->set.str2, "some other value");
- test_assert_strcmp(ctx->set.str3, "first");
- test_assert_strcmp(ctx->set.str4, "first %{second}");
- test_assert_strcmp(ctx->set.str5, "Hello first");
- test_assert_strcmp(ctx->set.str6, "foo$ENV:test bar");
- test_assert_strcmp(ctx->set.str7, "this is $ENV:test string literal");
- test_assert_strcmp(ctx->set.str8, "\\$ENV:test escaped");
- test_assert_strcmp(ctx->set.str9, "works bar");
- test_assert_strcmp(ctx->set.str10, "\\$escape \\escape \\\"escape\\\"");
- test_assert_strcmp(ctx->set.str11, "this is $ENV:test string literal");
- test_assert_strcmp(ctx->set.str12, "first first");
-
- test_assert(ctx->set.b_true == TRUE);
- test_assert(ctx->set.b_false == FALSE);
- test_assert(ctx->set.number == 1234);
-
- pool_unref(&pool);
-
- i_unlink_if_exists(TEST_SETTING_FILE);
- test_end();
-}
-
/*
* settings_get()
*/
int main(void)
{
static void (*const test_functions[])(void) = {
- test_settings_read_nosection,
test_settings_get,
NULL
};