From: Timo Sirainen Date: Mon, 29 Jan 2024 20:24:26 +0000 (+0200) Subject: lib-settings: Add SET_FILE type X-Git-Tag: 2.4.1~1080 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7c159777934e78db33bbf5473f81c441a2b1d50a;p=thirdparty%2Fdovecot%2Fcore.git lib-settings: Add SET_FILE type The string contains file's path, newline and file contents. If the newline is missing, the file is read immediately. --- diff --git a/src/lib-settings/settings-parser.c b/src/lib-settings/settings-parser.c index 133780ed7d..45ac30ed81 100644 --- a/src/lib-settings/settings-parser.c +++ b/src/lib-settings/settings-parser.c @@ -4,8 +4,13 @@ #include "array.h" #include "str.h" #include "str-parse.h" +#include "read-full.h" #include "settings-parser.h" +#include +#include +#include + struct boollist_removal { ARRAY_TYPE(const_string) *array; const char *key_suffix; @@ -243,6 +248,27 @@ static int get_enum(struct setting_parser_context *ctx, const char *value, return 0; } +static int +get_file(struct setting_parser_context *ctx, bool dup_value, const char **value) +{ + if (**value == '\0') + return 0; + const char *content = strchr(*value, '\n'); + if (content != NULL) { + if (dup_value) + *value = p_strdup(ctx->set_pool, *value); + return 0; + } + + const char *error; + if (settings_parse_read_file(*value, *value, ctx->set_pool, + value, &error) < 0) { + settings_parser_set_error(ctx, error); + return -1; + } + return 0; +} + static int get_in_port_zero(struct setting_parser_context *ctx, const char *value, in_port_t *result_r) @@ -255,6 +281,49 @@ get_in_port_zero(struct setting_parser_context *ctx, const char *value, return 0; } +int settings_parse_read_file(const char *path, const char *value_path, + pool_t pool, + const char **output_r, const char **error_r) +{ + struct stat st; + int fd; + + if ((fd = open(path, O_RDONLY)) == -1) { + *error_r = t_strdup_printf("open(%s) failed: %m", path); + return -1; + } + if (fstat(fd, &st) < 0) { + *error_r = t_strdup_printf("fstat(%s) failed: %m", path); + i_close_fd(&fd); + return -1; + } + size_t value_path_len = strlen(value_path); + char *buf = p_malloc(pool, value_path_len + 1 + st.st_size + 1); + memcpy(buf, value_path, value_path_len); + buf[value_path_len] = '\n'; + + int ret = read_full(fd, buf + value_path_len + 1, st.st_size); + i_close_fd(&fd); + if (ret < 0) { + *error_r = t_strdup_printf("read(%s) failed: %m", path); + return -1; + } + if (ret == 0) { + *error_r = t_strdup_printf( + "read(%s) failed: Unexpected EOF", path); + return -1; + } + if (memchr(buf + value_path_len + 1, '\0', st.st_size) != NULL) { + *error_r = t_strdup_printf( + "%s contains NUL characters - This is not supported", + path); + return -1; + } + + *output_r = buf; + return 0; +} + static void settings_parse_strlist(struct setting_parser_context *ctx, ARRAY_TYPE(const_string) *array, @@ -374,6 +443,38 @@ const char *const *settings_boollist_get(const ARRAY_TYPE(const_string) *array) } +void settings_file_get(const char *value, pool_t path_pool, + struct settings_file *file_r) +{ + const char *p; + + if (*value == '\0') { + file_r->path = ""; + file_r->content = ""; + return; + } + + p = strchr(value, '\n'); + if (p == NULL) + i_panic("Settings file value is missing LF"); + file_r->path = p_strdup_until(path_pool, value, p); + file_r->content = p + 1; +} + +const char *settings_file_get_value(pool_t pool, + const struct settings_file *file) +{ + const char *path = file->path != NULL ? file->path : ""; + size_t path_len = strlen(path); + size_t content_len = strlen(file->content); + + char *value = p_malloc(pool, path_len + 1 + content_len + 1); + memcpy(value, path, path_len); + value[path_len] = '\n'; + memcpy(value + path_len + 1, file->content, content_len); + return value; +} + static void boollist_null_terminate(ARRAY_TYPE(const_string) *array) { array_append_zero(array); @@ -515,6 +616,18 @@ settings_parse(struct setting_parser_context *ctx, value = p_strdup(ctx->set_pool, value); *((const char **)ptr) = value; break; + case SET_FILE: { + /* Read the file directly to get the content */ + if (get_file(ctx, dup_value, &value) < 0) { + /* We may be running settings_check()s in doveconf at a + time when the file couldn't yet be opened. To avoid + unnecessary errors, set the value unknown. */ + *((const char **)ptr) = set_value_unknown; + return -1; + } + *((const char **)ptr) = value; + break; + } case SET_ENUM: /* get the available values from default string */ i_assert(ctx->info->defaults != NULL); diff --git a/src/lib-settings/settings-parser.h b/src/lib-settings/settings-parser.h index b2488fee53..71f62f4734 100644 --- a/src/lib-settings/settings-parser.h +++ b/src/lib-settings/settings-parser.h @@ -30,6 +30,7 @@ enum setting_type { SET_STR, /* string with %variables */ SET_STR_NOVARS, /* string explicitly without %variables */ SET_ENUM, + SET_FILE, /* string: [ file contents] */ SET_STRLIST, /* of type ARRAY_TYPE(const_string) */ SET_BOOLLIST, /* of type ARRAY_TYPE(const_string) - guaranteed NULL-terminted */ SET_ALIAS, /* alias name for above setting definition */ @@ -85,6 +86,8 @@ struct setting_define { SETTING_DEFINE_STRUCT_TYPE(SET_STR_NOVARS, 0, const char *, key, name, struct_name) #define SETTING_DEFINE_STRUCT_ENUM(key, name, struct_name) \ SETTING_DEFINE_STRUCT_TYPE(SET_ENUM, 0, const char *, key, name, struct_name) +#define SETTING_DEFINE_STRUCT_FILE(key, name, struct_name) \ + SETTING_DEFINE_STRUCT_TYPE(SET_FILE, 0, const char *, key, name, struct_name) #define SETTING_DEFINE_STRUCT_BOOLLIST(key, name, struct_name) \ SETTING_DEFINE_STRUCT_TYPE(SET_BOOLLIST, 0, ARRAY_TYPE(const_string), key, name, struct_name) #define SETTING_DEFINE_STRUCT_STRLIST(key, name, struct_name) \ @@ -110,11 +113,21 @@ struct setting_define { SETTING_DEFINE_STRUCT_TYPE(SET_STR_NOVARS, SET_FLAG_HIDDEN, const char *, key, name, struct_name) #define SETTING_DEFINE_STRUCT_ENUM_HIDDEN(key, name, struct_name) \ SETTING_DEFINE_STRUCT_TYPE(SET_ENUM, SET_FLAG_HIDDEN, const char *, key, name, struct_name) +#define SETTING_DEFINE_STRUCT_FILE_HIDDEN(key, name, struct_name) \ + SETTING_DEFINE_STRUCT_TYPE(SET_FILE, SET_FLAG_HIDDEN, const char *, key, name, struct_name) #define SETTING_DEFINE_STRUCT_BOOLLIST_HIDDEN(key, name, struct_name) \ SETTING_DEFINE_STRUCT_TYPE(SET_BOOLLIST, SET_FLAG_HIDDEN, ARRAY_TYPE(const_string), key, name, struct_name) #define SETTING_DEFINE_STRUCT_STRLIST_HIDDEN(key, name, struct_name) \ SETTING_DEFINE_STRUCT_TYPE(SET_STRLIST, SET_FLAG_HIDDEN, ARRAY_TYPE(const_string), key, name, struct_name) +struct settings_file { + /* Path to the file. May be "" if the content is inlined. */ + const char *path; + /* File contents - always available. NULs inside the file are not + supported. */ + const char *content; +}; + struct setting_keyvalue { const char *key; const char *value; @@ -219,6 +232,11 @@ bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool, bool settings_check(struct event *event, const struct setting_parser_info *info, pool_t pool, void *set, const char **error_r); +/* Read a SET_FILE from the given path and write "value_path\ncontents" to + output_r. Returns 0 on success, -1 on error. */ +int settings_parse_read_file(const char *path, const char *value_path, + pool_t pool, + const char **output_r, const char **error_r); int settings_parse_boollist_string(const char *value, pool_t pool, ARRAY_TYPE(const_string) *dest, const char **error_r); @@ -228,6 +246,15 @@ int settings_parse_boollist_string(const char *value, pool_t pool, checks to try to make sure it's used only for boollists. */ const char *const *settings_boollist_get(const ARRAY_TYPE(const_string) *array); +/* Split the settings value into path and content. The path is allocated from + the path_pool, while content points directly to the value string. */ +void settings_file_get(const char *value, pool_t path_pool, + struct settings_file *file_r); +/* Convert settings_file into a value (path LF content). The file path may be + NULL, but the content must exist. */ +const char *settings_file_get_value(pool_t pool, + const struct settings_file *file); + /* Return section name escaped */ const char *settings_section_escape(const char *name); const char *settings_section_unescape(const char *name); diff --git a/src/lib-settings/settings.c b/src/lib-settings/settings.c index 15dcf185f2..7826eacbc4 100644 --- a/src/lib-settings/settings.c +++ b/src/lib-settings/settings.c @@ -500,6 +500,7 @@ settings_mmap_apply_key(struct settings_apply_ctx *ctx, unsigned int key_idx, const char *list_key, const char *value, const char **error_r) { + struct settings_file file; const char *key = ctx->info->defines[key_idx].key; const char *orig_value = value; if (list_key != NULL) @@ -526,6 +527,11 @@ settings_mmap_apply_key(struct settings_apply_ctx *ctx, unsigned int key_idx, ctx->info->defines[key_idx].type != SET_FILTER_ARRAY) { const char *error; str_truncate(ctx->str, 0); + if (ctx->info->defines[key_idx].type == SET_FILE) { + settings_file_get(value, &ctx->mpool->pool, &file); + /* Make sure only the file path is var-expanded. */ + value = file.path; + } if (var_expand_with_arrays(ctx->str, value, ctx->tables, ctx->func_tables, ctx->func_contexts, &error) <= 0 && @@ -535,7 +541,16 @@ settings_mmap_apply_key(struct settings_apply_ctx *ctx, unsigned int key_idx, key, error); return -1; } - if (strcmp(value, str_c(ctx->str)) != 0) + if (strcmp(value, str_c(ctx->str)) == 0) { + /* unchanged value */ + if (ctx->info->defines[key_idx].type == SET_FILE) { + /* Restore full SET_FILE value */ + value = settings_file_get_value(&ctx->mpool->pool, &file); + } + } else if (ctx->info->defines[key_idx].type == SET_FILE) { + file.path = p_strdup(&ctx->mpool->pool, str_c(ctx->str)); + value = settings_file_get_value(&ctx->mpool->pool, &file); + } else value = p_strdup(&ctx->mpool->pool, str_c(ctx->str)); } /* value points to mmap()ed memory, which is kept