gresolv: Start handling AAAA results in query
[framework/connectivity/connman.git] / gweb / gresolv.c
1 /*
2  *
3  *  Resolver library with GLib integration
4  *
5  *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <unistd.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <resolv.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netdb.h>
34 #include <arpa/inet.h>
35 #include <arpa/nameser.h>
36
37 #include "gresolv.h"
38
39 struct resolv_query {
40         GResolv *resolv;
41
42         guint id;
43         guint timeout;
44
45         uint16_t msgid;
46
47         GResolvResultFunc result_func;
48         gpointer result_data;
49 };
50
51 struct resolv_nameserver {
52         GResolv *resolv;
53
54         char *address;
55         uint16_t port;
56         unsigned long flags;
57
58         GIOChannel *udp_channel;
59         guint udp_watch;
60 };
61
62 struct _GResolv {
63         gint ref_count;
64
65         guint next_query_id;
66         GQueue *query_queue;
67
68         int index;
69         GList *nameserver_list;
70
71         struct __res_state res;
72
73         GResolvDebugFunc debug_func;
74         gpointer debug_data;
75 };
76
77 static inline void debug(GResolv *resolv, const char *format, ...)
78 {
79         char str[256];
80         va_list ap;
81
82         if (resolv->debug_func == NULL)
83                 return;
84
85         va_start(ap, format);
86
87         if (vsnprintf(str, sizeof(str), format, ap) > 0)
88                 resolv->debug_func(str, resolv->debug_data);
89
90         va_end(ap);
91 }
92
93 static void destroy_query(struct resolv_query *query)
94 {
95         if (query->timeout > 0)
96                 g_source_remove(query->timeout);
97
98         g_free(query);
99 }
100
101 static gboolean query_timeout(gpointer user_data)
102 {
103         struct resolv_query *query = user_data;
104         GResolv *resolv = query->resolv;
105
106         query->timeout = 0;
107
108         if (query->result_func != NULL)
109                 query->result_func(G_RESOLV_RESULT_STATUS_NO_RESPONSE,
110                                                 NULL, query->result_data);
111
112         destroy_query(query);
113         g_queue_remove(resolv->query_queue, query);
114
115         return FALSE;
116 }
117
118 static void free_nameserver(struct resolv_nameserver *nameserver)
119 {
120         if (nameserver == NULL)
121                 return;
122
123         if (nameserver->udp_watch > 0)
124                 g_source_remove(nameserver->udp_watch);
125
126         if (nameserver->udp_channel != NULL)
127                 g_io_channel_unref(nameserver->udp_channel);
128
129         g_free(nameserver->address);
130         g_free(nameserver);
131 }
132
133 static void flush_nameservers(GResolv *resolv)
134 {
135         GList *list;
136
137         for (list = g_list_first(resolv->nameserver_list);
138                                         list; list = g_list_next(list))
139                 free_nameserver(list->data);
140
141         g_list_free(resolv->nameserver_list);
142         resolv->nameserver_list = NULL;
143 }
144
145 static int send_query(GResolv *resolv, const unsigned char *buf, int len)
146 {
147         GList *list;
148
149         if (resolv->nameserver_list == NULL)
150                 return -ENOENT;
151
152         for (list = g_list_first(resolv->nameserver_list);
153                                         list; list = g_list_next(list)) {
154                 struct resolv_nameserver *nameserver = list->data;
155                 int sk, sent;
156
157                 if (nameserver->udp_channel == NULL)
158                         continue;
159
160                 sk = g_io_channel_unix_get_fd(nameserver->udp_channel);
161
162                 sent = send(sk, buf, len, 0);
163         }
164
165         return 0;
166 }
167
168 static gint compare_query_id(gconstpointer a, gconstpointer b)
169 {
170         const struct resolv_query *query = a;
171         guint id = GPOINTER_TO_UINT(b);
172
173         if (query->id < id)
174                 return -1;
175
176         if (query->id > id)
177                 return 1;
178
179         return 0;
180 }
181
182 static gint compare_query_msgid(gconstpointer a, gconstpointer b)
183 {
184         const struct resolv_query *query = a;
185         uint16_t msgid = GPOINTER_TO_UINT(b);
186
187         if (query->msgid < msgid)
188                 return -1;
189
190         if (query->msgid > msgid)
191                 return 1;
192
193         return 0;
194 }
195
196 static void parse_response(struct resolv_nameserver *nameserver,
197                                         const unsigned char *buf, int len)
198 {
199         GResolv *resolv = nameserver->resolv;
200         GResolvResultStatus status;
201         GList *list;
202         char **results;
203         ns_msg msg;
204         ns_rr rr;
205         int i, n, rcode, count;
206
207         debug(resolv, "response from %s", nameserver->address);
208
209         ns_initparse(buf, len, &msg);
210
211         rcode = ns_msg_getflag(msg, ns_f_rcode);
212         count = ns_msg_count(msg, ns_s_an);
213
214         debug(resolv, "msg id: 0x%04x rcode: %d count: %d",
215                                         ns_msg_id(msg), rcode, count);
216
217         switch (rcode) {
218         case 0:
219                 status = G_RESOLV_RESULT_STATUS_SUCCESS;
220                 break;
221         case 1:
222                 status = G_RESOLV_RESULT_STATUS_FORMAT_ERROR;
223                 break;
224         case 2:
225                 status = G_RESOLV_RESULT_STATUS_SERVER_FAILURE;
226                 break;
227         case 3:
228                 status = G_RESOLV_RESULT_STATUS_NAME_ERROR;
229                 break;
230         case 4:
231                 status = G_RESOLV_RESULT_STATUS_NOT_IMPLEMENTED;
232                 break;
233         case 5:
234                 status = G_RESOLV_RESULT_STATUS_REFUSED;
235                 break;
236         default:
237                 status = G_RESOLV_RESULT_STATUS_ERROR;
238                 break;
239         }
240
241         results = g_try_new(char *, count + 1);
242         if (results == NULL)
243                 return;
244
245         for (i = 0, n = 0; i < count; i++) {
246                 char result[100];
247
248                 ns_parserr(&msg, ns_s_an, i, &rr);
249
250                 if (ns_rr_class(rr) != ns_c_in)
251                         continue;
252
253                 if (ns_rr_type(rr) == ns_t_a &&
254                     ns_rr_rdlen(rr) == NS_INADDRSZ) {
255                         inet_ntop(AF_INET, ns_rr_rdata(rr), result, sizeof(result));
256                 } else if (ns_rr_type(rr) == ns_t_aaaa &&
257                            ns_rr_rdlen(rr) == NS_IN6ADDRSZ) {
258                         inet_ntop(AF_INET6, ns_rr_rdata(rr), result, sizeof(result));
259                 } else
260                         continue;
261
262                 results[n++] = g_strdup(result);
263         }
264
265         results[n] = NULL;
266
267         list = g_queue_find_custom(resolv->query_queue,
268                         GUINT_TO_POINTER(ns_msg_id(msg)), compare_query_msgid);
269
270         if (list != NULL) {
271                 struct resolv_query *query = list->data;
272
273                 /* FIXME: This set of results is *only* for a single A or AAAA
274                    query; we need to merge both results together and then sort
275                    them according to RFC3484. While honouring /etc/gai.conf */
276                 if (query->result_func != NULL)
277                         query->result_func(status, results,
278                                                 query->result_data);
279
280                 destroy_query(query);
281                 g_queue_remove(resolv->query_queue, query);
282         }
283
284         g_strfreev(results);
285 }
286
287 static gboolean received_udp_data(GIOChannel *channel, GIOCondition cond,
288                                                         gpointer user_data)
289 {
290         struct resolv_nameserver *nameserver = user_data;
291         unsigned char buf[4096];
292         int sk, len;
293
294         if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
295                 nameserver->udp_watch = 0;
296                 return FALSE;
297         }
298
299         sk = g_io_channel_unix_get_fd(nameserver->udp_channel);
300
301         len = recv(sk, buf, sizeof(buf), 0);
302         if (len < 12)
303                 return TRUE;
304
305         parse_response(nameserver, buf, len);
306
307         return TRUE;
308 }
309
310 static int connect_udp_channel(struct resolv_nameserver *nameserver)
311 {
312         struct addrinfo hints, *rp;
313         char portnr[6];
314         int err, sk;
315
316         memset(&hints, 0, sizeof(hints));
317         hints.ai_family = AF_UNSPEC;
318         hints.ai_socktype = SOCK_DGRAM;
319         hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_NUMERICHOST;
320
321         sprintf(portnr, "%d", nameserver->port);
322         err = getaddrinfo(nameserver->address, portnr, &hints, &rp);
323         if (err)
324                 return -EINVAL;
325
326         /* Do not blindly copy this code elsewhere; it doesn't loop over the
327            results using ->ai_next as it should. That's OK in *this* case
328            because it was a numeric lookup; we *know* there's only one. */
329         if (!rp)
330                 return -EINVAL;
331
332         sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
333         if (sk < 0) {
334                 freeaddrinfo(rp);
335                 return -EIO;
336         }
337
338         if (connect(sk, rp->ai_addr, rp->ai_addrlen) < 0) {
339                 close(sk);
340                 freeaddrinfo(rp);
341                 return -EIO;
342         }
343
344         freeaddrinfo(rp);
345
346         nameserver->udp_channel = g_io_channel_unix_new(sk);
347         if (nameserver->udp_channel == NULL) {
348                 close(sk);
349                 return -ENOMEM;
350         }
351
352         g_io_channel_set_close_on_unref(nameserver->udp_channel, TRUE);
353
354         nameserver->udp_watch = g_io_add_watch(nameserver->udp_channel,
355                                G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
356                                received_udp_data, nameserver);
357
358         return 0;
359 }
360
361 GResolv *g_resolv_new(int index)
362 {
363         GResolv *resolv;
364
365         if (index < 0)
366                 return NULL;
367
368         resolv = g_try_new0(GResolv, 1);
369         if (resolv == NULL)
370                 return NULL;
371
372         resolv->ref_count = 1;
373
374         resolv->next_query_id = 1;
375         resolv->query_queue = g_queue_new();
376
377         if (resolv->query_queue == NULL) {
378                 g_free(resolv);
379                 return NULL;
380         }
381
382         resolv->index = index;
383         resolv->nameserver_list = NULL;
384
385         res_ninit(&resolv->res);
386
387         return resolv;
388 }
389
390 GResolv *g_resolv_ref(GResolv *resolv)
391 {
392         if (resolv == NULL)
393                 return NULL;
394
395         g_atomic_int_inc(&resolv->ref_count);
396
397         return resolv;
398 }
399
400 void g_resolv_unref(GResolv *resolv)
401 {
402         struct resolv_query *query;
403
404         if (resolv == NULL)
405                 return;
406
407         if (g_atomic_int_dec_and_test(&resolv->ref_count) == FALSE)
408                 return;
409
410         while ((query = g_queue_pop_head(resolv->query_queue)))
411                 destroy_query(query);
412
413         g_queue_free(resolv->query_queue);
414
415         flush_nameservers(resolv);
416
417         res_nclose(&resolv->res);
418
419         g_free(resolv);
420 }
421
422 void g_resolv_set_debug(GResolv *resolv,
423                                 GResolvDebugFunc func, gpointer user_data)
424 {
425         if (resolv == NULL)
426                 return;
427
428         resolv->debug_func = func;
429         resolv->debug_data = user_data;
430 }
431
432 gboolean g_resolv_add_nameserver(GResolv *resolv, const char *address,
433                                         uint16_t port, unsigned long flags)
434 {
435         struct resolv_nameserver *nameserver;
436
437         if (resolv == NULL)
438                 return FALSE;
439
440         nameserver = g_try_new0(struct resolv_nameserver, 1);
441         if (nameserver == NULL)
442                 return FALSE;
443
444         nameserver->address = g_strdup(address);
445         nameserver->port = port;
446         nameserver->flags = flags;
447
448         if (connect_udp_channel(nameserver) < 0) {
449                 free_nameserver(nameserver);
450                 return FALSE;
451         }
452
453         nameserver->resolv = resolv;
454
455         resolv->nameserver_list = g_list_append(resolv->nameserver_list,
456                                                                 nameserver);
457
458         debug(resolv, "setting nameserver %s", address);
459
460         return TRUE;
461 }
462
463 void g_resolv_flush_nameservers(GResolv *resolv)
464 {
465         if (resolv == NULL)
466                 return;
467
468         flush_nameservers(resolv);
469 }
470
471 guint g_resolv_lookup_hostname(GResolv *resolv, const char *hostname,
472                                 GResolvResultFunc func, gpointer user_data)
473 {
474         struct resolv_query *query;
475         unsigned char buf[4096];
476         int len;
477
478         debug(resolv, "lookup hostname %s", hostname);
479
480         if (resolv == NULL)
481                 return 0;
482
483         if (resolv->nameserver_list == NULL) {
484                 int i;
485
486                 for (i = 0; i < resolv->res.nscount; i++) {
487                         char buf[100];
488                         int family = resolv->res.nsaddr_list[i].sin_family;
489                         void *sa_addr = &resolv->res.nsaddr_list[i].sin_addr;
490
491                         if (family != AF_INET && resolv->res._u._ext.nsaddrs[i]) {
492                                 family = AF_INET6;
493                                 sa_addr = &resolv->res._u._ext.nsaddrs[i]->sin6_addr;
494                         }
495                         if (family != AF_INET && family != AF_INET6)
496                                 continue;
497
498                         if (inet_ntop(family, sa_addr, buf, sizeof(buf)))
499                                 g_resolv_add_nameserver(resolv, buf, 53, 0);
500                 }
501
502                 if (resolv->nameserver_list == NULL)
503                         g_resolv_add_nameserver(resolv, "127.0.0.1", 53, 0);
504         }
505
506         query = g_try_new0(struct resolv_query, 1);
507         if (query == NULL)
508                 return 0;
509
510         query->id = resolv->next_query_id++;
511
512         /* FIXME: Send ns_t_aaaa query too, and see the FIXME in
513            parse_response() re merging and sorting the results */
514         len = res_mkquery(ns_o_query, hostname, ns_c_in, ns_t_a,
515                                         NULL, 0, NULL, buf, sizeof(buf));
516
517         query->msgid = buf[0] << 8 | buf[1];
518
519         query->result_func = func;
520         query->result_data = user_data;
521
522         if (send_query(resolv, buf, len) < 0) {
523                 g_free(query);
524                 return -EIO;
525         }
526
527         query->resolv = resolv;
528
529         g_queue_push_tail(resolv->query_queue, query);
530
531         query->timeout = g_timeout_add_seconds(5, query_timeout, query);
532
533         return query->id;
534 }
535
536 gboolean g_resolv_cancel_lookup(GResolv *resolv, guint id)
537 {
538         GList *list;
539
540         list = g_queue_find_custom(resolv->query_queue,
541                                 GUINT_TO_POINTER(id), compare_query_id);
542
543         if (list == NULL)
544                 return FALSE;
545
546         destroy_query(list->data);
547         g_queue_remove(resolv->query_queue, list->data);
548
549         return TRUE;
550 }