/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
#include "lib.h"
+#include "array.h"
#include "str.h"
#include "strescape.h"
#include "wildcard-match.h"
<NUL-terminated string: error string - if client attempts to access this
settings block, it must fail with this error.
NUL = no error, followed by settings>
+ <32bit big-endian: include group count>
+ Repeat for "include group count":
+ <NUL-terminated string: group label>
+ <NUL-terminated string: group name>
Repeat until "filter settings size" is reached:
<32bit big-endian: key index number>
[+|$ <strlist/boollist key>]
Repeat for "filter count":
<64bit big-endian: filter settings offset>
<trailing safety NUL>
+
+ The order of filters is important in the output. lib-settings applies the
+ settings in the same order. The applying is done in reverse order, so last
+ filter is applied first. Note that lib-settings uses the value from the
+ first matching filter and won't change it afterwards. (This is especially
+ important because we want to avoid expanding %variables multiple times for
+ the same setting. And what to do then if the expansion fails? A later value
+ expansion could still work. This is avoided by doing the expansion always
+ just once.)
+
+ The filters are written in the same order as they are defined in the config
+ file. This automatically causes the more specific filters to be written
+ after the less specific ones.
+
+ Groups
+ ------
+
+ The group definition order is different from group include order. They can't
+ be the same, because the same groups can be included from different places,
+ and also the groups can be changed by -o / userdb overrides.
+
+ The group definitions are placed all at the end of the written filters. When
+ the parsing code sees a filter that includes groups, it immediately
+ processes all the group filters and applies any matches. This is needed,
+ because group includes can exist hierarchically so that the most specific
+ (innermost filter) includes are fully applied before the less epecific
+ (outermost filter / global) includes. So if there is e.g. a global
+ @group=foo and namespace { @group=bar } which both modify the same setting,
+ the @group=bar must be applied first to get the expected value. If the same
+ filter has multiple group includes, their include order doesn't matter much,
+ but the behavior should be consistent.
*/
struct dump_context {
const struct setting_parser_info *info;
+ const ARRAY_TYPE(config_include_group) *include_groups;
const struct config_filter *filter;
unsigned int filter_idx;
bool filter_written;
const char *p = strchr(filter->filter_name, '/');
i_assert(p != NULL);
const char *filter_key = t_strdup_until(filter->filter_name, p);
+ bool group = filter_key[0] == SETTINGS_INCLUDE_GROUP_PREFIX;
if (strcmp(filter_key, SETTINGS_EVENT_MAILBOX_NAME_WITH_PREFIX) == 0)
filter_key = SETTINGS_EVENT_MAILBOX_NAME_WITHOUT_PREFIX;
- str_printfa(str, "(%s=\"%s\"", filter_key, str_escape(p + 1));
+ if (!group) {
+ str_printfa(str, "(%s=\"%s\" OR ",
+ filter_key, str_escape(p + 1));
+ }
/* the filter_name is used by settings_get_filter() for
finding a specific filter without wildcards messing
up the lookups. */
- str_printfa(str, " OR "SETTINGS_EVENT_FILTER_NAME
- "=\"%s/%s\")", filter_key,
+ str_printfa(str, SETTINGS_EVENT_FILTER_NAME
+ "=\"%s/%s\"", filter_key,
wildcard_str_escape(settings_section_escape(p + 1)));
+ if (!group)
+ str_append_c(str, ')');
str_append(str, " AND ");
} else if (filter->filter_name != NULL) {
const char *filter_name = filter->filter_name;
if (ctx->filter != NULL)
config_dump_full_append_filter(str, ctx->filter, TRUE);
str_append_c(str, '\n');
+
+ if (ctx->include_groups != NULL) {
+ const struct config_include_group *group;
+ array_foreach(ctx->include_groups, group) {
+ str_printfa(str, ":INCLUDE @%s %s\n",
+ group->label, group->name);
+ }
+ }
o_stream_nsend(ctx->output, str_data(str), str_len(str));
}
} T_END;
}
+static void config_include_groups_dump(struct dump_context *ctx)
+{
+ uint32_t include_count_be32 = 0;
+ if (ctx->include_groups == NULL) {
+ o_stream_nsend(ctx->output, &include_count_be32,
+ sizeof(include_count_be32));
+ } else {
+ include_count_be32 = cpu32_to_be(array_count(ctx->include_groups));
+ o_stream_nsend(ctx->output, &include_count_be32,
+ sizeof(include_count_be32));
+
+ const struct config_include_group *group;
+ array_foreach(ctx->include_groups, group) {
+ o_stream_nsend(ctx->output, group->label,
+ strlen(group->label) + 1);
+ o_stream_nsend(ctx->output, group->name,
+ strlen(group->name) + 1);
+ }
+ }
+}
+
static void config_dump_full_write_filter(struct dump_context *ctx)
{
if (ctx->filter_written)
/* Start by assuming there is no error. If there is, the error
handling code path truncates the file and writes the error. */
o_stream_nsend(ctx->output, "", 1);
+
+ config_include_groups_dump(ctx);
}
static void config_dump_full_callback(const struct config_export_setting *set,
}
size_t error_len = strlen(error) + 1;
- uint64_t blob_size = cpu64_to_be(error_len);
+ uint64_t blob_size = cpu64_to_be(error_len + 4);
o_stream_nsend(output, &blob_size, sizeof(blob_size));
o_stream_nsend(output, error, error_len);
+ uint32_t include_group_count = 0;
+ o_stream_nsend(output, &include_group_count,
+ sizeof(include_group_count));
dump_ctx->filter_written = TRUE;
return 0;
}
struct config_dump_full_context {
+ struct config_parsed *config;
struct ostream *output;
enum config_dump_full_dest dest;
uint64_t *filter_offsets_be64;
};
+enum config_dump_type {
+ CONFIG_DUMP_TYPE_DEFAULTS,
+ CONFIG_DUMP_TYPE_EXPLICIT,
+ CONFIG_DUMP_TYPE_GROUPS,
+};
+
+static bool filter_is_group(const struct config_filter *filter)
+{
+ for (; filter != NULL; filter = filter->parent) {
+ if (filter->filter_name_array &&
+ filter->filter_name[0] == SETTINGS_INCLUDE_GROUP_PREFIX)
+ return TRUE;
+ }
+ return FALSE;
+}
+
static int
config_dump_full_sections(struct config_dump_full_context *ctx,
unsigned int parser_idx,
const struct setting_parser_info *info,
const string_t *delayed_filter,
- bool dump_defaults)
+ enum config_dump_type dump_type)
{
struct ostream *output = ctx->output;
enum config_dump_full_dest dest = ctx->dest;
.output = output,
.info = info,
};
+ ARRAY_TYPE(config_include_group) groups;
+ t_array_init(&groups, 8);
for (unsigned int i = 1; ctx->filters[i] != NULL && ret == 0; i++) {
const struct config_filter_parser *filter = ctx->filters[i];
uoff_t start_offset = output->offset;
- if (filter->filter.default_settings != dump_defaults)
- continue;
- if (filter->module_parsers[parser_idx].settings == NULL &&
- filter->module_parsers[parser_idx].delayed_error == NULL)
+ if (filter_is_group(&filter->filter)) {
+ /* This is a group filter. Are we dumping groups?
+ Handle default groups the same as non-default
+ groups. */
+ if (dump_type != CONFIG_DUMP_TYPE_GROUPS)
+ continue;
+ } else {
+ /* This is not a group filter. */
+ switch (dump_type) {
+ case CONFIG_DUMP_TYPE_DEFAULTS:
+ if (!filter->filter.default_settings)
+ continue;
+ break;
+ case CONFIG_DUMP_TYPE_EXPLICIT:
+ if (filter->filter.default_settings)
+ continue;
+ break;
+ case CONFIG_DUMP_TYPE_GROUPS:
+ continue;
+ }
+ }
+
+ if (config_parsed_get_includes(ctx->config, filter,
+ parser_idx, &groups)) {
+ dump_ctx.include_groups = &groups;
+ } else if (filter->module_parsers[parser_idx].settings == NULL &&
+ filter->module_parsers[parser_idx].delayed_error == NULL) {
+ /* nothing to export in this filter */
continue;
+ } else {
+ dump_ctx.include_groups = NULL;
+ }
dump_ctx.filter = &filter->filter;
dump_ctx.filter_idx = i;
const char *error;
ret = config_export_parser(export_ctx, parser_idx, &error);
+ if (ret == 0 && dump_ctx.include_groups != NULL) {
+ if (dest == CONFIG_DUMP_FULL_DEST_STDOUT)
+ config_dump_full_stdout_write_filter(&dump_ctx);
+ else
+ config_dump_full_write_filter(&dump_ctx);
+ }
if (ret < 0) {
/* Delay the failure until the filter is accessed by
the config client. The error is written to the
ctx->filter_offsets_be64[ctx->filter_output_count] =
cpu64_to_be(output->offset);
- uint64_t blob_size = cpu64_to_be(1 + str_len(delayed_filter));
+ uint64_t blob_size = cpu64_to_be(5 + str_len(delayed_filter));
o_stream_nsend(output, &blob_size, sizeof(blob_size));
o_stream_nsend(output, "", 1); /* no error */
+ uint32_t include_group_count = 0;
+ o_stream_nsend(output, &include_group_count,
+ sizeof(include_group_count));
o_stream_nsend(output, str_data(delayed_filter),
str_len(delayed_filter));
ctx->filter_output_count++;
}
struct config_dump_full_context ctx = {
+ .config = config,
.output = output,
.dest = dest,
.filters = config_parsed_get_filter_parsers(config),
ctx.filter_indexes_be32 = t_new(uint32_t, max_filter_count);
ctx.filter_offsets_be64 = t_new(uint64_t, max_filter_count);
+ ARRAY_TYPE(config_include_group) groups;
+ t_array_init(&groups, 8);
+
unsigned int i, parser_count =
config_export_get_parser_count(export_ctx);
for (i = 0; i < parser_count; i++) {
sizeof(filter_count));
}
- /* Write default settings filters */
+ /* 1. Write built-in default settings */
int ret;
T_BEGIN {
- ret = config_dump_full_sections(&ctx, i, info, NULL, TRUE);
+ ret = config_dump_full_sections(&ctx, i, info, NULL,
+ CONFIG_DUMP_TYPE_DEFAULTS);
} T_END;
if (ret < 0)
break;
uoff_t blob_size_offset = output->offset;
- /* Write base settings - add it as an empty filter */
+ /* 2. Write global settings in config - use an empty filter */
ctx.filter_indexes_be32[ctx.filter_output_count] = 0;
ctx.filter_offsets_be64[ctx.filter_output_count] =
cpu64_to_be(blob_size_offset);
ctx.filter_output_count++;
+ if (config_parsed_get_includes(config, filter_parser,
+ i, &groups))
+ dump_ctx.include_groups = &groups;
+ else
+ dump_ctx.include_groups = NULL;
+
if (dest != CONFIG_DUMP_FULL_DEST_STDOUT) {
- /* Write a filter for the base settings, even if there
+ /* Write a filter for the global settings, even if there
are no settings. This allows lib-settings to apply
setting overrides at the proper position before
defaults. */
the error handling code path truncates the file
and writes the error. */
o_stream_nsend(output, "", 1);
+ config_include_groups_dump(&dump_ctx);
dump_ctx.filter_written = TRUE;
} else {
/* Make :FILTER visible */
blob_size_offset, error) < 0)
break;
}
+ if (dump_ctx.include_groups != NULL) {
+ if (dest == CONFIG_DUMP_FULL_DEST_STDOUT)
+ config_dump_full_stdout_write_filter(&dump_ctx);
+ else
+ config_dump_full_write_filter(&dump_ctx);
+ }
if (dest != CONFIG_DUMP_FULL_DEST_STDOUT) {
if (output_blob_size(output, blob_size_offset) < 0)
break;
}
- /* Write non-default settings filters */
+ /* 3. Write filter settings in config */
+ T_BEGIN {
+ ret = config_dump_full_sections(&ctx, i, info,
+ dump_ctx.delayed_output,
+ CONFIG_DUMP_TYPE_EXPLICIT);
+ } T_END;
+ if (ret < 0)
+ break;
+
+ /* 4. Write group filters */
T_BEGIN {
ret = config_dump_full_sections(&ctx, i, info,
- dump_ctx.delayed_output, FALSE);
+ dump_ctx.delayed_output,
+ CONFIG_DUMP_TYPE_GROUPS);
} T_END;
if (ret < 0)
break;
bool default_settings;
};
+struct config_include_group {
+ const char *label;
+ const char *name;
+};
+ARRAY_DEFINE_TYPE(config_include_group, struct config_include_group);
+
/* Each unique config_filter (including its parents in hierarchy) has its own
config_filter_parser. */
struct config_filter_parser {
struct config_filter_parser *parent;
struct config_filter_parser *children_head, *children_tail, *prev, *next;
+ /* When this filter is used, it includes settings from these groups. */
+ ARRAY_TYPE(config_include_group) include_groups;
/* Filter for this parser. Its parent filters must also match. */
struct config_filter filter;
/* NULL-terminated array of parsers for settings. All parsers have the
CONFIG_LINE_TYPE_SECTION_BEGIN,
/* } (key = "}", value = "") */
CONFIG_LINE_TYPE_SECTION_END,
+ /* group @key value { */
+ CONFIG_LINE_TYPE_GROUP_SECTION_BEGIN,
/* !include value (key = "!include") */
CONFIG_LINE_TYPE_INCLUDE,
/* !include_try value (key = "!include_try") */
unsigned int define_idx;
};
+struct config_include_group_filters {
+ const char *label;
+ ARRAY(struct config_filter_parser *) filters;
+};
+
struct config_parsed {
pool_t pool;
const char *dovecot_config_version;
struct config_module_parser *module_parsers;
ARRAY_TYPE(const_string) errors;
HASH_TABLE(const char *, const struct setting_define *) key_hash;
+ HASH_TABLE(const char *, struct config_include_group_filters *) include_groups;
};
ARRAY_DEFINE_TYPE(setting_parser_info_p, const struct setting_parser_info *);
return 0;
}
+static bool config_filter_has_include_group(const struct config_filter *filter)
+{
+ for (; filter != NULL; filter = filter->parent) {
+ if (filter->filter_name_array &&
+ filter->filter_name[0] == SETTINGS_INCLUDE_GROUP_PREFIX)
+ return TRUE;
+ }
+ return FALSE;
+}
+
static int
settings_value_check(struct config_parser_context *ctx,
const struct setting_parser_info *info,
return TRUE;
}
+static const char *filter_key_skip_group_prefix(const char *key)
+{
+ return key[0] == SETTINGS_INCLUDE_GROUP_PREFIX ? key + 1 : key;
+}
+
static int
config_apply_line_full(struct config_parser_context *ctx,
const struct config_line *line,
ctx->cur_section->filter_parser->filter.filter_name_array) {
/* For named list filters, try filter name { key } ->
filter_name_key first before anything else. */
- const char *filter_key =
- t_str_replace(ctx->cur_section->filter_parser->filter.filter_name, '/', '_');
+ const char *filter_key = filter_key_skip_group_prefix(
+ t_str_replace(ctx->cur_section->filter_parser->filter.filter_name, '/', '_'));
const char *key2 = t_strdup_printf("%s_%s", filter_key, key);
struct config_filter_parser *last_filter_parser =
ctx->cur_section->filter_parser;
/* first try the filter name-specific prefix, so e.g.
inet_listener { ssl=yes } won't try to change the global
ssl setting. */
- const char *filter_key =
- t_strcut(ctx->cur_section->filter_parser->filter.filter_name, '/');
+ const char *filter_key = filter_key_skip_group_prefix(
+ t_strcut(ctx->cur_section->filter_parser->filter.filter_name, '/'));
const char *key2 = t_strdup_printf("%s_%s", filter_key, key);
struct config_filter_parser *last_filter_parser =
ctx->cur_section->filter_parser;
i_zero(&filter);
filter.parent = parent;
- if (strcmp(key, "protocol") == 0) {
+ if (key[0] == SETTINGS_INCLUDE_GROUP_PREFIX) {
+ if (!config_filter_is_empty(parent)) {
+ ctx->error = "groups must defined at top-level, not under filters";
+ return TRUE;
+ }
+ filter.filter_name =
+ p_strdup_printf(ctx->pool, "%s/%s", key, value);
+ filter.filter_name_array = TRUE;
+ } else if (strcmp(key, "protocol") == 0) {
if (parent->service != NULL)
ctx->error = "Nested protocol { protocol { .. } } block not allowed";
else if (parent->filter_name != NULL)
/* b) + errors */
line[-1] = '\0';
- if (*line == '{')
+ if (*line == '{') {
config_line_r->value = "";
- else {
+ config_line_r->type = CONFIG_LINE_TYPE_SECTION_BEGIN;
+ } else if (strcmp(key, "group") == 0) {
+ /* group @group name { */
+ config_line_r->key = line;
+ while (!IS_WHITE(*line) && *line != '\0')
+ line++;
+ if (*line == '\0') {
+ config_line_r->value = "Expecting group name";
+ config_line_r->type = CONFIG_LINE_TYPE_ERROR;
+ return;
+ }
+ *line++ = '\0';
+ while (IS_WHITE(*line))
+ line++;
+
+ config_line_r->value = line;
+ while (!IS_WHITE(*line) && *line != '\0')
+ line++;
+ if (*line == '\0') {
+ config_line_r->value = "Expecting '{'";
+ config_line_r->type = CONFIG_LINE_TYPE_ERROR;
+ return;
+ }
+ *line++ = '\0';
+ while (IS_WHITE(*line))
+ line++;
+ if (*line != '{') {
+ config_line_r->value = "Expecting '{'";
+ config_line_r->type = CONFIG_LINE_TYPE_ERROR;
+ return;
+ }
+
+ config_line_r->type = CONFIG_LINE_TYPE_GROUP_SECTION_BEGIN;
+ } else {
/* get section name */
if (*line != '"') {
config_line_r->value = line;
config_line_r->type = CONFIG_LINE_TYPE_ERROR;
return;
}
+ config_line_r->type = CONFIG_LINE_TYPE_SECTION_BEGIN;
}
if (line[1] != '\0') {
config_line_r->value = "Garbage after '{'";
config_line_r->type = CONFIG_LINE_TYPE_ERROR;
- return;
}
- config_line_r->type = CONFIG_LINE_TYPE_SECTION_BEGIN;
+}
+
+static void config_parse_finish_includes(struct config_parsed *config)
+{
+ hash_table_create(&config->include_groups, config->pool, 0,
+ str_hash, strcmp);
+
+ for (unsigned int i = 0; config->filter_parsers[i] != NULL; i++) {
+ struct config_filter_parser *filter = config->filter_parsers[i];
+
+ if (!filter->filter.filter_name_array ||
+ filter->filter.filter_name[0] != SETTINGS_INCLUDE_GROUP_PREFIX)
+ continue;
+
+ /* This is a group filter's root (which may have child
+ filters) */
+ T_BEGIN {
+ const char *include_group =
+ t_strcut(filter->filter.filter_name + 1, '/');
+ struct config_include_group_filters *group =
+ hash_table_lookup(config->include_groups,
+ include_group);
+ if (group == NULL) {
+ group = p_new(config->pool,
+ struct config_include_group_filters, 1);
+ group->label = p_strdup(config->pool,
+ include_group);
+ p_array_init(&group->filters, config->pool, 4);
+ hash_table_insert(config->include_groups,
+ group->label, group);
+ }
+ array_push_back(&group->filters, &filter);
+ } T_END;
+ }
}
static int
new_config->filter_parsers = array_front(&ctx->all_filter_parsers);
new_config->module_parsers = ctx->root_module_parsers;
+ config_parse_finish_includes(new_config);
+
if (ret < 0)
;
else if ((ret = config_all_parsers_check(ctx, new_config, &error)) < 0) {
return TRUE;
}
+static void
+config_parser_include_add_or_update(struct config_parser_context *ctx,
+ const char *group, const char *name)
+{
+ struct config_filter_parser *filter_parser =
+ ctx->cur_section->filter_parser;
+ struct config_include_group *include_group;
+
+ if (!array_is_created(&filter_parser->include_groups))
+ p_array_init(&filter_parser->include_groups, ctx->pool, 4);
+ array_foreach_modifiable(&filter_parser->include_groups, include_group) {
+ if (strcmp(include_group->label, group) == 0) {
+ /* preserve original position */
+ include_group->name = p_strdup(ctx->pool, name);
+ return;
+ }
+ }
+ include_group = array_append_space(&filter_parser->include_groups);
+ include_group->label = p_strdup(ctx->pool, group);
+ include_group->name = p_strdup(ctx->pool, name);
+}
+
void config_parser_apply_line(struct config_parser_context *ctx,
const struct config_line *line)
{
if (config_write_value(ctx, line) < 0) {
if (config_apply_error(ctx, line->key) < 0)
break;
+ } else if (line->key[0] == SETTINGS_INCLUDE_GROUP_PREFIX) {
+ if (config_filter_has_include_group(&ctx->cur_section->filter_parser->filter)) {
+ ctx->error = "Recursive include groups not allowed";
+ break;
+ }
+ config_parser_include_add_or_update(ctx, line->key + 1,
+ str_c(ctx->value));
} else {
/* Either a global key or list/key */
const char *key_with_path =
}
}
break;
+ case CONFIG_LINE_TYPE_GROUP_SECTION_BEGIN:
+ ctx->cur_section = config_add_new_section(ctx);
+ ctx->cur_section->key = "group";
+
+ (void)config_filter_add_new_filter(ctx, line->key, line->value,
+ FALSE);
+ break;
case CONFIG_LINE_TYPE_SECTION_BEGIN: {
/* See if we need to prefix the key with filter name */
const struct config_filter *cur_filter =
return hash_table_lookup(config->key_hash, key);
}
+static bool config_filter_tree_has_settings(struct config_filter_parser *filter,
+ unsigned int parser_idx)
+{
+ if (filter->module_parsers[parser_idx].settings != NULL)
+ return TRUE;
+ for (filter = filter->children_head; filter != NULL; filter = filter->next) {
+ if (config_filter_tree_has_settings(filter, parser_idx))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+config_include_group_filters_have_settings(
+ struct config_include_group_filters *group_filters,
+ unsigned int parser_idx)
+{
+ struct config_filter_parser *group_filter;
+
+ /* See if this group modifies the wanted parser. Check the group's
+ root filter and all of its child filters. For example
+ group @foo bar { namespace inbox { separator=/ } } needs to
+ returns TRUE for namespace parser, which is modified in the child
+ namespace filter. */
+ array_foreach_elem(&group_filters->filters, group_filter) {
+ if (config_filter_tree_has_settings(group_filter, parser_idx))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool config_parsed_get_includes(struct config_parsed *config,
+ const struct config_filter_parser *filter,
+ unsigned int parser_idx,
+ ARRAY_TYPE(config_include_group) *groups)
+{
+ array_clear(groups);
+
+ if (!array_is_created(&filter->include_groups))
+ return FALSE;
+
+ const struct config_include_group *group;
+ array_foreach(&filter->include_groups, group) {
+ struct config_include_group_filters *group_filters =
+ hash_table_lookup(config->include_groups, group->label);
+ if (group_filters == NULL)
+ continue;
+
+ if (config_include_group_filters_have_settings(group_filters, parser_idx))
+ array_push_back(groups, group);
+ }
+ return array_count(groups) > 0;
+}
+
void config_parsed_free(struct config_parsed **_config)
{
struct config_parsed *config = *_config;
return;
*_config = NULL;
+ hash_table_destroy(&config->include_groups);
hash_table_destroy(&config->key_hash);
pool_unref(&config->pool);
}
#ifndef CONFIG_PARSER_H
#define CONFIG_PARSER_H
+#include "config-filter.h"
+
#define CONFIG_MODULE_DIR MODULEDIR"/settings"
#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')
/* Lookup setting with the specified key. */
const struct setting_define *
config_parsed_key_lookup(struct config_parsed *config, const char *key);
+/* Get the list of filter's include groups that have any settings in the given
+ module parser index. Returns TRUE if any groups were returned. */
+bool config_parsed_get_includes(struct config_parsed *config,
+ const struct config_filter_parser *filter,
+ unsigned int parser_idx,
+ ARRAY_TYPE(config_include_group) *groups);
+
void config_parsed_free(struct config_parsed **config);
void config_parse_load_modules(void);
#include "str.h"
#include "strescape.h"
#include "settings-parser.h"
+#include "settings.h"
#include "master-interface.h"
#include "master-service.h"
#include "all-settings.h"
return sc;
}
+static bool
+config_dump_human_include_group(struct config_filter_parser *filter_parser,
+ struct ostream *output,
+ const string_t *list_prefix,
+ unsigned int indent)
+{
+ const struct config_include_group *group;
+
+ if (array_is_empty(&filter_parser->include_groups))
+ return FALSE;
+
+ if (list_prefix != NULL) {
+ o_stream_nsend(output, str_data(list_prefix),
+ str_len(list_prefix));
+ }
+ array_foreach(&filter_parser->include_groups, group) {
+ o_stream_nsend(output, indent_str, indent*2);
+ o_stream_nsend_str(output, t_strdup_printf(
+ "@%s = %s\n", group->label, group->name));
+ }
+ return TRUE;
+}
+
static struct config_dump_human_context *
config_dump_human_init(enum config_dump_scope scope,
struct config_filter_parser *filter_parser)
str_printfa(str, "%s {\n", filter->filter_name);
else {
/* SET_FILTER_ARRAY */
+ if (filter->filter_name[0] == SETTINGS_INCLUDE_GROUP_PREFIX)
+ str_append(str, "group ");
str_printfa(str, "%s %s {\n",
t_strdup_until(filter->filter_name, p),
filter_name_escaped(p+1));
strip_prefix, strip_prefix2);
bool sub_list_prefix_sent = ctx->list_prefix_sent;
+ if (set_name_filter == NULL) {
+ if (config_dump_human_include_group(filter_parser, output,
+ sub_list_prefix_sent ? NULL :
+ list_prefix, sub_indent))
+ sub_list_prefix_sent = TRUE;
+ }
if (sub_list_prefix_sent) {
*list_prefix_sent = TRUE;
str_truncate(list_prefix, 0);
list_prefix, &list_prefix_sent,
hide_key, hide_passwords);
+ if (setting_name_filter == NULL)
+ config_dump_human_include_group(filter_parser, output, NULL, 0);
if (hide_key && output->offset == 0)
o_stream_nsend(output, "\n", 1);
/* flush output before writing errors */
case CONFIG_LINE_TYPE_INCLUDE:
case CONFIG_LINE_TYPE_INCLUDE_TRY:
case CONFIG_LINE_TYPE_KEYVARIABLE:
+ case CONFIG_LINE_TYPE_GROUP_SECTION_BEGIN:
break;
case CONFIG_LINE_TYPE_KEYFILE:
case CONFIG_LINE_TYPE_KEYVALUE:
"\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
"\x00"), // safety NUL
"'filter error string' points outside area" },
+ /* include group count is truncated */
+ { DATA("DOVECOT-CONFIG\t1.0\n"
+ "\x00\x00\x00\x00\x00\x00\x00\x40" // full size
+ "\x00\x00\x00\x01" // event filter count
+ "\x00" // event filter[0]
+ "\x00" // override event filter[0]
+ "\x00\x00\x00\x00\x00\x00\x00\x32" // block size
+ "master_service\x00" // block name
+ "\x00\x00\x00\x01" // settings count
+ "K\x00" // setting[0] key
+ "\x00\x00\x00\x01" // filter count
+ "\x00\x00\x00\x00\x00\x00\x00\x04" // filter settings size
+ "\x00" // filter error string
+ "\x00\x00\x00" // include group count
+ "\x00\x00\x00\x00" // event filter index
+ "\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
+ "\x00"), // safety NUL
+ "Area too small when reading uint of 'include group count'" },
+ /* include group count is too large */
+ { DATA("DOVECOT-CONFIG\t1.0\n"
+ "\x00\x00\x00\x00\x00\x00\x00\x41" // full size
+ "\x00\x00\x00\x01" // event filter count
+ "\x00" // event filter[0]
+ "\x00" // override event filter[0]
+ "\x00\x00\x00\x00\x00\x00\x00\x33" // block size
+ "master_service\x00" // block name
+ "\x00\x00\x00\x01" // settings count
+ "K\x00" // setting[0] key
+ "\x00\x00\x00\x01" // filter count
+ "\x00\x00\x00\x00\x00\x00\x00\x05" // filter settings size
+ "\x00" // filter error string
+ "\x00\x00\x00\x01" // include group count
+ "\x00\x00\x00\x00" // event filter index
+ "\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
+ "\x00"), // safety NUL
+ "'group label string' points outside area" },
+ /* group label not NUL-terminated */
+ { DATA("DOVECOT-CONFIG\t1.0\n"
+ "\x00\x00\x00\x00\x00\x00\x00\x42" // full size
+ "\x00\x00\x00\x01" // event filter count
+ "\x00" // event filter[0]
+ "\x00" // override event filter[0]
+ "\x00\x00\x00\x00\x00\x00\x00\x34" // block size
+ "master_service\x00" // block name
+ "\x00\x00\x00\x01" // settings count
+ "K\x00" // setting[0] key
+ "\x00\x00\x00\x01" // filter count
+ "\x00\x00\x00\x00\x00\x00\x00\x06" // filter settings size
+ "\x00" // filter error string
+ "\x00\x00\x00\x01" // include group count
+ "G" // group label
+ "\x00\x00\x00\x00" // event filter index
+ "\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
+ "\x00"), // safety NUL
+ "'group label string' points outside area" },
+ /* group name not NUL-terminated */
+ { DATA("DOVECOT-CONFIG\t1.0\n"
+ "\x00\x00\x00\x00\x00\x00\x00\x44" // full size
+ "\x00\x00\x00\x01" // event filter count
+ "\x00" // event filter[0]
+ "\x00" // override event filter[0]
+ "\x00\x00\x00\x00\x00\x00\x00\x36" // block size
+ "master_service\x00" // block name
+ "\x00\x00\x00\x01" // settings count
+ "K\x00" // setting[0] key
+ "\x00\x00\x00\x01" // filter count
+ "\x00\x00\x00\x00\x00\x00\x00\x08" // filter settings size
+ "\x00" // filter error string
+ "\x00\x00\x00\x01" // include group count
+ "G\x00" // group label
+ "N" // group name
+ "\x00\x00\x00\x00" // event filter index
+ "\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
+ "\x00"), // safety NUL
+ "'group name string' points outside area" },
/* invalid filter string */
{ DATA("DOVECOT-CONFIG\t1.0\n"
- "\x00\x00\x00\x00\x00\x00\x00\x32" // full size
+ "\x00\x00\x00\x00\x00\x00\x00\x36" // full size
"\x00\x00\x00\x01" // event filter count
"F\x00" // event filter[0]
"F\x00" // override event filter[0]
- "\x00\x00\x00\x00\x00\x00\x00\x22" // block size
+ "\x00\x00\x00\x00\x00\x00\x00\x26" // block size
"N\x00" // block name
"\x00\x00\x00\x01" // settings count
"K\x00" // setting[0] key
"\x00\x00\x00\x01" // filter count
- "\x00\x00\x00\x00\x00\x00\x00\x01" // filter settings size
+ "\x00\x00\x00\x00\x00\x00\x00\x05" // filter settings size
"\x00" // filter error string
+ "\x00\x00\x00\x00" // include group count
"\x00\x00\x00\x00" // event filter index
"\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
"\x00"), // safety NUL
/* Duplicate block name */
{ DATA("DOVECOT-CONFIG\t1.0\n"
- "\x00\x00\x00\x00\x00\x00\x00\x3A" // full size
+ "\x00\x00\x00\x00\x00\x00\x00\x3E" // full size
"\x00\x00\x00\x01" // event filter count
"\x00" // event filter[0]
"\x00" // override event filter[0]
- "\x00\x00\x00\x00\x00\x00\x00\x22" // block size
+ "\x00\x00\x00\x00\x00\x00\x00\x26" // block size
"N\x00" // block name
"\x00\x00\x00\x01" // settings count
"K\x00" // setting[0] key
"\x00\x00\x00\x01" // filter count
- "\x00\x00\x00\x00\x00\x00\x00\x01" // filter settings size
+ "\x00\x00\x00\x00\x00\x00\x00\x05" // filter settings size
"\x00" // filter error string
+ "\x00\x00\x00\x00" // include group count
"\x00\x00\x00\x00" // event filter index
"\x00\x00\x00\x00\x00\x00\x00\x00" // filter settings offset
"\x00" // safety NUL
ARRAY_DEFINE_TYPE(settings_override, struct settings_override);
ARRAY_DEFINE_TYPE(settings_override_p, struct settings_override *);
+struct settings_group {
+ const char *label;
+ const char *name;
+};
+ARRAY_DEFINE_TYPE(settings_group, struct settings_group);
+
struct settings_mmap_block {
const char *name;
size_t block_end_offset;
&filter_settings_size, error_r) < 0)
return -1;
+ /* <error string> */
size_t tmp_offset = offset;
size_t filter_end_offset = offset + filter_settings_size;
if (settings_block_read_str(mmap, &tmp_offset,
error_r) < 0)
return -1;
+ uint32_t include_count;
+ if (settings_block_read_uint32(mmap, &tmp_offset,
+ filter_end_offset,
+ "include group count",
+ &include_count, error_r) < 0)
+ return -1;
+
+ for (uint32_t i = 0; i < include_count; i++) {
+ /* <group label> */
+ const char *group_label;
+ if (settings_block_read_str(mmap, &tmp_offset,
+ filter_end_offset,
+ "group label string",
+ &group_label, error_r) < 0)
+ return -1;
+
+ /* <group name> */
+ const char *group_name;
+ if (settings_block_read_str(mmap, &tmp_offset,
+ filter_end_offset,
+ "group name string",
+ &group_name, error_r) < 0)
+ return -1;
+ }
+
/* skip over the filter contents for now */
offset += filter_settings_size;
}
.type = LOG_TYPE_DEBUG,
};
+ ARRAY_TYPE(settings_group) include_groups;
+ t_array_init(&include_groups, 4);
+
/* So through the filters in reverse sorted order, so we always set the
setting just once, never overriding anything. A filter for the base
settings is expected to always exist. */
+ struct event *event = ctx->event;
+ bool filters_matched[block->filter_count + 1];
+ memset(filters_matched, 0, block->filter_count + 1);
for (uint32_t i = block->filter_count; i > 0; ) {
i--;
uint32_t event_filter_idx = be32_to_cpu_unaligned(
mmap->event_filters[event_filter_idx];
if (event_filter == EVENT_FILTER_MATCH_NEVER)
;
- else if (event_filter == EVENT_FILTER_MATCH_ALWAYS ||
- event_filter_match(event_filter, ctx->event, &failure_ctx)) {
+ else if (filters_matched[i]) {
+ /* Group include restarted the filter matching. We
+ already applied this filter, so skip checking it. */
+ } else if (event_filter == EVENT_FILTER_MATCH_ALWAYS ||
+ event_filter_match(event_filter, event, &failure_ctx)) {
uint64_t filter_offset = be64_to_cpu_unaligned(
CONST_PTR_OFFSET(mmap->mmap_base,
block->filter_offsets_start_offset +
}
filter_offset += strlen(filter_error) + 1;
+ uint32_t include_count = be32_to_cpu_unaligned(
+ CONST_PTR_OFFSET(mmap->mmap_base, filter_offset));
+ filter_offset += sizeof(include_count);
+
+ array_clear(&include_groups);
+ for (uint32_t j = 0; j < include_count; j++) {
+ struct settings_group *include_group =
+ array_append_space(&include_groups);
+ include_group->label =
+ CONST_PTR_OFFSET(mmap->mmap_base,
+ filter_offset);
+ filter_offset += strlen(include_group->label) + 1;
+
+ include_group->name =
+ CONST_PTR_OFFSET(mmap->mmap_base,
+ filter_offset);
+ filter_offset += strlen(include_group->name) + 1;
+ }
+
if (ctx->filter_name != NULL && !ctx->seen_filter &&
event_filter != EVENT_FILTER_MATCH_ALWAYS) {
bool op_not;
filter_offset, filter_end_offset,
error_r) < 0)
return -1;
+
+ /* Don't try to apply this filter again if group
+ including restarts the filter processing */
+ filters_matched[i] = TRUE;
+ const struct settings_group *include_group;
+ array_foreach(&include_groups, include_group) {
+ /* Add @<group label>/<group name> to
+ matching filters and restart the filter
+ processing. */
+ char *filter_value = p_strdup_printf(
+ event_get_pool(ctx->event), "@%s/%s",
+ include_group->label, include_group->name);
+ event_strlist_append(ctx->event,
+ SETTINGS_EVENT_FILTER_NAME,
+ filter_value);
+ i = block->filter_count;
+ }
}
}
return ctx->seen_filter ? 1 : 0;
enum settings_get_flags flags;
};
+/* Setting name prefix that is used as include group. */
+#define SETTINGS_INCLUDE_GROUP_PREFIX '@'
+
/* Set struct settings_instance to events so settings_get() can
use it to get instance-specific settings. */
#define SETTINGS_EVENT_INSTANCE "settings_instance"