From 3be6c1a14442ba0a63efa839218b89a6fe83494a Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Thu, 30 Nov 2017 15:47:25 +0200 Subject: [PATCH] lib-master: Support validating config filters against requests Validation will sanitize the input request and drop any fields that have no filter in config. E.g. if you have a local block with name, and nothing else, then lip/rip will be dropped from the request. --- src/lib-master/Makefile.am | 2 + .../master-service-settings-cache.c | 90 +++++++++++++++++++ .../master-service-settings-cache.h | 2 +- src/lib-master/master-service-settings.c | 60 +++++++++++++ src/lib-master/master-service-settings.h | 3 + .../test-master-service-settings-cache.c | 8 ++ 6 files changed, 164 insertions(+), 1 deletion(-) diff --git a/src/lib-master/Makefile.am b/src/lib-master/Makefile.am index 43e4ee7a2a..09d945f2f0 100644 --- a/src/lib-master/Makefile.am +++ b/src/lib-master/Makefile.am @@ -4,6 +4,7 @@ noinst_LTLIBRARIES = libmaster.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-ssl-iostream \ @@ -58,6 +59,7 @@ noinst_PROGRAMS = $(test_programs) test_libs = \ ../lib-test/libtest.la \ + ../lib-dns/libdns.la \ ../lib/liblib.la test_deps = $(noinst_LTLIBRARIES) $(test_libs) diff --git a/src/lib-master/master-service-settings-cache.c b/src/lib-master/master-service-settings-cache.c index f1818814ae..d90893fbf7 100644 --- a/src/lib-master/master-service-settings-cache.c +++ b/src/lib-master/master-service-settings-cache.c @@ -1,9 +1,11 @@ /* Copyright (c) 2010-2017 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "wildcard-match.h" #include "hash.h" #include "llist.h" #include "settings-parser.h" +#include "dns-util.h" #include "master-service-private.h" #include "master-service-settings.h" #include "master-service-settings-cache.h" @@ -12,6 +14,14 @@ #define CACHE_INITIAL_ENTRY_POOL_SIZE (1024*16) #define CACHE_ADD_ENTRY_POOL_SIZE 1024 +struct config_filter { + struct config_filter *prev, *next; + + const char *local_name; + struct ip_addr local_ip, remote_ip; + unsigned int local_bits, remote_bits; +}; + struct settings_entry { struct settings_entry *prev, *next; @@ -41,6 +51,8 @@ struct master_service_settings_cache { HASH_TABLE(char *, struct settings_entry *) local_name_hash; HASH_TABLE(struct ip_addr *, struct settings_entry *) local_ip_hash; + struct config_filter *filters; + /* Initial size for new settings entry pools */ size_t approx_entry_pool_size; /* number of bytes malloced by cached settings entries @@ -70,6 +82,78 @@ master_service_settings_cache_init(struct master_service *service, return cache; } +int master_service_settings_cache_init_filter(struct master_service_settings_cache *cache) +{ + const char *const *filters; + const char *error; + + if (cache->filters != NULL) + return 0; + if (master_service_settings_get_filters(cache->service, &filters, &error) < 0) { + i_error("master-service: cannot get filters: %s", error); + return -1; + } + + /* parse filters */ + while(*filters != NULL) { + const char *const *keys = t_strsplit_spaces(*filters, " "); + struct config_filter *filter = + p_new(cache->pool, struct config_filter, 1); + while(*keys != NULL) { + if (strncmp(*keys, "local-net=", 10) == 0) { + (void)net_parse_range((*keys)+10, + &filter->local_ip, &filter->local_bits); + } else if (strncmp(*keys, "remote-net=", 11) == 0) { + (void)net_parse_range((*keys)+11, + &filter->remote_ip, &filter->remote_bits); + } else if (strncmp(*keys, "local-name=", 11) == 0) { + filter->local_name = p_strdup(cache->pool, (*keys)+11); + } + keys++; + } + DLLIST_PREPEND(&cache->filters, filter); + filters++; + } + return 0; +} + +/* Remove any elements which there is no filter for */ +static void +master_service_settings_cache_fix_input(struct master_service_settings_cache *cache, + const struct master_service_settings_input *input, + struct master_service_settings_input *new_input) +{ + bool found_lip, found_rip, found_local_name; + + found_lip = found_rip = found_local_name = FALSE; + + struct config_filter *filter = cache->filters; + while(filter != NULL) { + if (filter->local_bits > 0 && + net_is_in_network(&input->local_ip, &filter->local_ip, + filter->local_bits)) + found_lip = TRUE; + if (filter->remote_bits > 0 && + net_is_in_network(&input->remote_ip, &filter->remote_ip, + filter->remote_bits)) + found_rip = TRUE; + if (filter->local_name != NULL && + dns_match_wildcard(input->local_name, filter->local_name)) + found_local_name = TRUE; + filter = filter->next; + }; + + *new_input = *input; + + if (!found_lip) + i_zero(&new_input->local_ip); + if (!found_rip) + i_zero(&new_input->remote_ip); + if (!found_local_name) + new_input->local_name = NULL; +} + + void master_service_settings_cache_deinit(struct master_service_settings_cache **_cache) { struct master_service_settings_cache *cache = *_cache; @@ -273,6 +357,12 @@ int master_service_settings_cache_read(struct master_service_settings_cache *cac return 0; new_input = *input; + if (cache->filters != NULL) { + master_service_settings_cache_fix_input(cache, input, &new_input); + if (cache_find(cache, &new_input, parser_r)) + return 0; + } + if (dyn_parsers != NULL) { settings_parser_dyn_update(cache->pool, &new_input.roots, dyn_parsers); diff --git a/src/lib-master/master-service-settings-cache.h b/src/lib-master/master-service-settings-cache.h index de94042222..157132e639 100644 --- a/src/lib-master/master-service-settings-cache.h +++ b/src/lib-master/master-service-settings-cache.h @@ -6,7 +6,7 @@ master_service_settings_cache_init(struct master_service *service, const char *module, const char *service_name); void master_service_settings_cache_deinit(struct master_service_settings_cache **cache); - +int master_service_settings_cache_init_filter(struct master_service_settings_cache *cache); int master_service_settings_cache_read(struct master_service_settings_cache *cache, const struct master_service_settings_input *input, const struct dynamic_settings_parser *dyn_parsers, diff --git a/src/lib-master/master-service-settings.c b/src/lib-master/master-service-settings.c index 13607c8113..2c81193688 100644 --- a/src/lib-master/master-service-settings.c +++ b/src/lib-master/master-service-settings.c @@ -341,6 +341,18 @@ config_send_request(struct master_service *service, return 0; } +static int +config_send_filters_request(int fd, const char *path, const char **error_r) +{ + int ret; + ret = write_full(fd, CONFIG_HANDSHAKE"FILTERS\n", strlen(CONFIG_HANDSHAKE"FILTERS\n")); + if (ret < 0) { + *error_r = t_strdup_printf("write_full(%s) failed: %m", path); + return -1; + } + return 0; +} + static int master_service_apply_config_overrides(struct master_service *service, struct setting_parser_context *parser, @@ -437,6 +449,54 @@ void master_service_config_socket_try_open(struct master_service *service) service->config_fd = fd; } +int master_service_settings_get_filters(struct master_service *service, + const char *const **filters, + const char **error_r) +{ + struct master_service_settings_input input; + int fd; + bool retry = TRUE; + const char *path = NULL; + ARRAY_TYPE(const_string) filters_tmp; + t_array_init(&filters_tmp, 8); + i_zero(&input); + + if (getenv("DOVECONF_ENV") == NULL && + (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) { + retry = service->config_fd != -1; + for (;;) { + fd = master_service_open_config(service, &input, &path, error_r); + if (fd == -1) { + return -1; + } + if (config_send_filters_request(fd, path, error_r) == 0) + break; + + i_close_fd(&fd); + if (!retry) + return -1; + retry = FALSE; + } + service->config_fd = fd; + struct istream *is = i_stream_create_fd(fd, (size_t)-1); + const char *line; + /* try read response */ + while((line = i_stream_read_next_line(is)) != NULL) { + if (*line == '\0') + break; + if (strncmp(line, "FILTER\t", 7) == 0) { + line = t_strdup(line+7); + array_append(&filters_tmp, &line, 1); + } + } + i_stream_unref(&is); + } + + array_append_zero(&filters_tmp); + *filters = array_idx(&filters_tmp, 0); + return 0; +} + int master_service_settings_read(struct master_service *service, const struct master_service_settings_input *input, struct master_service_settings_output *output_r, diff --git a/src/lib-master/master-service-settings.h b/src/lib-master/master-service-settings.h index 2ae3b18f89..d987555a75 100644 --- a/src/lib-master/master-service-settings.h +++ b/src/lib-master/master-service-settings.h @@ -69,6 +69,9 @@ extern const struct setting_parser_info master_service_setting_parser_info; /* Try to open the config socket if it's going to be needed later by master_service_settings_read*() */ void master_service_config_socket_try_open(struct master_service *service); +int master_service_settings_get_filters(struct master_service *service, + const char *const **filters, + const char **error_r); int master_service_settings_read(struct master_service *service, const struct master_service_settings_input *input, struct master_service_settings_output *output_r, diff --git a/src/lib-master/test-master-service-settings-cache.c b/src/lib-master/test-master-service-settings-cache.c index 6b8318296a..a2c17d7df9 100644 --- a/src/lib-master/test-master-service-settings-cache.c +++ b/src/lib-master/test-master-service-settings-cache.c @@ -53,6 +53,14 @@ int master_service_settings_read(struct master_service *service ATTR_UNUSED, return 0; } +int master_service_settings_get_filters(struct master_service *service ATTR_UNUSED, + const char *const **filters ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return -1; +} + + const struct master_service_settings * master_service_settings_get(struct master_service *service ATTR_UNUSED) {