summaryrefslogtreecommitdiffstats
path: root/src/lib-master/master-service-settings-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-master/master-service-settings-cache.c')
-rw-r--r--src/lib-master/master-service-settings-cache.c410
1 files changed, 410 insertions, 0 deletions
diff --git a/src/lib-master/master-service-settings-cache.c b/src/lib-master/master-service-settings-cache.c
new file mode 100644
index 0000000..11dd66b
--- /dev/null
+++ b/src/lib-master/master-service-settings-cache.c
@@ -0,0 +1,410 @@
+/* Copyright (c) 2010-2018 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 "strescape.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+#include "master-service-settings-cache.h"
+
+/* we start with just a guess. it's updated later. */
+#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;
+
+ pool_t pool;
+ const char *local_name;
+ struct ip_addr local_ip;
+
+ struct setting_parser_context *parser;
+};
+
+struct master_service_settings_cache {
+ pool_t pool;
+
+ struct master_service *service;
+ const char *module;
+ const char *service_name;
+ size_t max_cache_size;
+
+ /* global settings for this service (after they've been read) */
+ struct setting_parser_context *global_parser;
+
+ /* cache for other settings (local_ip/local_name set) */
+ struct settings_entry *oldest, *newest;
+ /* separate list for entries whose parser=global_parser */
+ struct settings_entry *oldest_global, *newest_global;
+ /* local_name, local_ip => struct settings_entry */
+ 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
+ (doesn't count memory used by hash table or global sets) */
+ size_t cache_malloc_size;
+
+ bool done_initial_lookup:1;
+ bool service_uses_local:1;
+ bool service_uses_remote:1;
+};
+
+struct master_service_settings_cache *
+master_service_settings_cache_init(struct master_service *service,
+ const char *module, const char *service_name)
+{
+ struct master_service_settings_cache *cache;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"master service settings cache",
+ 1024*12);
+ cache = p_new(pool, struct master_service_settings_cache, 1);
+ cache->pool = pool;
+ cache->service = service;
+ cache->module = p_strdup(pool, module);
+ cache->service_name = p_strdup(pool, service_name);
+ cache->max_cache_size = SIZE_MAX;
+ 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_tabescaped(*filters);
+ struct config_filter *filter =
+ p_new(cache->pool, struct config_filter, 1);
+ while(*keys != NULL) {
+ if (str_begins(*keys, "local-net=")) {
+ (void)net_parse_range((*keys)+10,
+ &filter->local_ip, &filter->local_bits);
+ } else if (str_begins(*keys, "remote-net=")) {
+ (void)net_parse_range((*keys)+11,
+ &filter->remote_ip, &filter->remote_bits);
+ } else if (str_begins(*keys, "local-name=")) {
+ filter->local_name = p_strdup(cache->pool, (*keys)+11);
+ }
+ keys++;
+ }
+ DLLIST_PREPEND(&cache->filters, filter);
+ filters++;
+ }
+ return 0;
+}
+
+static bool
+match_local_name(const char *local_name,
+ const char *filter_local_name)
+{
+ /* Handle multiple names separated by spaces in local_name
+ * Ex: local_name "mail.domain.tld domain.tld mx.domain.tld" { ... } */
+ const char *ptr;
+ while((ptr = strchr(filter_local_name, ' ')) != NULL) {
+ if (dns_match_wildcard(local_name,
+ t_strdup_until(filter_local_name, ptr)) == 0)
+ return TRUE;
+ filter_local_name = ptr+1;
+ }
+ return dns_match_wildcard(local_name, filter_local_name) == 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 &&
+ match_local_name(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;
+ struct settings_entry *entry, *next;
+
+ /* parsers need to be deinitialized, because they reference the pool */
+ for (entry = cache->oldest_global; entry != NULL; entry = next) {
+ next = entry->next;
+ i_assert(entry->parser == cache->global_parser);
+ pool_unref(&entry->pool);
+ }
+ for (entry = cache->oldest; entry != NULL; entry = next) {
+ next = entry->next;
+ i_assert(entry->parser != cache->global_parser);
+ settings_parser_deinit(&entry->parser);
+ pool_unref(&entry->pool);
+ }
+ hash_table_destroy(&cache->local_name_hash);
+ hash_table_destroy(&cache->local_ip_hash);
+ if (cache->global_parser != NULL)
+ settings_parser_deinit(&cache->global_parser);
+ pool_unref(&cache->pool);
+}
+
+static bool
+cache_can_return_global(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input)
+{
+ if (cache->service_uses_local) {
+ if (input->local_name != NULL || input->local_ip.family != 0)
+ return FALSE;
+ }
+ if (cache->service_uses_remote) {
+ if (input->remote_ip.family != 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+cache_find(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct setting_parser_context **parser_r)
+{
+ struct settings_entry *entry = NULL;
+
+ if (!cache->done_initial_lookup)
+ return FALSE;
+
+ if (cache_can_return_global(cache, input)) {
+ if (cache->global_parser != NULL) {
+ *parser_r = cache->global_parser;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (cache->service_uses_remote)
+ return FALSE;
+
+ /* see if we have it already in cache. if local_name is specified,
+ don't even try to use local_ip (even though we have it), because
+ there may be different settings specifically for local_name */
+ if (input->local_name != NULL) {
+ if (hash_table_is_created(cache->local_name_hash)) {
+ entry = hash_table_lookup(cache->local_name_hash,
+ input->local_name);
+ }
+ } else if (hash_table_is_created(cache->local_ip_hash) &&
+ input->local_ip.family != 0) {
+ entry = hash_table_lookup(cache->local_ip_hash,
+ &input->local_ip);
+ }
+
+ if (entry != NULL) {
+ if (entry->parser != cache->global_parser) {
+ DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry);
+ DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
+ }
+ *parser_r = entry->parser;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+setting_entry_detach(struct master_service_settings_cache *cache,
+ struct settings_entry *entry)
+{
+ DLLIST2_REMOVE(&cache->oldest, &cache->newest, entry);
+ cache->cache_malloc_size -=
+ pool_alloconly_get_total_alloc_size(entry->pool);
+
+ if (entry->local_name != NULL)
+ hash_table_remove(cache->local_name_hash, entry->local_name);
+ else if (entry->local_ip.family != 0)
+ hash_table_remove(cache->local_ip_hash, &entry->local_ip);
+ settings_parser_deinit(&entry->parser);
+}
+
+static struct setting_parser_context *
+cache_add(struct master_service_settings_cache *cache,
+ const struct master_service_settings_input *input,
+ const struct master_service_settings_output *output,
+ struct setting_parser_context *parser)
+{
+ struct settings_entry *entry;
+ pool_t pool;
+ size_t pool_size;
+ char *entry_local_name;
+
+ if (!output->used_local && !output->used_remote) {
+ /* these are same as global settings */
+ if (cache->global_parser == NULL) {
+ cache->global_parser =
+ settings_parser_dup(parser, cache->pool);
+ }
+ }
+ if (cache->service_uses_remote) {
+ /* for now we don't try to handle caching remote IPs */
+ return parser;
+ }
+
+ if (input->local_name == NULL && input->local_ip.family == 0)
+ return parser;
+
+ if (!output->used_local) {
+ /* use global settings, but add local_ip/host to hash tables
+ so we'll find them */
+ pool = pool_alloconly_create("settings global entry", 256);
+ } else if (cache->cache_malloc_size >= cache->max_cache_size) {
+ /* free the oldest and reuse its pool */
+ pool = cache->oldest->pool;
+ setting_entry_detach(cache, cache->oldest);
+ p_clear(pool); /* note: frees also entry */
+ } else {
+ pool_size = cache->approx_entry_pool_size != 0 ?
+ cache->approx_entry_pool_size :
+ CACHE_INITIAL_ENTRY_POOL_SIZE;
+ pool = pool_alloconly_create("settings entry", pool_size);
+ }
+ entry = p_new(pool, struct settings_entry, 1);
+ entry->pool = pool;
+ entry_local_name = p_strdup(pool, input->local_name);
+ entry->local_name = entry_local_name;
+ entry->local_ip = input->local_ip;
+ if (!output->used_local) {
+ entry->parser = cache->global_parser;
+ DLLIST2_APPEND(&cache->oldest_global, &cache->newest_global,
+ entry);
+ } else {
+ entry->parser = settings_parser_dup(parser, entry->pool);
+ DLLIST2_APPEND(&cache->oldest, &cache->newest, entry);
+
+ pool_size = pool_alloconly_get_total_used_size(pool);
+ if (pool_size > cache->approx_entry_pool_size) {
+ cache->approx_entry_pool_size = pool_size +
+ CACHE_ADD_ENTRY_POOL_SIZE;
+ }
+ }
+ cache->cache_malloc_size += pool_alloconly_get_total_alloc_size(pool);
+
+ if (input->local_name != NULL) {
+ if (!hash_table_is_created(cache->local_name_hash)) {
+ hash_table_create(&cache->local_name_hash,
+ cache->pool, 0, str_hash, strcmp);
+ }
+ i_assert(hash_table_lookup(cache->local_name_hash,
+ entry_local_name) == NULL);
+ hash_table_insert(cache->local_name_hash,
+ entry_local_name, entry);
+ } else if (input->local_ip.family != 0) {
+ if (!hash_table_is_created(cache->local_ip_hash)) {
+ hash_table_create(&cache->local_ip_hash, cache->pool, 0,
+ net_ip_hash, net_ip_cmp);
+ }
+ i_assert(hash_table_lookup(cache->local_ip_hash,
+ &entry->local_ip) == NULL);
+ hash_table_insert(cache->local_ip_hash,
+ &entry->local_ip, entry);
+ }
+ return entry->parser;
+}
+
+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,
+ const struct setting_parser_context **parser_r,
+ const char **error_r)
+{
+ struct master_service_settings_output output;
+ struct master_service_settings_input new_input;
+ const struct master_service_settings *set;
+
+ i_assert(null_strcmp(input->module, cache->module) == 0);
+ i_assert(null_strcmp(input->service, cache->service_name) == 0);
+
+ if (cache_find(cache, input, parser_r))
+ 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);
+ }
+ if (master_service_settings_read(cache->service, &new_input,
+ &output, error_r) < 0)
+ return -1;
+
+ if (!cache->done_initial_lookup) {
+ cache->done_initial_lookup = TRUE;
+ cache->service_uses_local = output.service_uses_local;
+ cache->service_uses_remote = output.service_uses_remote;
+
+ set = master_service_settings_get(cache->service);
+ cache->max_cache_size = set->config_cache_size;
+ }
+
+ if (output.used_local && !cache->service_uses_local) {
+ *error_r = "BUG: config unexpectedly returned local settings";
+ return -1;
+ }
+ if (output.used_remote && !cache->service_uses_remote) {
+ *error_r = "BUG: config unexpectedly returned remote settings";
+ return -1;
+ }
+
+ *parser_r = cache_add(cache, &new_input, &output,
+ cache->service->set_parser);
+ return 0;
+}