| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2007-2009 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "connman.h" |
| |
| #define _DBG_RESOLVER(fmt, arg...) DBG(DBG_RESOLV, fmt, ## arg) |
| |
| static GSList *entry_list = NULL; |
| static GSList *resolver_list = NULL; |
| |
| static void reclaim_entry(struct connman_resolver_state *entry) |
| { |
| struct connman_resolver *resolver = entry->resolver; |
| |
| /* |
| * NB: resolver may be NULL if the plugin was unregister'd |
| */ |
| if (resolver != NULL && resolver->unset != NULL) |
| resolver->unset(entry); |
| |
| entry_list = g_slist_remove(entry_list, entry); |
| g_strfreev(entry->servers); |
| g_strfreev(entry->search_domains); |
| g_free(entry->domain); |
| g_free(entry->interface); |
| g_free(entry); |
| } |
| |
| static gint compare_priority(gconstpointer a, gconstpointer b) |
| { |
| const struct connman_resolver *resolver1 = a; |
| const struct connman_resolver *resolver2 = b; |
| |
| return resolver2->priority - resolver1->priority; |
| } |
| |
| /** |
| * connman_resolver_register: |
| * @resolver: resolver module |
| * |
| * Register a new resolver module |
| * |
| * Returns: %0 on success |
| */ |
| int connman_resolver_register(struct connman_resolver *resolver) |
| { |
| _DBG_RESOLVER("resolver %p name %s", resolver, resolver->name); |
| |
| resolver_list = g_slist_insert_sorted(resolver_list, resolver, |
| compare_priority); |
| if (resolver->set != NULL) { |
| GSList *list; |
| |
| /* try to bind existing entries */ |
| for (list = entry_list; list != NULL; list = list->next) { |
| struct connman_resolver_state *entry = list->data; |
| |
| if (entry->resolver == NULL && resolver->set(entry) == 0) |
| entry->resolver = resolver; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * connman_resolver_unregister: |
| * @resolver: resolver module |
| * |
| * Remove a previously registered resolver module |
| */ |
| void connman_resolver_unregister(struct connman_resolver *resolver) |
| { |
| GSList *list; |
| |
| _DBG_RESOLVER("resolver %p name %s", resolver, resolver->name); |
| |
| resolver_list = g_slist_remove(resolver_list, resolver); |
| /* |
| * Unbind existing entries. We do not reclaim the entries |
| * as that happens when the associated ipconfig records are |
| * torn down (doing it here would leave ipconfig holding |
| * stale references). |
| */ |
| for (list = entry_list; list != NULL; list = list->next) { |
| struct connman_resolver_state *entry = list->data; |
| |
| if (entry->resolver == resolver) { |
| if (resolver->unset != NULL) |
| resolver->unset(entry); |
| entry->resolver = NULL; |
| } |
| } |
| } |
| |
| /** |
| * connman_resolver_set: |
| * @interface: network interface |
| * @domain: local domain name |
| * @search_domains: names for searching !FQDN's |
| * @servers: server addresses |
| * |
| * Add resolver state to the current list |
| */ |
| struct connman_resolver_state *connman_resolver_set(const char *interface, |
| const char *domain, char **search_domains, char **servers) |
| { |
| struct connman_resolver_state *entry; |
| char **serverp; |
| GSList *list; |
| |
| if (servers == NULL) { |
| _DBG_RESOLVER("No DNS servers for domain %s interface %s", |
| domain, interface); |
| return NULL; |
| } |
| |
| connman_info("Add DNS servers for domain %s interface %s:", |
| domain, interface); |
| |
| for (serverp = servers; *serverp != NULL; serverp++) |
| connman_info(" server %s", *serverp); |
| |
| entry = g_try_new0(struct connman_resolver_state, 1); |
| if (entry == NULL) { |
| connman_error("%s: no memory for resolver state", __func__); |
| return NULL; |
| } |
| entry->interface = g_strdup(interface); |
| entry->domain = g_strdup(domain); |
| entry->search_domains = g_strdupv(search_domains); |
| entry->servers = g_strdupv(servers); |
| |
| entry_list = g_slist_append(entry_list, entry); |
| |
| for (list = resolver_list; list; list = list->next) { |
| struct connman_resolver *resolver = list->data; |
| |
| if (resolver->set == NULL) |
| continue; |
| |
| if (resolver->set(entry) == 0) { |
| entry->resolver = resolver; |
| break; |
| } |
| } |
| return entry; |
| } |
| |
| static int |
| g_strv_equal(char **a, char **b) |
| { |
| int i; |
| |
| if (a == NULL && b == NULL) |
| return TRUE; |
| if (a == NULL || b == NULL) |
| return FALSE; |
| /* NB: a and b != NULL */ |
| for (i = 0; a[i] != NULL; i++) { |
| if (b[i] == NULL) |
| return FALSE; |
| if (g_strcmp0(a[i], b[i]) != 0) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * connman_resolver_update: |
| * @interface: network interface |
| * @domain: local domain name |
| * @search_domains: names for searching !FQDN's |
| * @servers: server addresses |
| * |
| * Update state for a previously created resolver entry. |
| */ |
| struct connman_resolver_state *connman_resolver_update( |
| struct connman_resolver_state *entry, |
| const char *domain, char **search_domains, char **servers) |
| { |
| char **serverp; |
| int changed; |
| |
| if (servers == NULL) { |
| connman_resolver_unset(entry); |
| return NULL; |
| } |
| |
| connman_info("Update DNS servers for domain %s interface %s:", |
| domain, entry->interface); |
| |
| for (serverp = servers; *serverp != NULL; serverp++) |
| connman_info(" server %s", *serverp); |
| |
| changed = FALSE; |
| if (g_strcmp0(entry->domain, domain) != 0) { |
| g_free(entry->domain); |
| entry->domain = g_strdup(domain); |
| changed = TRUE; |
| } |
| if (g_strv_equal(search_domains, entry->search_domains) == FALSE) { |
| g_strfreev(entry->search_domains); |
| entry->search_domains = g_strdupv(search_domains); |
| changed = TRUE; |
| } |
| if (g_strv_equal(servers, entry->servers) == FALSE) { |
| g_strfreev(entry->servers); |
| entry->servers = g_strdupv(servers); |
| changed = TRUE; |
| } |
| |
| if (changed == TRUE && entry->resolver != NULL) { |
| struct connman_resolver *resolver = entry->resolver; |
| |
| resolver->unset(entry); |
| if (resolver->set(entry) != 0) |
| entry->resolver = NULL; |
| } |
| return entry; |
| } |
| |
| static int interface_compare(gconstpointer a, gconstpointer b) |
| { |
| const struct connman_resolver_state *entry = |
| (const struct connman_resolver_state *)a; |
| const char *interface = (const char *)b; |
| |
| return g_strcmp0(entry->interface, interface); |
| } |
| |
| /** |
| * connman_resolver_lookup: |
| * @interface: network interface |
| * |
| * Find the resolver state for a given interface |
| */ |
| const struct connman_resolver_state *connman_resolver_lookup( |
| const char *interface) |
| { |
| GSList *list; |
| |
| list = g_slist_find_custom(entry_list, interface, interface_compare); |
| if (list != NULL) |
| return (const struct connman_resolver_state *) list->data; |
| return NULL; |
| } |
| |
| /** |
| * connman_resolver_unset: |
| * @interface: network interface |
| * |
| * Remove all resolver server address for the specified interface |
| */ |
| void connman_resolver_unset(struct connman_resolver_state *entry) |
| { |
| _DBG_RESOLVER("Remove DNS servers for domain %s interface %s", |
| entry->domain, entry->interface); |
| reclaim_entry(entry); |
| } |
| |
| int connman_resolvfile_write(const char *filename, const char *domain, |
| char **search_domains, char **servers) |
| { |
| mode_t omask; |
| int fd, err, ret; |
| char *cmd; |
| char *search_list; |
| char **serverp; |
| /* |
| * Send queries one-at-a-time, rather than parallelizing IPv4 |
| * and IPv6 queries for a single host. Use minimum 1-second |
| * request timeout. |
| */ |
| const char extra_options[] = "options single-request timeout:1\n"; |
| |
| _DBG_RESOLVER("write file %s, domain %s search_domains %p servers %p", |
| filename, domain, search_domains, servers); |
| |
| omask = umask(022); |
| err = 0; |
| fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd < 0) { |
| connman_error("%s: open failed: %s", __func__, |
| strerror(errno)); |
| err = -errno; |
| goto done; |
| } |
| |
| cmd = "# Generated by Connection Manager\n"; |
| ret = write(fd, cmd, strlen(cmd)); |
| if (ret == -1) |
| goto badwrite; |
| |
| if (search_domains != NULL) |
| search_list = g_strjoinv(" ", search_domains); |
| else if (domain != NULL) |
| search_list = g_strdup_printf("%s.", domain); |
| else |
| search_list = NULL; |
| |
| if (search_list != NULL) { |
| cmd = g_strdup_printf("search %s\n", search_list); |
| g_free(search_list); |
| if (cmd == NULL) { |
| connman_error("%s: strdup failed", __func__); |
| err = -ENOMEM; |
| goto done; |
| } |
| |
| ret = write(fd, cmd, strlen(cmd)); |
| g_free(cmd); |
| if (ret == -1) |
| goto badwrite; |
| } |
| |
| for (serverp = servers; *serverp != NULL; serverp++) { |
| cmd = g_strdup_printf("nameserver %s\n", *serverp); |
| ret = write(fd, cmd, strlen(cmd)); |
| g_free(cmd); |
| if (ret == -1) |
| goto badwrite; |
| } |
| |
| ret = write(fd, extra_options, strlen(extra_options)); |
| if (ret == -1) |
| goto badwrite; |
| |
| done: |
| if (fd >= 0) |
| close(fd); |
| (void) umask(omask); |
| return err; |
| badwrite: |
| connman_error("%s: write failed: %s (len %zu)", |
| __func__, strerror(errno), strlen(cmd)); |
| err = -errno; |
| goto done; |
| } |
| |
| static int resolvfile_set(const struct connman_resolver_state *entry) |
| { |
| _DBG_RESOLVER("interface %s domain %s", entry->interface, |
| entry->domain); |
| |
| return connman_resolvfile_write("/etc/resolv.conf", entry->domain, |
| entry->search_domains, entry->servers); |
| } |
| |
| static int resolvfile_unset(const struct connman_resolver_state *entry) |
| { |
| _DBG_RESOLVER("interface %s", entry->interface); |
| |
| return 0; |
| } |
| |
| static struct connman_resolver resolvfile_resolver = { |
| .name = "resolvfile", |
| .priority = CONNMAN_RESOLVER_PRIORITY_LOW, |
| .set = resolvfile_set, |
| .unset = resolvfile_unset, |
| }; |
| |
| int __connman_resolver_init(void) |
| { |
| return connman_resolver_register(&resolvfile_resolver); |
| } |
| |
| void __connman_resolver_cleanup(void) |
| { |
| connman_resolver_unregister(&resolvfile_resolver); |
| } |