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