From 9cb46bc62f22e0104f1b41a423b014c281ef5fc2 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 26 Mar 2013 16:49:26 +0100 Subject: Refactor dynamic DNS updates Provides two new layers instead of the previous IPA specific layer: 1) dp_dyndns.c -- a very generic dyndns layer on the DP level. Its purpose it to make it possible for any back end to use dynamic DNS updates. 2) sdap_dyndns.c -- a wrapper around dp_dyndns.c that utilizes some LDAP-specific features like autodetecting the address from the LDAP connection. Also converts the dyndns code to new specific error codes. --- src/providers/dp_dyndns.c | 880 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 880 insertions(+) create mode 100644 src/providers/dp_dyndns.c (limited to 'src/providers/dp_dyndns.c') 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 + Jakub Hrozek + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#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; +} -- cgit