diff options
-rw-r--r-- | Makefile.am | 30 | ||||
-rw-r--r-- | src/providers/dp_dyndns.c | 880 | ||||
-rw-r--r-- | src/providers/dp_dyndns.h | 68 | ||||
-rw-r--r-- | src/providers/ipa/ipa_common.h | 2 | ||||
-rw-r--r-- | src/providers/ipa/ipa_dyndns.c | 1237 | ||||
-rw-r--r-- | src/providers/ldap/sdap_dyndns.c | 498 | ||||
-rw-r--r-- | src/providers/ldap/sdap_dyndns.h | 47 | ||||
-rw-r--r-- | src/tests/cmocka/test_dyndns.c | 339 | ||||
-rw-r--r-- | src/tests/common.h | 2 | ||||
-rw-r--r-- | src/tests/common_dom.c | 9 | ||||
-rw-r--r-- | src/tests/common_tev.c | 25 | ||||
-rw-r--r-- | src/util/util_errors.c | 3 | ||||
-rw-r--r-- | src/util/util_errors.h | 3 |
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 */ }; |