core: Update copyright information
[framework/connectivity/connman.git] / src / resolver.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2012  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 #define _GNU_SOURCE
27 #include <stdio.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <resolv.h>
34
35 #include "connman.h"
36
37 #define RESOLVER_FLAG_PUBLIC (1 << 0)
38
39 struct entry_data {
40         char *interface;
41         char *domain;
42         char *server;
43         unsigned int flags;
44         guint timeout;
45 };
46
47 static GSList *entry_list = NULL;
48 static connman_bool_t dnsproxy_enabled = FALSE;
49
50 struct resolvfile_entry {
51         char *interface;
52         char *domain;
53         char *server;
54 };
55
56 static GList *resolvfile_list = NULL;
57
58 static void resolvfile_remove_entries(GList *entries)
59 {
60         GList *list;
61
62         for (list = entries; list; list = list->next) {
63                 struct resolvfile_entry *entry = list->data;
64
65                 resolvfile_list = g_list_remove(resolvfile_list, entry);
66
67                 g_free(entry->server);
68                 g_free(entry->domain);
69                 g_free(entry->interface);
70                 g_free(entry);
71         }
72
73         g_list_free(entries);
74 }
75
76 static int resolvfile_export(void)
77 {
78         GList *list;
79         GString *content;
80         int fd, err;
81         unsigned int count;
82         mode_t old_umask;
83
84         content = g_string_new("# Generated by Connection Manager\n");
85
86         /*
87          * Domains and nameservers are added in reverse so that the most
88          * recently appended entry is the primary one. No more than
89          * MAXDNSRCH/MAXNS entries are used.
90          */
91
92         for (count = 0, list = g_list_last(resolvfile_list);
93                                                 list && (count < MAXDNSRCH);
94                                                 list = g_list_previous(list)) {
95                 struct resolvfile_entry *entry = list->data;
96
97                 if (!entry->domain)
98                         continue;
99
100                 if (count == 0)
101                         g_string_append_printf(content, "search ");
102
103                 g_string_append_printf(content, "%s ", entry->domain);
104                 count++;
105         }
106
107         if (count)
108                 g_string_append_printf(content, "\n");
109
110         for (count = 0, list = g_list_last(resolvfile_list);
111                                                 list && (count < MAXNS);
112                                                 list = g_list_previous(list)) {
113                 struct resolvfile_entry *entry = list->data;
114
115                 if (!entry->server)
116                         continue;
117
118                 g_string_append_printf(content, "nameserver %s\n",
119                                                                 entry->server);
120                 count++;
121         }
122
123         old_umask = umask(022);
124
125         fd = open("/etc/resolv.conf", O_RDWR | O_CREAT | O_CLOEXEC,
126                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
127         if (fd < 0) {
128                 err = -errno;
129                 goto done;
130         }
131
132         if (ftruncate(fd, 0) < 0) {
133                 err = -errno;
134                 goto failed;
135         }
136
137         err = 0;
138
139         if (write(fd, content->str, content->len) < 0)
140                 err = -errno;
141
142 failed:
143         close(fd);
144
145 done:
146         g_string_free(content, TRUE);
147         umask(old_umask);
148
149         return err;
150 }
151
152 int __connman_resolvfile_append(const char *interface, const char *domain,
153                                                         const char *server)
154 {
155         struct resolvfile_entry *entry;
156
157         DBG("interface %s server %s", interface, server);
158
159         if (interface == NULL)
160                 return -ENOENT;
161
162         entry = g_try_new0(struct resolvfile_entry, 1);
163         if (entry == NULL)
164                 return -ENOMEM;
165
166         entry->interface = g_strdup(interface);
167         entry->domain = g_strdup(domain);
168         entry->server = g_strdup(server);
169
170         resolvfile_list = g_list_append(resolvfile_list, entry);
171
172         return resolvfile_export();
173 }
174
175 int __connman_resolvfile_remove(const char *interface, const char *domain,
176                                                         const char *server)
177 {
178         GList *list, *matches = NULL;
179
180         DBG("interface %s server %s", interface, server);
181
182         for (list = resolvfile_list; list; list = g_list_next(list)) {
183                 struct resolvfile_entry *entry = list->data;
184
185                 if (interface != NULL &&
186                                 g_strcmp0(entry->interface, interface) != 0)
187                         continue;
188
189                 if (domain != NULL && g_strcmp0(entry->domain, domain) != 0)
190                         continue;
191
192                 if (g_strcmp0(entry->server, server) != 0)
193                         continue;
194
195                 matches = g_list_append(matches, entry);
196         }
197
198         resolvfile_remove_entries(matches);
199
200         return resolvfile_export();
201 }
202
203 static void remove_entries(GSList *entries)
204 {
205         GSList *list;
206
207         for (list = entries; list; list = list->next) {
208                 struct entry_data *entry = list->data;
209
210                 entry_list = g_slist_remove(entry_list, entry);
211
212                 if (dnsproxy_enabled == TRUE) {
213                         __connman_dnsproxy_remove(entry->interface, entry->domain,
214                                                         entry->server);
215                 } else {
216                         __connman_resolvfile_remove(entry->interface, entry->domain,
217                                                         entry->server);
218                 }
219
220                 if (entry->timeout)
221                         g_source_remove(entry->timeout);
222                 g_free(entry->server);
223                 g_free(entry->domain);
224                 g_free(entry->interface);
225                 g_free(entry);
226         }
227
228         g_slist_free(entries);
229 }
230
231 static gboolean resolver_expire_cb(gpointer user_data)
232 {
233         struct entry_data *entry = user_data;
234         GSList *list;
235         int index;
236
237         DBG("interface %s domain %s server %s",
238                         entry->interface, entry->domain, entry->server);
239
240         list = g_slist_append(NULL, entry);
241
242         index = connman_inet_ifindex(entry->interface);
243         if (index >= 0) {
244                 struct connman_service *service;
245                 service = __connman_service_lookup_from_index(index);
246                 if (service != NULL)
247                         __connman_service_nameserver_remove(service,
248                                                         entry->server, TRUE);
249         }
250
251         remove_entries(list);
252
253         return FALSE;
254 }
255
256 static int append_resolver(const char *interface, const char *domain,
257                                 const char *server, unsigned int lifetime,
258                                                         unsigned int flags)
259 {
260         struct entry_data *entry;
261
262         DBG("interface %s domain %s server %s lifetime %d flags %d",
263                                 interface, domain, server, lifetime, flags);
264
265         if (server == NULL && domain == NULL)
266                 return -EINVAL;
267
268         entry = g_try_new0(struct entry_data, 1);
269         if (entry == NULL)
270                 return -ENOMEM;
271
272         entry->interface = g_strdup(interface);
273         entry->domain = g_strdup(domain);
274         entry->server = g_strdup(server);
275         entry->flags = flags;
276         if (lifetime) {
277                 int index;
278                 entry->timeout = g_timeout_add_seconds(lifetime,
279                                                 resolver_expire_cb, entry);
280
281                 /*
282                  * We update the service only for those nameservers
283                  * that are automagically added via netlink (lifetime > 0)
284                  */
285                 index = connman_inet_ifindex(interface);
286                 if (index >= 0) {
287                         struct connman_service *service;
288                         service = __connman_service_lookup_from_index(index);
289                         if (service != NULL)
290                                 __connman_service_nameserver_append(service,
291                                                                 server, TRUE);
292                 }
293         }
294         entry_list = g_slist_append(entry_list, entry);
295
296         if (dnsproxy_enabled == TRUE)
297                 __connman_dnsproxy_append(interface, domain, server);
298         else
299                 __connman_resolvfile_append(interface, domain, server);
300
301         return 0;
302 }
303
304 /**
305  * connman_resolver_append:
306  * @interface: network interface
307  * @domain: domain limitation
308  * @server: server address
309  *
310  * Append resolver server address to current list
311  */
312 int connman_resolver_append(const char *interface, const char *domain,
313                                                 const char *server)
314 {
315         GSList *list, *matches = NULL;
316
317         DBG("interface %s domain %s server %s", interface, domain, server);
318
319         if (server == NULL && domain == NULL)
320                 return -EINVAL;
321
322         for (list = entry_list; list; list = list->next) {
323                 struct entry_data *entry = list->data;
324
325                 if (entry->timeout > 0 ||
326                                 g_strcmp0(entry->interface, interface) != 0 ||
327                                 g_strcmp0(entry->domain, domain) != 0 ||
328                                 g_strcmp0(entry->server, server) != 0)
329                         continue;
330
331                 matches = g_slist_append(matches, entry);
332         }
333
334         if (matches != NULL)
335                 remove_entries(matches);
336
337         return append_resolver(interface, domain, server, 0, 0);
338 }
339
340 /**
341  * connman_resolver_append_lifetime:
342  * @interface: network interface
343  * @domain: domain limitation
344  * @server: server address
345  * @timeout: server lifetime in seconds
346  *
347  * Append resolver server address to current list
348  */
349 int connman_resolver_append_lifetime(const char *interface, const char *domain,
350                                 const char *server, unsigned int lifetime)
351 {
352         GSList *list;
353
354         DBG("interface %s domain %s server %s lifetime %d",
355                                 interface, domain, server, lifetime);
356
357         if (server == NULL)
358                 return -EINVAL;
359
360         for (list = entry_list; list; list = list->next) {
361                 struct entry_data *entry = list->data;
362
363                 if (!entry->timeout ||
364                                 g_strcmp0(entry->interface, interface) ||
365                                 g_strcmp0(entry->domain, domain) ||
366                                 g_strcmp0(entry->server, server))
367                         continue;
368
369                 g_source_remove(entry->timeout);
370
371                 if (lifetime == 0) {
372                         resolver_expire_cb(entry);
373                         return 0;
374                 }
375
376                 entry->timeout = g_timeout_add_seconds(lifetime,
377                                                 resolver_expire_cb, entry);
378                 return 0;
379         }
380
381         return append_resolver(interface, domain, server, lifetime, 0);
382 }
383
384 /**
385  * connman_resolver_remove:
386  * @interface: network interface
387  * @domain: domain limitation
388  * @server: server address
389  *
390  * Remover resolver server address from current list
391  */
392 int connman_resolver_remove(const char *interface, const char *domain,
393                                                         const char *server)
394 {
395         GSList *list, *matches = NULL;
396
397         DBG("interface %s domain %s server %s", interface, domain, server);
398
399         if (server == NULL)
400                 return -EINVAL;
401
402         for (list = entry_list; list; list = list->next) {
403                 struct entry_data *entry = list->data;
404
405                 if (interface != NULL &&
406                                 g_strcmp0(entry->interface, interface) != 0)
407                         continue;
408
409                 if (domain != NULL && g_strcmp0(entry->domain, domain) != 0)
410                         continue;
411
412                 if (g_strcmp0(entry->server, server) != 0)
413                         continue;
414
415                 matches = g_slist_append(matches, entry);
416         }
417
418         if (matches == NULL)
419                 return -ENOENT;
420
421         remove_entries(matches);
422
423         return 0;
424 }
425
426 /**
427  * connman_resolver_remove_all:
428  * @interface: network interface
429  *
430  * Remove all resolver server address for the specified interface
431  */
432 int connman_resolver_remove_all(const char *interface)
433 {
434         GSList *list, *matches = NULL;
435
436         DBG("interface %s", interface);
437
438         if (interface == NULL)
439                 return -EINVAL;
440
441         for (list = entry_list; list; list = list->next) {
442                 struct entry_data *entry = list->data;
443
444                 if (g_strcmp0(entry->interface, interface) != 0)
445                         continue;
446
447                 matches = g_slist_append(matches, entry);
448         }
449
450         if (matches == NULL)
451                 return -ENOENT;
452
453         remove_entries(matches);
454
455         return 0;
456 }
457
458 /**
459  * connman_resolver_flush:
460  *
461  * Flush pending resolver requests
462  */
463 void connman_resolver_flush(void)
464 {
465         if (dnsproxy_enabled == TRUE)
466                 __connman_dnsproxy_flush();
467
468         return;
469 }
470
471 int __connman_resolver_redo_servers(const char *interface)
472 {
473         GSList *list;
474
475         if (dnsproxy_enabled == FALSE)
476                 return 0;
477
478         DBG("interface %s", interface);
479
480         if (interface == NULL)
481                 return -EINVAL;
482
483         for (list = entry_list; list; list = list->next) {
484                 struct entry_data *entry = list->data;
485
486                 if (entry->timeout == 0 ||
487                                 g_strcmp0(entry->interface, interface) != 0)
488                         continue;
489
490                 /*
491                  * We remove the server, and then re-create so that it will
492                  * use proper source addresses when sending DNS queries.
493                  */
494                 __connman_dnsproxy_remove(entry->interface, entry->domain,
495                                         entry->server);
496
497                 __connman_dnsproxy_append(entry->interface, entry->domain,
498                                         entry->server);
499         }
500
501         return 0;
502 }
503
504 static void free_entry(gpointer data)
505 {
506         struct entry_data *entry = data;
507         g_free(entry->interface);
508         g_free(entry->domain);
509         g_free(entry->server);
510         g_free(entry);
511 }
512
513 static void free_resolvfile(gpointer data)
514 {
515         struct resolvfile_entry *entry = data;
516         g_free(entry->interface);
517         g_free(entry->domain);
518         g_free(entry->server);
519         g_free(entry);
520 }
521
522 int __connman_resolver_init(connman_bool_t dnsproxy)
523 {
524         int i;
525         char **ns;
526
527         DBG("dnsproxy %d", dnsproxy);
528
529         if (dnsproxy == FALSE)
530                 return 0;
531
532         if (__connman_dnsproxy_init() < 0) {
533                 /* Fall back to resolv.conf */
534                 return 0;
535         }
536
537         dnsproxy_enabled = TRUE;
538
539         ns = connman_setting_get_string_list("FallbackNameservers");
540         for (i = 0; ns != NULL && ns[i] != NULL; i += 1) {
541                 DBG("server %s", ns[i]);
542                 append_resolver(NULL, NULL, ns[i], 0, RESOLVER_FLAG_PUBLIC);
543         }
544
545         return 0;
546 }
547
548 void __connman_resolver_cleanup(void)
549 {
550         DBG("");
551
552         if (dnsproxy_enabled == TRUE)
553                 __connman_dnsproxy_cleanup();
554         else {
555                 GList *list;
556                 GSList *slist;
557
558                 for (list = resolvfile_list; list; list = g_list_next(list))
559                         free_resolvfile(list->data);
560                 g_list_free(resolvfile_list);
561                 resolvfile_list = NULL;
562
563                 for (slist = entry_list; slist; slist = g_slist_next(slist))
564                         free_entry(slist->data);
565                 g_slist_free(entry_list);
566                 entry_list = NULL;
567         }
568 }