1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "net.h"
5 #include "str.h"
6 #include "hash.h"
7 #include "llist.h"
8 #include "array.h"
9 #include "ioloop.h"
10 #include "istream.h"
11 #include "ostream.h"
12 #include "connection.h"
13 #include "dns-lookup.h"
14 #include "iostream-rawlog.h"
15 #include "iostream-ssl.h"
16 #include "http-url.h"
17 
18 #include "http-client-private.h"
19 
20 /* Structure:
21 
22    http_client_context:
23 
24    Shared context between multiple independent HTTP clients. This allows host
25    name lookup data, peer status and idle connections to be shared between
26    clients.
27 
28    http_client:
29 
30    Acts much like a browser; it is not dedicated to a single host. Client can
31    accept requests to different hosts, which can be served at different IPs.
32    Redirects are handled in the background by making a new connection.
33    Connections to new hosts are created once needed for servicing a request.
34 
35    http_client_request:
36 
37    The request semantics are similar to imapc commands. Create a request,
38    optionally modify some aspects of it, and finally submit it. Once finished,
39    a callback is called with the returned response.
40 
41    http_client_host_shared:
42 
43    We maintain a 'cache' of hosts for which we have looked up IPs. This cache
44    is maintained in client context, so multiple clients can share it. One host
45    can have multiple IPs.
46 
47    http_client_host:
48 
49    A host object maintains client-specific information for a host. The queues
50    that the client has for this host are listed here. For one host, there is a
51    separate queue for each used server port.
52 
53    http_client_queue:
54 
55    Requests are queued in a queue object. These queues are maintained for each
56    host:port target and listed in the host object. The queue object is
57    responsible for starting connection attempts to TCP port at the various IPs
58    known for the host.
59 
60    http_client_peer_pool:
61 
62    A peer pool lists all unused and pending connections to a peer, grouped by
63    a compatible configuration, e.g. in terms of SSL and rawlog. Once needed,
64    peers can claim/request an existing/new connection from the pool.
65 
66    http_client_peer_shared:
67 
68    The shared peer object records state information about a peer, which is a
69    service access point (ip:port or unix socket path). The peer object also
70    maintains lists of idle and pending connections to this service, which are
71    grouped in pools with compatible client configuration. Each client has a
72    separate (non-shared) peer object for client-specific state information.
73 
74    http_client_peer:
75 
76    A peer object maintains client-specific information for a peer. Claimed
77    connections are dedicated to one peer (and therefore one client).
78 
79    http-client-connection:
80 
81    This is an actual connection to a server. Once a connection is ready to
82    handle requests, it claims a request from a queue object. One connection can
83    service multiple hosts and one host can have multiple associated connections,
84    possibly to different ips and ports.
85 
86  */
87 
88 static struct event_category event_category_http_client = {
89 	.name = "http-client"
90 };
91 
92 static struct http_client_context *http_client_global_context = NULL;
93 
94 static void
95 http_client_context_add_client(struct http_client_context *cctx,
96 			       struct http_client *client);
97 static void
98 http_client_context_remove_client(struct http_client_context *cctx,
99 				  struct http_client *client);
100 
101 /*
102  * Client
103  */
104 
105 struct http_client *
http_client_init_shared(struct http_client_context * cctx,const struct http_client_settings * set)106 http_client_init_shared(struct http_client_context *cctx,
107 	const struct http_client_settings *set)
108 {
109 	static unsigned int id = 0;
110 	struct http_client *client;
111 	const char *log_prefix;
112 	pool_t pool;
113 	size_t pool_size;
114 
115 	pool_size = (set != NULL && set->ssl != NULL) ? 8192 : 1024; /* certs will be >4K */
116 	pool = pool_alloconly_create("http client", pool_size);
117 	client = p_new(pool, struct http_client, 1);
118 	client->pool = pool;
119 	client->ioloop = current_ioloop;
120 
121 	/* create private context if none is provided */
122 	id++;
123 	if (cctx != NULL) {
124 		client->cctx = cctx;
125 		http_client_context_ref(cctx);
126 		log_prefix = t_strdup_printf("http-client[%u]: ", id);
127 	} else {
128 		i_assert(set != NULL);
129 		client->cctx = cctx = http_client_context_create(set);
130 		log_prefix = "http-client: ";
131 	}
132 
133 	struct event *parent_event;
134 	if (set != NULL && set->event_parent != NULL)
135 		parent_event = set->event_parent;
136 	else if (cctx->event == NULL)
137 		parent_event = NULL;
138 	else {
139 		/* FIXME: we could use cctx->event, but it already has a log
140 		   prefix that we don't want.. should we update event API to
141 		   support replacing parent's log prefix? */
142 		parent_event = event_get_parent(cctx->event);
143 	}
144 	client->event = event_create(parent_event);
145 	event_add_category(client->event, &event_category_http_client);
146 	event_set_forced_debug(client->event,
147 			       (set != NULL && set->debug) || (cctx != NULL && cctx->set.debug));
148 	event_set_append_log_prefix(client->event, log_prefix);
149 
150 	/* merge provided settings with context defaults */
151 	client->set = cctx->set;
152 	if (set != NULL) {
153 		client->set.dns_client = set->dns_client;
154 		client->set.dns_client_socket_path =
155 			p_strdup_empty(pool, set->dns_client_socket_path);
156 		client->set.dns_ttl_msecs = set->dns_ttl_msecs;
157 
158 		if (set->user_agent != NULL && *set->user_agent != '\0')
159 			client->set.user_agent = p_strdup_empty(pool, set->user_agent);
160 		if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0')
161 			client->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir);
162 
163 		if (set->ssl != NULL)
164 			client->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);
165 
166 		if (set->proxy_socket_path != NULL && *set->proxy_socket_path != '\0') {
167 			client->set.proxy_socket_path = p_strdup(pool, set->proxy_socket_path);
168 			client->set.proxy_url = NULL;
169 		} else if (set->proxy_url != NULL) {
170 			client->set.proxy_url = http_url_clone(pool, set->proxy_url);
171 			client->set.proxy_socket_path = NULL;
172 		}
173 		if (set->proxy_username != NULL && *set->proxy_username != '\0') {
174 			client->set.proxy_username = p_strdup_empty(pool, set->proxy_username);
175 			client->set.proxy_password = p_strdup(pool, set->proxy_password);
176 		} else if (set->proxy_url != NULL && set->proxy_url->user != NULL &&
177 			*set->proxy_url->user != '\0') {
178 			client->set.proxy_username =
179 				p_strdup_empty(pool, set->proxy_url->user);
180 			client->set.proxy_password =
181 				p_strdup(pool, set->proxy_url->password);
182 		}
183 
184 		if (set->max_idle_time_msecs > 0)
185 			client->set.max_idle_time_msecs = set->max_idle_time_msecs;
186 		if (set->max_parallel_connections > 0)
187 			client->set.max_parallel_connections = set->max_parallel_connections;
188 		if (set->max_pipelined_requests > 0)
189 			client->set.max_pipelined_requests = set->max_pipelined_requests;
190 		if (set->max_attempts > 0)
191 			client->set.max_attempts = set->max_attempts;
192 		if (set->max_connect_attempts > 0)
193 			client->set.max_connect_attempts = set->max_connect_attempts;
194 		if (set->connect_backoff_time_msecs > 0) {
195 			client->set.connect_backoff_time_msecs =
196 				set->connect_backoff_time_msecs;
197 		}
198 		if (set->connect_backoff_max_time_msecs > 0) {
199 			client->set.connect_backoff_max_time_msecs =
200 				set->connect_backoff_max_time_msecs;
201 		}
202 		client->set.no_auto_redirect =
203 			client->set.no_auto_redirect || set->no_auto_redirect;
204 		client->set.no_auto_retry =
205 			client->set.no_auto_retry || set->no_auto_retry;
206 		client->set.no_ssl_tunnel =
207 			client->set.no_ssl_tunnel || set->no_ssl_tunnel;
208 		if (set->max_redirects > 0)
209 			client->set.max_redirects = set->max_redirects;
210 		if (set->request_absolute_timeout_msecs > 0) {
211 			client->set.request_absolute_timeout_msecs =
212 				set->request_absolute_timeout_msecs;
213 		}
214 		if (set->request_timeout_msecs > 0)
215 			client->set.request_timeout_msecs = set->request_timeout_msecs;
216 		if (set->connect_timeout_msecs > 0)
217 			client->set.connect_timeout_msecs = set->connect_timeout_msecs;
218 		if (set->soft_connect_timeout_msecs > 0)
219 			client->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
220 		if (set->socket_send_buffer_size > 0)
221 			client->set.socket_send_buffer_size = set->socket_send_buffer_size;
222 		if (set->socket_recv_buffer_size > 0)
223 			client->set.socket_recv_buffer_size = set->socket_recv_buffer_size;
224 		if (set->max_auto_retry_delay > 0)
225 			client->set.max_auto_retry_delay = set->max_auto_retry_delay;
226 		client->set.debug = client->set.debug || set->debug;
227 	}
228 
229 	i_array_init(&client->delayed_failing_requests, 1);
230 
231 	http_client_context_add_client(cctx, client);
232 
233 	return client;
234 }
235 
236 struct http_client *
http_client_init(const struct http_client_settings * set)237 http_client_init(const struct http_client_settings *set)
238 {
239 	return http_client_init_shared(http_client_get_global_context(), set);
240 }
241 
242 struct http_client *
http_client_init_private(const struct http_client_settings * set)243 http_client_init_private(const struct http_client_settings *set)
244 {
245 	return http_client_init_shared(NULL, set);
246 }
247 
http_client_deinit(struct http_client ** _client)248 void http_client_deinit(struct http_client **_client)
249 {
250 	struct http_client *client = *_client;
251 	struct http_client_request *req;
252 	struct http_client_host *host;
253 	struct http_client_peer *peer;
254 
255 	*_client = NULL;
256 
257 	/* destroy requests without calling callbacks */
258 	req = client->requests_list;
259 	while (req != NULL) {
260 		struct http_client_request *next_req = req->next;
261 		http_client_request_destroy(&req);
262 		req = next_req;
263 	}
264 	i_assert(client->requests_count == 0);
265 
266 	/* free peers */
267 	while (client->peers_list != NULL) {
268 		peer = client->peers_list;
269 		http_client_peer_close(&peer);
270 	}
271 
272 	/* free hosts */
273 	while (client->hosts_list != NULL) {
274 		host = client->hosts_list;
275 		http_client_host_free(&host);
276 	}
277 
278 	array_free(&client->delayed_failing_requests);
279 	timeout_remove(&client->to_failing_requests);
280 
281 	if (client->ssl_ctx != NULL)
282 		ssl_iostream_context_unref(&client->ssl_ctx);
283 	http_client_context_remove_client(client->cctx, client);
284 	http_client_context_unref(&client->cctx);
285 	event_unref(&client->event);
286 	pool_unref(&client->pool);
287 }
288 
http_client_do_switch_ioloop(struct http_client * client)289 static void http_client_do_switch_ioloop(struct http_client *client)
290 {
291 	struct http_client_peer *peer;
292 	struct http_client_host *host;
293 
294 	/* move peers */
295 	for (peer = client->peers_list; peer != NULL;
296 		peer = peer->client_next)
297 		http_client_peer_switch_ioloop(peer);
298 
299 	/* move hosts/queues */
300 	for (host = client->hosts_list; host != NULL;
301 		host = host->client_next)
302 		http_client_host_switch_ioloop(host);
303 
304 	/* move timeouts */
305 	if (client->to_failing_requests != NULL) {
306 		client->to_failing_requests =
307 			io_loop_move_timeout(&client->to_failing_requests);
308 	}
309 }
310 
http_client_switch_ioloop(struct http_client * client)311 struct ioloop *http_client_switch_ioloop(struct http_client *client)
312 {
313 	struct ioloop *prev_ioloop = client->ioloop;
314 
315 	client->ioloop = current_ioloop;
316 
317 	http_client_do_switch_ioloop(client);
318 	http_client_context_switch_ioloop(client->cctx);
319 
320 	return prev_ioloop;
321 }
322 
http_client_wait(struct http_client * client)323 void http_client_wait(struct http_client *client)
324 {
325 	struct ioloop *prev_ioloop, *client_ioloop, *prev_client_ioloop;
326 
327 	if (client->requests_count == 0)
328 		return;
329 
330 	prev_ioloop = current_ioloop;
331 	client_ioloop = io_loop_create();
332 	prev_client_ioloop = http_client_switch_ioloop(client);
333 	if (client->set.dns_client != NULL)
334 		dns_client_switch_ioloop(client->set.dns_client);
335 	/* either we're waiting for network I/O or we're getting out of a
336 	   callback using timeout_add_short(0) */
337 	i_assert(io_loop_have_ios(client_ioloop) ||
338 		 io_loop_have_immediate_timeouts(client_ioloop));
339 
340 	client->waiting = TRUE;
341 	do {
342 		e_debug(client->event,
343 			"Waiting for %d requests to finish", client->requests_count);
344 		io_loop_run(client_ioloop);
345 	} while (client->requests_count > 0);
346 	client->waiting = FALSE;
347 
348 	e_debug(client->event, "All requests finished");
349 
350 	if (prev_client_ioloop != NULL)
351 		io_loop_set_current(prev_client_ioloop);
352 	else
353 		io_loop_set_current(prev_ioloop);
354 	(void)http_client_switch_ioloop(client);
355 	if (client->set.dns_client != NULL)
356 		dns_client_switch_ioloop(client->set.dns_client);
357 	io_loop_set_current(client_ioloop);
358 	io_loop_destroy(&client_ioloop);
359 }
360 
http_client_get_pending_request_count(struct http_client * client)361 unsigned int http_client_get_pending_request_count(struct http_client *client)
362 {
363 	return client->requests_count;
364 }
365 
http_client_init_ssl_ctx(struct http_client * client,const char ** error_r)366 int http_client_init_ssl_ctx(struct http_client *client, const char **error_r)
367 {
368 	const char *error;
369 
370 	if (client->ssl_ctx != NULL)
371 		return 0;
372 
373 	if (client->set.ssl == NULL) {
374 		*error_r = "Requested https connection, but no SSL settings given";
375 		return -1;
376 	}
377 	if (ssl_iostream_client_context_cache_get(client->set.ssl, &client->ssl_ctx, &error) < 0) {
378 		*error_r = t_strdup_printf("Couldn't initialize SSL context: %s",
379 					   error);
380 		return -1;
381 	}
382 	return 0;
383 }
384 
385 /*
386  * Delayed request errors
387  */
388 
389 static void
http_client_handle_request_errors(struct http_client * client)390 http_client_handle_request_errors(struct http_client *client)
391 {
392 	struct http_client_request *req;
393 
394 	timeout_remove(&client->to_failing_requests);
395 
396 	array_foreach_elem(&client->delayed_failing_requests, req) {
397 		i_assert(req->refcount == 1);
398 		http_client_request_error_delayed(&req);
399 	}
400 	array_clear(&client->delayed_failing_requests);
401 }
402 
http_client_delay_request_error(struct http_client * client,struct http_client_request * req)403 void http_client_delay_request_error(struct http_client *client,
404 	struct http_client_request *req)
405 {
406 	if (client->to_failing_requests == NULL) {
407 		client->to_failing_requests =
408 			timeout_add_short_to(client->ioloop, 0,
409 				http_client_handle_request_errors, client);
410 	}
411 	array_push_back(&client->delayed_failing_requests, &req);
412 }
413 
http_client_remove_request_error(struct http_client * client,struct http_client_request * req)414 void http_client_remove_request_error(struct http_client *client,
415 	struct http_client_request *req)
416 {
417 	struct http_client_request *const *reqs;
418 	unsigned int i, count;
419 
420 	reqs = array_get(&client->delayed_failing_requests, &count);
421 	for (i = 0; i < count; i++) {
422 		if (reqs[i] == req) {
423 			array_delete(&client->delayed_failing_requests, i, 1);
424 			return;
425 		}
426 	}
427 }
428 
429 /*
430  * Client shared context
431  */
432 
433 struct http_client_context *
http_client_context_create(const struct http_client_settings * set)434 http_client_context_create(const struct http_client_settings *set)
435 {
436 	struct http_client_context *cctx;
437 	pool_t pool;
438 	size_t pool_size;
439 
440 	pool_size = (set->ssl != NULL) ? 8192 : 1024; /* certs will be >4K */
441 	pool = pool_alloconly_create("http client context", pool_size);
442 	cctx = p_new(pool, struct http_client_context, 1);
443 	cctx->pool = pool;
444 	cctx->refcount = 1;
445 	cctx->ioloop = current_ioloop;
446 
447 	cctx->event = event_create(set->event_parent);
448 	event_add_category(cctx->event, &event_category_http_client);
449 	event_set_forced_debug(cctx->event, set->debug);
450 	event_set_append_log_prefix(cctx->event, "http-client: ");
451 
452 	cctx->set.dns_client = set->dns_client;
453 	cctx->set.dns_client_socket_path =
454 		p_strdup_empty(pool, set->dns_client_socket_path);
455 	cctx->set.dns_ttl_msecs = (set->dns_ttl_msecs == 0 ?
456 			HTTP_CLIENT_DEFAULT_DNS_TTL_MSECS : set->dns_ttl_msecs);
457 	cctx->set.user_agent = p_strdup_empty(pool, set->user_agent);
458 	cctx->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir);
459 
460 	if (set->ssl != NULL)
461 		cctx->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);
462 
463 	if (set->proxy_socket_path != NULL && *set->proxy_socket_path != '\0') {
464 		cctx->set.proxy_socket_path = p_strdup(pool, set->proxy_socket_path);
465 	} else if (set->proxy_url != NULL) {
466 		cctx->set.proxy_url = http_url_clone(pool, set->proxy_url);
467 	}
468 	if (set->proxy_username != NULL && *set->proxy_username != '\0') {
469 		cctx->set.proxy_username = p_strdup_empty(pool, set->proxy_username);
470 		cctx->set.proxy_password = p_strdup(pool, set->proxy_password);
471 	} else if (set->proxy_url != NULL) {
472 		cctx->set.proxy_username =
473 			p_strdup_empty(pool, set->proxy_url->user);
474 		cctx->set.proxy_password =
475 			p_strdup(pool, set->proxy_url->password);
476 	}
477 
478 	cctx->set.max_idle_time_msecs = set->max_idle_time_msecs;
479 	cctx->set.max_pipelined_requests =
480 		(set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1);
481 	cctx->set.max_parallel_connections =
482 		(set->max_parallel_connections > 0 ? set->max_parallel_connections : 1);
483 	cctx->set.max_attempts = set->max_attempts;
484 	cctx->set.max_connect_attempts = set->max_connect_attempts;
485 	cctx->set.connect_backoff_time_msecs =
486 		set->connect_backoff_time_msecs == 0 ?
487 			HTTP_CLIENT_DEFAULT_BACKOFF_TIME_MSECS :
488 			set->connect_backoff_time_msecs;
489 	cctx->set.connect_backoff_max_time_msecs =
490 		set->connect_backoff_max_time_msecs == 0 ?
491 			HTTP_CLIENT_DEFAULT_BACKOFF_MAX_TIME_MSECS :
492 			set->connect_backoff_max_time_msecs;
493 	cctx->set.no_auto_redirect = set->no_auto_redirect;
494 	cctx->set.no_auto_retry = set->no_auto_retry;
495 	cctx->set.no_ssl_tunnel = set->no_ssl_tunnel;
496 	cctx->set.max_redirects = set->max_redirects;
497 	cctx->set.response_hdr_limits = set->response_hdr_limits;
498 	cctx->set.request_absolute_timeout_msecs =
499 		set->request_absolute_timeout_msecs;
500 	cctx->set.request_timeout_msecs =
501 		set->request_timeout_msecs == 0 ?
502 			HTTP_CLIENT_DEFAULT_REQUEST_TIMEOUT_MSECS :
503 			set->request_timeout_msecs;
504 	cctx->set.connect_timeout_msecs = set->connect_timeout_msecs;
505 	cctx->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
506 	cctx->set.max_auto_retry_delay = set->max_auto_retry_delay;
507 	cctx->set.socket_send_buffer_size = set->socket_send_buffer_size;
508 	cctx->set.socket_recv_buffer_size = set->socket_recv_buffer_size;
509 	cctx->set.debug = set->debug;
510 
511 	cctx->conn_list = http_client_connection_list_init();
512 
513 	hash_table_create(&cctx->hosts, default_pool, 0, str_hash, strcmp);
514 
515 	hash_table_create(&cctx->peers, default_pool, 0,
516 		http_client_peer_addr_hash, http_client_peer_addr_cmp);
517 
518 	return cctx;
519 }
520 
http_client_context_ref(struct http_client_context * cctx)521 void http_client_context_ref(struct http_client_context *cctx)
522 {
523 	cctx->refcount++;
524 }
525 
http_client_context_unref(struct http_client_context ** _cctx)526 void http_client_context_unref(struct http_client_context **_cctx)
527 {
528 	struct http_client_context *cctx = *_cctx;
529 	struct http_client_peer_shared *peer;
530 	struct http_client_host_shared *hshared;
531 
532 	*_cctx = NULL;
533 
534 	i_assert(cctx->refcount > 0);
535 	if (--cctx->refcount > 0)
536 		return;
537 
538 	/* free hosts */
539 	while (cctx->hosts_list != NULL) {
540 		hshared = cctx->hosts_list;
541 		http_client_host_shared_free(&hshared);
542 	}
543 	hash_table_destroy(&cctx->hosts);
544 
545 	/* close all idle connections */
546 	while (cctx->peers_list != NULL) {
547 		peer = cctx->peers_list;
548 		http_client_peer_shared_close(&peer);
549 		i_assert(peer == NULL);
550 	}
551 	hash_table_destroy(&cctx->peers);
552 
553 	connection_list_deinit(&cctx->conn_list);
554 
555 	event_unref(&cctx->event);
556 	pool_unref(&cctx->pool);
557 }
558 
559 static unsigned int
http_client_get_dns_lookup_timeout_msecs(const struct http_client_settings * set)560 http_client_get_dns_lookup_timeout_msecs(const struct http_client_settings *set)
561 {
562 	if (set->connect_timeout_msecs > 0)
563 		return set->connect_timeout_msecs;
564 	if (set->request_timeout_msecs > 0)
565 		return set->request_timeout_msecs;
566 	return HTTP_CLIENT_DEFAULT_DNS_LOOKUP_TIMEOUT_MSECS;
567 }
568 
569 static void
http_client_context_update_settings(struct http_client_context * cctx)570 http_client_context_update_settings(struct http_client_context *cctx)
571 {
572 	struct http_client *client;
573 	bool debug;
574 
575 	/* revert back to context settings */
576 	cctx->dns_client = cctx->set.dns_client;
577 	cctx->dns_client_socket_path = cctx->set.dns_client_socket_path;
578 	cctx->dns_ttl_msecs = cctx->set.dns_ttl_msecs;
579 	cctx->dns_lookup_timeout_msecs =
580 		http_client_get_dns_lookup_timeout_msecs(&cctx->set);
581 	debug = cctx->set.debug;
582 
583 	i_assert(cctx->dns_ttl_msecs > 0);
584 	i_assert(cctx->dns_lookup_timeout_msecs > 0);
585 
586 	/* override with available client settings */
587 	for (client = cctx->clients_list; client != NULL;
588 	     client = client->next) {
589 		unsigned dns_lookup_timeout_msecs =
590 			http_client_get_dns_lookup_timeout_msecs(&client->set);
591 
592 		if (cctx->dns_client == NULL)
593 			cctx->dns_client = client->set.dns_client;
594 		if (cctx->dns_client_socket_path == NULL) {
595 			cctx->dns_client_socket_path =
596 				client->set.dns_client_socket_path;
597 		}
598 		if (client->set.dns_ttl_msecs != 0 &&
599 		    cctx->dns_ttl_msecs > client->set.dns_ttl_msecs)
600 			cctx->dns_ttl_msecs = client->set.dns_ttl_msecs;
601 		if (dns_lookup_timeout_msecs != 0 &&
602 		    cctx->dns_lookup_timeout_msecs > dns_lookup_timeout_msecs) {
603 			cctx->dns_lookup_timeout_msecs =
604 				dns_lookup_timeout_msecs;
605 		}
606 		debug = debug || client->set.debug;
607 	}
608 
609 	event_set_forced_debug(cctx->event, debug);
610 }
611 
612 static void
http_client_context_add_client(struct http_client_context * cctx,struct http_client * client)613 http_client_context_add_client(struct http_client_context *cctx,
614 			       struct http_client *client)
615 {
616 	DLLIST_PREPEND(&cctx->clients_list, client);
617 	http_client_context_update_settings(cctx);
618 }
619 
620 static void
http_client_context_remove_client(struct http_client_context * cctx,struct http_client * client)621 http_client_context_remove_client(struct http_client_context *cctx,
622 				  struct http_client *client)
623 {
624 	DLLIST_REMOVE(&cctx->clients_list, client);
625 	http_client_context_update_settings(cctx);
626 
627 	if (cctx->ioloop != current_ioloop &&
628 	    cctx->ioloop == client->ioloop &&
629 	    cctx->clients_list != NULL) {
630 		struct ioloop *prev_ioloop = current_ioloop;
631 
632 		io_loop_set_current(cctx->clients_list->ioloop);
633 		http_client_context_switch_ioloop(cctx);
634 		io_loop_set_current(prev_ioloop);
635 	}
636 }
637 
http_client_context_close(struct http_client_context * cctx)638 static void http_client_context_close(struct http_client_context *cctx)
639 {
640 	struct connection *_conn, *_conn_next;
641 	struct http_client_host_shared *hshared;
642 	struct http_client_peer_shared *pshared;
643 
644 	/* Switching to NULL ioloop;
645 	   close all hosts, peers, and connections */
646 	i_assert(cctx->clients_list == NULL);
647 
648 	_conn = cctx->conn_list->connections;
649 	while (_conn != NULL) {
650 		struct http_client_connection *conn =
651 			(struct http_client_connection *)_conn;
652 		_conn_next = _conn->next;
653 		http_client_connection_close(&conn);
654 		_conn = _conn_next;
655 	}
656 	while (cctx->hosts_list != NULL) {
657 		hshared = cctx->hosts_list;
658 		http_client_host_shared_free(&hshared);
659 	}
660 	while (cctx->peers_list != NULL) {
661 		pshared = cctx->peers_list;
662 		http_client_peer_shared_close(&pshared);
663 	}
664 }
665 
666 static void
http_client_context_do_switch_ioloop(struct http_client_context * cctx)667 http_client_context_do_switch_ioloop(struct http_client_context *cctx)
668 {
669 	struct connection *_conn = cctx->conn_list->connections;
670 	struct http_client_host_shared *hshared;
671 	struct http_client_peer_shared *pshared;
672 
673 	/* move connections */
674 	/* FIXME: we wouldn't necessarily need to switch all of them
675 	   immediately, only those that have requests now. but also connections
676 	   that get new requests before ioloop is switched again.. */
677 	for (; _conn != NULL; _conn = _conn->next) {
678 		struct http_client_connection *conn =
679 			(struct http_client_connection *)_conn;
680 
681 		http_client_connection_switch_ioloop(conn);
682 	}
683 
684 	/* move backoff timeouts */
685 	for (pshared = cctx->peers_list; pshared != NULL;
686 		pshared = pshared->next)
687 		http_client_peer_shared_switch_ioloop(pshared);
688 
689 	/* move dns lookups and delayed requests */
690 	for (hshared = cctx->hosts_list; hshared != NULL;
691 		hshared = hshared->next)
692 		http_client_host_shared_switch_ioloop(hshared);
693 }
694 
http_client_context_switch_ioloop(struct http_client_context * cctx)695 void http_client_context_switch_ioloop(struct http_client_context *cctx)
696 {
697 	cctx->ioloop = current_ioloop;
698 
699 	http_client_context_do_switch_ioloop(cctx);
700 }
701 
702 static void
http_client_global_context_ioloop_switched(struct ioloop * prev_ioloop ATTR_UNUSED)703 http_client_global_context_ioloop_switched(
704 	struct ioloop *prev_ioloop ATTR_UNUSED)
705 {
706 	struct http_client_context *cctx = http_client_global_context;
707 
708 	i_assert(cctx != NULL);
709 	if (current_ioloop == NULL) {
710 		http_client_context_close(cctx);
711 		return;
712 	}
713 	if (cctx->clients_list == NULL) {
714 		/* follow the current ioloop if there is no client */
715 		http_client_context_switch_ioloop(cctx);
716 	}
717 }
718 
http_client_global_context_free(void)719 static void http_client_global_context_free(void)
720 {
721 	/* drop ioloop switch callback to make absolutely sure there is no
722 	   recursion. */
723 	io_loop_remove_switch_callback(http_client_global_context_ioloop_switched);
724 
725 	http_client_context_unref(&http_client_global_context);
726 }
727 
http_client_get_global_context(void)728 struct http_client_context *http_client_get_global_context(void)
729 {
730 	if (http_client_global_context != NULL)
731 		return http_client_global_context;
732 
733 	struct http_client_settings set;
734 	i_zero(&set);
735 	http_client_global_context = http_client_context_create(&set);
736 	/* keep this a bit higher than lib-ssl-iostream */
737 	lib_atexit_priority(http_client_global_context_free, LIB_ATEXIT_PRIORITY_LOW-1);
738 	io_loop_add_switch_callback(http_client_global_context_ioloop_switched);
739 	return http_client_global_context;
740 }
741