blob: aa545f55e8b1619606d05c43b3da9f6e444bacf5 [file] [log] [blame]
/*
*
* 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);
}