1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "str.h"
6 #include "array.h"
7 #include "ostream.h"
8 #include "connection.h"
9 #include "lib-event.h"
10 #include "llist.h"
11 #include "istream.h"
12 #include "write-full.h"
13 #include "time-util.h"
14 #include "dns-lookup.h"
15 
16 #include <stdio.h>
17 #include <unistd.h>
18 
19 #define MAX_INBUF_SIZE 512
20 
21 static struct event_category event_category_dns = {
22 	.name = "dns"
23 };
24 
25 struct dns_lookup {
26 	struct dns_lookup *prev, *next;
27 	struct dns_client *client;
28 	pool_t pool;
29 	bool ptr_lookup;
30 
31 	struct timeout *to;
32 
33 	struct timeval start_time;
34 	unsigned int warn_msecs;
35 
36 	struct dns_lookup_result result;
37 	struct event *event;
38 
39 	dns_lookup_callback_t *callback;
40 	void *context;
41 };
42 
43 struct dns_client {
44 	struct connection conn;
45 	struct connection_list *clist;
46 	struct dns_lookup *head, *tail;
47 	struct timeout *to_idle;
48 	struct ioloop *ioloop;
49 	char *path;
50 
51 	unsigned int timeout_msecs;
52 	unsigned int idle_timeout_msecs;
53 
54 	bool connected:1;
55 	bool deinit_client_at_free:1;
56 };
57 
58 #undef dns_lookup
59 #undef dns_lookup_ptr
60 #undef dns_client_lookup
61 #undef dns_client_lookup_ptr
62 
63 static void dns_lookup_free(struct dns_lookup **_lookup);
64 
65 static void dns_lookup_save_msecs(struct dns_lookup *lookup);
66 
dns_lookup_callback(struct dns_lookup * lookup)67 static void dns_lookup_callback(struct dns_lookup *lookup)
68 {
69 	struct event_passthrough *e =
70 		event_create_passthrough(lookup->event)->
71 		set_name("dns_request_finished");
72 
73 	dns_lookup_save_msecs(lookup);
74 
75 	if (lookup->result.ret != 0) {
76 		e->add_int("error_code", lookup->result.ret);
77 		e->add_str("error", lookup->result.error);
78 		e_debug(e->event(), "Lookup failed after %u msecs: %s",
79 			lookup->result.msecs, lookup->result.error);
80 	} else {
81 		e_debug(e->event(), "Lookup successful after %u msecs",
82 			lookup->result.msecs);
83 	}
84 	lookup->callback(&lookup->result, lookup->context);
85 }
86 
dns_client_disconnect(struct dns_client * client,const char * error)87 static void dns_client_disconnect(struct dns_client *client, const char *error)
88 {
89 	struct dns_lookup *lookup, *next;
90 	struct dns_lookup_result result;
91 
92 	if (!client->connected)
93 	        return;
94 	timeout_remove(&client->to_idle);
95 
96 	connection_disconnect(&client->conn);
97 	client->connected = FALSE;
98 
99 	i_zero(&result);
100 	result.ret = EAI_FAIL;
101 	result.error = error;
102 	e_debug(client->conn.event, "Disconnect: %s", error);
103 
104 	lookup = client->head;
105 	client->head = NULL;
106 	while (lookup != NULL) {
107 		next = lookup->next;
108 		dns_lookup_callback(lookup);
109 		dns_lookup_free(&lookup);
110 		lookup = next;
111 	}
112 }
113 
dns_client_destroy(struct connection * conn)114 static void dns_client_destroy(struct connection *conn)
115 {
116 	struct dns_client *client = container_of(conn, struct dns_client, conn);
117 	client->connected = FALSE;
118 	connection_deinit(conn);
119 }
120 
dns_lookup_input_args(struct dns_lookup * lookup,const char * const * args)121 static int dns_lookup_input_args(struct dns_lookup *lookup, const char *const *args)
122 {
123 	struct dns_lookup_result *result = &lookup->result;
124 
125 	if (str_to_int(args[0], &result->ret) < 0)
126 		return -1;
127 	if (result->ret != 0) {
128 		result->error = args[1];
129 		return 1;
130 	}
131 
132 	if (lookup->ptr_lookup) {
133 		result->name = p_strdup(lookup->pool, args[1]);
134 		return 1;
135 	}
136 
137 	ARRAY(struct ip_addr) ips;
138 	p_array_init(&ips, lookup->pool, 2);
139 	for(unsigned int i = 1; args[i] != NULL; i++) {
140 		struct ip_addr *ip = array_append_space(&ips);
141 		if (net_addr2ip(args[i], ip) < 0)
142 			return -1;
143 	}
144 	result->ips = array_get(&ips, &result->ips_count);
145 
146 	return 1;
147 }
148 
dns_lookup_save_msecs(struct dns_lookup * lookup)149 static void dns_lookup_save_msecs(struct dns_lookup *lookup)
150 {
151 	struct timeval now;
152 	int diff;
153 
154 	i_gettimeofday(&now);
155 
156 	diff = timeval_diff_msecs(&now, &lookup->start_time);
157 	if (diff > 0)
158 		lookup->result.msecs = diff;
159 }
160 
dns_client_input_args(struct connection * conn,const char * const * args)161 static int dns_client_input_args(struct connection *conn, const char *const *args)
162 {
163 	struct dns_client *client = container_of(conn, struct dns_client, conn);
164 	struct dns_lookup *lookup = client->head;
165 	bool retry = FALSE;
166 	int ret = 0;
167 
168 	if (lookup == NULL) {
169 		dns_client_disconnect(client, t_strdup_printf(
170 			"Unexpected input from %s", conn->name));
171 		return -1;
172 	}
173 
174 	if ((ret = dns_lookup_input_args(lookup, args)) == 0) {
175 		return 1; /* keep on reading */
176 	} else if (ret < 0) {
177 		dns_client_disconnect(client, t_strdup_printf(
178 			"Invalid input from %s", conn->name));
179 		return -1;
180 	} else if (ret > 0) {
181 		dns_lookup_callback(lookup);
182 		retry = !lookup->client->deinit_client_at_free;
183 		dns_lookup_free(&lookup);
184 	}
185 
186 	return retry ? 1 : -1;
187 }
188 
dns_lookup_timeout(struct dns_lookup * lookup)189 static void dns_lookup_timeout(struct dns_lookup *lookup)
190 {
191 	lookup->result.error = "Lookup timed out";
192 
193 	dns_lookup_callback(lookup);
194 	dns_lookup_free(&lookup);
195 }
196 
dns_lookup(const char * host,const struct dns_lookup_settings * set,dns_lookup_callback_t * callback,void * context,struct dns_lookup ** lookup_r)197 int dns_lookup(const char *host, const struct dns_lookup_settings *set,
198 	       dns_lookup_callback_t *callback, void *context,
199 	       struct dns_lookup **lookup_r)
200 {
201 	struct dns_client *client;
202 
203 	client = dns_client_init(set);
204 	event_add_category(client->conn.event, &event_category_dns);
205 	client->deinit_client_at_free = TRUE;
206 	return dns_client_lookup(client, host, callback, context, lookup_r);
207 }
208 
dns_lookup_ptr(const struct ip_addr * ip,const struct dns_lookup_settings * set,dns_lookup_callback_t * callback,void * context,struct dns_lookup ** lookup_r)209 int dns_lookup_ptr(const struct ip_addr *ip,
210 		   const struct dns_lookup_settings *set,
211 		   dns_lookup_callback_t *callback, void *context,
212 		   struct dns_lookup **lookup_r)
213 {
214 	struct dns_client *client;
215 
216 	client = dns_client_init(set);
217 	event_add_category(client->conn.event, &event_category_dns);
218 	client->deinit_client_at_free = TRUE;
219 	return dns_client_lookup_ptr(client, ip, callback, context, lookup_r);
220 }
221 
dns_client_idle_timeout(struct dns_client * client)222 static void dns_client_idle_timeout(struct dns_client *client)
223 {
224 	i_assert(client->head == NULL);
225 
226 	/* send QUIT */
227 	o_stream_nsend_str(client->conn.output, "QUIT\n");
228 	dns_client_disconnect(client, "Idle timeout");
229 }
230 
dns_lookup_free(struct dns_lookup ** _lookup)231 static void dns_lookup_free(struct dns_lookup **_lookup)
232 {
233 	struct dns_lookup *lookup = *_lookup;
234 	struct dns_client *client = lookup->client;
235 
236 	*_lookup = NULL;
237 
238 	DLLIST2_REMOVE(&client->head, &client->tail, lookup);
239 	timeout_remove(&lookup->to);
240 	if (client->deinit_client_at_free)
241 		dns_client_deinit(&client);
242 	else if (client->head == NULL && client->connected) {
243 		client->to_idle = timeout_add_to(client->ioloop,
244 						 client->idle_timeout_msecs,
245 						 dns_client_idle_timeout, client);
246 	}
247 	event_unref(&lookup->event);
248 	pool_unref(&lookup->pool);
249 }
250 
dns_lookup_abort(struct dns_lookup ** lookup)251 void dns_lookup_abort(struct dns_lookup **lookup)
252 {
253 	dns_lookup_free(lookup);
254 }
255 
dns_lookup_switch_ioloop_real(struct dns_lookup * lookup)256 static void dns_lookup_switch_ioloop_real(struct dns_lookup *lookup)
257 {
258 	if (lookup->to != NULL)
259 		lookup->to = io_loop_move_timeout(&lookup->to);
260 }
261 
dns_lookup_switch_ioloop(struct dns_lookup * lookup)262 void dns_lookup_switch_ioloop(struct dns_lookup *lookup)
263 {
264 	/* dns client ioloop switch switches all lookups too */
265 	if (lookup->client->deinit_client_at_free)
266 		dns_client_switch_ioloop(lookup->client);
267 	else
268 		dns_lookup_switch_ioloop_real(lookup);
269 }
270 
dns_client_connected(struct connection * conn,bool success)271 static void dns_client_connected(struct connection *conn, bool success)
272 {
273 	struct dns_client *client = container_of(conn, struct dns_client, conn);
274 	if (!success)
275 		return;
276 	client->connected = TRUE;
277 }
278 
279 static const struct connection_vfuncs dns_client_vfuncs = {
280 	.destroy = dns_client_destroy,
281 	.input_args = dns_client_input_args,
282 	.client_connected = dns_client_connected,
283 };
284 
285 static const struct connection_settings dns_client_set = {
286 	.service_name_in = "dns",
287 	.service_name_out = "dns-client",
288 	.major_version = 1,
289 	.minor_version = 0,
290 	.input_max_size = SIZE_MAX,
291 	.output_max_size = SIZE_MAX,
292 	.client = TRUE,
293 };
294 
dns_client_init(const struct dns_lookup_settings * set)295 struct dns_client *dns_client_init(const struct dns_lookup_settings *set)
296 {
297 	struct dns_client *client;
298 
299 	client = i_new(struct dns_client, 1);
300 	client->timeout_msecs = set->timeout_msecs;
301 	client->idle_timeout_msecs = set->idle_timeout_msecs;
302 	client->clist = connection_list_init(&dns_client_set, &dns_client_vfuncs);
303 	client->ioloop = set->ioloop == NULL ? current_ioloop : set->ioloop;
304 	client->path = i_strdup(set->dns_client_socket_path);
305 	client->conn.event_parent=set->event_parent;
306 	connection_init_client_unix(client->clist, &client->conn, client->path);
307 	return client;
308 }
309 
dns_client_deinit(struct dns_client ** _client)310 void dns_client_deinit(struct dns_client **_client)
311 {
312 	struct dns_client *client = *_client;
313 	struct connection_list *clist = client->clist;
314 	*_client = NULL;
315 
316 	i_assert(client->head == NULL);
317 
318 	dns_client_disconnect(client, "deinit");
319 	connection_list_deinit(&clist);
320 	i_free(client->path);
321 	i_free(client);
322 }
323 
dns_client_connect(struct dns_client * client,const char ** error_r)324 int dns_client_connect(struct dns_client *client, const char **error_r)
325 {
326 	if (client->connected)
327 		return 0;
328 	if (client->ioloop != NULL)
329 		connection_switch_ioloop_to(&client->conn, client->ioloop);
330 	int ret = connection_client_connect(&client->conn);
331 	if (ret < 0)
332 		*error_r = t_strdup_printf("Failed to connect to %s: %m",
333 					   client->path);
334 	return ret;
335 }
336 
337 static int
dns_client_send_request(struct dns_client * client,const char * cmd,const char ** error_r)338 dns_client_send_request(struct dns_client *client, const char *cmd,
339 			const char **error_r)
340 {
341 	int ret;
342 
343 	if (!client->connected) {
344 		if (dns_client_connect(client, error_r) < 0)
345 			return -1;
346 	}
347 
348 	if ((ret = o_stream_send(client->conn.output, cmd, strlen(cmd))) < 0) {
349 		*error_r = t_strdup_printf("write(%s) failed: %s",
350 					   client->conn.name,
351 					   o_stream_get_error(client->conn.output));
352 		dns_client_disconnect(client, "Cannot send data");
353 	}
354 
355 	return ret;
356 }
357 
358 static int
dns_client_lookup_common(struct dns_client * client,const char * cmd,const char * param,bool ptr_lookup,dns_lookup_callback_t * callback,void * context,struct dns_lookup ** lookup_r)359 dns_client_lookup_common(struct dns_client *client,
360 			 const char *cmd, const char *param, bool ptr_lookup,
361 			 dns_lookup_callback_t *callback, void *context,
362 			 struct dns_lookup **lookup_r)
363 {
364 	struct dns_lookup *lookup;
365 	int ret;
366 
367 	i_assert(param != NULL && *param != '\0');
368 	cmd = t_strdup_printf("%s\t%s\n", cmd, param);
369 
370 	pool_t pool = pool_alloconly_create("dns lookup", 512);
371 	lookup = p_new(pool, struct dns_lookup, 1);
372 	lookup->pool = pool;
373 
374 	i_gettimeofday(&lookup->start_time);
375 
376 	lookup->client = client;
377 	lookup->callback = callback;
378 	lookup->context = context;
379 	lookup->ptr_lookup = ptr_lookup;
380 	lookup->result.ret = EAI_FAIL;
381 	lookup->event = event_create(client->conn.event);
382 	event_set_append_log_prefix(lookup->event, t_strconcat("dns(", param, "): ", NULL));
383 	struct event_passthrough *e =
384 		event_create_passthrough(lookup->event)->
385 		set_name("dns_request_started");
386 	e_debug(e->event(), "Lookup started");
387 
388 	if ((ret = dns_client_send_request(client, cmd, &lookup->result.error)) <= 0) {
389 		if (ret == 0) {
390 			/* retry once */
391 			ret = dns_client_send_request(client, cmd,
392 						      &lookup->result.error);
393 		}
394 		if (ret <= 0) {
395 			dns_lookup_callback(lookup);
396 			dns_lookup_free(&lookup);
397 			return -1;
398 		}
399 	}
400 
401 	if (client->timeout_msecs != 0) {
402 		lookup->to = timeout_add_to(client->ioloop,
403 					    client->timeout_msecs,
404 					    dns_lookup_timeout, lookup);
405 	}
406 	timeout_remove(&client->to_idle);
407 	DLLIST2_APPEND(&client->head, &client->tail, lookup);
408 	*lookup_r = lookup;
409 	return 0;
410 }
411 
dns_client_lookup(struct dns_client * client,const char * host,dns_lookup_callback_t * callback,void * context,struct dns_lookup ** lookup_r)412 int dns_client_lookup(struct dns_client *client, const char *host,
413 		      dns_lookup_callback_t *callback, void *context,
414 		      struct dns_lookup **lookup_r)
415 {
416 	return dns_client_lookup_common(client, "IP", host, FALSE,
417 					callback, context, lookup_r);
418 }
419 
dns_client_lookup_ptr(struct dns_client * client,const struct ip_addr * ip,dns_lookup_callback_t * callback,void * context,struct dns_lookup ** lookup_r)420 int dns_client_lookup_ptr(struct dns_client *client, const struct ip_addr *ip,
421 			  dns_lookup_callback_t *callback, void *context,
422 			  struct dns_lookup **lookup_r)
423 {
424 	return dns_client_lookup_common(client, "NAME", net_ip2addr(ip), TRUE,
425 					callback, context, lookup_r);
426 }
427 
dns_client_switch_ioloop(struct dns_client * client)428 void dns_client_switch_ioloop(struct dns_client *client)
429 {
430 	struct dns_lookup *lookup;
431 
432 	connection_switch_ioloop(&client->conn);
433 	client->to_idle = io_loop_move_timeout(&client->to_idle);
434 	client->ioloop = current_ioloop;
435 
436 	for (lookup = client->head; lookup != NULL; lookup = lookup->next)
437 		dns_lookup_switch_ioloop_real(lookup);
438 }
439