From 234f47479441467d3e3e2da9a2e99df884a281d7 Mon Sep 17 00:00:00 2001
From: Apollon Oikonomopoulos <apoikos@debian.org>
Date: Thu, 1 Mar 2018 14:27:47 +0200
Subject: [PATCH 2/3] Fix CVE-2017-15130: TLS SNI config lookups are
 inefficient and can be used for DoS

commit 1b24047359dcc17c9bf0daf62f9b4bbd7257dde4
Author: Stephan Bosch <stephan.bosch@dovecot.fi>
Date:   Sun Dec 18 13:20:20 2016 +0100

    lib: Created i_zero() wrapper for memset(p, 0, sizeof(*p)).

    Also creates an i_zero_safe() version for safe_memset().

    (cherry picked from commit 365096bcd90cd5b50f413035f2a1f16237b365c1)

commit 05724ecd376d20835a7b52b70505fe1afe2326b5
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Mon Feb 26 12:53:19 2018 +0200

    lib-master: Fix dns_match_wildcard result value check

    It returns 0, not TRUE.

    (cherry picked from commit 00016646cc32a3fa1cf54c22ed7388ed06bbc0f1)

commit 14c97b9e49c993163b794d6cbdb5623d1c49e3e6
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Thu Nov 30 20:52:11 2017 +0200

    login-common: Enable config filtering by local name

    Prevents servername misuse.

    (cherry picked from commit bc27538d084e01a7a1aca3330e27aebfc0e311eb)

commit 693dcc3b6f840cc97d5699ae248cf8e7ed06636e
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Thu Nov 30 15:47:25 2017 +0200

    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.

    (cherry picked from commit 390592e6af07e02064ebdbb1bbcf06528887370f)

commit adc1243e5cee2cf98ac3f978300eec2f4db887de
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Thu Nov 30 15:46:52 2017 +0200

    config: Add command to request all filters

    (cherry picked from commit 02da33a59fddd51cc3b8d95989de95574b7332f1)

commit 57975bd472d8713f89a5faa691ff71097d56a51e
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Thu Nov 30 15:46:40 2017 +0200

    config: Add config_filter_get_all

    Returns all filters

    (cherry picked from commit f3504763c27c2661716c0d1dbd3e0fc662107a21)

commit b099284e46af9e0f8c9014b1719ef58da6b4414b
Author: Aki Tuomi <aki.tuomi@dovecot.fi>
Date:   Mon Feb 19 14:19:08 2018 +0200

    lib-dns: Move before lib-master

    (cherry picked from commit 22311315b9f780211329c1522eb5aaa4faaa9391)
---
 src/Makefile.am                                    |  2 +-
 src/config/config-connection.c                     | 34 ++++++++
 src/config/config-filter.c                         | 15 ++++
 src/config/config-filter.h                         |  3 +
 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 ++
 src/lib/macros.h                                   |  4 +
 src/login-common/login-settings.c                  |  8 ++
 12 files changed, 229 insertions(+), 2 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 35a8d60b4..4e79bcc93 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,11 +7,11 @@ LIBDOVECOT_SUBDIRS = \
 	lib \
 	lib-settings \
 	lib-auth \
+	lib-dns \
 	lib-master \
 	lib-charset \
 	lib-ssl-iostream \
 	lib-dcrypt \
-	lib-dns \
 	lib-dict \
 	lib-sasl \
 	lib-stats \
diff --git a/src/config/config-connection.c b/src/config/config-connection.c
index 14aec5d9f..e1edee784 100644
--- a/src/config/config-connection.c
+++ b/src/config/config-connection.c
@@ -149,6 +149,36 @@ static int config_connection_request(struct config_connection *conn,
 	return 0;
 }
 
+static int config_filters_request(struct config_connection *conn)
+{
+	struct config_filter_parser *const *filters = config_filter_get_all(config_filter);
+	o_stream_cork(conn->output);
+	while(*filters != NULL) {
+		const struct config_filter *filter = &(*filters)->filter;
+		o_stream_nsend_str(conn->output, "FILTER");
+		if (filter->service != NULL)
+			o_stream_nsend_str(conn->output, t_strdup_printf("\tservice=%s",
+					   filter->service));
+		if (filter->local_name != NULL)
+			o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-name=%s",
+					   filter->local_name));
+		if (filter->local_bits > 0)
+			o_stream_nsend_str(conn->output, t_strdup_printf("\tlocal-net=%s/%u",
+					   net_ip2addr(&filter->local_net),
+					   filter->local_bits));
+		if (filter->remote_bits > 0)
+			o_stream_nsend_str(conn->output, t_strdup_printf("\tremote-net=%s/%u",
+					   net_ip2addr(&filter->remote_net),
+					   filter->remote_bits));
+		o_stream_nsend_str(conn->output, "\n");
+		filters++;
+	}
+	o_stream_nsend_str(conn->output, "\n");
+	o_stream_uncork(conn->output);
+	return 0;
+}
+
+
 static void config_connection_input(struct config_connection *conn)
 {
 	const char *const *args, *line;
@@ -185,6 +215,10 @@ static void config_connection_input(struct config_connection *conn)
 			if (config_connection_request(conn, args + 1) < 0)
 				break;
 		}
