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