/* * Copyright (C) 2012-2016 Tobias Brunner * Copyright (C) 2009 Martin Willi * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * 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. */ #include "resolve_handler.h" #include #include #include #include #include #include #include /* path to resolvconf executable */ #define RESOLVCONF_EXEC "/sbin/resolvconf" /* default prefix used for resolvconf interfaces (should have high prio) */ #define RESOLVCONF_PREFIX "lo.inet.ipsec." typedef struct private_resolve_handler_t private_resolve_handler_t; /** * Private data of an resolve_handler_t object. */ struct private_resolve_handler_t { /** * Public resolve_handler_t interface. */ resolve_handler_t public; /** * resolv.conf file to use */ char *file; /** * Use resolvconf instead of writing directly to resolv.conf */ bool use_resolvconf; /** * Prefix to be used for interface names sent to resolvconf */ char *iface_prefix; /** * Mutex to access file exclusively */ mutex_t *mutex; /** * Reference counting for DNS servers dns_server_t */ array_t *servers; }; /** * Reference counting for DNS servers */ typedef struct { /** * DNS server address */ host_t *server; /** * Reference count */ u_int refcount; } dns_server_t; /** * Compare a server and a stored reference */ static int dns_server_find(const void *a, const void *b) { host_t *server = (host_t*)a; dns_server_t *item = (dns_server_t*)b; return chunk_compare(server->get_address(server), item->server->get_address(item->server)); } /** * Sort references by DNS server */ static int dns_server_sort(const void *a, const void *b, void *user) { const dns_server_t *da = a, *db = b; return chunk_compare(da->server->get_address(da->server), db->server->get_address(db->server)); } /** * Writes the given nameserver to resolv.conf */ static bool write_nameserver(private_resolve_handler_t *this, host_t *addr) { FILE *in, *out; char buf[1024]; size_t len; bool handled = FALSE; in = fopen(this->file, "r"); /* allows us to stream from in to out */ unlink(this->file); out = fopen(this->file, "w"); if (out) { fprintf(out, "nameserver %H # by strongSwan\n", addr); DBG1(DBG_IKE, "installing DNS server %H to %s", addr, this->file); handled = TRUE; /* copy rest of the file */ if (in) { while ((len = fread(buf, 1, sizeof(buf), in))) { ignore_result(fwrite(buf, 1, len, out)); } } fclose(out); } if (in) { fclose(in); } return handled; } /** * Removes the given nameserver from resolv.conf */ static void remove_nameserver(private_resolve_handler_t *this, host_t *addr) { FILE *in, *out; char line[1024], matcher[512]; in = fopen(this->file, "r"); if (in) { /* allows us to stream from in to out */ unlink(this->file); out = fopen(this->file, "w"); if (out) { snprintf(matcher, sizeof(matcher), "nameserver %H # by strongSwan\n", addr); /* copy all, but matching line */ while (fgets(line, sizeof(line), in)) { if (strpfx(line, matcher)) { DBG1(DBG_IKE, "removing DNS server %H from %s", addr, this->file); } else { fputs(line, out); } } fclose(out); } fclose(in); } } /** * Add or remove the given nameserver by invoking resolvconf. */ static bool invoke_resolvconf(private_resolve_handler_t *this, host_t *addr, bool install) { process_t *process; FILE *shell; int in, out, retval; /* we use the nameserver's IP address as part of the interface name to * make them unique */ process = process_start_shell(NULL, install ? &in : NULL, &out, NULL, "2>&1 %s %s %s%H", RESOLVCONF_EXEC, install ? "-a" : "-d", this->iface_prefix, addr); if (!process) { return FALSE; } if (install) { shell = fdopen(in, "w"); if (shell) { DBG1(DBG_IKE, "installing DNS server %H via resolvconf", addr); fprintf(shell, "nameserver %H\n", addr); fclose(shell); } else { close(in); close(out); process->wait(process, NULL); return FALSE; } } else { DBG1(DBG_IKE, "removing DNS server %H via resolvconf", addr); } shell = fdopen(out, "r"); if (shell) { while (TRUE) { char resp[128], *e; if (fgets(resp, sizeof(resp), shell) == NULL) { if (ferror(shell)) { DBG1(DBG_IKE, "error reading from resolvconf"); } break; } else { e = resp + strlen(resp); if (e > resp && e[-1] == '\n') { e[-1] = '\0'; } DBG1(DBG_IKE, "resolvconf: %s", resp); } } fclose(shell); } else { close(out); } if (!process->wait(process, &retval) || retval != EXIT_SUCCESS) { if (install) { /* revert changes when installing fails */ invoke_resolvconf(this, addr, FALSE); return FALSE; } } return TRUE; } METHOD(attribute_handler_t, handle, bool, private_resolve_handler_t *this, ike_sa_t *ike_sa, configuration_attribute_type_t type, chunk_t data) { dns_server_t *found = NULL; host_t *addr; bool handled; switch (type) { case INTERNAL_IP4_DNS: addr = host_create_from_chunk(AF_INET, data, 0); break; case INTERNAL_IP6_DNS: addr = host_create_from_chunk(AF_INET6, data, 0); break; default: return FALSE; } if (!addr || addr->is_anyaddr(addr)) { DESTROY_IF(addr); return FALSE; } this->mutex->lock(this->mutex); if (array_bsearch(this->servers, addr, dns_server_find, &found) == -1) { if (this->use_resolvconf) { handled = invoke_resolvconf(this, addr, TRUE); } else { handled = write_nameserver(this, addr); } if (handled) { INIT(found, .server = addr->clone(addr), .refcount = 1, ); array_insert_create(&this->servers, ARRAY_TAIL, found); array_sort(this->servers, dns_server_sort, NULL); } } else { DBG1(DBG_IKE, "DNS server %H already installed, increasing refcount", addr); found->refcount++; handled = TRUE; } this->mutex->unlock(this->mutex); addr->destroy(addr); if (!handled) { DBG1(DBG_IKE, "adding DNS server failed"); } return handled; } METHOD(attribute_handler_t, release, void, private_resolve_handler_t *this, ike_sa_t *ike_sa, configuration_attribute_type_t type, chunk_t data) { dns_server_t *found = NULL; host_t *addr; int family, idx; switch (type) { case INTERNAL_IP4_DNS: family = AF_INET; break; case INTERNAL_IP6_DNS: family = AF_INET6; break; default: return; } addr = host_create_from_chunk(family, data, 0); this->mutex->lock(this->mutex); idx = array_bsearch(this->servers, addr, dns_server_find, &found); if (idx != -1) { if (--found->refcount > 0) { DBG1(DBG_IKE, "DNS server %H still used, decreasing refcount", addr); } else { if (this->use_resolvconf) { invoke_resolvconf(this, addr, FALSE); } else { remove_nameserver(this, addr); } array_remove(this->servers, idx, NULL); found->server->destroy(found->server); free(found); } } this->mutex->unlock(this->mutex); addr->destroy(addr); } /** * Attribute enumerator implementation */ typedef struct { /** implements enumerator_t interface */ enumerator_t public; /** request IPv4 DNS? */ bool v4; /** request IPv6 DNS? */ bool v6; } attribute_enumerator_t; static bool attribute_enumerate(attribute_enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) { if (this->v4) { *type = INTERNAL_IP4_DNS; *data = chunk_empty; this->v4 = FALSE; return TRUE; } if (this->v6) { *type = INTERNAL_IP6_DNS; *data = chunk_empty; this->v6 = FALSE; return TRUE; } return FALSE; } /** * Check if a list has a host of given family */ static bool has_host_family(linked_list_t *list, int family) { enumerator_t *enumerator; host_t *host; bool found = FALSE; enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &host)) { if (host->get_family(host) == family) { found = TRUE; break; } } enumerator->destroy(enumerator); return found; } METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*, private_resolve_handler_t *this, ike_sa_t *ike_sa, linked_list_t *vips) { attribute_enumerator_t *enumerator; INIT(enumerator, .public = { .enumerate = (void*)attribute_enumerate, .destroy = (void*)free, }, .v4 = has_host_family(vips, AF_INET), .v6 = has_host_family(vips, AF_INET6), ); return &enumerator->public; } METHOD(resolve_handler_t, destroy, void, private_resolve_handler_t *this) { array_destroy(this->servers); this->mutex->destroy(this->mutex); free(this); } /** * See header */ resolve_handler_t *resolve_handler_create() { private_resolve_handler_t *this; struct stat st; INIT(this, .public = { .handler = { .handle = _handle, .release = _release, .create_attribute_enumerator = _create_attribute_enumerator, }, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .file = lib->settings->get_str(lib->settings, "%s.plugins.resolve.file", RESOLV_CONF, lib->ns), ); if (stat(RESOLVCONF_EXEC, &st) == 0) { this->use_resolvconf = TRUE; this->iface_prefix = lib->settings->get_str(lib->settings, "%s.plugins.resolve.resolvconf.iface_prefix", RESOLVCONF_PREFIX, lib->ns); } return &this->public; }