Add support multiple nameservers in /etc/resolv.conf
[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 #include <stdio.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <resolv.h>
33
34 #include "connman.h"
35
36 #define RESOLVER_FLAG_PUBLIC (1 << 0)
37
38 struct entry_data {
39         struct connman_resolver *resolver;
40         char *interface;
41         char *domain;
42         char *server;
43         unsigned int flags;
44 };
45
46 static GSList *entry_list = NULL;
47 static GSList *resolver_list = NULL;
48
49 static void remove_entries(GSList *entries)
50 {
51         GSList *list;
52
53         for (list = entries; list; list = list->next) {
54                 struct entry_data *entry = list->data;
55                 struct connman_resolver *resolver = entry->resolver;
56
57                 entry_list = g_slist_remove(entry_list, entry);
58
59                 if (resolver && resolver->remove)
60                         resolver->remove(entry->interface, entry->domain,
61                                                                 entry->server);
62
63                 g_free(entry->server);
64                 g_free(entry->domain);
65                 g_free(entry->interface);
66                 g_free(entry);
67         }
68
69         g_slist_free(entries);
70 }
71
72 static gint compare_priority(gconstpointer a, gconstpointer b)
73 {
74         const struct connman_resolver *resolver1 = a;
75         const struct connman_resolver *resolver2 = b;
76
77         return resolver2->priority - resolver1->priority;
78 }
79
80 /**
81  * connman_resolver_register:
82  * @resolver: resolver module
83  *
84  * Register a new resolver module
85  *
86  * Returns: %0 on success
87  */
88 int connman_resolver_register(struct connman_resolver *resolver)
89 {
90         GSList *list;
91
92         DBG("resolver %p name %s", resolver, resolver->name);
93
94         resolver_list = g_slist_insert_sorted(resolver_list, resolver,
95                                                         compare_priority);
96
97         if (resolver->append == NULL)
98                 return 0;
99
100         for (list = entry_list; list; list = list->next) {
101                 struct entry_data *entry = list->data;
102
103                 if (entry->resolver)
104                         continue;
105
106                 if (resolver->append(entry->interface, entry->domain,
107                                                         entry->server) == 0)
108                         entry->resolver = resolver;
109         }
110
111         return 0;
112 }
113
114 /**
115  * connman_resolver_unregister:
116  * @resolver: resolver module
117  *
118  * Remove a previously registered resolver module
119  */
120 void connman_resolver_unregister(struct connman_resolver *resolver)
121 {
122         GSList *list, *matches = NULL;
123
124         DBG("resolver %p name %s", resolver, resolver->name);
125
126         resolver_list = g_slist_remove(resolver_list, resolver);
127
128         for (list = entry_list; list; list = list->next) {
129                 struct entry_data *entry = list->data;
130
131                 if (entry->resolver != resolver)
132                         continue;
133
134                 matches = g_slist_append(matches, entry);
135         }
136
137         remove_entries(matches);
138 }
139
140 static int append_resolver(const char *interface, const char *domain,
141                                         const char *server, unsigned int flags)
142 {
143         struct entry_data *entry;
144         GSList *list;
145
146         DBG("interface %s domain %s server %s flags %d",
147                                         interface, domain, server, flags);
148
149         if (server == NULL)
150                 return -EINVAL;
151
152         entry = g_try_new0(struct entry_data, 1);
153         if (entry == NULL)
154                 return -ENOMEM;
155
156         entry->interface = g_strdup(interface);
157         entry->domain = g_strdup(domain);
158         entry->server = g_strdup(server);
159         entry->flags = flags;
160
161         entry_list = g_slist_append(entry_list, entry);
162
163         for (list = resolver_list; list; list = list->next) {
164                 struct connman_resolver *resolver = list->data;
165
166                 if (resolver->append == NULL)
167                         continue;
168
169                 if (resolver->append(interface, domain, server) == 0) {
170                         entry->resolver = resolver;
171                         break;
172                 }
173         }
174
175         return 0;
176 }
177
178 /**
179  * connman_resolver_append:
180  * @interface: network interface
181  * @domain: domain limitation
182  * @server: server address
183  *
184  * Append resolver server address to current list
185  */
186 int connman_resolver_append(const char *interface, const char *domain,
187                                                         const char *server)
188 {
189         DBG("interface %s domain %s server %s", interface, domain, server);
190
191         return append_resolver(interface, domain, server, 0);
192 }
193
194 /**
195  * connman_resolver_remove:
196  * @interface: network interface
197  * @domain: domain limitation
198  * @server: server address
199  *
200  * Remover resolver server address from current list
201  */
202 int connman_resolver_remove(const char *interface, const char *domain,
203                                                         const char *server)
204 {
205         GSList *list, *matches = NULL;
206
207         DBG("interface %s domain %s server %s", interface, domain, server);
208
209         if (server == NULL)
210                 return -EINVAL;
211
212         for (list = entry_list; list; list = list->next) {
213                 struct entry_data *entry = list->data;
214
215                 if (interface != NULL &&
216                                 g_strcmp0(entry->interface, interface) != 0)
217                         continue;
218
219                 if (domain != NULL && g_strcmp0(entry->domain, domain) != 0)
220                         continue;
221
222                 if (g_strcmp0(entry->server, server) != 0)
223                         continue;
224
225                 matches = g_slist_append(matches, entry);
226         }
227
228         if (matches == NULL)
229                 return -ENOENT;
230
231         remove_entries(matches);
232
233         return 0;
234 }
235
236 /**
237  * connman_resolver_remove_all:
238  * @interface: network interface
239  *
240  * Remove all resolver server address for the specified interface
241  */
242 int connman_resolver_remove_all(const char *interface)
243 {
244         GSList *list, *matches = NULL;
245
246         DBG("interface %s", interface);
247
248         if (interface == NULL)
249                 return -EINVAL;
250
251         for (list = entry_list; list; list = list->next) {
252                 struct entry_data *entry = list->data;
253
254                 if (g_strcmp0(entry->interface, interface) != 0)
255                         continue;
256
257                 matches = g_slist_append(matches, entry);
258         }
259
260         if (matches == NULL)
261                 return -ENOENT;
262
263         remove_entries(matches);
264
265         return 0;
266 }
267
268 /**
269  * connman_resolver_append_public_server:
270  * @server: server address
271  *
272  * Append public resolver server address to current list
273  */
274 int connman_resolver_append_public_server(const char *server)
275 {
276         DBG("server %s", server);
277
278         return append_resolver(NULL, NULL, server, RESOLVER_FLAG_PUBLIC);
279 }
280
281 /**
282  * connman_resolver_remove_public_server:
283  * @server: server address
284  *
285  * Remove public resolver server address to current list
286  */
287 int connman_resolver_remove_public_server(const char *server)
288 {
289         DBG("server %s", server);
290
291         return connman_resolver_remove(NULL, NULL, server);
292 }
293
294 static int selftest_append(const char *interface, const char *domain,
295                                                         const char *server)
296 {
297         DBG("server %s", server);
298
299         return 0;
300 }
301
302 static int selftest_remove(const char *interface, const char *domain,
303                                                         const char *server)
304 {
305         DBG("server %s", server);
306
307         return 0;
308 }
309
310 static struct connman_resolver selftest_resolver = {
311         .name     = "selftest",
312         .priority = CONNMAN_RESOLVER_PRIORITY_HIGH + 42,
313         .append   = selftest_append,
314         .remove   = selftest_remove,
315 };
316
317 int __connman_resolver_selftest(void)
318 {
319         connman_resolver_append("wlan0", "lwn.net", "192.168.0.1");
320
321         connman_resolver_register(&selftest_resolver);
322
323         connman_resolver_append("eth0", "moblin.org", "192.168.42.1");
324         connman_resolver_append("wlan0", "lwn.net", "192.168.0.2");
325
326         connman_resolver_append_public_server("8.8.8.8");
327
328         connman_resolver_remove_public_server("8.8.8.8");
329
330         connman_resolver_remove_all("wlan0");
331
332         connman_resolver_unregister(&selftest_resolver);
333
334         return 0;
335 }
336
337 struct resolvfile_entry {
338         char *interface;
339         char *domain;
340         char *server;
341 };
342
343 static GList *resolvfile_list = NULL;
344
345 static void resolvfile_remove_entries(GList *entries)
346 {
347         GList *list;
348
349         for (list = entries; list; list = list->next) {
350                 struct resolvfile_entry *entry = list->data;
351
352                 resolvfile_list = g_list_remove(
353                                         resolvfile_list, entry);
354
355                 g_free(entry->server);
356                 g_free(entry->domain);
357                 g_free(entry->interface);
358                 g_free(entry);
359         }
360
361         g_list_free(entries);
362 }
363
364 static int resolvfile_export(void)
365 {
366         GList *list;
367         GString *content;
368         int fd, err;
369         unsigned int count;
370         mode_t old_umask;
371
372         content = g_string_new("# Generated by Connection Manager\n"
373                                                 "options edns0\n");
374
375         /* Nameservers are added in reverse so that the most recently appended
376          * entry is the primary nameserver.  No more than MAXNS nameservers are
377          * used.
378          */
379         for (count = 0, list = g_list_last(resolvfile_list);
380                                                 list && (count < MAXNS);
381                                                 list = g_list_previous(list)) {
382                 struct resolvfile_entry *entry = list->data;
383                 g_string_append_printf(content, "nameserver %s\n",
384                                                                 entry->server);
385                 count++;
386         }
387
388         old_umask = umask(022);
389
390         fd = open("/etc/resolv.conf", O_RDWR | O_CREAT,
391                                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
392         if (fd < 0) {
393                 err = -errno;
394                 goto done;
395         }
396
397         if (ftruncate(fd, 0) < 0) {
398                 err = -errno;
399                 goto failed;
400         }
401
402         err = 0;
403
404         if (write(fd, content->str, content->len) < 0)
405                 err = -errno;
406
407 failed:
408         close(fd);
409
410 done:
411         g_string_free(content, TRUE);
412         umask(old_umask);
413
414         return err;
415 }
416
417 static int resolvfile_append(const char *interface, const char *domain,
418                                                         const char *server)
419 {
420         struct resolvfile_entry *entry;
421
422         DBG("interface %s server %s", interface, server);
423
424         if (interface == NULL)
425                 return -ENOENT;
426
427         entry = g_try_new0(struct resolvfile_entry, 1);
428         if (entry == NULL)
429                 return -ENOMEM;
430
431         entry->interface = g_strdup(interface);
432         entry->domain = g_strdup(domain);
433         entry->server = g_strdup(server);
434
435         resolvfile_list = g_list_append(resolvfile_list, entry);
436
437         return resolvfile_export();
438 }
439
440 static int resolvfile_remove(const char *interface, const char *domain,
441                                                         const char *server)
442 {
443         GList *list, *matches = NULL;
444
445         DBG("interface %s server %s", interface, server);
446
447         for (list = resolvfile_list; list; list = g_list_next(list)) {
448                 struct resolvfile_entry *entry = list->data;
449
450                 if (interface != NULL &&
451                                 g_strcmp0(entry->interface, interface) != 0)
452                         continue;
453
454                 if (domain != NULL && g_strcmp0(entry->domain, domain) != 0)
455                         continue;
456
457                 if (g_strcmp0(entry->server, server) != 0)
458                         continue;
459
460                 matches = g_list_append(matches, entry);
461         }
462
463         resolvfile_remove_entries(matches);
464
465         return resolvfile_export();
466 }
467
468 static struct connman_resolver resolvfile_resolver = {
469         .name           = "resolvfile",
470         .priority       = CONNMAN_RESOLVER_PRIORITY_LOW,
471         .append         = resolvfile_append,
472         .remove         = resolvfile_remove,
473 };
474
475 int __connman_resolver_init(void)
476 {
477         DBG("");
478
479         return connman_resolver_register(&resolvfile_resolver);
480 }
481
482 void __connman_resolver_cleanup(void)
483 {
484         DBG("");
485
486         connman_resolver_unregister(&resolvfile_resolver);
487 }