diff options
Diffstat (limited to 'src/director/mail-host.c')
-rw-r--r-- | src/director/mail-host.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/src/director/mail-host.c b/src/director/mail-host.c new file mode 100644 index 0000000..50966f2 --- /dev/null +++ b/src/director/mail-host.c @@ -0,0 +1,560 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "crc32.h" +#include "md5.h" +#include "director.h" +#include "user-directory.h" +#include "mail-host.h" + +#define VHOST_MULTIPLIER 100 + +struct mail_host_list { + struct director *dir; + ARRAY_TYPE(mail_tag) tags; + ARRAY_TYPE(mail_host) hosts; + user_free_hook_t *user_free_hook; + unsigned int hosts_hash; + unsigned int user_expire_secs; + bool vhosts_unsorted; + bool have_vhosts; +}; + +static int +mail_host_cmp(struct mail_host *const *h1, struct mail_host *const *h2) +{ + return net_ip_cmp(&(*h1)->ip, &(*h2)->ip); +} + +static int +mail_vhost_cmp(const struct mail_vhost *h1, const struct mail_vhost *h2) +{ + if (h1->hash < h2->hash) + return -1; + else if (h1->hash > h2->hash) + return 1; + /* hash collision. not ideal, but we'll need to keep the order + consistent across directors so compare the IPs next. */ + return net_ip_cmp(&h1->host->ip, &h2->host->ip); +} + +static int +mail_vhost_hash_cmp(const unsigned int *hash, const struct mail_vhost *vhost) +{ + if (vhost->hash < *hash) + return 1; + else if (vhost->hash > *hash) + return -1; + else + return 0; +} + +static void mail_vhost_add(struct mail_tag *tag, struct mail_host *host) +{ + struct mail_vhost *vhost; + struct md5_context md5_ctx, md5_ctx2; + unsigned char md5[MD5_RESULTLEN]; + char num_str[MAX_INT_STRLEN]; + unsigned int i, j; + + if (host->down || host->tag != tag) + return; + + md5_init(&md5_ctx); + md5_update(&md5_ctx, host->ip_str, strlen(host->ip_str)); + + for (i = 0; i < host->vhost_count; i++) { + md5_ctx2 = md5_ctx; + i_snprintf(num_str, sizeof(num_str), "-%u", i); + md5_update(&md5_ctx2, num_str, strlen(num_str)); + md5_final(&md5_ctx2, md5); + + vhost = array_append_space(&tag->vhosts); + vhost->host = host; + for (j = 0; j < sizeof(vhost->hash); j++) + vhost->hash = (vhost->hash << CHAR_BIT) | md5[j]; + } +} + +static void +mail_tag_vhosts_sort_ring(struct mail_host_list *list, struct mail_tag *tag) +{ + struct mail_host *host; + + /* rebuild vhosts */ + array_clear(&tag->vhosts); + array_foreach_elem(&list->hosts, host) + mail_vhost_add(tag, host); + array_sort(&tag->vhosts, mail_vhost_cmp); +} + +static void +mail_hosts_sort(struct mail_host_list *list) +{ + struct mail_host *host; + struct mail_tag *tag; + uint32_t num; + + array_sort(&list->hosts, mail_host_cmp); + + list->have_vhosts = FALSE; + array_foreach_elem(&list->tags, tag) { + mail_tag_vhosts_sort_ring(list, tag); + if (array_count(&tag->vhosts) > 0) + list->have_vhosts = TRUE; + } + list->vhosts_unsorted = FALSE; + + /* recalculate the hosts_hash */ + list->hosts_hash = 0; + array_foreach_elem(&list->hosts, host) { + num = (host->down ? 1 : 0) ^ host->vhost_count; + list->hosts_hash = crc32_data_more(list->hosts_hash, + &num, sizeof(num)); + num = net_ip_hash(&host->ip); + list->hosts_hash = crc32_data_more(list->hosts_hash, + &num, sizeof(num)); + list->hosts_hash = crc32_str_more(list->hosts_hash, + host->tag->name); + } +} + +struct mail_tag * +mail_tag_find(struct mail_host_list *list, const char *tag_name) +{ + struct mail_tag *tag; + + array_foreach_elem(&list->tags, tag) { + if (strcmp(tag->name, tag_name) == 0) + return tag; + } + return NULL; +} + +static struct mail_tag * +mail_tag_get(struct mail_host_list *list, const char *tag_name) +{ + struct mail_tag *tag; + + tag = mail_tag_find(list, tag_name); + if (tag == NULL) { + tag = i_new(struct mail_tag, 1); + tag->name = i_strdup(tag_name); + i_array_init(&tag->vhosts, 16*VHOST_MULTIPLIER); + tag->users = user_directory_init(list->dir, + list->user_expire_secs, + list->user_free_hook); + array_push_back(&list->tags, &tag); + } + return tag; +} + +static void mail_tag_free(struct mail_tag *tag) +{ + user_directory_deinit(&tag->users); + array_free(&tag->vhosts); + i_free(tag->name); + i_free(tag); +} + +struct mail_host * +mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip, + const char *tag_name) +{ + struct mail_host *host; + + i_assert(tag_name != NULL); + + host = i_new(struct mail_host, 1); + host->list = list; + host->vhost_count = VHOST_MULTIPLIER; + host->ip = *ip; + host->ip_str = i_strdup(net_ip2addr(ip)); + host->tag = mail_tag_get(list, tag_name); + array_push_back(&list->hosts, &host); + + list->vhosts_unsorted = TRUE; + return host; +} + +struct mail_host * +mail_host_add_hostname(struct mail_host_list *list, const char *hostname, + const struct ip_addr *ip, const char *tag_name) +{ + struct mail_host *host; + + host = mail_host_add_ip(list, ip, tag_name); + if (hostname != NULL && hostname[0] != '\0') + host->hostname = i_strdup(hostname); + return host; +} + +static int +mail_host_add(struct mail_host_list *list, const char *hostname, + const char *tag_name) +{ + struct ip_addr *ips, ip; + unsigned int i, ips_count; + + if (net_addr2ip(hostname, &ip) == 0) { + (void)mail_host_add_ip(list, &ip, tag_name); + return 0; + } + + if (net_gethostbyname(hostname, &ips, &ips_count) < 0) { + e_error(list->dir->event, "Unknown mail host: %s", hostname); + return -1; + } + + for (i = 0; i < ips_count; i++) + (void)mail_host_add_hostname(list, hostname, &ips[i], tag_name); + return 0; +} + +static int +mail_hosts_add_range(struct mail_host_list *list, + struct ip_addr ip1, struct ip_addr ip2, + const char *tag_name) +{ + uint32_t *ip1_arr, *ip2_arr; + uint32_t i1, i2; + unsigned int i, j, max_bits, last_bits; + + if (ip1.family != ip2.family) { + e_error(list->dir->event, "IP address family mismatch: %s vs %s", + net_ip2addr(&ip1), net_ip2addr(&ip2)); + return -1; + } + if (net_ip_cmp(&ip1, &ip2) > 0) { + e_error(list->dir->event, "IP addresses reversed: %s-%s", + net_ip2addr(&ip1), net_ip2addr(&ip2)); + return -1; + } + if (IPADDR_IS_V4(&ip1)) { + ip1_arr = &ip1.u.ip4.s_addr; + ip2_arr = &ip2.u.ip4.s_addr; + max_bits = 32; + last_bits = 8; + } else { + ip1_arr = (void *)&ip1.u.ip6; + ip2_arr = (void *)&ip2.u.ip6; + max_bits = 128; + last_bits = 16; + } + + /* make sure initial bits match */ + for (i = 0; i < (max_bits-last_bits)/32; i++) { + if (ip1_arr[i] != ip2_arr[i]) { + e_error(list->dir->event, "IP address range too large: %s-%s", + net_ip2addr(&ip1), net_ip2addr(&ip2)); + return -1; + } + } + i1 = htonl(ip1_arr[i]); + i2 = htonl(ip2_arr[i]); + + for (j = last_bits; j < 32; j++) { + if ((i1 & (1U << j)) != (i2 & (1U << j))) { + e_error(list->dir->event, "IP address range too large: %s-%s", + net_ip2addr(&ip1), net_ip2addr(&ip2)); + return -1; + } + } + + /* create hosts from the final bits */ + do { + ip1_arr[i] = ntohl(i1); + (void)mail_host_add_ip(list, &ip1, tag_name); + i1++; + } while (ip1_arr[i] != ip2_arr[i]); + return 0; +} + +int mail_hosts_parse_and_add(struct mail_host_list *list, + const char *hosts_string) +{ + int ret = 0; + + T_BEGIN { + const char *const *tmp, *p, *host1, *host2; + struct ip_addr ip1, ip2; + + tmp = t_strsplit_spaces(hosts_string, " "); + for (; *tmp != NULL; tmp++) { + const char *tag, *value = *tmp; + + p = strchr(value, '@'); + if (p == NULL) + tag = ""; + else { + value = t_strdup_until(value, p++); + tag = p; + } + p = strchr(value, '-'); + if (p != NULL) { + /* see if this is ip1-ip2 range */ + host1 = t_strdup_until(value, p); + host2 = p + 1; + if (net_addr2ip(host1, &ip1) == 0 && + net_addr2ip(host2, &ip2) == 0) { + if (mail_hosts_add_range(list, ip1, ip2, + tag) < 0) + ret = -1; + continue; + } + } + + if (mail_host_add(list, value, tag) < 0) + ret = -1; + } + } T_END; + + if (array_count(&list->hosts) == 0) { + if (ret < 0) + e_error(list->dir->event, "No valid servers specified"); + else + e_error(list->dir->event, "Empty server list"); + ret = -1; + } + return ret; +} + +const char *mail_host_get_tag(const struct mail_host *host) +{ + return host->tag->name; +} + +void mail_host_set_tag(struct mail_host *host, const char *tag_name) +{ + i_assert(tag_name != NULL); + + /* If the host already has users, forget all of them. Otherwise state + becomes inconsistent, since tag->users won't match + user->host->tag. */ + user_directory_remove_host(host->tag->users, host); + + host->tag = mail_tag_get(host->list, tag_name); + host->list->vhosts_unsorted = TRUE; +} + +void mail_host_set_down(struct mail_host *host, bool down, + time_t timestamp, const char *log_prefix) +{ + if (host->down != down) { + const char *updown = down ? "down" : "up"; + e_info(host->list->dir->event, "%sHost %s changed %s " + "(vhost_count=%u last_updown_change=%ld)", + log_prefix, host->ip_str, updown, + host->vhost_count, (long)host->last_updown_change); + + host->down = down; + host->last_updown_change = timestamp; + host->list->vhosts_unsorted = TRUE; + } +} + +void mail_host_set_vhost_count(struct mail_host *host, unsigned int vhost_count, + const char *log_prefix) +{ + e_info(host->list->dir->event, + "%sHost %s vhost count changed from %u to %u", + log_prefix, host->ip_str, + host->vhost_count, vhost_count); + + host->vhost_count = vhost_count; + host->list->vhosts_unsorted = TRUE; +} + +static void mail_host_free(struct mail_host *host) +{ + i_free(host->hostname); + i_free(host->ip_str); + i_free(host); +} + +void mail_host_remove(struct mail_host *host) +{ + struct mail_host_list *list = host->list; + struct mail_host *const *hosts; + unsigned int i, count; + + hosts = array_get(&list->hosts, &count); + for (i = 0; i < count; i++) { + if (hosts[i] == host) { + array_delete(&host->list->hosts, i, 1); + break; + } + } + mail_host_free(host); + list->vhosts_unsorted = TRUE; +} + +struct mail_host * +mail_host_lookup(struct mail_host_list *list, const struct ip_addr *ip) +{ + struct mail_host *host; + + if (list->vhosts_unsorted) + mail_hosts_sort(list); + + array_foreach_elem(&list->hosts, host) { + if (net_ip_compare(&host->ip, ip)) + return host; + } + return NULL; +} + +static struct mail_host * +mail_host_get_by_hash_ring(struct mail_tag *tag, unsigned int hash) +{ + const struct mail_vhost *vhosts; + unsigned int count, idx; + + vhosts = array_get(&tag->vhosts, &count); + (void)array_bsearch_insert_pos(&tag->vhosts, &hash, + mail_vhost_hash_cmp, &idx); + i_assert(idx <= count); + if (idx == count) { + if (count == 0) + return NULL; + idx = 0; + } + return vhosts[idx % count].host; +} + +struct mail_host * +mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash, + const char *tag_name) +{ + struct mail_tag *tag; + + if (list->vhosts_unsorted) + mail_hosts_sort(list); + + tag = mail_tag_find(list, tag_name); + if (tag == NULL) + return NULL; + + return mail_host_get_by_hash_ring(tag, hash); +} + +void mail_hosts_set_synced(struct mail_host_list *list) +{ + struct mail_host *host; + + array_foreach_elem(&list->hosts, host) + host->desynced = FALSE; +} + +unsigned int mail_hosts_hash(struct mail_host_list *list) +{ + if (list->vhosts_unsorted) + mail_hosts_sort(list); + /* don't retun 0 as hash, since we're using it as "doesn't exist" in + some places. */ + return list->hosts_hash == 0 ? 1 : list->hosts_hash; +} + +bool mail_hosts_have_usable(struct mail_host_list *list) +{ + if (list->vhosts_unsorted) + mail_hosts_sort(list); + return list->have_vhosts; +} + +const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list) +{ + if (list->vhosts_unsorted) + mail_hosts_sort(list); + return &list->hosts; +} + +bool mail_hosts_have_tags(struct mail_host_list *list) +{ + struct mail_tag *tag; + + if (list->vhosts_unsorted) + mail_hosts_sort(list); + + array_foreach_elem(&list->tags, tag) { + if (tag->name[0] != '\0' && array_count(&tag->vhosts) > 0) + return TRUE; + } + return FALSE; +} + +const ARRAY_TYPE(mail_tag) *mail_hosts_get_tags(struct mail_host_list *list) +{ + return &list->tags; +} + +struct mail_host_list * +mail_hosts_init(struct director *dir, + unsigned int user_expire_secs, + user_free_hook_t *user_free_hook) +{ + struct mail_host_list *list; + + list = i_new(struct mail_host_list, 1); + list->dir = dir; + list->user_expire_secs = user_expire_secs; + list->user_free_hook = user_free_hook; + + i_array_init(&list->hosts, 16); + i_array_init(&list->tags, 4); + return list; +} + +void mail_hosts_deinit(struct mail_host_list **_list) +{ + struct mail_host_list *list = *_list; + struct mail_host *host; + struct mail_tag *tag; + + *_list = NULL; + + array_foreach_elem(&list->tags, tag) + mail_tag_free(tag); + array_foreach_elem(&list->hosts, host) + mail_host_free(host); + array_free(&list->hosts); + array_free(&list->tags); + i_free(list); +} + +static struct mail_host * +mail_host_dup(struct mail_host_list *dest_list, const struct mail_host *src) +{ + struct mail_host *dest; + + dest = i_new(struct mail_host, 1); + *dest = *src; + dest->tag = mail_tag_get(dest_list, src->tag->name); + dest->ip_str = i_strdup(src->ip_str); + dest->hostname = i_strdup(src->hostname); + return dest; +} + +struct mail_host_list *mail_hosts_dup(const struct mail_host_list *src) +{ + struct mail_host_list *dest; + struct mail_host *host, *dest_host; + + dest = mail_hosts_init(src->dir, src->user_expire_secs, src->user_free_hook); + array_foreach_elem(&src->hosts, host) { + dest_host = mail_host_dup(dest, host); + array_push_back(&dest->hosts, &dest_host); + } + mail_hosts_sort(dest); + return dest; +} + +void mail_hosts_sort_users(struct mail_host_list *list) +{ + struct mail_tag *tag; + + array_foreach_elem(&list->tags, tag) + user_directory_sort(tag->users); +} |