From 390592e6af07e02064ebdbb1bbcf06528887370f Mon Sep 17 00:00:00 2001
From: Aki Tuomi <aki.tuomi@dovecot.fi>
Date: Thu, 30 Nov 2017 15:47:25 +0200
Subject: [PATCH 4/6] 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 +
 src/lib-master/master-service-settings-cache.c     | 90 ++++++++++++++++++++++
 src/lib-master/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 59f89a4..7d400cb 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 d6164ff..11e9204 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 (input->local_name != NULL && 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 de94042..157132e 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 92d22e1..59d2165 100644
--- a/src/lib-master/master-service-settings.c
+++ b/src/lib-master/master-service-settings.c
@@ -304,6 +304,18 @@ config_send_request(struct master_service *service,
 }
 
 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,
 				      const char **error_r)
@@ -399,6 +411,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, FALSE);
+		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 43e4541..39ae8b4 100644
--- a/src/lib-master/master-service-settings.h
+++ b/src/lib-master/master-service-settings.h
@@ -67,6 +67,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 506fe2b..fdc390d 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)
 {
-- 
2.1.4

--- a/src/Makefile.in	2017-06-26 13:32:21.000000000 +0200
+++ b/src/Makefile.in	2018-08-21 13:33:56.840985600 +0200
@@ -154,8 +154,8 @@
   done | $(am__uniquify_input)`
 ETAGS = etags
 CTAGS = ctags
-DIST_SUBDIRS = lib-test lib lib-settings lib-auth lib-master \
-	lib-charset lib-ssl-iostream lib-dcrypt lib-dns lib-dict \
+DIST_SUBDIRS = lib-test lib lib-settings lib-auth lib-dns lib-master \
+	lib-charset lib-ssl-iostream lib-dcrypt lib-dict \
 	lib-sasl lib-stats lib-http lib-fs lib-mail lib-smtp lib-imap \
 	lib-imap-storage lib-program-client lib-oauth2 lib-dict-extra \
 	lib-dovecot lib-ldap lib-fts lib-imap-client lib-imap-urlauth \
@@ -389,11 +389,11 @@
 	lib \
 	lib-settings \
 	lib-auth \
+	lib-dns \
 	lib-master \
 	lib-charset \
 	lib-ssl-iostream \
 	lib-dcrypt \
-	lib-dns \
 	lib-dict \
 	lib-sasl \
 	lib-stats \
--- a/src/lib-master/Makefile.in	2017-06-26 13:32:22.000000000 +0200
+++ b/src/lib-master/Makefile.in	2018-08-21 13:29:11.173340936 +0200
@@ -414,6 +414,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 \
@@ -465,6 +466,7 @@
 
 test_libs = \
 	../lib-test/libtest.la \
+	../lib-dns/libdns.la \
 	../lib/liblib.la
 
 test_deps = $(noinst_LTLIBRARIES) $(test_libs)
