summaryrefslogtreecommitdiffstats
path: root/src/providers/be_dyndns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/be_dyndns.c')
-rw-r--r--src/providers/be_dyndns.c1371
1 files changed, 1371 insertions, 0 deletions
diff --git a/src/providers/be_dyndns.c b/src/providers/be_dyndns.c
new file mode 100644
index 0000000..2c655ef
--- /dev/null
+++ b/src/providers/be_dyndns.c
@@ -0,0 +1,1371 @@
+/*
+ SSSD
+
+ dp_dyndns.c
+
+ Authors:
+ Stephen Gallagher <sgallagh@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <ctype.h>
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "util/child_common.h"
+#include "providers/data_provider.h"
+#include "providers/backend.h"
+#include "providers/be_dyndns.h"
+#include "resolv/async_resolv.h"
+
+#ifndef DYNDNS_TIMEOUT
+#define DYNDNS_TIMEOUT 15
+#endif /* DYNDNS_TIMEOUT */
+
+/* MASK represents special value for matching all interfaces */
+#define MASK "*"
+
+struct sss_iface_addr {
+ struct sss_iface_addr *next;
+ struct sss_iface_addr *prev;
+
+ struct sockaddr *addr;
+};
+
+struct sockaddr *
+sss_iface_addr_get_address(struct sss_iface_addr *address)
+{
+ if (address == NULL) {
+ return NULL;
+ }
+
+ return address->addr;
+}
+
+struct sss_iface_addr *sss_iface_addr_get_next(struct sss_iface_addr *address)
+{
+ if (address) {
+ return address->next;
+ }
+
+ return NULL;
+}
+
+void sss_iface_addr_concatenate(struct sss_iface_addr **list,
+ struct sss_iface_addr *list2)
+{
+ DLIST_CONCATENATE((*list), list2, struct sss_iface_addr*);
+}
+
+static errno_t addr_to_str(struct sockaddr *addr,
+ char *dst, size_t size)
+{
+ const void *src;
+ const char *res;
+ errno_t ret;
+
+ switch(addr->sa_family) {
+ case AF_INET:
+ src = &(((struct sockaddr_in *)addr)->sin_addr);
+ break;
+ case AF_INET6:
+ src = &(((struct sockaddr_in6 *)addr)->sin6_addr);
+ break;
+ default:
+ ret = ERR_ADDR_FAMILY_NOT_SUPPORTED;
+ goto done;
+ }
+
+ res = inet_ntop(addr->sa_family, src, dst, size);
+ if (res == NULL) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE, "inet_ntop failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+errno_t
+sss_iface_addr_list_as_str_list(TALLOC_CTX *mem_ctx,
+ struct sss_iface_addr *ifaddr_list,
+ char ***_straddrs)
+{
+ struct sss_iface_addr *ifaddr;
+ size_t count;
+ int ai;
+ char **straddrs;
+ char ip_addr[INET6_ADDRSTRLEN];
+ errno_t ret;
+
+ count = 0;
+ DLIST_FOR_EACH(ifaddr, ifaddr_list) {
+ count++;
+ }
+
+ straddrs = talloc_array(mem_ctx, char *, count+1);
+ if (straddrs == NULL) {
+ return ENOMEM;
+ }
+
+ ai = 0;
+ DLIST_FOR_EACH(ifaddr, ifaddr_list) {
+
+ ret = addr_to_str(ifaddr->addr, ip_addr, INET6_ADDRSTRLEN);
+ if (ret == ERR_ADDR_FAMILY_NOT_SUPPORTED) {
+ continue;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n",
+ ret, sss_strerror(ret));
+ goto fail;
+ }
+
+ straddrs[ai] = talloc_strdup(straddrs, ip_addr);
+ if (straddrs[ai] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ai++;
+ }
+
+ straddrs[count] = NULL;
+ *_straddrs = straddrs;
+ return EOK;
+
+fail:
+ talloc_free(straddrs);
+ return ret;
+}
+
+static bool
+ok_for_dns(struct sockaddr *sa)
+{
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+
+ switch (sa->sa_family) {
+ case AF_INET6:
+ memcpy(&sa6, sa, sizeof(struct sockaddr_in6));
+ return check_ipv6_addr(&sa6.sin6_addr, SSS_NO_SPECIAL);
+ case AF_INET:
+ memcpy(&sa4, sa, sizeof(struct sockaddr_in));
+ return check_ipv4_addr(&sa4.sin_addr, SSS_NO_SPECIAL);
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown address family\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool supported_address_family(sa_family_t sa_family)
+{
+ return sa_family == AF_INET || sa_family == AF_INET6;
+}
+
+static bool matching_name(const char *ifname, const char *ifname2)
+{
+ return (strcmp(MASK, ifname) == 0) || (strcasecmp(ifname, ifname2) == 0);
+}
+
+/* Collect IP addresses associated with an interface */
+errno_t
+sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname,
+ struct sss_iface_addr **_addrlist)
+{
+ struct ifaddrs *ifaces = NULL;
+ struct ifaddrs *ifa;
+ errno_t ret;
+ size_t addrsize;
+ struct sss_iface_addr *address;
+ struct sss_iface_addr *addrlist = NULL;
+
+ /* Get the IP addresses associated with the
+ * specified interface
+ */
+ errno = 0;
+ ret = getifaddrs(&ifaces);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not read interfaces [%d][%s]\n", ret, strerror(ret));
+ goto done;
+ }
+
+ for (ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) {
+ /* Some interfaces don't have an ifa_addr */
+ if (!ifa->ifa_addr) continue;
+
+ /* Add IP addresses to the list */
+ if (supported_address_family(ifa->ifa_addr->sa_family)
+ && matching_name(ifname, ifa->ifa_name)
+ && ok_for_dns(ifa->ifa_addr)) {
+
+ /* Add this address to the IP address list */
+ address = talloc_zero(mem_ctx, struct sss_iface_addr);
+ if (!address) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ addrsize = ifa->ifa_addr->sa_family == AF_INET ? \
+ sizeof(struct sockaddr_in) : \
+ sizeof(struct sockaddr_in6);
+
+ address->addr = talloc_memdup(address, ifa->ifa_addr,
+ addrsize);
+ if (address->addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* steal old dlist to the new head */
+ talloc_steal(address, addrlist);
+ DLIST_ADD(addrlist, address);
+ }
+ }
+
+ if (addrlist != NULL) {
+ /* OK, some result was found */
+ ret = EOK;
+ *_addrlist = addrlist;
+ } else {
+ /* No result was found */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "No IP usable for DNS was found for interface: %s.\n", ifname);
+ ret = ENOENT;
+ }
+
+done:
+ freeifaddrs(ifaces);
+ return ret;
+}
+
+static char *
+nsupdate_msg_add_fwd(char *update_msg, struct sss_iface_addr *addresses,
+ const char *hostname, int ttl, uint8_t remove_af, bool update_per_family)
+{
+ struct sss_iface_addr *new_record;
+ char ip_addr[INET6_ADDRSTRLEN];
+ char *updateipv4 = talloc_strdup(update_msg, "");
+ char *updateipv6 = talloc_strdup(update_msg, "");
+ errno_t ret;
+
+ /* Remove existing entries as needed */
+ if (remove_af & DYNDNS_REMOVE_A) {
+ updateipv4 = talloc_asprintf_append(updateipv4,
+ "update delete %s. in A\n",
+ hostname);
+ if (updateipv4 == NULL) {
+ return NULL;
+ }
+ }
+
+ if (remove_af & DYNDNS_REMOVE_AAAA) {
+ updateipv6 = talloc_asprintf_append(updateipv6,
+ "update delete %s. in AAAA\n",
+ hostname);
+ if (updateipv6 == NULL) {
+ return NULL;
+ }
+ }
+
+ DLIST_FOR_EACH(new_record, addresses) {
+ ret = addr_to_str(new_record->addr, ip_addr, INET6_ADDRSTRLEN);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "addr_to_str failed: %d:[%s],\n",
+ ret, sss_strerror(ret));
+ return NULL;
+ }
+
+ switch (new_record->addr->sa_family) {
+ case AF_INET:
+ updateipv4 = talloc_asprintf_append(updateipv4,
+ "update add %s. %d in %s %s\n",
+ hostname, ttl, "A", ip_addr);
+ if (updateipv4 == NULL) {
+ return NULL;
+ }
+
+ break;
+ case AF_INET6:
+ updateipv6 = talloc_asprintf_append(updateipv6,
+ "update add %s. %d in %s %s\n",
+ hostname, ttl, "AAAA", ip_addr);
+ if (updateipv6 == NULL) {
+ return NULL;
+ }
+
+ break;
+ }
+ }
+
+ if (update_per_family && updateipv4[0] && updateipv6[0]) {
+ /* update per family and both families present */
+ return talloc_asprintf_append(update_msg,
+ "%s"
+ "send\n"
+ "%s"
+ "send\n",
+ updateipv4,
+ updateipv6);
+ }
+
+ return talloc_asprintf_append(update_msg,
+ "%s"
+ "%s"
+ "send\n",
+ updateipv4,
+ updateipv6);
+}
+
+static uint8_t *nsupdate_convert_address(struct sockaddr *add_address)
+{
+ uint8_t *addr;
+
+ switch(add_address->sa_family) {
+ case AF_INET:
+ addr = (uint8_t *) &((struct sockaddr_in *) add_address)->sin_addr;
+ break;
+ case AF_INET6:
+ addr = (uint8_t *) &((struct sockaddr_in6 *) add_address)->sin6_addr;
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown address family\n");
+ addr = NULL;
+ break;
+ }
+
+ return addr;
+}
+
+static char *
+nsupdate_msg_add_ptr(char *update_msg, struct sss_iface_addr *addresses,
+ const char *hostname, int ttl, uint8_t remove_af,
+ bool update_per_family)
+{
+ char *updateipv4 = talloc_strdup(update_msg, "");
+ char *updateipv6 = talloc_strdup(update_msg, "");
+ char *ptr;
+ struct sss_iface_addr *address_it;
+ uint8_t *addr;
+
+ if (!updateipv4 || !updateipv6) {
+ return NULL;
+ }
+
+ DLIST_FOR_EACH(address_it, addresses) {
+ addr = nsupdate_convert_address(address_it->addr);
+ if (addr == NULL) {
+ return NULL;
+ }
+
+ ptr = resolv_get_string_ptr_address(update_msg, address_it->addr->sa_family,
+ addr);
+ if (ptr == NULL) {
+ return NULL;
+ }
+
+ switch (address_it->addr->sa_family) {
+ case AF_INET:
+ if (remove_af & DYNDNS_REMOVE_A) {
+ updateipv4 = talloc_asprintf_append(updateipv4,
+ "update delete %s in PTR\n",
+ ptr);
+ if (updateipv4 == NULL) {
+ return NULL;
+ }
+ }
+
+ updateipv4 = talloc_asprintf_append(updateipv4,
+ "update add %s %d in PTR %s.\nsend\n",
+ ptr, ttl, hostname);
+ break;
+ case AF_INET6:
+ if (remove_af & DYNDNS_REMOVE_AAAA) {
+ updateipv6 = talloc_asprintf_append(updateipv6,
+ "update delete %s in PTR\n",
+ ptr);
+ if (updateipv6 == NULL) {
+ return NULL;
+ }
+ }
+ updateipv6 = talloc_asprintf_append(updateipv6,
+ "update add %s %d in PTR %s.\nsend\n",
+ ptr, ttl, hostname);
+ break;
+ }
+
+ talloc_free(ptr);
+ if (!updateipv4 || !updateipv6) {
+ return NULL;
+ }
+ }
+
+ return talloc_asprintf_append(update_msg,
+ "%s"
+ "%s",
+ updateipv4,
+ updateipv6);
+}
+
+static char *
+nsupdate_msg_add_realm_cmd(TALLOC_CTX *mem_ctx, const char *realm)
+{
+ if (realm != NULL) {
+ return talloc_asprintf(mem_ctx, "realm %s\n", realm);
+ } else {
+ return talloc_asprintf(mem_ctx, "\n");
+ }
+}
+
+static char *
+nsupdate_msg_create_common(TALLOC_CTX *mem_ctx, const char *realm,
+ const char *servername)
+{
+ char *realm_directive;
+ char *update_msg;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) return NULL;
+
+ realm_directive = nsupdate_msg_add_realm_cmd(tmp_ctx, realm);
+ if (!realm_directive) {
+ goto fail;
+ }
+
+ /* The realm_directive would now either contain an empty string or be
+ * completely empty so we don't need to add another newline here
+ */
+ if (servername) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Creating update message for server [%s] and realm [%s].\n",
+ servername, realm);
+
+ /* Add the server, realm and headers */
+ update_msg = talloc_asprintf(tmp_ctx, "server %s\n%s",
+ servername, realm_directive);
+ } else if (realm != NULL) {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Creating update message for realm [%s].\n", realm);
+ /* Add the realm headers */
+ update_msg = talloc_asprintf(tmp_ctx, "%s", realm_directive);
+ } else {
+ DEBUG(SSSDBG_FUNC_DATA,
+ "Creating update message for auto-discovered realm.\n");
+ update_msg = talloc_asprintf(tmp_ctx, "%s", realm_directive);
+ }
+ talloc_free(realm_directive);
+ if (update_msg == NULL) {
+ goto fail;
+ }
+
+ update_msg = talloc_steal(mem_ctx, update_msg);
+ talloc_free(tmp_ctx);
+ return update_msg;
+
+fail:
+ talloc_free(tmp_ctx);
+ return NULL;
+}
+
+errno_t
+be_nsupdate_create_fwd_msg(TALLOC_CTX *mem_ctx, const char *realm,
+ const char *servername,
+ const char *hostname, const unsigned int ttl,
+ uint8_t remove_af, struct sss_iface_addr *addresses,
+ bool update_per_family,
+ char **_update_msg)
+{
+ int ret;
+ char *update_msg;
+ TALLOC_CTX *tmp_ctx;
+
+ /* in some cases realm could have been NULL if we weren't using TSIG */
+ if (hostname == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) return ENOMEM;
+
+ update_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ update_msg = nsupdate_msg_add_fwd(update_msg, addresses, hostname,
+ ttl, remove_af, update_per_family);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ " -- Begin nsupdate message -- \n"
+ "%s"
+ " -- End nsupdate message -- \n",
+ update_msg);
+
+ ret = ERR_OK;
+ *_update_msg = talloc_steal(mem_ctx, update_msg);
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+errno_t
+be_nsupdate_create_ptr_msg(TALLOC_CTX *mem_ctx, const char *realm,
+ const char *servername,
+ const char *hostname, const unsigned int ttl,
+ uint8_t remove_af, struct sss_iface_addr *addresses,
+ bool update_per_family,
+ char **_update_msg)
+{
+ errno_t ret;
+ char *update_msg;
+ TALLOC_CTX *tmp_ctx;
+
+ /* in some cases realm could have been NULL if we weren't using TSIG */
+ if (hostname == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) return ENOMEM;
+
+ update_msg = nsupdate_msg_create_common(tmp_ctx, realm, servername);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ update_msg = nsupdate_msg_add_ptr(update_msg, addresses, hostname,
+ ttl, remove_af, update_per_family);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ " -- Begin nsupdate message -- \n"
+ "%s"
+ " -- End nsupdate message -- \n",
+ update_msg);
+
+ ret = ERR_OK;
+ *_update_msg = talloc_steal(mem_ctx, update_msg);
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+struct nsupdate_get_addrs_state {
+ struct tevent_context *ev;
+ struct be_resolv_ctx *be_res;
+ enum host_database *db;
+ const char *hostname;
+
+ /* Use sss_addr in this request */
+ struct sss_iface_addr *addrlist;
+ size_t count;
+};
+
+static void nsupdate_get_addrs_done(struct tevent_req *subreq);
+
+struct tevent_req *
+nsupdate_get_addrs_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_resolv_ctx *be_res,
+ const char *hostname)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct nsupdate_get_addrs_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct nsupdate_get_addrs_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->be_res = be_res;
+ state->ev = ev;
+ state->hostname = talloc_strdup(state, hostname);
+ if (state->hostname == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ state->db = talloc_array(state, enum host_database, 2);
+ if (state->db == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ state->db[0] = DB_DNS;
+ state->db[1] = DB_SENTINEL;
+
+ subreq = resolv_gethostbyname_send(state, ev, be_res->resolv, hostname,
+ state->be_res->family_order,
+ state->db);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, nsupdate_get_addrs_done, req);
+
+ ret = ERR_OK;
+done:
+ if (ret != ERR_OK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void
+nsupdate_get_addrs_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ size_t count;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct nsupdate_get_addrs_state *state = tevent_req_data(req,
+ struct nsupdate_get_addrs_state);
+ struct resolv_hostent *rhostent;
+ struct sss_iface_addr *addr;
+ int i;
+ int resolv_status;
+ enum restrict_family retry_family_order;
+
+ ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL,
+ &rhostent);
+ talloc_zfree(subreq);
+
+ /* If the retry did not match, simply quit */
+ if (ret == ENOENT) {
+ /* If the resolver is set to honor both address families
+ * it automatically retries the other one internally, so ENOENT
+ * means neither matched and we can simply quit.
+ */
+ ret = EOK;
+ goto done;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not resolve address for this machine, error [%d]: %s, "
+ "resolver returned: [%d]: %s\n", ret, sss_strerror(ret),
+ resolv_status, resolv_strerror(resolv_status));
+ goto done;
+ }
+
+ /* EOK */
+
+ if (rhostent->addr_list) {
+ for (count=0; rhostent->addr_list[count]; count++);
+ } else {
+ /* The address list is NULL. This is probably a bug in
+ * c-ares, but we need to handle it gracefully.
+ */
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Lookup of [%s] returned no addresses. Skipping.\n",
+ rhostent->name);
+ count = 0;
+ }
+
+ for (i=0; i < count; i++) {
+ addr = talloc(state, struct sss_iface_addr);
+ if (addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ addr->addr = resolv_get_sockaddr_address_index(addr, rhostent, 0, i, NULL);
+ if (addr->addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (state->addrlist) {
+ talloc_steal(state->addrlist, addr);
+ }
+
+ /* steal old dlist to the new head */
+ talloc_steal(addr, state->addrlist);
+ DLIST_ADD(state->addrlist, addr);
+ }
+ state->count += count;
+
+ /* If the resolver is set to honor both address families
+ * and the first one matched, retry the second one to
+ * get the complete list.
+ */
+ if (((state->be_res->family_order == IPV4_FIRST &&
+ rhostent->family == AF_INET) ||
+ (state->be_res->family_order == IPV6_FIRST &&
+ rhostent->family == AF_INET6))) {
+
+ retry_family_order = (state->be_res->family_order == IPV4_FIRST) ? \
+ IPV6_ONLY : \
+ IPV4_ONLY;
+
+ subreq = resolv_gethostbyname_send(state, state->ev,
+ state->be_res->resolv,
+ state->hostname,
+ retry_family_order,
+ state->db);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, nsupdate_get_addrs_done, req);
+ return;
+ }
+
+ /* The second address matched either immediately or after a retry.
+ * No need to retry again. */
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ /* All done */
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "nsupdate_get_addrs_done failed: [%d]: [%s]\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ }
+ /* EAGAIN - another lookup in progress */
+}
+
+errno_t
+nsupdate_get_addrs_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct sss_iface_addr **_addrlist,
+ size_t *_count)
+{
+ struct nsupdate_get_addrs_state *state = tevent_req_data(req,
+ struct nsupdate_get_addrs_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (_addrlist) {
+ *_addrlist = talloc_steal(mem_ctx, state->addrlist);
+ }
+
+ if (_count) {
+ *_count = state->count;
+ }
+
+ return EOK;
+}
+
+/* Write the nsupdate_msg into the already forked child, wait until
+ * the child finishes
+ *
+ * This is not a typical tevent_req styled request as it ends either after
+ * a timeout or when the child finishes operation.
+ */
+struct nsupdate_child_state {
+ int pipefd_to_child;
+ struct tevent_timer *timeout_handler;
+ struct sss_child_ctx_old *child_ctx;
+
+ int child_status;
+};
+
+static void
+nsupdate_child_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+static void
+nsupdate_child_handler(int child_status,
+ struct tevent_signal *sige,
+ void *pvt);
+
+static void nsupdate_child_stdin_done(struct tevent_req *subreq);
+
+static struct tevent_req *
+nsupdate_child_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int pipefd_to_child,
+ pid_t child_pid,
+ char *child_stdin)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct nsupdate_child_state *state;
+ struct timeval tv;
+
+ req = tevent_req_create(mem_ctx, &state, struct nsupdate_child_state);
+ if (req == NULL) {
+ close(pipefd_to_child);
+ return NULL;
+ }
+ state->pipefd_to_child = pipefd_to_child;
+
+ /* Set up SIGCHLD handler */
+ ret = child_handler_setup(ev, child_pid, nsupdate_child_handler, req,
+ &state->child_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n",
+ ret, sss_strerror(ret));
+ ret = ERR_DYNDNS_FAILED;
+ goto done;
+ }
+
+ /* Set up timeout handler */
+ tv = tevent_timeval_current_ofs(DYNDNS_TIMEOUT, 0);
+ state->timeout_handler = tevent_add_timer(ev, req, tv,
+ nsupdate_child_timeout, req);
+ if(state->timeout_handler == NULL) {
+ ret = ERR_DYNDNS_FAILED;
+ goto done;
+ }
+
+ /* Write the update message to the nsupdate child */
+ subreq = write_pipe_send(req, ev,
+ (uint8_t *) child_stdin,
+ strlen(child_stdin)+1,
+ state->pipefd_to_child);
+ if (subreq == NULL) {
+ ret = ERR_DYNDNS_FAILED;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, nsupdate_child_stdin_done, req);
+
+ ret = EOK;
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void
+nsupdate_child_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req =
+ talloc_get_type(pvt, struct tevent_req);
+ struct nsupdate_child_state *state =
+ tevent_req_data(req, struct nsupdate_child_state);
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for dynamic DNS update\n");
+ child_handler_destroy(state->child_ctx);
+ state->child_ctx = NULL;
+ state->child_status = ETIMEDOUT;
+ tevent_req_error(req, ERR_DYNDNS_TIMEOUT);
+}
+
+static void
+nsupdate_child_stdin_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct nsupdate_child_state *state =
+ tevent_req_data(req, struct nsupdate_child_state);
+
+ /* Verify that the buffer was sent, then return
+ * and wait for the sigchld handler to finish.
+ */
+ DEBUG(SSSDBG_TRACE_LIBS, "Sending nsupdate data complete\n");
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Sending nsupdate data failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ERR_DYNDNS_FAILED);
+ return;
+ }
+
+ PIPE_FD_CLOSE(state->pipefd_to_child);
+
+ /* Now either wait for the timeout to fire or the child
+ * to finish
+ */
+}
+
+static void
+nsupdate_child_handler(int child_status,
+ struct tevent_signal *sige,
+ void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct nsupdate_child_state *state =
+ tevent_req_data(req, struct nsupdate_child_state);
+
+ state->child_status = child_status;
+
+ if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Dynamic DNS child failed with status [%d]\n", child_status);
+ tevent_req_error(req, ERR_DYNDNS_FAILED);
+ return;
+ }
+
+ if (WIFSIGNALED(child_status)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Dynamic DNS child was terminated by signal [%d]\n",
+ WTERMSIG(child_status));
+ tevent_req_error(req, ERR_DYNDNS_FAILED);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static errno_t
+nsupdate_child_recv(struct tevent_req *req, int *child_status)
+{
+ struct nsupdate_child_state *state =
+ tevent_req_data(req, struct nsupdate_child_state);
+
+ *child_status = state->child_status;
+
+ PIPE_FD_CLOSE(state->pipefd_to_child);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return ERR_OK;
+}
+
+/* Fork a nsupdate child, write the nsupdate_msg into stdin and wait for the child
+ * to finish one way or another
+ */
+struct be_nsupdate_state {
+ int child_status;
+};
+
+static void be_nsupdate_done(struct tevent_req *subreq);
+static char **be_nsupdate_args(TALLOC_CTX *mem_ctx,
+ enum be_nsupdate_auth auth_type,
+ bool force_tcp);
+
+struct tevent_req *be_nsupdate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ enum be_nsupdate_auth auth_type,
+ char *nsupdate_msg,
+ bool force_tcp)
+{
+ int pipefd_to_child[2] = PIPE_INIT;
+ pid_t child_pid;
+ errno_t ret;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct be_nsupdate_state *state;
+ char **args;
+ int debug_fd;
+
+ req = tevent_req_create(mem_ctx, &state, struct be_nsupdate_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->child_status = 0;
+
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ child_pid = fork();
+
+ if (child_pid == 0) { /* child */
+ PIPE_FD_CLOSE(pipefd_to_child[1]);
+ ret = dup2(pipefd_to_child[0], STDIN_FILENO);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "dup2 failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ if (debug_level >= SSSDBG_TRACE_LIBS) {
+ debug_fd = get_fd_from_debug_file();
+ ret = dup2(debug_fd, STDERR_FILENO);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "dup2 failed [%d][%s].\n", ret, strerror(ret));
+ /* stderr is not fatal */
+ }
+ }
+
+ args = be_nsupdate_args(state, auth_type, force_tcp);
+ if (args == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ errno = 0;
+ execv(NSUPDATE_PATH, args);
+ /* The child should never end up here */
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "execv failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ } else if (child_pid > 0) { /* parent */
+ PIPE_FD_CLOSE(pipefd_to_child[0]);
+
+ /* the nsupdate_child request now owns the pipefd and is responsible
+ * for closing it
+ */
+ subreq = nsupdate_child_send(state, ev, pipefd_to_child[1],
+ child_pid, nsupdate_msg);
+ if (subreq == NULL) {
+ ret = ERR_DYNDNS_FAILED;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, be_nsupdate_done, req);
+ } else { /* error */
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "fork failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ if (ret != EOK) {
+ PIPE_CLOSE(pipefd_to_child);
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static char **
+be_nsupdate_args(TALLOC_CTX *mem_ctx,
+ enum be_nsupdate_auth auth_type,
+ bool force_tcp)
+{
+ char **argv;
+ int argc = 0;
+
+ argv = talloc_zero_array(mem_ctx, char *, 6);
+ if (argv == NULL) {
+ return NULL;
+ }
+
+ argv[argc] = talloc_strdup(argv, NSUPDATE_PATH);
+ if (argv[argc] == NULL) {
+ goto fail;
+ }
+ argc++;
+
+ switch (auth_type) {
+ case BE_NSUPDATE_AUTH_NONE:
+ DEBUG(SSSDBG_FUNC_DATA, "nsupdate auth type: none\n");
+ break;
+ case BE_NSUPDATE_AUTH_GSS_TSIG:
+ DEBUG(SSSDBG_FUNC_DATA, "nsupdate auth type: GSS-TSIG\n");
+ argv[argc] = talloc_strdup(argv, "-g");
+ if (argv[argc] == NULL) {
+ goto fail;
+ }
+ argc++;
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown nsupdate auth type %d\n", auth_type);
+ goto fail;
+ }
+
+ if (force_tcp) {
+ DEBUG(SSSDBG_FUNC_DATA, "TCP is set to on\n");
+ argv[argc] = talloc_strdup(argv, "-v");
+ if (argv[argc] == NULL) {
+ goto fail;
+ }
+ argc++;
+ }
+
+ if (debug_level >= SSSDBG_TRACE_LIBS) {
+ argv[argc] = talloc_strdup(argv, "-d");
+ if (argv[argc] == NULL) {
+ goto fail;
+ }
+ argc++;
+ }
+
+ if (debug_level >= SSSDBG_TRACE_INTERNAL) {
+ argv[argc] = talloc_strdup(argv, "-D");
+ if (argv[argc] == NULL) {
+ goto fail;
+ }
+ argc++;
+ }
+
+ return argv;
+
+fail:
+ talloc_free(argv);
+ return NULL;
+}
+
+static void
+be_nsupdate_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct be_nsupdate_state *state =
+ tevent_req_data(req, struct be_nsupdate_state);
+ errno_t ret;
+
+ ret = nsupdate_child_recv(subreq, &state->child_status);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "nsupdate child execution failed [%d]: %s\n",
+ ret, sss_strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(SSSDBG_FUNC_DATA,
+ "nsupdate child status: %d\n", state->child_status);
+ tevent_req_done(req);
+}
+
+errno_t
+be_nsupdate_recv(struct tevent_req *req, int *child_status)
+{
+ struct be_nsupdate_state *state =
+ tevent_req_data(req, struct be_nsupdate_state);
+
+ *child_status = state->child_status;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+errno_t
+be_nsupdate_check(void)
+{
+ errno_t ret;
+ struct stat stat_buf;
+
+ /* Ensure that nsupdate exists */
+ errno = 0;
+ ret = stat(NSUPDATE_PATH, &stat_buf);
+ if (ret == -1) {
+ ret = errno;
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "%s does not exist. Dynamic DNS updates disabled\n",
+ NSUPDATE_PATH);
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not set up dynamic DNS updates: [%d][%s]\n",
+ ret, strerror(ret));
+ }
+ }
+
+ return ret;
+}
+
+static struct dp_option default_dyndns_opts[] = {
+ { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "dyndns_update_per_family", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER },
+ { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER },
+ { "dyndns_update_ptr", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE },
+ { "dyndns_force_tcp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "dyndns_auth", DP_OPT_STRING, { "gss-tsig" }, NULL_STRING },
+ { "dyndns_auth_ptr", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "dyndns_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+
+ DP_OPTION_TERMINATOR
+};
+
+errno_t
+be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
+ struct dp_option *defopts,
+ struct be_nsupdate_ctx **_ctx)
+{
+ errno_t ret;
+ struct dp_option *src_opts;
+ struct be_nsupdate_ctx *ctx;
+ char *strauth;
+
+ ctx = talloc_zero(mem_ctx, struct be_nsupdate_ctx);
+ if (ctx == NULL) return ENOMEM;
+
+ src_opts = defopts ? defopts : default_dyndns_opts;
+
+ ret = dp_get_options(ctx, be_ctx->cdb, be_ctx->conf_path,
+ src_opts, DP_OPT_DYNDNS, &ctx->opts);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve dynamic DNS options\n");
+ return ret;
+ }
+
+ strauth = dp_opt_get_string(ctx->opts, DP_OPT_DYNDNS_AUTH);
+ if (strcasecmp(strauth, "gss-tsig") == 0) {
+ ctx->auth_type = BE_NSUPDATE_AUTH_GSS_TSIG;
+ } else if (strcasecmp(strauth, "none") == 0) {
+ ctx->auth_type = BE_NSUPDATE_AUTH_NONE;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE, "Unknown dyndns auth type %s\n", strauth);
+ return EINVAL;
+ }
+
+ strauth = dp_opt_get_string(ctx->opts, DP_OPT_DYNDNS_AUTH_PTR);
+ if (strauth == NULL) {
+ ctx->auth_ptr_type = ctx->auth_type;
+ } else if (strcasecmp(strauth, "gss-tsig") == 0) {
+ ctx->auth_ptr_type = BE_NSUPDATE_AUTH_GSS_TSIG;
+ } else if (strcasecmp(strauth, "none") == 0) {
+ ctx->auth_ptr_type = BE_NSUPDATE_AUTH_NONE;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE, "Unknown dyndns ptr auth type %s\n", strauth);
+ return EINVAL;
+ }
+
+ *_ctx = ctx;
+ return ERR_OK;
+}
+
+static bool match_ip(const struct sockaddr *sa,
+ const struct sockaddr *sb)
+{
+ size_t addrsize;
+ bool res;
+ const void *addr_a;
+ const void *addr_b;
+
+ if (sa->sa_family == AF_INET) {
+ addrsize = sizeof(struct in_addr);
+ addr_a = (const void *) &((const struct sockaddr_in *) sa)->sin_addr;
+ addr_b = (const void *) &((const struct sockaddr_in *) sb)->sin_addr;
+ } else if (sa->sa_family == AF_INET6) {
+ addrsize = sizeof(struct in6_addr);
+ addr_a = (const void *) &((const struct sockaddr_in6 *) sa)->sin6_addr;
+ addr_b = (const void *) &((const struct sockaddr_in6 *) sb)->sin6_addr;
+ } else {
+ res = false;
+ goto done;
+ }
+
+ if (sa->sa_family != sb->sa_family) {
+ res = false;
+ goto done;
+ }
+
+ res = memcmp(addr_a, addr_b, addrsize) == 0;
+
+done:
+ return res;
+}
+
+static errno_t find_iface_by_addr(TALLOC_CTX *mem_ctx,
+ const struct sockaddr *ss,
+ const char **_iface_name)
+{
+ struct ifaddrs *ifaces = NULL;
+ struct ifaddrs *ifa;
+ errno_t ret;
+
+ ret = getifaddrs(&ifaces);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not read interfaces [%d][%s]\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ for (ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) {
+
+ /* Some interfaces don't have an ifa_addr */
+ if (!ifa->ifa_addr) continue;
+
+ if (match_ip(ss, ifa->ifa_addr)) {
+ const char *iface_name;
+ iface_name = talloc_strdup(mem_ctx, ifa->ifa_name);
+ if (iface_name == NULL) {
+ ret = ENOMEM;
+ } else {
+ *_iface_name = iface_name;
+ ret = EOK;
+ }
+ goto done;
+ }
+ }
+ ret = ENOENT;
+
+done:
+ freeifaddrs(ifaces);
+ return ret;
+}
+
+errno_t sss_get_dualstack_addresses(TALLOC_CTX *mem_ctx,
+ struct sockaddr *ss,
+ struct sss_iface_addr **_iface_addrs)
+{
+ struct sss_iface_addr *iface_addrs;
+ const char *iface_name = NULL;
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = find_iface_by_addr(tmp_ctx, ss, &iface_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "find_iface_by_addr failed: %d:[%s]\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = sss_iface_addr_list_get(tmp_ctx, iface_name, &iface_addrs);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "sss_iface_addr_list_get failed: %d:[%s]\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+ *_iface_addrs = talloc_steal(mem_ctx, iface_addrs);
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}