summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am30
-rw-r--r--src/providers/dp_dyndns.c880
-rw-r--r--src/providers/dp_dyndns.h68
-rw-r--r--src/providers/ipa/ipa_common.h2
-rw-r--r--src/providers/ipa/ipa_dyndns.c1237
-rw-r--r--src/providers/ldap/sdap_dyndns.c498
-rw-r--r--src/providers/ldap/sdap_dyndns.h47
-rw-r--r--src/tests/cmocka/test_dyndns.c339
-rw-r--r--src/tests/common.h2
-rw-r--r--src/tests/common_dom.c9
-rw-r--r--src/tests/common_tev.c25
-rw-r--r--src/util/util_errors.c3
-rw-r--r--src/util/util_errors.h3
13 files changed, 1968 insertions, 1175 deletions
diff --git a/Makefile.am b/Makefile.am
index 19248667..542a490d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -149,7 +149,9 @@ if HAVE_CMOCKA
nss-srv-tests \
test-find-uid \
test-io \
- sss_nss_idmap-tests
+ sss_nss_idmap-tests \
+ test-io \
+ dyndns-tests
endif
check_PROGRAMS = \
@@ -434,6 +436,7 @@ dist_noinst_HEADERS = \
src/confdb/confdb_setup.h \
src/providers/data_provider.h \
src/providers/dp_backend.h \
+ src/providers/dp_dyndns.h \
src/providers/fail_over.h \
src/providers/fail_over_srv.h \
src/util/child_common.h \
@@ -455,6 +458,7 @@ dist_noinst_HEADERS = \
src/providers/ldap/ldap_opts.h \
src/providers/ldap/sdap_range.h \
src/providers/ldap/sdap_users.h \
+ src/providers/ldap/sdap_dyndns.h \
src/providers/ipa/ipa_common.h \
src/providers/ipa/ipa_config.h \
src/providers/ipa/ipa_access.h \
@@ -701,6 +705,7 @@ sssd_be_SOURCES = \
src/providers/data_provider_fo.c \
src/providers/data_provider_opts.c \
src/providers/data_provider_callbacks.c \
+ src/providers/dp_dyndns.c \
$(SSSD_FAILOVER_OBJ)
sssd_be_LDADD = \
-ldl \
@@ -1215,8 +1220,9 @@ krb5_child_test_LDADD = \
libsss_test_common.la
if HAVE_CMOCKA
-TEST_MOCK_OBJ = \
- src/tests/common.c
+TEST_MOCK_OBJ = \
+ src/tests/common.c \
+ src/tests/leak_check.c
TEST_MOCK_RESP_OBJ = \
$(TEST_MOCK_OBJ) \
@@ -1276,6 +1282,23 @@ test_io_CFLAGS = \
$(AM_CFLAGS)
test_io_LDADD = \
$(CMOCKA_LIBS)
+
+dyndns_tests_SOURCES = \
+ $(TEST_MOCK_OBJ) \
+ $(SSSD_RESOLV_OBJ) \
+ src/tests/common_tev.c \
+ src/tests/cmocka/test_dyndns.c
+dyndns_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ -DDYNDNS_TIMEOUT=2
+dyndns_tests_LDFLAGS = \
+ -Wl,-wrap,execv \
+ -Wl,-wrap,getifaddrs \
+ -Wl,-wrap,freeifaddrs
+dyndns_tests_LDADD = \
+ $(CARES_LIBS) \
+ $(CMOCKA_LIBS) \
+ libsss_util.la
endif
noinst_PROGRAMS = pam_test_client
@@ -1416,6 +1439,7 @@ libsss_ldap_common_la_SOURCES = \
src/providers/ldap/sdap_idmap.h \
src/providers/ldap/sdap_range.c \
src/providers/ldap/sdap_reinit.c \
+ src/providers/ldap/sdap_dyndns.c \
src/providers/ldap/sdap.c
libsss_ldap_common_la_LDFLAGS = \
-avoid-version
diff --git a/src/providers/dp_dyndns.c b/src/providers/dp_dyndns.c
new file mode 100644
index 00000000..7e5cc690
--- /dev/null
+++ b/src/providers/dp_dyndns.c
@@ -0,0 +1,880 @@
+/*
+ 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/dp_backend.h"
+#include "providers/dp_dyndns.h"
+#include "resolv/async_resolv.h"
+
+#ifndef DYNDNS_TIMEOUT
+#define DYNDNS_TIMEOUT 15
+#endif /* DYNDNS_TIMEOUT */
+
+struct sss_iface_addr {
+ struct sss_iface_addr *next;
+ struct sss_iface_addr *prev;
+
+ struct sockaddr_storage *addr;
+};
+
+struct sss_iface_addr *
+sss_iface_addr_add(TALLOC_CTX *mem_ctx, struct sss_iface_addr **list,
+ struct sockaddr_storage *ss)
+{
+ struct sss_iface_addr *address;
+
+ address = talloc(mem_ctx, struct sss_iface_addr);
+ if (address == NULL) {
+ return NULL;
+ }
+
+ address->addr = talloc_memdup(address, ss,
+ sizeof(struct sockaddr_storage));
+ if(address->addr == NULL) {
+ talloc_zfree(address);
+ return NULL;
+ }
+ DLIST_ADD(*list, address);
+
+ return address;
+}
+
+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;
+ const char *ip;
+ 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) {
+ switch(ifaddr->addr->ss_family) {
+ case AF_INET:
+ errno = 0;
+ ip = inet_ntop(ifaddr->addr->ss_family,
+ &(((struct sockaddr_in *)ifaddr->addr)->sin_addr),
+ ip_addr, INET6_ADDRSTRLEN);
+ if (ip == NULL) {
+ ret = errno;
+ goto fail;
+ }
+ break;
+
+ case AF_INET6:
+ errno = 0;
+ ip = inet_ntop(ifaddr->addr->ss_family,
+ &(((struct sockaddr_in6 *)ifaddr->addr)->sin6_addr),
+ ip_addr, INET6_ADDRSTRLEN);
+ if (ip == NULL) {
+ ret = errno;
+ goto fail;
+ }
+ break;
+
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Unknown address family\n"));
+ continue;
+ }
+
+ straddrs[ai] = talloc_strdup(straddrs, ip);
+ 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)
+{
+ char straddr[INET6_ADDRSTRLEN];
+ struct in6_addr *addr6;
+ struct in_addr *addr;
+
+ switch (sa->sa_family) {
+ case AF_INET6:
+ addr6 = &((struct sockaddr_in6 *) sa)->sin6_addr;
+
+ if (inet_ntop(AF_INET6, addr6, straddr, INET6_ADDRSTRLEN) == NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("inet_ntop failed, won't log IP addresses\n"));
+ snprintf(straddr, INET6_ADDRSTRLEN, "unknown");
+ }
+
+ if (IN6_IS_ADDR_LINKLOCAL(addr6)) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Link local IPv6 address %s\n", straddr));
+ return false;
+ } else if (IN6_IS_ADDR_LOOPBACK(addr6)) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Loopback IPv6 address %s\n", straddr));
+ return false;
+ } else if (IN6_IS_ADDR_MULTICAST(addr6)) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Multicast IPv6 address %s\n", straddr));
+ return false;
+ }
+ break;
+ case AF_INET:
+ addr = &((struct sockaddr_in *) sa)->sin_addr;
+
+ if (inet_ntop(AF_INET, addr, straddr, INET6_ADDRSTRLEN) == NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("inet_ntop failed, won't log IP addresses\n"));
+ snprintf(straddr, INET6_ADDRSTRLEN, "unknown");
+ }
+
+ if (IN_MULTICAST(ntohl(addr->s_addr))) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Multicast IPv4 address %s\n", straddr));
+ return false;
+ } else if (inet_netof(*addr) == IN_LOOPBACKNET) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Loopback IPv4 address %s\n", straddr));
+ return false;
+ } else if ((addr->s_addr & 0xffff0000) == 0xa9fe0000) {
+ /* 169.254.0.0/16 */
+ DEBUG(SSSDBG_FUNC_DATA, ("Link-local IPv4 address %s\n", straddr));
+ return false;
+ } else if (addr->s_addr == htonl(INADDR_BROADCAST)) {
+ DEBUG(SSSDBG_FUNC_DATA, ("Broadcast IPv4 address %s\n", straddr));
+ return false;
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Unknown address family\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/* 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 ((ifa->ifa_addr->sa_family == AF_INET ||
+ ifa->ifa_addr->sa_family == AF_INET6) &&
+ strcasecmp(ifa->ifa_name, ifname) == 0 &&
+ 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) {
+ 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;
+ }
+ DLIST_ADD(addrlist, address);
+ }
+ }
+
+ ret = EOK;
+ *_addrlist = addrlist;
+done:
+ freeifaddrs(ifaces);
+ return ret;
+}
+
+errno_t
+be_nsupdate_create_msg(TALLOC_CTX *mem_ctx, const char *realm,
+ const char *zone, const char *servername,
+ const char *hostname, const unsigned int ttl,
+ uint8_t remove_af, struct sss_iface_addr *addresses,
+ char **_update_msg)
+{
+ int ret;
+ char *realm_directive;
+ char ip_addr[INET6_ADDRSTRLEN];
+ const char *ip;
+ struct sss_iface_addr *new_record;
+ char *update_msg;
+ TALLOC_CTX *tmp_ctx;
+
+ /* in some cases realm could have been NULL if we weren't using TSIG */
+ if (zone == NULL || hostname == NULL) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) return ENOMEM;
+
+#ifdef HAVE_NSUPDATE_REALM
+ realm_directive = talloc_asprintf(tmp_ctx, "realm %s\n", realm);
+#else
+ realm_directive = talloc_asprintf(tmp_ctx, "");
+#endif
+ if (!realm_directive) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* 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], realm [%s] "
+ "and zone [%s].\n", servername, realm, zone));
+
+ /* Add the server, realm and zone headers */
+ update_msg = talloc_asprintf(tmp_ctx, "server %s\n%szone %s.\n",
+ servername, realm_directive, zone);
+ } else {
+ DEBUG(SSSDBG_FUNC_DATA,
+ ("Creating update message for realm [%s] and zone [%s].\n",
+ realm, zone));
+
+ /* Add the realm and zone headers */
+ update_msg = talloc_asprintf(tmp_ctx, "%szone %s.\n",
+ realm_directive, zone);
+ }
+ talloc_free(realm_directive);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Remove existing entries as needed */
+ if (remove_af & DYNDNS_REMOVE_A) {
+ update_msg = talloc_asprintf_append(update_msg,
+ "update delete %s. in A\nsend\n",
+ hostname);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ if (remove_af & DYNDNS_REMOVE_AAAA) {
+ update_msg = talloc_asprintf_append(update_msg,
+ "update delete %s. in AAAA\nsend\n",
+ hostname);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ DLIST_FOR_EACH(new_record, addresses) {
+ switch(new_record->addr->ss_family) {
+ case AF_INET:
+ ip = inet_ntop(new_record->addr->ss_family,
+ &(((struct sockaddr_in *)new_record->addr)->sin_addr),
+ ip_addr, INET6_ADDRSTRLEN);
+ if (ip == NULL) {
+ ret = errno;
+ goto done;
+ }
+ break;
+
+ case AF_INET6:
+ ip = inet_ntop(new_record->addr->ss_family,
+ &(((struct sockaddr_in6 *)new_record->addr)->sin6_addr),
+ ip_addr, INET6_ADDRSTRLEN);
+ if (ip == NULL) {
+ ret = errno;
+ goto done;
+ }
+ break;
+
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Unknown address family\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Format the record update */
+ update_msg = talloc_asprintf_append(update_msg,
+ "update add %s. %d in %s %s\n",
+ hostname, ttl,
+ new_record->addr->ss_family == AF_INET ? "A" : "AAAA",
+ ip_addr);
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ update_msg = talloc_asprintf_append(update_msg, "send\n");
+ if (update_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ (" -- Begin nsupdate message -- \n%s",
+ update_msg));
+ DEBUG(SSSDBG_TRACE_FUNC,
+ (" -- End nsupdate message -- \n"));
+
+ 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 */
+ char **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;
+ int i;
+ int resolv_status;
+
+ 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;
+ }
+
+ state->addrlist = talloc_realloc(state, state->addrlist, char *,
+ state->count + count + 1);
+ if (!state->addrlist) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i=0; i < count; i++) {
+ state->addrlist[state->count + i] = \
+ resolv_get_string_address_index(state->addrlist,
+ rhostent, i);
+
+ if (state->addrlist[state->count + i] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ state->count += count;
+ state->addrlist[state->count] = NULL;
+
+ /* 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))) {
+
+ state->be_res->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,
+ state->be_res->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 immediatelly 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",
+ 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,
+ char ***_addrlist)
+{
+ struct nsupdate_get_addrs_state *state = tevent_req_data(req,
+ struct nsupdate_get_addrs_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *_addrlist = talloc_steal(mem_ctx, state->addrlist);
+ 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;
+
+ 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) {
+ return NULL;
+ }
+ state->pipefd_to_child = pipefd_to_child;
+
+ /* Set up SIGCHLD handler */
+ ret = child_handler_setup(ev, child_pid, nsupdate_child_handler, req);
+ 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"));
+ 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;
+ }
+
+ close(state->pipefd_to_child);
+ state->pipefd_to_child = -1;
+
+ /* 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;
+
+ 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);
+
+struct tevent_req *be_nsupdate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ char *nsupdate_msg)
+{
+ int pipefd_to_child[2];
+ pid_t child_pid;
+ errno_t ret;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct be_nsupdate_state *state;
+ char *args[3];
+
+ 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 */
+ args[0] = talloc_strdup(state, NSUPDATE_PATH);
+ args[1] = talloc_strdup(state, "-g");
+ args[2] = NULL;
+ if (args[0] == NULL || args[1] == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ 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;
+ }
+
+ 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 */
+ close(pipefd_to_child[0]);
+
+ 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) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+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;
+}
diff --git a/src/providers/dp_dyndns.h b/src/providers/dp_dyndns.h
new file mode 100644
index 00000000..b0020560
--- /dev/null
+++ b/src/providers/dp_dyndns.h
@@ -0,0 +1,68 @@
+/*
+ SSSD
+
+ dp_dyndns.h
+
+ Authors:
+ 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/>.
+*/
+
+/* dynamic dns helpers */
+struct sss_iface_addr;
+
+#define DYNDNS_REMOVE_A 0x1
+#define DYNDNS_REMOVE_AAAA 0x2
+
+errno_t
+sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname,
+ struct sss_iface_addr **_addrlist);
+
+struct sss_iface_addr *
+sss_iface_addr_add(TALLOC_CTX *mem_ctx, struct sss_iface_addr **list,
+ struct sockaddr_storage *ss);
+
+errno_t
+sss_iface_addr_list_as_str_list(TALLOC_CTX *mem_ctx,
+ struct sss_iface_addr *ifaddr_list,
+ char ***_straddrs);
+
+errno_t
+be_nsupdate_create_msg(TALLOC_CTX *mem_ctx, const char *realm,
+ const char *zone, const char *servername,
+ const char *hostname, const unsigned int ttl,
+ uint8_t remove_af, struct sss_iface_addr *addresses,
+ char **_update_msg);
+
+/* Returns:
+ * * ERR_OK - on success
+ * * ERR_DYNDNS_FAILED - if nsupdate fails for any reason
+ * * ERR_DYNDNS_TIMEOUT - if the update times out. child_status
+ * is ETIMEDOUT in this case
+ */
+struct tevent_req *be_nsupdate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ char *nsupdate_msg);
+errno_t be_nsupdate_recv(struct tevent_req *req, int *child_status);
+
+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 nsupdate_get_addrs_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char ***_addrlist);
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index d5c10a51..6e77c997 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -138,7 +138,7 @@ struct ipa_options {
/* id provider */
struct sdap_options *id;
struct ipa_id_ctx *id_ctx;
- struct resolv_ctx *resolv;
+ struct be_resolv_ctx *be_res;
/* auth and chpass provider */
struct dp_option *auth;
diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c
index 0bd8ad00..79918a26 100644
--- a/src/providers/ipa/ipa_dyndns.c
+++ b/src/providers/ipa/ipa_dyndns.c
@@ -22,863 +22,94 @@
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 "providers/ldap/sdap_dyndns.h"
#include "providers/ipa/ipa_common.h"
#include "providers/ipa/ipa_dyndns.h"
-#include "util/child_common.h"
#include "providers/data_provider.h"
-#include "providers/ldap/ldap_common.h"
-#include "providers/ldap/sdap_async_private.h"
-#include "resolv/async_resolv.h"
-
-#define IPA_DYNDNS_TIMEOUT 15
-
-#define IPA_DYNDNS_REMOVE_A 0x1
-#define IPA_DYNDNS_REMOVE_AAAA 0x2
-
-struct ipa_ipaddress {
- struct ipa_ipaddress *next;
- struct ipa_ipaddress *prev;
-
- struct sockaddr_storage *addr;
- bool matched;
-};
-
-struct ipa_dyndns_ctx {
- struct ipa_options *ipa_ctx;
- struct sdap_id_op* sdap_op;
- char *hostname;
- struct ipa_ipaddress *addresses;
- bool use_server_with_nsupdate;
- uint8_t remove_af;
- enum restrict_family family_order;
-};
-
-
-static struct tevent_req * ipa_dyndns_update_send(struct ipa_options *ctx);
-
-static void ipa_dyndns_update_done(struct tevent_req *req);
-
-static errno_t
-ipa_ipaddress_list_as_string_list(TALLOC_CTX *mem_ctx,
- struct ipa_ipaddress *ipa_addr_list,
- char ***_straddrs)
-{
- struct ipa_ipaddress *ipa_addr;
- size_t count;
- int ai;
- char **straddrs;
- const char *ip;
- char ip_addr[INET6_ADDRSTRLEN];
- errno_t ret;
-
- count = 0;
- DLIST_FOR_EACH(ipa_addr, ipa_addr_list) {
- count++;
- }
-
- straddrs = talloc_array(mem_ctx, char *, count+1);
- if (straddrs == NULL) {
- return ENOMEM;
- }
-
- ai = 0;
- DLIST_FOR_EACH(ipa_addr, ipa_addr_list) {
- switch(ipa_addr->addr->ss_family) {
- case AF_INET:
- errno = 0;
- ip = inet_ntop(ipa_addr->addr->ss_family,
- &(((struct sockaddr_in *)ipa_addr->addr)->sin_addr),
- ip_addr, INET6_ADDRSTRLEN);
- if (ip == NULL) {
- ret = errno;
- goto fail;
- }
- break;
-
- case AF_INET6:
- errno = 0;
- ip = inet_ntop(ipa_addr->addr->ss_family,
- &(((struct sockaddr_in6 *)ipa_addr->addr)->sin6_addr),
- ip_addr, INET6_ADDRSTRLEN);
- if (ip == NULL) {
- ret = errno;
- goto fail;
- }
- break;
-
- default:
- DEBUG(0, ("Unknown address family\n"));
- continue;
- }
-
- straddrs[ai] = talloc_strdup(straddrs, ip);
- if (straddrs[ai] == NULL) {
- ret = ENOMEM;
- goto fail;
- }
- ai++;
- }
-
- straddrs[count] = NULL;
- *_straddrs = straddrs;
- return EOK;
-
-fail:
- talloc_free(straddrs);
- return ret;
-}
+void ipa_dyndns_update(void *pvt);
errno_t ipa_dyndns_init(struct be_ctx *be_ctx,
struct ipa_options *ctx)
{
errno_t ret;
- ctx->resolv = be_ctx->be_res->resolv;
+ ctx->be_res = be_ctx->be_res;
+ if (ctx->be_res == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Resolver must be initialized in order "
+ "to use the IPA dynamic DNS updates\n"));
+ return EINVAL;
+ }
ret = be_add_online_cb(be_ctx, be_ctx,
ipa_dyndns_update,
ctx, NULL);
if (ret != EOK) {
- DEBUG(1, ("Could not set up online callback\n"));
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Could not set up online callback\n"));
return ret;
}
return EOK;
}
+static struct tevent_req *ipa_dyndns_update_send(struct ipa_options *ctx);
+static errno_t ipa_dyndns_update_recv(struct tevent_req *req);
+
+static void ipa_dyndns_nsupdate_done(struct tevent_req *subreq);
+
void ipa_dyndns_update(void *pvt)
{
struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options);
struct tevent_req *req = ipa_dyndns_update_send(ctx);
if (req == NULL) {
- DEBUG(1, ("Could not update DNS\n"));
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Could not update DNS\n"));
return;
}
- tevent_req_set_callback(req, ipa_dyndns_update_done, NULL);
-}
-
-static bool ok_for_dns(struct sockaddr *sa)
-{
- char straddr[INET6_ADDRSTRLEN];
-
- if (sa->sa_family == AF_INET6) {
- struct in6_addr *addr = &((struct sockaddr_in6 *) sa)->sin6_addr;
-
- if (inet_ntop(AF_INET6, addr, straddr, INET6_ADDRSTRLEN) == NULL) {
- DEBUG(SSSDBG_MINOR_FAILURE,
- ("inet_ntop failed, won't log IP addresses\n"));
- snprintf(straddr, INET6_ADDRSTRLEN, "unknown");
- }
-
- if (IN6_IS_ADDR_LINKLOCAL(addr)) {
- DEBUG(SSSDBG_FUNC_DATA, ("Link local IPv6 address %s\n", straddr));
- return false;
- } else if (IN6_IS_ADDR_LOOPBACK(addr)) {
- DEBUG(SSSDBG_FUNC_DATA, ("Loopback IPv6 address %s\n", straddr));
- return false;
- } else if (IN6_IS_ADDR_MULTICAST(addr)) {
- DEBUG(SSSDBG_FUNC_DATA, ("Multicast IPv6 address %s\n", straddr));
- return false;
- }
- } else if (sa->sa_family == AF_INET) {
- struct in_addr *addr = &((struct sockaddr_in *) sa)->sin_addr;
-
- if (inet_ntop(AF_INET, addr, straddr, INET6_ADDRSTRLEN) == NULL) {
- DEBUG(SSSDBG_MINOR_FAILURE,
- ("inet_ntop failed, won't log IP addresses\n"));
- snprintf(straddr, INET6_ADDRSTRLEN, "unknown");
- }
-
- if (IN_MULTICAST(ntohl(addr->s_addr))) {
- DEBUG(SSSDBG_FUNC_DATA, ("Multicast IPv4 address %s\n", straddr));
- return false;
- } else if (inet_netof(*addr) == IN_LOOPBACKNET) {
- DEBUG(SSSDBG_FUNC_DATA, ("Loopback IPv4 address %s\n", straddr));
- return false;
- } else if ((addr->s_addr & 0xffff0000) == 0xa9fe0000) {
- /* 169.254.0.0/16 */
- DEBUG(SSSDBG_FUNC_DATA, ("Link-local IPv4 address %s\n", straddr));
- return false;
- } else if (addr->s_addr == htonl(INADDR_BROADCAST)) {
- DEBUG(SSSDBG_FUNC_DATA, ("Broadcast IPv4 address %s\n", straddr));
- return false;
- }
- } else {
- DEBUG(SSSDBG_CRIT_FAILURE, ("Unknown address family\n"));
- return false;
- }
-
- return true;
+ tevent_req_set_callback(req, ipa_dyndns_nsupdate_done, NULL);
}
-static void ipa_dyndns_sdap_connect_done(struct tevent_req *subreq);
-static int ipa_dyndns_add_ldap_iface(struct ipa_dyndns_ctx *state,
- struct sdap_handle *sh);
-static int ipa_dyndns_gss_tsig_update_step(struct tevent_req *req);
-
-static struct tevent_req *
-ipa_dyndns_gss_tsig_update_send(struct ipa_dyndns_ctx *ctx);
-
-static void ipa_dyndns_gss_tsig_update_done(struct tevent_req *subreq);
-
-static struct tevent_req *
-ipa_dyndns_update_send(struct ipa_options *ctx)
+static void ipa_dyndns_nsupdate_done(struct tevent_req *req)
{
- int ret;
- char *iface;
- struct ipa_dyndns_ctx *state;
- struct ifaddrs *ifaces;
- struct ifaddrs *ifa;
- struct ipa_ipaddress *address;
- struct tevent_req *req, *subreq;
- size_t addrsize;
- struct sdap_id_ctx *id_ctx = ctx->id_ctx->sdap_id_ctx;
-
- DEBUG (9, ("Performing update\n"));
-
- req = tevent_req_create(ctx, &state, struct ipa_dyndns_ctx);
- if (req == NULL) {
- return NULL;
- }
- state->ipa_ctx = ctx;
- state->use_server_with_nsupdate = false;
- state->family_order = id_ctx->be->be_res->family_order;
-
- iface = dp_opt_get_string(ctx->basic, IPA_DYNDNS_IFACE);
-
- if (iface) {
- /* Get the IP addresses associated with the
- * specified interface
- */
- errno = 0;
- ret = getifaddrs(&ifaces);
- if (ret == -1) {
- ret = errno;
- DEBUG(0, ("Could not read interfaces [%d][%s]\n",
- ret, strerror(ret)));
- goto failed;
- }
-
- 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((ifa->ifa_addr->sa_family == AF_INET ||
- ifa->ifa_addr->sa_family == AF_INET6) &&
- strcasecmp(ifa->ifa_name, iface) == 0 &&
- ok_for_dns(ifa->ifa_addr)) {
-
- /* Add this address to the IP address list */
- address = talloc_zero(state, struct ipa_ipaddress);
- if (!address) {
- goto failed;
- }
-
- 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) {
- goto failed;
- }
- DLIST_ADD(state->addresses, address);
- }
- }
-
- freeifaddrs(ifaces);
-
- ret = ipa_dyndns_gss_tsig_update_step(req);
- if (ret != EOK) {
- goto failed;
- }
- }
-
- else {
- /* Detect DYNDNS interface from LDAP connection */
- state->sdap_op = sdap_id_op_create(state, state->ipa_ctx->id_ctx->sdap_id_ctx->conn_cache);
- if (!state->sdap_op) {
- DEBUG(1, ("sdap_id_op_create failed\n"));
- ret = ENOMEM;
- goto failed;
- }
-
- subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
- if (!subreq) {
- DEBUG(1, ("sdap_id_op_connect_send failed: [%d](%s)\n",
- ret, strerror(ret)));
-
- goto failed;
- }
-
- tevent_req_set_callback(subreq, ipa_dyndns_sdap_connect_done, req);
- }
-
- return req;
-
-failed:
+ int ret = ipa_dyndns_update_recv(req);
talloc_free(req);
- return NULL;
-}
-
-static void ipa_dyndns_sdap_connect_done(struct tevent_req *subreq)
-{
- struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
- struct ipa_dyndns_ctx *state = tevent_req_data(req, struct ipa_dyndns_ctx);
- int ret, dp_error;
-
- ret = sdap_id_op_connect_recv(subreq, &dp_error);
- talloc_zfree(subreq);
-
- if (ret != EOK) {
- if (dp_error == DP_ERR_OFFLINE) {
- DEBUG(9,("No LDAP server is available, dynamic DNS update is skipped in OFFLINE mode.\n"));
- } else {
- DEBUG(9,("Failed to connect to LDAP server: [%d](%s)\n",
- ret, strerror(ret)));
- }
-
- goto failed;
- }
-
- ret = ipa_dyndns_add_ldap_iface(state, sdap_id_op_handle(state->sdap_op));
- talloc_zfree(state->sdap_op);
if (ret != EOK) {
- goto failed;
- }
-
- ret = ipa_dyndns_gss_tsig_update_step(req);
- if (ret != EOK) {
- goto failed;
- }
-
- return;
-
-failed:
- tevent_req_error(req, ret);
-}
-
-static int ipa_dyndns_add_ldap_iface(struct ipa_dyndns_ctx *state,
- struct sdap_handle *sh)
-{
- int ret;
- int fd;
- struct ipa_ipaddress *address;
- struct sockaddr_storage ss;
- socklen_t ss_len = sizeof(ss);
-
- if (!sh) {
- return EINVAL;
- }
-
- /* Get the file descriptor for the primary LDAP connection */
- ret = get_fd_from_ldap(sh->ldap, &fd);
- if (ret != EOK) {
- return ret;
- }
-
- errno = 0;
- ret = getsockname(fd, (struct sockaddr *) &ss, &ss_len);
- if (ret == -1) {
- ret = errno;
- DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to get socket name\n"));
- return ret;
- }
-
- switch(ss.ss_family) {
- case AF_INET:
- case AF_INET6:
- address = talloc(state, struct ipa_ipaddress);
- if (!address) {
- return ENOMEM;
- }
- address->addr = talloc_memdup(address, &ss,
- sizeof(struct sockaddr_storage));
- if(address->addr == NULL) {
- talloc_zfree(address);
- return ENOMEM;
- }
- DLIST_ADD(state->addresses, address);
- break;
- default:
- DEBUG(1, ("Connection to LDAP is neither IPv4 nor IPv6\n"));
- return EIO;
- }
-
- return EOK;
-}
-
-static struct tevent_req *
-ipa_dyndns_update_get_addrs_send(TALLOC_CTX *mem_ctx,
- struct ipa_dyndns_ctx *ctx,
- enum restrict_family family_order);
-static errno_t
-ipa_dyndns_update_get_addrs_recv(struct tevent_req *req,
- TALLOC_CTX *mem_ctx,
- char ***_addrlist);
-
-static errno_t
-ipa_dyndns_gss_tsig_update_setup_check(struct ipa_dyndns_ctx *state);
-static void
-ipa_dyndns_gss_tsig_update_check(struct tevent_req *subreq);
-
-static int ipa_dyndns_gss_tsig_update_step(struct tevent_req *req)
-{
- struct ipa_dyndns_ctx *state = tevent_req_data(req, struct ipa_dyndns_ctx);
- char *ipa_hostname;
- struct tevent_req *subreq;
- errno_t ret;
-
- /* Get the IPA hostname */
- ipa_hostname = dp_opt_get_string(state->ipa_ctx->basic,
- IPA_HOSTNAME);
- if (!ipa_hostname) {
- /* This should never happen, but we'll protect
- * against it anyway.
- */
- return EINVAL;
- }
-
- state->hostname = talloc_strdup(state, ipa_hostname);
- if (state->hostname == NULL) {
- return ENOMEM;
- }
-
- DEBUG(7, ("Checking if the update is needed\n"));
-
- ret = ipa_dyndns_gss_tsig_update_setup_check(state);
- if (ret != EOK) {
- return ret;
- }
-
- subreq = ipa_dyndns_update_get_addrs_send(state, state,
- state->family_order);
- if (subreq == NULL) {
- return ENOMEM;
- }
- tevent_req_set_callback(subreq,
- ipa_dyndns_gss_tsig_update_check,
- req);
- return EOK;
-}
-
-static errno_t
-ipa_dyndns_gss_tsig_update_setup_check(struct ipa_dyndns_ctx *state)
-{
- if (dp_opt_get_string(state->ipa_ctx->basic, IPA_DYNDNS_IFACE)) {
- /* Unless one family is restricted, just replace all
- * address families during the update
- */
- switch (state->family_order) {
- case IPV4_ONLY:
- state->remove_af |= IPA_DYNDNS_REMOVE_A;
- break;
- case IPV6_ONLY:
- state->remove_af |= IPA_DYNDNS_REMOVE_AAAA;
- break;
- case IPV4_FIRST:
- case IPV6_FIRST:
- state->remove_af |= (IPA_DYNDNS_REMOVE_A |
- IPA_DYNDNS_REMOVE_AAAA);
- break;
- }
- } else {
- /* If the interface isn't specified, we ONLY want to have the address
- * that's connected to the LDAP server stored, so we need to check
- * (and later remove) both address families.
- */
- state->family_order = IPV4_FIRST;
- state->remove_af = (IPA_DYNDNS_REMOVE_A |
- IPA_DYNDNS_REMOVE_AAAA);
- }
-
- return EOK;
-}
-
-static void
-ipa_dyndns_gss_tsig_update_check(struct tevent_req *subreq)
-{
- struct tevent_req *req =
- tevent_req_callback_data(subreq, struct tevent_req);
- struct ipa_dyndns_ctx *state = tevent_req_data(req,
- struct ipa_dyndns_ctx);
-
- errno_t ret;
- char **str_dnslist = NULL, **str_local_list = NULL;
- char **dns_only = NULL, **local_only = NULL;
- bool do_update = false;
- int i;
- TALLOC_CTX *tmp_ctx;
-
- tmp_ctx = talloc_new(NULL);
- if (!tmp_ctx) {
- ret = ENOMEM;
- goto fail;
- }
-
- ret = ipa_dyndns_update_get_addrs_recv(subreq, tmp_ctx, &str_dnslist);
- talloc_zfree(subreq);
- if (ret != EOK) {
- DEBUG(3, ("Getting the current list of addresses failed [%d]: %s\n",
- ret, strerror(ret)));
- goto fail;
- }
-
- ret = ipa_ipaddress_list_as_string_list(tmp_ctx,
- state->addresses, &str_local_list);
- if (ret != EOK) {
- DEBUG(3, ("Converting DNS IP addresses to strings failed: [%d]: %s\n",
- ret, strerror(ret)));
- goto fail;
- }
-
- /* Compare the lists */
- ret = diff_string_lists(tmp_ctx, str_dnslist, str_local_list,
- &dns_only, &local_only, NULL);
- if (ret != EOK) {
- DEBUG(3, ("diff_string_lists failed: [%d]: %s\n", ret, strerror(ret)));
- goto fail;
- }
-
- if (dns_only) {
- for (i=0; dns_only[i]; i++) {
- DEBUG(7, ("Address in DNS only: %s\n", dns_only[i]));
- do_update = true;
- }
- }
-
- if (local_only) {
- for (i=0; local_only[i]; i++) {
- DEBUG(7, ("Address on localhost only: %s\n", local_only[i]));
- do_update = true;
- }
- }
-
- if (do_update) {
- DEBUG(6, ("Detected IP addresses change, will perform an update\n"));
- subreq = ipa_dyndns_gss_tsig_update_send(state);
- if(subreq == NULL) {
- ret = ENOMEM;
- goto fail;
- }
- tevent_req_set_callback(subreq,
- ipa_dyndns_gss_tsig_update_done,
- req);
- talloc_free(tmp_ctx);
+ DEBUG(SSSDBG_OP_FAILURE, ("Updating DNS entry failed [%d]: %s\n",
+ ret, sss_strerror(ret)));
return;
}
- DEBUG(6, ("No DNS update needed, addresses did not change\n"));
- tevent_req_done(req);
- talloc_free(tmp_ctx);
- return;
-
-fail:
- talloc_free(tmp_ctx);
- tevent_req_error(req, ret);
+ DEBUG(SSSDBG_OP_FAILURE, ("DNS update finished\n"));
}
-struct ipa_dyndns_update_get_addrs_state {
- struct ipa_dyndns_ctx *dctx;
-
- enum host_database *db;
- enum restrict_family family_order;
-
- char **addrlist;
- size_t count;
+struct ipa_dyndns_update_state {
+ struct ipa_options *ipa_ctx;
};
-static void ipa_dyndns_update_get_addrs_done(struct tevent_req *subreq);
-static errno_t ipa_dyndns_update_get_addrs_step(struct tevent_req *req);
+static void ipa_dyndns_sdap_update_done(struct tevent_req *subreq);
static struct tevent_req *
-ipa_dyndns_update_get_addrs_send(TALLOC_CTX *mem_ctx,
- struct ipa_dyndns_ctx *ctx,
- enum restrict_family family_order)
-{
- errno_t ret;
- struct tevent_req *req;
- struct ipa_dyndns_update_get_addrs_state *state;
- struct sdap_id_ctx *id_ctx = ctx->ipa_ctx->id_ctx->sdap_id_ctx;
-
- req = tevent_req_create(mem_ctx, &state,
- struct ipa_dyndns_update_get_addrs_state);
- if (req == NULL) {
- return NULL;
- }
- state->dctx = ctx;
- state->family_order = family_order;
-
- state->db = talloc_array(state, enum host_database, 2);
- if (state->db == NULL) {
- ret = ENOMEM;
- goto immediate;
- }
- state->db[0] = DB_DNS;
- state->db[1] = DB_SENTINEL;
-
- ret = ipa_dyndns_update_get_addrs_step(req);
- if (ret != EOK) {
- goto immediate;
- }
-
-immediate:
- if (ret != EOK) {
- tevent_req_error(req, ret);
- tevent_req_post(req, id_ctx->be->ev);
- }
- return req;
-}
-
-static errno_t
-ipa_dyndns_update_get_addrs_step(struct tevent_req *req)
-{
- struct tevent_req *subreq;
- struct ipa_dyndns_update_get_addrs_state *state = tevent_req_data(req,
- struct ipa_dyndns_update_get_addrs_state);
- struct ipa_id_ctx *ipa_id_ctx = state->dctx->ipa_ctx->id_ctx;
-
- subreq = resolv_gethostbyname_send(state,
- ipa_id_ctx->sdap_id_ctx->be->ev,
- state->dctx->ipa_ctx->resolv,
- state->dctx->hostname,
- state->family_order,
- state->db);
- if (!subreq) {
- return ENOMEM;
- }
-
- tevent_req_set_callback(subreq, ipa_dyndns_update_get_addrs_done, req);
- return EOK;
-}
-
-static void
-ipa_dyndns_update_get_addrs_done(struct tevent_req *subreq)
+ipa_dyndns_update_send(struct ipa_options *ctx)
{
int ret;
- size_t count;
- struct tevent_req *req =
- tevent_req_callback_data(subreq, struct tevent_req);
- struct ipa_dyndns_update_get_addrs_state *state = tevent_req_data(req,
- struct ipa_dyndns_update_get_addrs_state);
- struct resolv_hostent *rhostent;
+ struct ipa_dyndns_update_state *state;
+ struct tevent_req *req, *subreq;
+ struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx;
+ char *dns_zone;
+ const char *servername;
int i;
- int resolv_status;
-
- 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
- * retry the second one
- */
- if (state->family_order == IPV4_FIRST ||
- state->family_order == IPV6_FIRST) {
-
- state->family_order = (state->family_order == IPV4_FIRST) ? \
- IPV6_ONLY : IPV4_ONLY;
-
- ret = ipa_dyndns_update_get_addrs_step(req);
- if (ret != EOK) {
- tevent_req_error(req, ret);
- }
- return;
- }
-
- /* Nothing to retry, simply quit */
- tevent_req_done(req);
- return;
- } else if (ret != EOK) {
- DEBUG(SSSDBG_OP_FAILURE,
- ("Could not resolve address for this machine, error [%d]: %s, "
- "resolver returned: [%d]: %s\n", ret, strerror(ret),
- resolv_status, resolv_strerror(resolv_status)));
- tevent_req_error(req, ret);
- return;
- }
-
- /* 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;
- }
-
- state->addrlist = talloc_realloc(state, state->addrlist, char *,
- state->count + count + 1);
- if (!state->addrlist) {
- tevent_req_error(req, ENOMEM);
- return;
- }
-
- for (i=0; i < count; i++) {
- state->addrlist[state->count + i] = \
- resolv_get_string_address_index(state->addrlist,
- rhostent, i);
-
- if (state->addrlist[state->count + i] == NULL) {
- tevent_req_error(req, ENOMEM);
- return;
- }
- }
- state->count += count;
- state->addrlist[state->count] = NULL;
-
- /* 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->family_order == IPV4_FIRST &&
- rhostent->family == AF_INET) ||
- (state->family_order == IPV6_FIRST &&
- rhostent->family == AF_INET6))) {
-
- state->family_order = (state->family_order == IPV4_FIRST) ? \
- IPV6_ONLY : IPV4_ONLY;
-
- ret = ipa_dyndns_update_get_addrs_step(req);
- if (ret != EOK) {
- tevent_req_error(req, ret);
- }
- return;
- }
-
- /* The second address matched either immediatelly or after a retry.
- * No need to retry again. */
- tevent_req_done(req);
- return;
-}
-
-static errno_t
-ipa_dyndns_update_get_addrs_recv(struct tevent_req *req,
- TALLOC_CTX *mem_ctx,
- char ***_addrlist)
-{
- struct ipa_dyndns_update_get_addrs_state *state = tevent_req_data(req,
- struct ipa_dyndns_update_get_addrs_state);
-
- TEVENT_REQ_RETURN_ON_ERROR(req);
-
- *_addrlist = talloc_steal(mem_ctx, state->addrlist);
- return EOK;
-}
-
-struct ipa_nsupdate_ctx {
- char *update_msg;
- struct ipa_dyndns_ctx *dyndns_ctx;
- int pipefd_to_child;
- struct tevent_timer *timeout_handler;
- int child_status;
-};
-
-
-static int create_nsupdate_message(struct ipa_nsupdate_ctx *ctx,
- uint8_t remove_af,
- bool use_server_with_nsupdate);
-
-static struct tevent_req *
-fork_nsupdate_send(struct ipa_nsupdate_ctx *ctx);
-
-static void fork_nsupdate_done(struct tevent_req *subreq);
-static struct tevent_req *
-ipa_dyndns_gss_tsig_update_send(struct ipa_dyndns_ctx *ctx)
-{
- int ret;
- struct ipa_nsupdate_ctx *state;
- struct tevent_req *req;
- struct tevent_req *subreq;
+ DEBUG(SSSDBG_TRACE_FUNC, ("Performing update\n"));
- req = tevent_req_create(ctx, &state, struct ipa_nsupdate_ctx);
- if(req == NULL) {
+ req = tevent_req_create(ctx, &state, struct ipa_dyndns_update_state);
+ if (req == NULL) {
return NULL;
}
- state->dyndns_ctx = ctx;
- state->child_status = 0;
-
- /* Format the message to pass to the nsupdate command */
- ret = create_nsupdate_message(state, ctx->remove_af,
- ctx->use_server_with_nsupdate);
- if (ret != EOK) {
- goto failed;
- }
-
- /* Fork a child process to perform the DNS update */
- subreq = fork_nsupdate_send(state);
- if(subreq == NULL) {
- goto failed;
- }
- tevent_req_set_callback(subreq, fork_nsupdate_done, req);
-
- return req;
-
-failed:
- talloc_free(req);
- return NULL;
-}
-
-struct nsupdate_send_ctx {
- struct ipa_nsupdate_ctx *nsupdate_ctx;
- int child_status;
-};
-
-static int create_nsupdate_message(struct ipa_nsupdate_ctx *ctx,
- uint8_t remove_af,
- bool use_server_with_nsupdate)
-{
- int ret, i, ttl;
- char *servername = NULL;
- char *realm;
- char *realm_directive;
- char *zone;
- char ip_addr[INET6_ADDRSTRLEN];
- const char *ip;
- struct ipa_ipaddress *new_record;
- TALLOC_CTX *tmp_ctx;
-
- tmp_ctx = talloc_new(NULL);
- if (!tmp_ctx) return ENOMEM;
-
- realm = dp_opt_get_string(ctx->dyndns_ctx->ipa_ctx->basic, IPA_KRB5_REALM);
- if (!realm) {
- ret = EIO;
- goto done;
- }
-
-#ifdef HAVE_NSUPDATE_REALM
- realm_directive = talloc_asprintf(tmp_ctx, "realm %s\n", realm);
-#else
- realm_directive = talloc_asprintf(tmp_ctx, "");
-#endif
- if (!realm_directive) {
- ret = ENOMEM;
- goto done;
- }
+ state->ipa_ctx = ctx;
- zone = dp_opt_get_string(ctx->dyndns_ctx->ipa_ctx->basic,
- IPA_DOMAIN);
- if (!zone) {
+ dns_zone = dp_opt_get_string(ctx->basic, IPA_DOMAIN);
+ if (!dns_zone) {
ret = EIO;
goto done;
}
@@ -886,333 +117,63 @@ static int create_nsupdate_message(struct ipa_nsupdate_ctx *ctx,
/* The DNS zone for IPA is the lower-case
* version of the IPA domain
*/
- for(i = 0; zone[i] != '\0'; i++) {
- zone[i] = tolower(zone[i]);
+ for (i = 0; dns_zone[i] != '\0'; i++) {
+ dns_zone[i] = tolower(dns_zone[i]);
}
- if (use_server_with_nsupdate) {
- if (strncmp(ctx->dyndns_ctx->ipa_ctx->service->sdap->uri,
- "ldap://", 7) != 0) {
- DEBUG(1, ("Unexpected format of LDAP URI.\n"));
- ret = EIO;
- goto done;
- }
- servername = ctx->dyndns_ctx->ipa_ctx->service->sdap->uri + 7;
- if (!servername) {
- ret = EIO;
- goto done;
- }
-
- DEBUG(SSSDBG_FUNC_DATA,
- ("Creating update message for server [%s], realm [%s] "
- "and zone [%s].\n", servername, realm, zone));
-
- /* Add the server, realm and zone headers */
- ctx->update_msg = talloc_asprintf(ctx, "server %s\n%szone %s.\n",
- servername, realm_directive,
- zone);
- } else {
- DEBUG(SSSDBG_FUNC_DATA,
- ("Creating update message for realm [%s] and zone [%s].\n",
- realm, zone));
-
- /* Add the realm and zone headers */
- ctx->update_msg = talloc_asprintf(ctx, "%szone %s.\n",
- realm_directive, zone);
- }
- if (ctx->update_msg == NULL) {
- ret = ENOMEM;
+ if (strncmp(ctx->service->sdap->uri,
+ "ldap://", 7) != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Unexpected format of LDAP URI.\n"));
+ ret = EIO;
goto done;
}
-
- /* Get the TTL details for the record(s) */
-
- ttl = dp_opt_get_int(ctx->dyndns_ctx->ipa_ctx->basic,
- IPA_DYNDNS_TTL);
- /* Should not happen but just in case set the default */
- if (!ttl) {
- ttl = 1200;
- }
-
- /* Remove existing entries as needed */
- if (remove_af & IPA_DYNDNS_REMOVE_A) {
- ctx->update_msg = talloc_asprintf_append(ctx->update_msg,
- "update delete %s. in A\nsend\n",
- ctx->dyndns_ctx->hostname);
- if (ctx->update_msg == NULL) {
- ret = ENOMEM;
- goto done;
- }
- }
- if (remove_af & IPA_DYNDNS_REMOVE_AAAA) {
- ctx->update_msg = talloc_asprintf_append(ctx->update_msg,
- "update delete %s. in AAAA\nsend\n",
- ctx->dyndns_ctx->hostname);
- if (ctx->update_msg == NULL) {
- ret = ENOMEM;
- goto done;
- }
- }
-
- DLIST_FOR_EACH(new_record, ctx->dyndns_ctx->addresses) {
- switch(new_record->addr->ss_family) {
- case AF_INET:
- ip = inet_ntop(new_record->addr->ss_family,
- &(((struct sockaddr_in *)new_record->addr)->sin_addr),
- ip_addr, INET6_ADDRSTRLEN);
- if (ip == NULL) {
- ret = EIO;
- goto done;
- }
- break;
-
- case AF_INET6:
- ip = inet_ntop(new_record->addr->ss_family,
- &(((struct sockaddr_in6 *)new_record->addr)->sin6_addr),
- ip_addr, INET6_ADDRSTRLEN);
- if (ip == NULL) {
- ret = EIO;
- goto done;
- }
- break;
-
- default:
- DEBUG(0, ("Unknown address family\n"));
- ret = EIO;
- goto done;
- }
-
- /* Format the record update */
- ctx->update_msg = talloc_asprintf_append(
- ctx->update_msg,
- "update add %s. %d in %s %s\n",
- ctx->dyndns_ctx->hostname,
- ttl,
- new_record->addr->ss_family == AF_INET ? "A" : "AAAA",
- ip_addr);
- if (ctx->update_msg == NULL) {
- ret = ENOMEM;
- goto done;
- }
+ servername = ctx->service->sdap->uri + 7;
+ if (!servername) {
+ ret = EIO;
+ goto done;
}
- ctx->update_msg = talloc_asprintf_append(ctx->update_msg, "send\n");
- if (ctx->update_msg == NULL) {
- ret = ENOMEM;
+ subreq = sdap_dyndns_update_send(state, sdap_ctx->be->ev,
+ sdap_ctx->be, sdap_ctx,
+ dp_opt_get_string(ctx->basic,
+ IPA_DYNDNS_IFACE),
+ dp_opt_get_string(ctx->basic,
+ IPA_HOSTNAME),
+ dns_zone,
+ dp_opt_get_string(ctx->basic,
+ IPA_KRB5_REALM),
+ servername,
+ dp_opt_get_int(ctx->basic,
+ IPA_DYNDNS_TTL),
+ true);
+ if (!subreq) {
+ ret = EIO;
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("sdap_id_op_connect_send failed: [%d](%s)\n",
+ ret, sss_strerror(ret)));
goto done;
}
-
- DEBUG(SSSDBG_TRACE_FUNC,
- (" -- Begin nsupdate message -- \n%s",
- ctx->update_msg));
- DEBUG(SSSDBG_TRACE_FUNC,
- (" -- End nsupdate message -- \n"));
+ tevent_req_set_callback(subreq, ipa_dyndns_sdap_update_done, req);
ret = EOK;
-
done:
- talloc_free(tmp_ctx);
- return ret;
-}
-
-static void ipa_dyndns_stdin_done(struct tevent_req *subreq);
-
-static void ipa_dyndns_child_handler(int child_status,
- struct tevent_signal *sige,
- void *pvt);
-
-static void ipa_dyndns_timeout(struct tevent_context *ev,
- struct tevent_timer *te,
- struct timeval tv, void *pvt);
-
-static struct tevent_req *
-fork_nsupdate_send(struct ipa_nsupdate_ctx *ctx)
-{
- int pipefd_to_child[2];
- pid_t pid;
- int ret;
- errno_t err;
- struct timeval tv;
- struct tevent_req *req = NULL;
- struct tevent_req *subreq = NULL;
- struct nsupdate_send_ctx *state;
- char *args[3];
-
- req = tevent_req_create(ctx, &state, struct nsupdate_send_ctx);
- if (req == NULL) {
- return NULL;
- }
- state->nsupdate_ctx = ctx;
- state->child_status = 0;
-
- ret = pipe(pipefd_to_child);
- if (ret == -1) {
- err = errno;
- DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err)));
- return NULL;
- }
-
- pid = fork();
-
- if (pid == 0) { /* child */
- args[0] = talloc_strdup(ctx, NSUPDATE_PATH);
- args[1] = talloc_strdup(ctx, "-g");
- args[2] = NULL;
- if (args[0] == NULL || args[1] == NULL) {
- return NULL;
- }
-
- close(pipefd_to_child[1]);
- ret = dup2(pipefd_to_child[0], STDIN_FILENO);
- if (ret == -1) {
- err = errno;
- DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
- return NULL;
- }
-
- errno = 0;
- execv(NSUPDATE_PATH, args);
- err = errno;
- DEBUG(SSSDBG_CRIT_FAILURE, ("execv failed [%d][%s].\n", err, strerror(err)));
- return NULL;
- }
-
- else if (pid > 0) { /* parent */
- close(pipefd_to_child[0]);
-
- ctx->pipefd_to_child = pipefd_to_child[1];
-
- /* Write the update message to the nsupdate child */
- subreq = write_pipe_send(req,
- ctx->dyndns_ctx->ipa_ctx->id_ctx->sdap_id_ctx->be->ev,
- (uint8_t *)ctx->update_msg,
- strlen(ctx->update_msg)+1,
- ctx->pipefd_to_child);
- if (subreq == NULL) {
- return NULL;
- }
- tevent_req_set_callback(subreq, ipa_dyndns_stdin_done, req);
-
- /* Set up SIGCHLD handler */
- ret = child_handler_setup(ctx->dyndns_ctx->ipa_ctx->id_ctx->sdap_id_ctx->be->ev,
- pid, ipa_dyndns_child_handler, req);
- if (ret != EOK) {
- return NULL;
- }
-
- /* Set up timeout handler */
- tv = tevent_timeval_current_ofs(IPA_DYNDNS_TIMEOUT, 0);
- ctx->timeout_handler = tevent_add_timer(
- ctx->dyndns_ctx->ipa_ctx->id_ctx->sdap_id_ctx->be->ev,
- req, tv, ipa_dyndns_timeout, req);
- if(ctx->timeout_handler == NULL) {
- return NULL;
- }
- }
-
- else { /* error */
- err = errno;
- DEBUG(1, ("fork failed [%d][%s].\n", err, strerror(err)));
- return NULL;
- }
-
- return req;
-}
-
-static void ipa_dyndns_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);
-
- DEBUG(1, ("Timeout reached for dynamic DNS update\n"));
-
- tevent_req_error(req, ETIMEDOUT);
-}
-
-static void ipa_dyndns_stdin_done(struct tevent_req *subreq)
-{
- /* Verify that the buffer was sent, then return
- * and wait for the sigchld handler to finish.
- */
- DEBUG(9, ("Sending nsupdate data complete\n"));
-
- int ret;
- struct tevent_req *req =
- tevent_req_callback_data(subreq, struct tevent_req);
- struct nsupdate_send_ctx *state =
- tevent_req_data(req, struct nsupdate_send_ctx);
-
- ret = write_pipe_recv(subreq);
- talloc_zfree(subreq);
if (ret != EOK) {
- DEBUG(1, ("Sending nsupdate data failed\n"));
tevent_req_error(req, ret);
- return;
+ tevent_req_post(req, sdap_ctx->be->ev);
}
-
- close(state->nsupdate_ctx->pipefd_to_child);
- state->nsupdate_ctx->pipefd_to_child = -1;
-}
-
-static void ipa_dyndns_child_handler(int child_status,
- struct tevent_signal *sige,
- void *pvt)
-{
- struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
- struct nsupdate_send_ctx *state =
- tevent_req_data(req, struct nsupdate_send_ctx);
-
- state->child_status = child_status;
-
- if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) {
- DEBUG(1, ("Dynamic DNS child failed with status [%d]\n",
- child_status));
- tevent_req_error(req, EIO);
- return;
- }
-
- if WIFSIGNALED(child_status) {
- DEBUG(1, ("Dynamic DNS child was terminated by signal [%d]\n",
- WTERMSIG(child_status)));
- tevent_req_error(req, EIO);
- return;
- }
-
- tevent_req_done(req);
-}
-
-static int ipa_dyndns_child_recv(struct tevent_req *req, int *child_status)
-{
- struct nsupdate_send_ctx *state =
- tevent_req_data(req, struct nsupdate_send_ctx);
-
- *child_status = state->child_status;
-
- TEVENT_REQ_RETURN_ON_ERROR(req);
-
- return EOK;
-}
-
-static int ipa_dyndns_generic_recv(struct tevent_req *req)
-{
- TEVENT_REQ_RETURN_ON_ERROR(req);
-
- return EOK;
+ return req;
}
-static void fork_nsupdate_done(struct tevent_req *subreq)
+static void ipa_dyndns_sdap_update_done(struct tevent_req *subreq)
{
- int ret;
- struct tevent_req *req =
- tevent_req_callback_data(subreq, struct tevent_req);
- struct ipa_nsupdate_ctx *state = tevent_req_data(req,
- struct ipa_nsupdate_ctx);
+ struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+ errno_t ret;
- ret = ipa_dyndns_child_recv(subreq, &state->child_status);
+ ret = sdap_dyndns_update_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Dynamic DNS update failed [%d]: %s\n", ret, sss_strerror(ret)));
tevent_req_error(req, ret);
return;
}
@@ -1220,59 +181,9 @@ static void fork_nsupdate_done(struct tevent_req *subreq)
tevent_req_done(req);
}
-static int fork_nsupdate_recv(struct tevent_req *req, int *child_status)
+static errno_t ipa_dyndns_update_recv(struct tevent_req *req)
{
- struct ipa_nsupdate_ctx *state =
- tevent_req_data(req, struct ipa_nsupdate_ctx);
-
- *child_status = state->child_status;
-
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
-
-static void ipa_dyndns_gss_tsig_update_done(struct tevent_req *subreq)
-{
- /* Check the return code from the sigchld handler
- * and return it to the parent request.
- */
- int ret;
- int child_status;
-
- struct tevent_req *req =
- tevent_req_callback_data(subreq, struct tevent_req);
- struct ipa_dyndns_ctx *state = tevent_req_data(req, struct ipa_dyndns_ctx);
-
- ret = fork_nsupdate_recv(subreq, &child_status);
- talloc_zfree(subreq);
- if (ret != EOK) {
- if (state->use_server_with_nsupdate == false &&
- WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) {
- DEBUG(9, ("nsupdate failed, retrying with server name.\n"));
- state->use_server_with_nsupdate = true;
- ret = ipa_dyndns_gss_tsig_update_step(req);
- if (ret != EOK) {
- tevent_req_error(req, ret);
- }
- return;
- } else {
- tevent_req_error(req, ret);
- return;
- }
- }
-
- tevent_req_done(req);
-}
-
-static void ipa_dyndns_update_done(struct tevent_req *req)
-{
- int ret = ipa_dyndns_generic_recv(req);
- talloc_free(req);
- if (ret != EOK) {
- DEBUG(1, ("Updating DNS entry failed\n"));
- return;
- }
-
- DEBUG(1, ("DNS update finished\n"));
-}
diff --git a/src/providers/ldap/sdap_dyndns.c b/src/providers/ldap/sdap_dyndns.c
new file mode 100644
index 00000000..e7fad7ba
--- /dev/null
+++ b/src/providers/ldap/sdap_dyndns.c
@@ -0,0 +1,498 @@
+/*
+ SSSD
+
+ sdap_dyndns.c: LDAP specific dynamic DNS update
+
+ Authors:
+ 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 "util/util.h"
+#include "resolv/async_resolv.h"
+#include "providers/dp_backend.h"
+#include "providers/dp_dyndns.h"
+#include "providers/ldap/sdap_async_private.h"
+#include "providers/ldap/sdap_id_op.h"
+#include "providers/ldap/ldap_common.h"
+
+static struct tevent_req *
+sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *sdap_ctx,
+ const char *iface);
+static errno_t
+sdap_dyndns_get_addrs_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct sss_iface_addr **_addresses);
+
+struct sdap_dyndns_update_state {
+ struct tevent_context *ev;
+ struct be_resolv_ctx *be_res;
+
+ const char *hostname;
+ const char *dns_zone;
+ const char *realm;
+ const char *servername;
+ int ttl;
+
+ struct sss_iface_addr *addresses;
+ uint8_t remove_af;
+
+ bool check_diff;
+ bool use_server_with_nsupdate;
+ char *update_msg;
+};
+
+static void sdap_dyndns_update_addrs_done(struct tevent_req *subreq);
+static void sdap_dyndns_addrs_check_done(struct tevent_req *subreq);
+static errno_t sdap_dyndns_update_step(struct tevent_req *req);
+static void sdap_dyndns_update_done(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_dyndns_update_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct sdap_id_ctx *sdap_ctx,
+ const char *ifname,
+ const char *hostname,
+ const char *dns_zone,
+ const char *realm,
+ const char *servername,
+ const int ttl,
+ bool check_diff)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sdap_dyndns_update_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sdap_dyndns_update_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->check_diff = check_diff;
+ state->hostname = hostname;
+ state->dns_zone = dns_zone;
+ state->realm = realm;
+ state->servername = servername;
+ state->use_server_with_nsupdate = false;
+ state->ttl = ttl;
+ state->be_res = be_ctx->be_res;
+ state->ev = ev;
+
+ if (ifname) {
+ /* Unless one family is restricted, just replace all
+ * address families during the update
+ */
+ switch (state->be_res->family_order) {
+ case IPV4_ONLY:
+ state->remove_af |= DYNDNS_REMOVE_A;
+ break;
+ case IPV6_ONLY:
+ state->remove_af |= DYNDNS_REMOVE_AAAA;
+ break;
+ case IPV4_FIRST:
+ case IPV6_FIRST:
+ state->remove_af |= (DYNDNS_REMOVE_A |
+ DYNDNS_REMOVE_AAAA);
+ break;
+ }
+ } else {
+ /* If the interface isn't specified, we ONLY want to have the address
+ * that's connected to the LDAP server stored, so we need to check
+ * (and later remove) both address families.
+ */
+ state->remove_af = (DYNDNS_REMOVE_A | DYNDNS_REMOVE_AAAA);
+ }
+
+ subreq = sdap_dyndns_get_addrs_send(state, state->ev, sdap_ctx, ifname);
+ if (!subreq) {
+ ret = EIO;
+ DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: [%d](%s)\n",
+ ret, sss_strerror(ret)));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sdap_dyndns_update_addrs_done, req);
+
+ ret = EOK;
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void
+sdap_dyndns_update_addrs_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct sdap_dyndns_update_state *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_dyndns_update_state);
+
+ ret = sdap_dyndns_get_addrs_recv(subreq, state, &state->addresses);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Can't get addresses for DNS update\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->check_diff) {
+ /* Check if we need the update at all */
+ subreq = nsupdate_get_addrs_send(state, state->ev,
+ state->be_res, state->hostname);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Can't initiate address check\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_dyndns_addrs_check_done, req);
+ return;
+ }
+
+ /* Perform update */
+ ret = sdap_dyndns_update_step(req);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /* Execution will resume in sdap_dyndns_update_done */
+}
+
+static void
+sdap_dyndns_addrs_check_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int i;
+ struct tevent_req *req;
+ struct sdap_dyndns_update_state *state;
+ char **str_dnslist = NULL, **str_local_list = NULL;
+ char **dns_only = NULL, **local_only = NULL;
+ bool do_update;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_dyndns_update_state);
+
+ ret = nsupdate_get_addrs_recv(subreq, state, &str_dnslist);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Could not receive list of current addresses [%d]: %s\n",
+ ret, sss_strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sss_iface_addr_list_as_str_list(state,
+ state->addresses, &str_local_list);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Converting DNS IP addresses to strings failed: [%d]: %s\n",
+ ret, sss_strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* Compare the lists */
+ ret = diff_string_lists(state, str_dnslist, str_local_list,
+ &dns_only, &local_only, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("diff_string_lists failed: [%d]: %s\n", ret, sss_strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (dns_only) {
+ for (i=0; dns_only[i]; i++) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ ("Address in DNS only: %s\n", dns_only[i]));
+ do_update = true;
+ }
+ }
+
+ if (local_only) {
+ for (i=0; local_only[i]; i++) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ ("Address on localhost only: %s\n", local_only[i]));
+ do_update = true;
+ }
+ }
+
+ if (do_update) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ ("Detected IP addresses change, will perform an update\n"));
+ ret = sdap_dyndns_update_step(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Could not start the update [%d]: %s\n",
+ ret, sss_strerror(ret)));
+ tevent_req_error(req, ret);
+ }
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ ("No DNS update needed, addresses did not change\n"));
+ tevent_req_done(req);
+ return;
+}
+
+static errno_t
+sdap_dyndns_update_step(struct tevent_req *req)
+{
+ errno_t ret;
+ struct sdap_dyndns_update_state *state;
+ const char *servername;
+ struct tevent_req *subreq;
+
+ state = tevent_req_data(req, struct sdap_dyndns_update_state);
+
+ servername = NULL;
+ if (state->use_server_with_nsupdate == true &&
+ state->servername) {
+ servername = state->servername;
+ }
+
+ ret = be_nsupdate_create_msg(state, state->realm, state->dns_zone,
+ servername, state->hostname,
+ state->ttl, state->remove_af,
+ state->addresses,
+ &state->update_msg);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Can't get addresses for DNS update\n"));
+ return ret;
+ }
+
+ /* Fork a child process to perform the DNS update */
+ subreq = be_nsupdate_send(state, state->ev, state->update_msg);
+ if (subreq == NULL) {
+ return EIO;
+ }
+
+ tevent_req_set_callback(subreq, sdap_dyndns_update_done, req);
+ return EOK;
+}
+
+static void
+sdap_dyndns_update_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int child_status;
+ struct tevent_req *req;
+ struct sdap_dyndns_update_state *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_dyndns_update_state);
+
+ ret = be_nsupdate_recv(subreq, &child_status);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ /* If the update didn't succeed, we can retry using the server name */
+ if (state->use_server_with_nsupdate == false && state->servername &&
+ WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) {
+ state->use_server_with_nsupdate = true;
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("nsupdate failed, retrying with server name\n"));
+ ret = sdap_dyndns_update_step(req);
+ if (ret == EOK) {
+ return;
+ }
+ }
+
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+errno_t
+sdap_dyndns_update_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ return EOK;
+}
+
+/* A request to get addresses to update with */
+struct sdap_dyndns_get_addrs_state {
+ struct sdap_id_op* sdap_op;
+ struct sss_iface_addr *addresses;
+};
+
+static void sdap_dyndns_get_addrs_done(struct tevent_req *subreq);
+static errno_t sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state,
+ struct sdap_handle *sh);
+
+static struct tevent_req *
+sdap_dyndns_get_addrs_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *sdap_ctx,
+ const char *iface)
+{
+ errno_t ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sdap_dyndns_get_addrs_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct sdap_dyndns_get_addrs_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (iface) {
+ ret = sss_iface_addr_list_get(state, iface, &state->addresses);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Cannot get list of addresses from interface %s\n", iface));
+ }
+ /* We're done. Just fake an async request completion */
+ goto done;
+ }
+
+ /* Detect DYNDNS address from LDAP connection */
+ state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn_cache);
+ if (!state->sdap_op) {
+ ret = ENOMEM;
+ DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed\n"));
+ goto done;
+ }
+
+ subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
+ if (!subreq) {
+ ret = EIO;
+ DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: [%d](%s)\n",
+ ret, sss_strerror(ret)));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sdap_dyndns_get_addrs_done, req);
+
+ ret = EAGAIN;
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ /* EAGAIN - resolution in progress */
+ return req;
+}
+
+static void
+sdap_dyndns_get_addrs_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int dp_error;
+ struct tevent_req *req;
+ struct sdap_dyndns_get_addrs_state *state;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state);
+
+ ret = sdap_id_op_connect_recv(subreq, &dp_error);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_MINOR_FAILURE, ("No LDAP server is available, "
+ "dynamic DNS update is skipped in offline mode.\n"));
+ ret = ERR_DYNDNS_OFFLINE;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Failed to connect to LDAP server: [%d](%s)\n",
+ ret, sss_strerror(ret)));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sdap_dyndns_add_ldap_conn(state, sdap_id_op_handle(state->sdap_op));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Can't get addresses from LDAP connection\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* Got the address! Done! */
+ tevent_req_done(req);
+}
+
+static errno_t
+sdap_dyndns_add_ldap_conn(struct sdap_dyndns_get_addrs_state *state,
+ struct sdap_handle *sh)
+{
+ int ret;
+ int fd;
+ struct sss_iface_addr *address;
+ struct sockaddr_storage ss;
+ socklen_t ss_len = sizeof(ss);
+
+ if (sh == NULL) {
+ return EINVAL;
+ }
+
+ /* Get the file descriptor for the primary LDAP connection */
+ ret = get_fd_from_ldap(sh->ldap, &fd);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ errno = 0;
+ ret = getsockname(fd, (struct sockaddr *) &ss, &ss_len);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to get socket name\n"));
+ return ret;
+ }
+
+ switch(ss.ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ address = sss_iface_addr_add(state, &state->addresses, &ss);
+ if (address == NULL) {
+ return ENOMEM;
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ ("Connection to LDAP is neither IPv4 nor IPv6\n"));
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static errno_t
+sdap_dyndns_get_addrs_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct sss_iface_addr **_addresses)
+{
+ struct sdap_dyndns_get_addrs_state *state;
+
+ state = tevent_req_data(req, struct sdap_dyndns_get_addrs_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *_addresses = talloc_steal(mem_ctx, state->addresses);
+ return EOK;
+}
diff --git a/src/providers/ldap/sdap_dyndns.h b/src/providers/ldap/sdap_dyndns.h
new file mode 100644
index 00000000..1602938e
--- /dev/null
+++ b/src/providers/ldap/sdap_dyndns.h
@@ -0,0 +1,47 @@
+/*
+ SSSD
+
+ sdap_dyndns.h: LDAP specific dynamic DNS update
+
+ Authors:
+ 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/>.
+*/
+
+#ifndef SDAP_DYNDNS_H_
+#define SDAP_DYNDNS_H_
+
+#include "util/util.h"
+#include "providers/dp_backend.h"
+#include "providers/ldap/ldap_common.h"
+
+struct tevent_req *
+sdap_dyndns_update_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct sdap_id_ctx *sdap_ctx,
+ const char *ifname,
+ const char *hostname,
+ const char *dns_zone,
+ const char *realm,
+ const char *servername,
+ const int ttl,
+ bool check_diff);
+
+errno_t sdap_dyndns_update_recv(struct tevent_req *req);
+
+#endif /* SDAP_DYNDNS_H_ */
diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c
new file mode 100644
index 00000000..fd924043
--- /dev/null
+++ b/src/tests/cmocka/test_dyndns.c
@@ -0,0 +1,339 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ SSSD tests: Dynamic DNS tests
+
+ 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 <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+
+/* In order to access opaque types */
+#include "providers/dp_dyndns.c"
+
+#include "tests/cmocka/common_mock.h"
+#include "src/providers/dp_dyndns.h"
+
+enum mock_nsupdate_states {
+ MOCK_NSUPDATE_OK,
+ MOCK_NSUPDATE_ERR,
+ MOCK_NSUPDATE_TIMEOUT,
+};
+
+struct dyndns_test_ctx {
+ struct sss_test_ctx *tctx;
+
+ enum mock_nsupdate_states state;
+ int child_status;
+ int child_retval;
+};
+
+static struct dyndns_test_ctx *dyndns_test_ctx;
+
+void __wrap_execv(const char *path, char *const argv[])
+{
+ int err;
+
+ switch (dyndns_test_ctx->state) {
+ case MOCK_NSUPDATE_OK:
+ DEBUG(SSSDBG_FUNC_DATA, ("nsupdate success test case\n"));
+ err = 0;
+ break;
+ case MOCK_NSUPDATE_ERR:
+ DEBUG(SSSDBG_FUNC_DATA, ("nsupdate error test case\n"));
+ err = 1;
+ break;
+ case MOCK_NSUPDATE_TIMEOUT:
+ DEBUG(SSSDBG_FUNC_DATA, ("nsupdate timeout test case\n"));
+ err = 2;
+ sleep(3);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, ("unknown test case\n"));
+ err = 255;
+ break;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, ("Child exiting with status %d\n", err));
+ _exit(err);
+}
+
+int __wrap_getifaddrs(struct ifaddrs **_ifap)
+{
+ struct ifaddrs *ifap;
+ struct ifaddrs *ifap_prev = NULL;
+ struct ifaddrs *ifap_head = NULL;
+ char *name;
+ char *straddr;
+
+ while ((name = sss_mock_ptr_type(char *)) != NULL) {
+ straddr = sss_mock_ptr_type(char *);
+ if (straddr == NULL) {
+ errno = EINVAL;
+ goto fail;
+ }
+
+ ifap = talloc_zero(global_talloc_context, struct ifaddrs);
+ if (ifap == NULL) {
+ errno = ENOMEM; /* getifaddrs sets errno, too */
+ goto fail;
+ }
+
+ if (ifap_prev) {
+ ifap_prev->ifa_next = ifap;
+ } else {
+ ifap_head = ifap;
+ }
+ ifap_prev = ifap;
+
+ ifap->ifa_name = talloc_strdup(ifap, name);
+ if (ifap == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ ifap->ifa_addr = (struct sockaddr *) talloc(ifap, struct sockaddr_in);
+ if (ifap->ifa_addr == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+ ((struct sockaddr_in *) ifap->ifa_addr)->sin_family = AF_INET;
+
+ /* convert straddr into ifa_addr */
+ if (inet_pton(AF_INET, straddr,
+ &(((struct sockaddr_in *) ifap->ifa_addr)->sin_addr)) != 1) {
+ goto fail;
+ }
+ }
+
+ *_ifap = ifap_head;
+ return 0;
+
+fail:
+ talloc_free(ifap);
+ return -1;
+}
+
+void __wrap_freeifaddrs(struct ifaddrs *ifap)
+{
+ talloc_free(ifap);
+}
+
+static void dyndns_test_done(struct tevent_req *req)
+{
+ struct dyndns_test_ctx *ctx =
+ tevent_req_callback_data(req, struct dyndns_test_ctx);
+
+ ctx->child_retval = -1;
+ ctx->tctx->error = be_nsupdate_recv(req, &ctx->child_status);
+ talloc_zfree(req);
+
+ ctx->tctx->done = true;
+}
+
+void will_return_getifaddrs(const char *ifname, const char *straddr)
+{
+ will_return(__wrap_getifaddrs, ifname);
+ if (ifname) {
+ will_return(__wrap_getifaddrs, straddr);
+ }
+}
+
+void dyndns_test_get_ifaddr(void **state)
+{
+ errno_t ret;
+ struct sss_iface_addr *addrlist;
+ char straddr[128];
+
+ check_leaks_push(dyndns_test_ctx);
+ will_return_getifaddrs("eth0", "192.168.0.1");
+ will_return_getifaddrs("eth1", "192.168.0.2");
+ will_return_getifaddrs(NULL, NULL); /* sentinel */
+ ret = sss_iface_addr_list_get(dyndns_test_ctx, "eth0", &addrlist);
+ assert_int_equal(ret, EOK);
+
+ /* There must be only one address with the correct value */
+ assert_non_null(addrlist);
+ assert_non_null(addrlist->addr);
+ assert_null(addrlist->next);
+ assert_null(addrlist->prev);
+
+ assert_non_null(inet_ntop(AF_INET,
+ &((struct sockaddr_in *) addrlist->addr)->sin_addr,
+ straddr, INET6_ADDRSTRLEN));
+ assert_string_equal(straddr, "192.168.0.1");
+
+ talloc_free(addrlist);
+ assert_true(check_leaks_pop(dyndns_test_ctx) == true);
+}
+
+void dyndns_test_ok(void **state)
+{
+ struct tevent_req *req;
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(global_talloc_context);
+ assert_non_null(tmp_ctx);
+ check_leaks_push(tmp_ctx);
+
+ dyndns_test_ctx->state = MOCK_NSUPDATE_OK;
+
+ req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev,
+ discard_const("test message"));
+ assert_non_null(req);
+ tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(dyndns_test_ctx->tctx);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ ("Child request returned [%d]: %s\n", ret, strerror(ret)));
+ assert_int_equal(ret, EOK);
+
+ assert_true(WIFEXITED(dyndns_test_ctx->child_status));
+ assert_int_equal(WEXITSTATUS(dyndns_test_ctx->child_status), 0);
+
+ assert_true(check_leaks_pop(tmp_ctx) == true);
+ talloc_free(tmp_ctx);
+}
+
+void dyndns_test_error(void **state)
+{
+ struct tevent_req *req;
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(global_talloc_context);
+ assert_non_null(tmp_ctx);
+ check_leaks_push(tmp_ctx);
+
+ dyndns_test_ctx->state = MOCK_NSUPDATE_ERR;
+
+ req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev,
+ discard_const("test message"));
+ assert_non_null(req);
+ tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx);
+
+ /* Wait until the test finishes with EIO (child error) */
+ ret = test_ev_loop(dyndns_test_ctx->tctx);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ ("Child request returned [%d]: %s\n", ret, strerror(ret)));
+ assert_int_equal(ret, ERR_DYNDNS_FAILED);
+
+ assert_true(WIFEXITED(dyndns_test_ctx->child_status));
+ assert_int_equal(WEXITSTATUS(dyndns_test_ctx->child_status), 1);
+
+ assert_true(check_leaks_pop(tmp_ctx) == true);
+ talloc_free(tmp_ctx);
+}
+
+void dyndns_test_timeout(void **state)
+{
+ struct tevent_req *req;
+ errno_t ret;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(global_talloc_context);
+ assert_non_null(tmp_ctx);
+ check_leaks_push(tmp_ctx);
+
+ dyndns_test_ctx->state = MOCK_NSUPDATE_TIMEOUT;
+
+ req = be_nsupdate_send(tmp_ctx, dyndns_test_ctx->tctx->ev,
+ discard_const("test message"));
+ assert_non_null(req);
+ tevent_req_set_callback(req, dyndns_test_done, dyndns_test_ctx);
+
+ /* Wait until the test finishes with EIO (child error) */
+ ret = test_ev_loop(dyndns_test_ctx->tctx);
+ DEBUG(SSSDBG_TRACE_LIBS,
+ ("Child request returned [%d]: %s\n", ret, strerror(ret)));
+ assert_int_equal(ret, ERR_DYNDNS_TIMEOUT);
+
+ assert_true(check_leaks_pop(tmp_ctx) == true);
+ talloc_free(tmp_ctx);
+}
+
+/* Testsuite setup and teardown */
+void dyndns_test_setup(void **state)
+{
+ assert_true(leak_check_setup());
+ dyndns_test_ctx = talloc_zero(global_talloc_context, struct dyndns_test_ctx);
+ assert_non_null(dyndns_test_ctx);
+
+ dyndns_test_ctx->tctx = create_ev_test_ctx(dyndns_test_ctx);
+ assert_non_null(dyndns_test_ctx->tctx);
+}
+
+void dyndns_test_teardown(void **state)
+{
+ talloc_free(dyndns_test_ctx);
+ assert_true(leak_check_teardown());
+}
+
+int main(int argc, const char *argv[])
+{
+ int rv;
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ POPT_TABLEEND
+ };
+
+ const UnitTest tests[] = {
+ /* Utility functions unit test */
+ unit_test(dyndns_test_get_ifaddr),
+
+ /* Dynamic DNS update unit tests*/
+ unit_test_setup_teardown(dyndns_test_ok,
+ dyndns_test_setup, dyndns_test_teardown),
+ unit_test_setup_teardown(dyndns_test_error,
+ dyndns_test_setup, dyndns_test_teardown),
+ unit_test_setup_teardown(dyndns_test_timeout,
+ dyndns_test_setup, dyndns_test_teardown),
+ };
+
+ /* Set debug level to invalid value so we can deside if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ DEBUG_INIT(debug_level);
+ tests_set_cwd();
+ rv = run_tests(tests);
+
+ return rv;
+}
diff --git a/src/tests/common.h b/src/tests/common.h
index e7fc812c..931e603c 100644
--- a/src/tests/common.h
+++ b/src/tests/common.h
@@ -72,6 +72,8 @@ struct sss_test_conf_param {
const char *value;
};
+struct sss_test_ctx *create_ev_test_ctx(TALLOC_CTX *mem_ctx);
+
struct sss_test_ctx *
create_dom_test_ctx(TALLOC_CTX *mem_ctx,
const char *tests_path,
diff --git a/src/tests/common_dom.c b/src/tests/common_dom.c
index db329787..00e7f5ae 100644
--- a/src/tests/common_dom.c
+++ b/src/tests/common_dom.c
@@ -42,19 +42,12 @@ create_dom_test_ctx(TALLOC_CTX *mem_ctx,
errno_t ret;
char *dompath;
- test_ctx = talloc_zero(mem_ctx, struct sss_test_ctx);
+ test_ctx = create_ev_test_ctx(mem_ctx);
if (test_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed\n"));
goto fail;
}
- /* Create an event context */
- test_ctx->ev = tevent_context_init(test_ctx);
- if (test_ctx->ev == NULL) {
- DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_context_init failed\n"));
- goto fail;
- }
-
conf_db = talloc_asprintf(test_ctx, "%s/%s", tests_path, confdb_path);
if (conf_db == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_asprintf failed\n"));
diff --git a/src/tests/common_tev.c b/src/tests/common_tev.c
index e9db8d05..81b97d33 100644
--- a/src/tests/common_tev.c
+++ b/src/tests/common_tev.c
@@ -26,6 +26,31 @@
#include "tests/common.h"
+struct sss_test_ctx *
+create_ev_test_ctx(TALLOC_CTX *mem_ctx)
+{
+ struct sss_test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(mem_ctx, struct sss_test_ctx);
+ if (test_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed\n"));
+ goto fail;
+ }
+
+ /* Create an event context */
+ test_ctx->ev = tevent_context_init(test_ctx);
+ if (test_ctx->ev == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_context_init failed\n"));
+ goto fail;
+ }
+
+ return test_ctx;
+
+fail:
+ talloc_free(test_ctx);
+ return NULL;
+}
+
struct tevent_req *
test_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, errno_t err)
{
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index b5c1928c..b617f540 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -43,6 +43,9 @@ struct err_string error_to_str[] = {
{ "Host Access Denied" }, /* ERR_ACCESS_DENIED */
{ "SRV record not found" }, /* ERR_SRV_NOT_FOUND */
{ "SRV lookup error" }, /* ERR_SRV_LOOKUP_ERROR */
+ { "Dynamic DNS update failed" }, /* ERR_DYNDNS_FAILED */
+ { "Dynamic DNS update timed out" }, /* ERR_DYNDNS_TIMEOUT */
+ { "Dynamic DNS update not possible while offline" }, /* ERR_DYNDNS_OFFLINE */
};
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 4f7c0086..a602a6ea 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -65,6 +65,9 @@ enum sssd_errors {
ERR_ACCESS_DENIED,
ERR_SRV_NOT_FOUND,
ERR_SRV_LOOKUP_ERROR,
+ ERR_DYNDNS_FAILED,
+ ERR_DYNDNS_TIMEOUT,
+ ERR_DYNDNS_OFFLINE,
ERR_LAST /* ALWAYS LAST */
};