+		if (strcmp(args[0], "FILTERS") == 0) {
+			if (config_filters_request(conn) < 0)
+				break;
+		}
 	}
 }
 
diff --git a/src/config/config-filter.c b/src/config/config-filter.c
index 4c2071a2c..b8f980100 100644
--- a/src/config/config-filter.c
+++ b/src/config/config-filter.c
@@ -241,6 +241,21 @@ config_filter_find_all(struct config_filter_context *ctx, pool_t pool,
 	return array_idx(&matches, 0);
 }
 
+struct config_filter_parser *const *
+config_filter_get_all(struct config_filter_context *ctx)
+{
+	ARRAY_TYPE(config_filter_parsers) filters;
+	unsigned int i;
+
+	t_array_init(&filters, 8);
+	for (i = 0; ctx->parsers[i] != NULL; i++) {
+		array_append(&filters, &ctx->parsers[i], 1);
+	}
+	array_sort(&filters, config_filter_parser_cmp_rev);
+	array_append_zero(&filters);
+	return array_idx(&filters, 0);
+}
+
 struct config_filter_parser *const *
 config_filter_find_subset(struct config_filter_context *ctx,
 			  const struct config_filter *filter)
diff --git a/src/config/config-filter.h b/src/config/config-filter.h
index 7e45fc1a7..fda3182f3 100644
--- a/src/config/config-filter.h
+++ b/src/config/config-filter.h
@@ -45,6 +45,9 @@ struct config_filter_parser *const *
 config_filter_find_subset(struct config_filter_context *ctx,
 			  const struct config_filter *filter);
 
+struct config_filter_parser *const *
+config_filter_get_all(struct config_filter_context *ctx);
+
 /* Returns TRUE if filter matches mask. */
 bool config_filter_match(const struct config_filter *mask,
 			 const struct config_filter *filter);
diff --git a/src/lib-master/Makefile.am b/src/lib-master/Makefile.am
index 59f89a4b2..7d400cb35 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 e3be9da50..7fdf6e99c 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-2016 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) == 0)
+			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 de9404222..157132e63 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 4a25f6592..62ad0c717 100644
--- a/src/lib-master/master-service-settings.c
+++ b/src/lib-master/master-service-settings.c
@@ -287,6 +287,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,
@@ -383,6 +395,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 3820740bb..f6bdead63 100644
--- a/src/lib-master/master-service-settings.h
+++ b/src/lib-master/master-service-settings.h
@@ -66,6 +66,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 8d9a4d2a3..510441670 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)
 {
diff --git a/src/lib/macros.h b/src/lib/macros.h
index 566c176d1..5bb8aeb0a 100644
--- a/src/lib/macros.h
+++ b/src/lib/macros.h
@@ -240,4 +240,8 @@
 #  define STATIC_ARRAY
 #endif
 
+/* Convenience wrappers for initializing a struct */
+#define i_zero(p) memset(p, 0, sizeof(*(p)))
+#define i_zero_safe(p) safe_memset(p, 0, sizeof(*(p)))
+
 #endif
diff --git a/src/login-common/login-settings.c b/src/login-common/login-settings.c
index 73a8a7270..61ca0bc3b 100644
--- a/src/login-common/login-settings.c
+++ b/src/login-common/login-settings.c
@@ -183,6 +183,14 @@ login_settings_read(pool_t pool,
 		set_cache = master_service_settings_cache_init(master_service,
 							       input.module,
 							       input.service);
+		/* lookup filters
+
+		   this is only enabled if service_count > 1 because otherwise
+		   login process will process only one request and this is only
+		   useful when more than one request is processed.
+		*/
+		if (master_service_get_service_count(master_service) > 1)
+			master_service_settings_cache_init_filter(set_cache);
 	}
 
 	if (master_service_settings_cache_read(set_cache, &input, NULL,
-- 
2.15.1

