f6fd0632d6eca7e170d4c17657f76d3cfb87bde7
[platform/upstream/connman.git] / src / resolver.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2013  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 #include <netdb.h>
35
36 #include "connman.h"
37
38 #define RESOLVER_FLAG_PUBLIC (1 << 0)
39
40 #if defined TIZEN_EXT
41 #include <sys/smack.h>
42 #endif
43
44 /*
45  * Threshold for RDNSS lifetime. Will be used to trigger RS
46  * before RDNSS entries actually expire
47  */
48 #define RESOLVER_LIFETIME_REFRESH_THRESHOLD 0.8
49
50 struct entry_data {
51         int index;
52         char *domain;
53         char *server;
54         int family;
55         unsigned int flags;
56         unsigned int lifetime;
57         guint timeout;
58 };
59
60 static GSList *entry_list = NULL;
61 static bool dnsproxy_enabled = false;
62
63 struct resolvfile_entry {
64         int index;
65         char *domain;
66         char *server;
67 };
68
69 static GList *resolvfile_list = NULL;
70
71 static void resolvfile_remove_entries(GList *entries)
72 {
73         GList *list;
74
75         for (list = entries; list; list = list->next) {
76                 struct resolvfile_entry *entry = list->data;
77
78                 resolvfile_list = g_list_remove(resolvfile_list, entry);
79
80                 g_free(entry->server);
81                 g_free(entry->domain);
82                 g_free(entry);
83         }
84
85         g_list_free(entries);
86 }
87
88 static int resolvfile_export(void)
89 {
90         GList *list;
91         GString *content;
92         int fd, err;
93         unsigned int count;
94         mode_t old_umask;
95
96         content = g_string_new("# Generated by Connection Manager\n");
97
98         /*
99          * Domains and nameservers are added in reverse so that the most
100          * recently appended entry is the primary one. No more than
101          * MAXDNSRCH/MAXNS entries are used.
102          */
103
104         for (count = 0, list = g_list_last(resolvfile_list);
105                                                 list && (count < MAXDNSRCH);
106                                                 list = g_list_previous(list)) {
107                 struct resolvfile_entry *entry = list->data;
108
109                 if (!entry->domain)
110                         continue;
111
112                 if (count == 0)
113                         g_string_append_printf(content, "search ");
114
115                 g_string_append_printf(content, "%s ", entry->domain);
116                 count++;
117         }
118
119         if (count)
120                 g_string_append_printf(content, "\n");
121
122         for (count = 0, list = g_list_last(resolvfile_list);
123                                                 list && (count < MAXNS);
124                                                 list = g_list_previous(list)) {
125                 struct resolvfile_entry *entry = list->data;
126
127                 if (!entry->server)
128                         continue;
129
130                 g_string_append_printf(content, "nameserver %s\n",
131                                                                 entry->server);
132                 count++;
133         }
134
135         old_umask = umask(022);
136
137         fd = open("/etc/resolv.conf", O_RDWR | O_CREAT | O_CLOEXEC,
138                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
139         if (fd < 0) {
140                 err = -errno;
141                 goto done;
142         }
143
144 #if defined TIZEN_EXT
145         if (smack_fsetlabel(fd, "_", SMACK_LABEL_ACCESS) != 0)
146                 DBG("Failed to label _");
147 #endif
148
149         if (ftruncate(fd, 0) < 0) {
150                 err = -errno;
151                 goto failed;
152         }
153
154         err = 0;
155
156         if (write(fd, content->str, content->len) < 0)
157                 err = -errno;
158
159 failed:
160         close(fd);
161
162 done:
163         g_string_free(content, TRUE);
164         umask(old_umask);
165
166         return err;
167 }
168
169 int __connman_resolvfile_append(int index, const char *domain,
170                                                         const char *server)
171 {
172         struct resolvfile_entry *entry;
173
174         DBG("index %d server %s", index, server);
175
176         if (index < 0)
177                 return -ENOENT;
178
179         entry = g_try_new0(struct resolvfile_entry, 1);
180         if (!entry)
181                 return -ENOMEM;
182
183         entry->index = index;
184         entry->domain = g_strdup(domain);
185         entry->server = g_strdup(server);
186
187         resolvfile_list = g_list_append(resolvfile_list, entry);
188
189         return resolvfile_export();
190 }
191
192 int __connman_resolvfile_remove(int index, const char *domain,
193                                                         const char *server)
194 {
195         GList *list, *matches = NULL;
196
197         DBG("index %d server %s", index, server);
198
199         for (list = resolvfile_list; list; list = g_list_next(list)) {
200                 struct resolvfile_entry *entry = list->data;
201
202                 if (index >= 0 && entry->index != index)
203                         continue;
204
205                 if (domain && g_strcmp0(entry->domain, domain) != 0)
206                         continue;
207
208                 if (g_strcmp0(entry->server, server) != 0)
209                         continue;
210
211                 matches = g_list_append(matches, entry);
212         }
213
214         resolvfile_remove_entries(matches);
215
216         return resolvfile_export();
217 }
218
219 static void append_fallback_nameservers(void)
220 {
221         GSList *list;
222
223         for (list = entry_list; list; list = list->next) {
224                 struct entry_data *entry = list->data;
225
226                 if (entry->index >= 0 && entry->server)
227                         return;
228         }
229
230         for (list = entry_list; list; list = list->next) {
231                 struct entry_data *entry = list->data;
232
233                 if (entry->index != -1 || !entry->server)
234                         continue;
235
236                 DBG("index %d server %s", entry->index, entry->server);
237
238                 if (dnsproxy_enabled) {
239                         __connman_dnsproxy_append(entry->index, entry->domain,
240                                         entry->server);
241                 } else {
242                         __connman_resolvfile_append(entry->index,
243                                         entry->domain, entry->server);
244                 }
245         }
246 }
247
248 static void remove_fallback_nameservers(void)
249 {
250         GSList *list;
251
252         for (list = entry_list; list; list = list->next) {
253                 struct entry_data *entry = list->data;
254
255                 if (entry->index >= 0 || !entry->server)
256                         continue;
257
258                 DBG("index %d server %s", entry->index, entry->server);
259
260                 if (dnsproxy_enabled) {
261                         __connman_dnsproxy_remove(entry->index, entry->domain,
262                                         entry->server);
263                 } else {
264                         __connman_resolvfile_remove(entry->index,
265                                         entry->domain, entry->server);
266                 }
267         }
268 }
269
270 static void remove_entries(GSList *entries)
271 {
272         GSList *list;
273
274         for (list = entries; list; list = list->next) {
275                 struct entry_data *entry = list->data;
276
277                 entry_list = g_slist_remove(entry_list, entry);
278
279                 if (dnsproxy_enabled) {
280                         __connman_dnsproxy_remove(entry->index, entry->domain,
281                                                         entry->server);
282                 } else {
283                         __connman_resolvfile_remove(entry->index, entry->domain,
284                                                         entry->server);
285                 }
286
287                 if (entry->timeout)
288                         g_source_remove(entry->timeout);
289                 g_free(entry->server);
290                 g_free(entry->domain);
291                 g_free(entry);
292         }
293
294         g_slist_free(entries);
295 }
296
297 static gboolean resolver_expire_cb(gpointer user_data)
298 {
299         struct entry_data *entry = user_data;
300         GSList *list;
301
302         DBG("index %d domain %s server %s",
303                         entry->index, entry->domain, entry->server);
304
305         list = g_slist_prepend(NULL, entry);
306
307         if (entry->index >= 0) {
308                 struct connman_service *service;
309                 service = __connman_service_lookup_from_index(entry->index);
310                 if (service)
311                         __connman_service_nameserver_remove(service,
312                                                         entry->server, true);
313         }
314
315         remove_entries(list);
316
317         return FALSE;
318 }
319
320 static gboolean resolver_refresh_cb(gpointer user_data)
321 {
322         struct entry_data *entry = user_data;
323         unsigned int interval;
324         struct connman_service *service = NULL;
325
326         /* Round up what we have left from lifetime */
327         interval = entry->lifetime *
328                 (1 - RESOLVER_LIFETIME_REFRESH_THRESHOLD) + 1.0;
329
330         DBG("RDNSS start index %d domain %s "
331                         "server %s remaining lifetime %d",
332                         entry->index, entry->domain,
333                         entry->server, interval);
334
335         entry->timeout = g_timeout_add_seconds(interval,
336                         resolver_expire_cb, entry);
337
338         if (entry->index >= 0) {
339                 service = __connman_service_lookup_from_index(entry->index);
340                 if (service) {
341                         /*
342                          * Send Router Solicitation to refresh RDNSS entries
343                          * before their lifetime expires
344                          */
345                         __connman_network_refresh_rs_ipv6(
346                                         __connman_service_get_network(service),
347                                         entry->index);
348                 }
349         }
350         return FALSE;
351 }
352
353 static int append_resolver(int index, const char *domain,
354                                 const char *server, unsigned int lifetime,
355                                                         unsigned int flags)
356 {
357         struct entry_data *entry;
358         unsigned int interval;
359
360         DBG("index %d domain %s server %s lifetime %d flags %d",
361                                 index, domain, server, lifetime, flags);
362
363         if (!server && !domain)
364                 return -EINVAL;
365
366         entry = g_try_new0(struct entry_data, 1);
367         if (!entry)
368                 return -ENOMEM;
369
370         entry->index = index;
371         entry->domain = g_strdup(domain);
372         entry->server = g_strdup(server);
373         entry->flags = flags;
374         entry->lifetime = lifetime;
375
376         if (server)
377                 entry->family = connman_inet_check_ipaddress(server);
378
379         if (lifetime) {
380                 interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD;
381
382                 DBG("RDNSS start index %d domain %s "
383                                 "server %s lifetime threshold %d",
384                                 index, domain, server, interval);
385
386                 entry->timeout = g_timeout_add_seconds(interval,
387                                 resolver_refresh_cb, entry);
388
389                 /*
390                  * We update the service only for those nameservers
391                  * that are automagically added via netlink (lifetime > 0)
392                  */
393                 if (server && entry->index >= 0) {
394                         struct connman_service *service;
395                         service = __connman_service_lookup_from_index(entry->index);
396                         if (service)
397                                 __connman_service_nameserver_append(service,
398                                                                 server, true);
399                 }
400         }
401
402         if (entry->index >= 0 && entry->server)
403                 remove_fallback_nameservers();
404
405         entry_list = g_slist_append(entry_list, entry);
406
407         if (dnsproxy_enabled)
408                 __connman_dnsproxy_append(entry->index, domain, server);
409         else
410                 __connman_resolvfile_append(entry->index, domain, server);
411
412         return 0;
413 }
414
415 /**
416  * connman_resolver_append:
417  * @index: network interface index
418  * @domain: domain limitation
419  * @server: server address
420  *
421  * Append resolver server address to current list
422  */
423 int connman_resolver_append(int index, const char *domain,
424                                                 const char *server)
425 {
426         GSList *list;
427
428         DBG("index %d domain %s server %s", index, domain, server);
429
430         if (!server && !domain)
431                 return -EINVAL;
432
433         for (list = entry_list; list; list = list->next) {
434                 struct entry_data *entry = list->data;
435
436                 if (entry->timeout > 0)
437                         continue;
438
439                 if (entry->index == index &&
440                                 g_strcmp0(entry->domain, domain) == 0 &&
441                                 g_strcmp0(entry->server, server) == 0) {
442                         if (dnsproxy_enabled)
443                                 __connman_dnsproxy_append(entry->index, domain,
444                                                 server);
445
446                         return -EEXIST;
447                 }
448         }
449
450         return append_resolver(index, domain, server, 0, 0);
451 }
452
453 /**
454  * connman_resolver_append_lifetime:
455  * @index: network interface index
456  * @domain: domain limitation
457  * @server: server address
458  * @timeout: server lifetime in seconds
459  *
460  * Append resolver server address to current list
461  */
462 int connman_resolver_append_lifetime(int index, const char *domain,
463                                 const char *server, unsigned int lifetime)
464 {
465         GSList *list;
466         unsigned int interval;
467
468         DBG("index %d domain %s server %s lifetime %d",
469                                 index, domain, server, lifetime);
470
471         if (!server && !domain)
472                 return -EINVAL;
473
474         for (list = entry_list; list; list = list->next) {
475                 struct entry_data *entry = list->data;
476
477                 if (entry->timeout == 0 ||
478                                 entry->index != index ||
479                                 g_strcmp0(entry->domain, domain) != 0 ||
480                                 g_strcmp0(entry->server, server) != 0)
481                         continue;
482
483                 g_source_remove(entry->timeout);
484
485                 if (lifetime == 0) {
486                         resolver_expire_cb(entry);
487                         return 0;
488                 }
489
490                 interval = lifetime * RESOLVER_LIFETIME_REFRESH_THRESHOLD;
491
492                 DBG("RDNSS start index %d domain %s "
493                                 "server %s lifetime threshold %d",
494                                 index, domain, server, interval);
495
496                 entry->timeout = g_timeout_add_seconds(interval,
497                                 resolver_refresh_cb, entry);
498                 return 0;
499         }
500
501         return append_resolver(index, domain, server, lifetime, 0);
502 }
503
504 /**
505  * connman_resolver_remove:
506  * @index: network interface index
507  * @domain: domain limitation
508  * @server: server address
509  *
510  * Remover resolver server address from current list
511  */
512 int connman_resolver_remove(int index, const char *domain, const char *server)
513 {
514         GSList *list, *matches = NULL;
515
516         DBG("index %d domain %s server %s", index, domain, server);
517
518         for (list = entry_list; list; list = list->next) {
519                 struct entry_data *entry = list->data;
520
521                 if (entry->index != index)
522                         continue;
523
524                 if (g_strcmp0(entry->domain, domain) != 0)
525                         continue;
526
527                 if (g_strcmp0(entry->server, server) != 0)
528                         continue;
529
530                 matches = g_slist_prepend(matches, entry);
531                 break;
532         }
533
534         if (!matches)
535                 return -ENOENT;
536
537         remove_entries(matches);
538
539         return 0;
540 }
541
542 /**
543  * connman_resolver_remove_all:
544  * @index: network interface index
545  *
546  * Remove all resolver server address for the specified interface index
547  */
548 int connman_resolver_remove_all(int index)
549 {
550         GSList *list, *matches = NULL;
551
552         DBG("index %d", index);
553
554         if (index < 0)
555                 return -EINVAL;
556
557         for (list = entry_list; list; list = list->next) {
558                 struct entry_data *entry = list->data;
559
560                 if (entry->index != index)
561                         continue;
562
563                 matches = g_slist_prepend(matches, entry);
564         }
565
566         if (!matches)
567                 return -ENOENT;
568
569         remove_entries(matches);
570
571         return 0;
572 }
573
574 /**
575  * connman_resolver_flush:
576  *
577  * Flush pending resolver requests
578  */
579 void connman_resolver_flush(void)
580 {
581         append_fallback_nameservers();
582
583         if (dnsproxy_enabled)
584                 __connman_dnsproxy_flush();
585
586         return;
587 }
588
589 int __connman_resolver_redo_servers(int index)
590 {
591         GSList *list;
592
593         if (!dnsproxy_enabled)
594                 return 0;
595
596         DBG("index %d", index);
597
598         if (index < 0)
599                 return -EINVAL;
600
601         for (list = entry_list; list; list = list->next) {
602                 struct entry_data *entry = list->data;
603
604                 if (entry->timeout == 0 || entry->index != index)
605                         continue;
606
607                 /*
608                  * This function must only check IPv6 server addresses so
609                  * do not remove IPv4 name servers unnecessarily.
610                  */
611                 if (entry->family != AF_INET6)
612                         continue;
613
614                 /*
615                  * We remove the server, and then re-create so that it will
616                  * use proper source addresses when sending DNS queries.
617                  */
618                 __connman_dnsproxy_remove(entry->index, entry->domain,
619                                         entry->server);
620                 /*
621                  * Remove also the resolver timer for the old server entry.
622                  * A new timer will be set for the new server entry
623                  * when the next Router Advertisement message arrives
624                  * with RDNSS/DNSSL settings.
625                  */
626                 g_source_remove(entry->timeout);
627                 entry->timeout = 0;
628
629                 __connman_dnsproxy_append(entry->index, entry->domain,
630                                         entry->server);
631         }
632
633         return 0;
634 }
635
636 static void free_entry(gpointer data)
637 {
638         struct entry_data *entry = data;
639         g_free(entry->domain);
640         g_free(entry->server);
641         g_free(entry);
642 }
643
644 static void free_resolvfile(gpointer data)
645 {
646         struct resolvfile_entry *entry = data;
647         g_free(entry->domain);
648         g_free(entry->server);
649         g_free(entry);
650 }
651
652 int __connman_resolver_init(gboolean dnsproxy)
653 {
654         int i;
655         char **ns;
656
657         DBG("dnsproxy %d", dnsproxy);
658
659         if (!dnsproxy)
660                 return 0;
661
662         if (__connman_dnsproxy_init() < 0) {
663                 /* Fall back to resolv.conf */
664                 return 0;
665         }
666
667         dnsproxy_enabled = true;
668
669         ns = connman_setting_get_string_list("FallbackNameservers");
670         for (i = 0; ns && ns[i]; i += 1) {
671                 DBG("server %s", ns[i]);
672                 append_resolver(-1, NULL, ns[i], 0, RESOLVER_FLAG_PUBLIC);
673         }
674
675         return 0;
676 }
677
678 void __connman_resolver_cleanup(void)
679 {
680         DBG("");
681
682         if (dnsproxy_enabled)
683                 __connman_dnsproxy_cleanup();
684         else {
685                 GList *list;
686                 GSList *slist;
687
688                 for (list = resolvfile_list; list; list = g_list_next(list))
689                         free_resolvfile(list->data);
690                 g_list_free(resolvfile_list);
691                 resolvfile_list = NULL;
692
693                 for (slist = entry_list; slist; slist = g_slist_next(slist))
694                         free_entry(slist->data);
695                 g_slist_free(entry_list);
696                 entry_list = NULL;
697         }
698 }