f5674145f2afe720bfa4b4ef115e38d7beea372d
[framework/connectivity/connman.git] / src / resolver.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-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 #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)
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_append_public_server:
460  * @server: server address
461  *
462  * Append public resolver server address to current list
463  */
464 int connman_resolver_append_public_server(const char *server)
465 {
466         DBG("server %s", server);
467
468         return append_resolver(NULL, NULL, server, 0, RESOLVER_FLAG_PUBLIC);
469 }
470
471 /**
472  * connman_resolver_remove_public_server:
473  * @server: server address
474  *
475  * Remove public resolver server address to current list
476  */
477 int connman_resolver_remove_public_server(const char *server)
478 {
479         DBG("server %s", server);
480
481         return connman_resolver_remove(NULL, NULL, server);
482 }
483
484 /**
485  * connman_resolver_flush:
486  *
487  * Flush pending resolver requests
488  */
489 void connman_resolver_flush(void)
490 {
491         if (dnsproxy_enabled == TRUE)
492                 __connman_dnsproxy_flush();
493
494         return;
495 }
496
497 static void free_entry(gpointer data)
498 {
499         struct entry_data *entry = data;
500         g_free(entry->interface);
501         g_free(entry->domain);
502         g_free(entry->server);
503         g_free(entry);
504 }
505
506 static void free_resolvfile(gpointer data)
507 {
508         struct resolvfile_entry *entry = data;
509         g_free(entry->interface);
510         g_free(entry->domain);
511         g_free(entry->server);
512         g_free(entry);
513 }
514
515 int __connman_resolver_init(connman_bool_t dnsproxy)
516 {
517         DBG("dnsproxy %d", dnsproxy);
518
519         if (dnsproxy == FALSE)
520                 return 0;
521
522         if (__connman_dnsproxy_init() < 0) {
523                 /* Fall back to resolv.conf */
524                 return 0;
525         }
526
527         dnsproxy_enabled = TRUE;
528
529         return 0;
530 }
531
532 void __connman_resolver_cleanup(void)
533 {
534         DBG("");
535
536         if (dnsproxy_enabled == TRUE)
537                 __connman_dnsproxy_cleanup();
538         else {
539                 GList *list;
540                 GSList *slist;
541
542                 for (list = resolvfile_list; list; list = g_list_next(list))
543                         free_resolvfile(list->data);
544                 g_list_free(resolvfile_list);
545                 resolvfile_list = NULL;
546
547                 for (slist = entry_list; slist; slist = g_slist_next(slist))
548                         free_entry(slist->data);
549                 g_slist_free(entry_list);
550                 entry_list = NULL;
551         }
552 }