diff options
Diffstat (limited to 'src/lib-master/master-service-settings-cache.c')
-rw-r--r-- | src/lib-master/master-service-settings-cache.c | 410 |
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; +} |