summaryrefslogtreecommitdiffstats
path: root/src/director/mail-host.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/director/mail-host.c')
-rw-r--r--src/director/mail-host.c560
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);
+}