1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "bsearch-insert-pos.h"
6 #include "crc32.h"
7 #include "md5.h"
8 #include "director.h"
9 #include "user-directory.h"
10 #include "mail-host.h"
11 
12 #define VHOST_MULTIPLIER 100
13 
14 struct mail_host_list {
15 	struct director *dir;
16 	ARRAY_TYPE(mail_tag) tags;
17 	ARRAY_TYPE(mail_host) hosts;
18 	user_free_hook_t *user_free_hook;
19 	unsigned int hosts_hash;
20 	unsigned int user_expire_secs;
21 	bool vhosts_unsorted;
22 	bool have_vhosts;
23 };
24 
25 static int
mail_host_cmp(struct mail_host * const * h1,struct mail_host * const * h2)26 mail_host_cmp(struct mail_host *const *h1, struct mail_host *const *h2)
27 {
28 	return net_ip_cmp(&(*h1)->ip, &(*h2)->ip);
29 }
30 
31 static int
mail_vhost_cmp(const struct mail_vhost * h1,const struct mail_vhost * h2)32 mail_vhost_cmp(const struct mail_vhost *h1, const struct mail_vhost *h2)
33 {
34 	if (h1->hash < h2->hash)
35 		return -1;
36 	else if (h1->hash > h2->hash)
37 		return 1;
38 	/* hash collision. not ideal, but we'll need to keep the order
39 	   consistent across directors so compare the IPs next. */
40 	return net_ip_cmp(&h1->host->ip, &h2->host->ip);
41 }
42 
43 static int
mail_vhost_hash_cmp(const unsigned int * hash,const struct mail_vhost * vhost)44 mail_vhost_hash_cmp(const unsigned int *hash, const struct mail_vhost *vhost)
45 {
46 	if (vhost->hash < *hash)
47 		return 1;
48 	else if (vhost->hash > *hash)
49 		return -1;
50 	else
51 		return 0;
52 }
53 
mail_vhost_add(struct mail_tag * tag,struct mail_host * host)54 static void mail_vhost_add(struct mail_tag *tag, struct mail_host *host)
55 {
56 	struct mail_vhost *vhost;
57 	struct md5_context md5_ctx, md5_ctx2;
58 	unsigned char md5[MD5_RESULTLEN];
59 	char num_str[MAX_INT_STRLEN];
60 	unsigned int i, j;
61 
62 	if (host->down || host->tag != tag)
63 		return;
64 
65 	md5_init(&md5_ctx);
66 	md5_update(&md5_ctx, host->ip_str, strlen(host->ip_str));
67 
68 	for (i = 0; i < host->vhost_count; i++) {
69 		md5_ctx2 = md5_ctx;
70 		i_snprintf(num_str, sizeof(num_str), "-%u", i);
71 		md5_update(&md5_ctx2, num_str, strlen(num_str));
72 		md5_final(&md5_ctx2, md5);
73 
74 		vhost = array_append_space(&tag->vhosts);
75 		vhost->host = host;
76 		for (j = 0; j < sizeof(vhost->hash); j++)
77 			vhost->hash = (vhost->hash << CHAR_BIT) | md5[j];
78 	}
79 }
80 
81 static void
mail_tag_vhosts_sort_ring(struct mail_host_list * list,struct mail_tag * tag)82 mail_tag_vhosts_sort_ring(struct mail_host_list *list, struct mail_tag *tag)
83 {
84 	struct mail_host *host;
85 
86 	/* rebuild vhosts */
87 	array_clear(&tag->vhosts);
88 	array_foreach_elem(&list->hosts, host)
89 		mail_vhost_add(tag, host);
90 	array_sort(&tag->vhosts, mail_vhost_cmp);
91 }
92 
93 static void
mail_hosts_sort(struct mail_host_list * list)94 mail_hosts_sort(struct mail_host_list *list)
95 {
96 	struct mail_host *host;
97 	struct mail_tag *tag;
98 	uint32_t num;
99 
100 	array_sort(&list->hosts, mail_host_cmp);
101 
102 	list->have_vhosts = FALSE;
103 	array_foreach_elem(&list->tags, tag) {
104 		mail_tag_vhosts_sort_ring(list, tag);
105 		if (array_count(&tag->vhosts) > 0)
106 			list->have_vhosts = TRUE;
107 	}
108 	list->vhosts_unsorted = FALSE;
109 
110 	/* recalculate the hosts_hash */
111 	list->hosts_hash = 0;
112 	array_foreach_elem(&list->hosts, host) {
113 		num = (host->down ? 1 : 0) ^ host->vhost_count;
114 		list->hosts_hash = crc32_data_more(list->hosts_hash,
115 						   &num, sizeof(num));
116 		num = net_ip_hash(&host->ip);
117 		list->hosts_hash = crc32_data_more(list->hosts_hash,
118 						   &num, sizeof(num));
119 		list->hosts_hash = crc32_str_more(list->hosts_hash,
120 						  host->tag->name);
121 	}
122 }
123 
124 struct mail_tag *
mail_tag_find(struct mail_host_list * list,const char * tag_name)125 mail_tag_find(struct mail_host_list *list, const char *tag_name)
126 {
127 	struct mail_tag *tag;
128 
129 	array_foreach_elem(&list->tags, tag) {
130 		if (strcmp(tag->name, tag_name) == 0)
131 			return tag;
132 	}
133 	return NULL;
134 }
135 
136 static struct mail_tag *
mail_tag_get(struct mail_host_list * list,const char * tag_name)137 mail_tag_get(struct mail_host_list *list, const char *tag_name)
138 {
139 	struct mail_tag *tag;
140 
141 	tag = mail_tag_find(list, tag_name);
142 	if (tag == NULL) {
143 		tag = i_new(struct mail_tag, 1);
144 		tag->name = i_strdup(tag_name);
145 		i_array_init(&tag->vhosts, 16*VHOST_MULTIPLIER);
146 		tag->users = user_directory_init(list->dir,
147 						 list->user_expire_secs,
148 						 list->user_free_hook);
149 		array_push_back(&list->tags, &tag);
150 	}
151 	return tag;
152 }
153 
mail_tag_free(struct mail_tag * tag)154 static void mail_tag_free(struct mail_tag *tag)
155 {
156 	user_directory_deinit(&tag->users);
157 	array_free(&tag->vhosts);
158 	i_free(tag->name);
159 	i_free(tag);
160 }
161 
162 struct mail_host *
mail_host_add_ip(struct mail_host_list * list,const struct ip_addr * ip,const char * tag_name)163 mail_host_add_ip(struct mail_host_list *list, const struct ip_addr *ip,
164 		 const char *tag_name)
165 {
166 	struct mail_host *host;
167 
168 	i_assert(tag_name != NULL);
169 
170 	host = i_new(struct mail_host, 1);
171 	host->list = list;
172 	host->vhost_count = VHOST_MULTIPLIER;
173 	host->ip = *ip;
174 	host->ip_str = i_strdup(net_ip2addr(ip));
175 	host->tag = mail_tag_get(list, tag_name);
176 	array_push_back(&list->hosts, &host);
177 
178 	list->vhosts_unsorted = TRUE;
179 	return host;
180 }
181 
182 struct mail_host *
mail_host_add_hostname(struct mail_host_list * list,const char * hostname,const struct ip_addr * ip,const char * tag_name)183 mail_host_add_hostname(struct mail_host_list *list, const char *hostname,
184 		       const struct ip_addr *ip, const char *tag_name)
185 {
186 	struct mail_host *host;
187 
188 	host = mail_host_add_ip(list, ip, tag_name);
189 	if (hostname != NULL && hostname[0] != '\0')
190 		host->hostname = i_strdup(hostname);
191 	return host;
192 }
193 
194 static int
mail_host_add(struct mail_host_list * list,const char * hostname,const char * tag_name)195 mail_host_add(struct mail_host_list *list, const char *hostname,
196 	      const char *tag_name)
197 {
198 	struct ip_addr *ips, ip;
199 	unsigned int i, ips_count;
200 
201 	if (net_addr2ip(hostname, &ip) == 0) {
202 		(void)mail_host_add_ip(list, &ip, tag_name);
203 		return 0;
204 	}
205 
206 	if (net_gethostbyname(hostname, &ips, &ips_count) < 0) {
207 		e_error(list->dir->event, "Unknown mail host: %s", hostname);
208 		return -1;
209 	}
210 
211 	for (i = 0; i < ips_count; i++)
212 		(void)mail_host_add_hostname(list, hostname, &ips[i], tag_name);
213 	return 0;
214 }
215 
216 static int
mail_hosts_add_range(struct mail_host_list * list,struct ip_addr ip1,struct ip_addr ip2,const char * tag_name)217 mail_hosts_add_range(struct mail_host_list *list,
218 		     struct ip_addr ip1, struct ip_addr ip2,
219 		     const char *tag_name)
220 {
221 	uint32_t *ip1_arr, *ip2_arr;
222 	uint32_t i1, i2;
223 	unsigned int i, j, max_bits, last_bits;
224 
225 	if (ip1.family != ip2.family) {
226 		e_error(list->dir->event, "IP address family mismatch: %s vs %s",
227 			net_ip2addr(&ip1), net_ip2addr(&ip2));
228 		return -1;
229 	}
230 	if (net_ip_cmp(&ip1, &ip2) > 0) {
231 		e_error(list->dir->event, "IP addresses reversed: %s-%s",
232 			net_ip2addr(&ip1), net_ip2addr(&ip2));
233 		return -1;
234 	}
235 	if (IPADDR_IS_V4(&ip1)) {
236 		ip1_arr = &ip1.u.ip4.s_addr;
237 		ip2_arr = &ip2.u.ip4.s_addr;
238 		max_bits = 32;
239 		last_bits = 8;
240 	} else {
241 		ip1_arr = (void *)&ip1.u.ip6;
242 		ip2_arr = (void *)&ip2.u.ip6;
243 		max_bits = 128;
244 		last_bits = 16;
245 	}
246 
247 	/* make sure initial bits match */
248 	for (i = 0; i < (max_bits-last_bits)/32; i++) {
249 		if (ip1_arr[i] != ip2_arr[i]) {
250 			e_error(list->dir->event, "IP address range too large: %s-%s",
251 				net_ip2addr(&ip1), net_ip2addr(&ip2));
252 			return -1;
253 		}
254 	}
255 	i1 = htonl(ip1_arr[i]);
256 	i2 = htonl(ip2_arr[i]);
257 
258 	for (j = last_bits; j < 32; j++) {
259 		if ((i1 & (1U << j)) != (i2 & (1U << j))) {
260 			e_error(list->dir->event, "IP address range too large: %s-%s",
261 				net_ip2addr(&ip1), net_ip2addr(&ip2));
262 			return -1;
263 		}
264 	}
265 
266 	/* create hosts from the final bits */
267 	do {
268 		ip1_arr[i] = ntohl(i1);
269 		(void)mail_host_add_ip(list, &ip1, tag_name);
270 		i1++;
271 	} while (ip1_arr[i] != ip2_arr[i]);
272 	return 0;
273 }
274 
mail_hosts_parse_and_add(struct mail_host_list * list,const char * hosts_string)275 int mail_hosts_parse_and_add(struct mail_host_list *list,
276 			     const char *hosts_string)
277 {
278 	int ret = 0;
279 
280 	T_BEGIN {
281 		const char *const *tmp, *p, *host1, *host2;
282 		struct ip_addr ip1, ip2;
283 
284 		tmp = t_strsplit_spaces(hosts_string, " ");
285 		for (; *tmp != NULL; tmp++) {
286 			const char *tag, *value = *tmp;
287 
288 			p = strchr(value, '@');
289 			if (p == NULL)
290 				tag = "";
291 			else {
292 				value = t_strdup_until(value, p++);
293 				tag = p;
294 			}
295 			p = strchr(value, '-');
296 			if (p != NULL) {
297 				/* see if this is ip1-ip2 range */
298 				host1 = t_strdup_until(value, p);
299 				host2 = p + 1;
300 				if (net_addr2ip(host1, &ip1) == 0 &&
301 				    net_addr2ip(host2, &ip2) == 0) {
302 					if (mail_hosts_add_range(list, ip1, ip2,
303 								 tag) < 0)
304 						ret = -1;
305 					continue;
306 				}
307 			}
308 
309 			if (mail_host_add(list, value, tag) < 0)
310 				ret = -1;
311 		}
312 	} T_END;
313 
314 	if (array_count(&list->hosts) == 0) {
315 		if (ret < 0)
316 			e_error(list->dir->event, "No valid servers specified");
317 		else
318 			e_error(list->dir->event, "Empty server list");
319 		ret = -1;
320 	}
321 	return ret;
322 }
323 
mail_host_get_tag(const struct mail_host * host)324 const char *mail_host_get_tag(const struct mail_host *host)
325 {
326 	return host->tag->name;
327 }
328 
mail_host_set_tag(struct mail_host * host,const char * tag_name)329 void mail_host_set_tag(struct mail_host *host, const char *tag_name)
330 {
331 	i_assert(tag_name != NULL);
332 
333 	host->tag = mail_tag_get(host->list, tag_name);
334 	host->list->vhosts_unsorted = TRUE;
335 }
336 
mail_host_set_down(struct mail_host * host,bool down,time_t timestamp,const char * log_prefix)337 void mail_host_set_down(struct mail_host *host, bool down,
338 			time_t timestamp, const char *log_prefix)
339 {
340 	if (host->down != down) {
341 		const char *updown = down ? "down" : "up";
342 		e_info(host->list->dir->event, "%sHost %s changed %s "
343 		       "(vhost_count=%u last_updown_change=%ld)",
344 		       log_prefix, host->ip_str, updown,
345 		       host->vhost_count, (long)host->last_updown_change);
346 
347 		host->down = down;
348 		host->last_updown_change = timestamp;
349 		host->list->vhosts_unsorted = TRUE;
350 	}
351 }
352 
mail_host_set_vhost_count(struct mail_host * host,unsigned int vhost_count,const char * log_prefix)353 void mail_host_set_vhost_count(struct mail_host *host, unsigned int vhost_count,
354 			       const char *log_prefix)
355 {
356 	e_info(host->list->dir->event,
357 	       "%sHost %s vhost count changed from %u to %u",
358 	       log_prefix, host->ip_str,
359 	       host->vhost_count, vhost_count);
360 
361 	host->vhost_count = vhost_count;
362 	host->list->vhosts_unsorted = TRUE;
363 }
364 
mail_host_free(struct mail_host * host)365 static void mail_host_free(struct mail_host *host)
366 {
367 	i_free(host->hostname);
368 	i_free(host->ip_str);
369 	i_free(host);
370 }
371 
mail_host_remove(struct mail_host * host)372 void mail_host_remove(struct mail_host *host)
373 {
374 	struct mail_host_list *list = host->list;
375 	struct mail_host *const *hosts;
376 	unsigned int i, count;
377 
378 	hosts = array_get(&list->hosts, &count);
379 	for (i = 0; i < count; i++) {
380 		if (hosts[i] == host) {
381 			array_delete(&host->list->hosts, i, 1);
382 			break;
383 		}
384 	}
385 	mail_host_free(host);
386 	list->vhosts_unsorted = TRUE;
387 }
388 
389 struct mail_host *
mail_host_lookup(struct mail_host_list * list,const struct ip_addr * ip)390 mail_host_lookup(struct mail_host_list *list, const struct ip_addr *ip)
391 {
392 	struct mail_host *host;
393 
394 	if (list->vhosts_unsorted)
395 		mail_hosts_sort(list);
396 
397 	array_foreach_elem(&list->hosts, host) {
398 		if (net_ip_compare(&host->ip, ip))
399 			return host;
400 	}
401 	return NULL;
402 }
403 
404 static struct mail_host *
mail_host_get_by_hash_ring(struct mail_tag * tag,unsigned int hash)405 mail_host_get_by_hash_ring(struct mail_tag *tag, unsigned int hash)
406 {
407 	const struct mail_vhost *vhosts;
408 	unsigned int count, idx;
409 
410 	vhosts = array_get(&tag->vhosts, &count);
411 	(void)array_bsearch_insert_pos(&tag->vhosts, &hash,
412 				       mail_vhost_hash_cmp, &idx);
413 	i_assert(idx <= count);
414 	if (idx == count) {
415 		if (count == 0)
416 			return NULL;
417 		idx = 0;
418 	}
419 	return vhosts[idx % count].host;
420 }
421 
422 struct mail_host *
mail_host_get_by_hash(struct mail_host_list * list,unsigned int hash,const char * tag_name)423 mail_host_get_by_hash(struct mail_host_list *list, unsigned int hash,
424 		      const char *tag_name)
425 {
426 	struct mail_tag *tag;
427 
428 	if (list->vhosts_unsorted)
429 		mail_hosts_sort(list);
430 
431 	tag = mail_tag_find(list, tag_name);
432 	if (tag == NULL)
433 		return NULL;
434 
435 	return mail_host_get_by_hash_ring(tag, hash);
436 }
437 
mail_hosts_set_synced(struct mail_host_list * list)438 void mail_hosts_set_synced(struct mail_host_list *list)
439 {
440 	struct mail_host *host;
441 
442 	array_foreach_elem(&list->hosts, host)
443 		host->desynced = FALSE;
444 }
445 
mail_hosts_hash(struct mail_host_list * list)446 unsigned int mail_hosts_hash(struct mail_host_list *list)
447 {
448 	if (list->vhosts_unsorted)
449 		mail_hosts_sort(list);
450 	/* don't retun 0 as hash, since we're using it as "doesn't exist" in
451 	   some places. */
452 	return list->hosts_hash == 0 ? 1 : list->hosts_hash;
453 }
454 
mail_hosts_have_usable(struct mail_host_list * list)455 bool mail_hosts_have_usable(struct mail_host_list *list)
456 {
457 	if (list->vhosts_unsorted)
458 		mail_hosts_sort(list);
459 	return list->have_vhosts;
460 }
461 
ARRAY_TYPE(mail_host)462 const ARRAY_TYPE(mail_host) *mail_hosts_get(struct mail_host_list *list)
463 {
464 	if (list->vhosts_unsorted)
465 		mail_hosts_sort(list);
466 	return &list->hosts;
467 }
468 
mail_hosts_have_tags(struct mail_host_list * list)469 bool mail_hosts_have_tags(struct mail_host_list *list)
470 {
471 	struct mail_tag *tag;
472 
473 	if (list->vhosts_unsorted)
474 		mail_hosts_sort(list);
475 
476 	array_foreach_elem(&list->tags, tag) {
477 		if (tag->name[0] != '\0' && array_count(&tag->vhosts) > 0)
478 			return TRUE;
479 	}
480 	return FALSE;
481 }
482 
ARRAY_TYPE(mail_tag)483 const ARRAY_TYPE(mail_tag) *mail_hosts_get_tags(struct mail_host_list *list)
484 {
485 	return &list->tags;
486 }
487 
488 struct mail_host_list *
mail_hosts_init(struct director * dir,unsigned int user_expire_secs,user_free_hook_t * user_free_hook)489 mail_hosts_init(struct director *dir,
490 		unsigned int user_expire_secs,
491 		user_free_hook_t *user_free_hook)
492 {
493 	struct mail_host_list *list;
494 
495 	list = i_new(struct mail_host_list, 1);
496 	list->dir = dir;
497 	list->user_expire_secs = user_expire_secs;
498 	list->user_free_hook = user_free_hook;
499 
500 	i_array_init(&list->hosts, 16);
501 	i_array_init(&list->tags, 4);
502 	return list;
503 }
504 
mail_hosts_deinit(struct mail_host_list ** _list)505 void mail_hosts_deinit(struct mail_host_list **_list)
506 {
507 	struct mail_host_list *list = *_list;
508 	struct mail_host *host;
509 	struct mail_tag *tag;
510 
511 	*_list = NULL;
512 
513 	array_foreach_elem(&list->tags, tag)
514 		mail_tag_free(tag);
515 	array_foreach_elem(&list->hosts, host)
516 		mail_host_free(host);
517 	array_free(&list->hosts);
518 	array_free(&list->tags);
519 	i_free(list);
520 }
521 
522 static struct mail_host *
mail_host_dup(struct mail_host_list * dest_list,const struct mail_host * src)523 mail_host_dup(struct mail_host_list *dest_list, const struct mail_host *src)
524 {
525 	struct mail_host *dest;
526 
527 	dest = i_new(struct mail_host, 1);
528 	*dest = *src;
529 	dest->tag = mail_tag_get(dest_list, src->tag->name);
530 	dest->ip_str = i_strdup(src->ip_str);
531 	dest->hostname = i_strdup(src->hostname);
532 	return dest;
533 }
534 
mail_hosts_dup(const struct mail_host_list * src)535 struct mail_host_list *mail_hosts_dup(const struct mail_host_list *src)
536 {
537 	struct mail_host_list *dest;
538 	struct mail_host *host, *dest_host;
539 
540 	dest = mail_hosts_init(src->dir, src->user_expire_secs, src->user_free_hook);
541 	array_foreach_elem(&src->hosts, host) {
542 		dest_host = mail_host_dup(dest, host);
543 		array_push_back(&dest->hosts, &dest_host);
544 	}
545 	mail_hosts_sort(dest);
546 	return dest;
547 }
548 
mail_hosts_sort_users(struct mail_host_list * list)549 void mail_hosts_sort_users(struct mail_host_list *list)
550 {
551 	struct mail_tag *tag;
552 
553 	array_foreach_elem(&list->tags, tag)
554 		user_directory_sort(tag->users);
555 }
556