diff options
Diffstat (limited to 'src/providers/ldap/sdap_dyndns.c')
-rw-r--r-- | src/providers/ldap/sdap_dyndns.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_dyndns.c b/src/providers/ldap/sdap_dyndns.c new file mode 100644 index 0000000..3535fb4 --- /dev/null +++ b/src/providers/ldap/sdap_dyndns.c @@ -0,0 +1,713 @@ +/* + 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/backend.h" +#include "providers/be_dyndns.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_dyndns.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; + struct dp_option *opts; + + const char *hostname; + const char *realm; + const char *servername; + int ttl; + + struct sss_iface_addr *addresses; + struct sss_iface_addr *dns_addrlist; + uint8_t remove_af; + + bool update_per_family; + bool update_ptr; + bool check_diff; + enum be_nsupdate_auth auth_type; + enum be_nsupdate_auth auth_ptr_type; + bool fallback_mode; + char *update_msg; +}; + +static void sdap_dyndns_update_addrs_done(struct tevent_req *subreq); +static void sdap_dyndns_dns_addrs_done(struct tevent_req *subreq); +static errno_t sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, + bool *_do_update); +static errno_t sdap_dyndns_update_step(struct tevent_req *req); +static errno_t sdap_dyndns_update_ptr_step(struct tevent_req *req); +static void sdap_dyndns_update_done(struct tevent_req *subreq); +static void sdap_dyndns_update_ptr_done(struct tevent_req *subreq); + +static bool should_retry(int nsupdate_ret, int child_status) +{ + if ((WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) + || nsupdate_ret == ERR_DYNDNS_TIMEOUT) { + return true; + } + + return false; +} + +struct tevent_req * +sdap_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct dp_option *opts, + struct sdap_id_ctx *sdap_ctx, + enum be_nsupdate_auth auth_type, + enum be_nsupdate_auth auth_ptr_type, + const char *ifname, + const char *hostname, + const char *realm, + const int ttl, + bool check_diff) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sdap_dyndns_update_state *state; + const char *conf_servername; + + req = tevent_req_create(mem_ctx, &state, struct sdap_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->check_diff = check_diff; + state->update_per_family = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PER_FAMILY); + state->update_ptr = dp_opt_get_bool(opts, DP_OPT_DYNDNS_UPDATE_PTR); + state->hostname = hostname; + state->realm = realm; + state->servername = NULL; + state->fallback_mode = false; + state->ttl = ttl; + state->be_res = be_ctx->be_res; + state->ev = ev; + state->opts = opts; + state->auth_type = auth_type; + state->auth_ptr_type = auth_ptr_type; + + /* fallback servername is overridden by user option */ + conf_servername = dp_opt_get_string(opts, DP_OPT_DYNDNS_SERVER); + if (conf_servername != NULL) { + state->servername = conf_servername; + } + + 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); + talloc_zfree(subreq); + 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 || state->update_ptr) { + /* Check if we need the update at all. In case we are updating the PTR + * records as well, we need to know the old addresses to be able to + * reliably delete the PTR records */ + 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_dns_addrs_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_dns_addrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct sdap_dyndns_update_state *state; + errno_t ret; + 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, &state->dns_addrlist, NULL); + 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; + } + + if (state->check_diff) { + ret = sdap_dyndns_addrs_diff(state, &do_update); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check the diff between DNS " + "and current addresses [%d]: %s\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (do_update == false) { + DEBUG(SSSDBG_TRACE_FUNC, + "No DNS update needed, addresses did not change\n"); + tevent_req_done(req); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, + "Detected IP addresses change, will perform an update\n"); + } + + /* Either we needed the addresses for updating PTR records only or + * the addresses have changed (or both) */ + 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; +} + +static errno_t +sdap_dyndns_addrs_diff(struct sdap_dyndns_update_state *state, bool *_do_update) +{ + errno_t ret; + int i; + char **str_dnslist = NULL, **str_local_list = NULL; + char **dns_only = NULL, **local_only = NULL; + bool do_update = false; + + ret = sss_iface_addr_list_as_str_list(state, + state->dns_addrlist, &str_dnslist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting DNS IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = sss_iface_addr_list_as_str_list(state, + state->addresses, &str_local_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Converting local IP addresses to strings failed: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + /* 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)); + return ret; + } + + 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; + } + } + + *_do_update = do_update; + return EOK; +} + +static errno_t +sdap_dyndns_update_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_fwd_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &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->auth_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + 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->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, + "nsupdate failed, retrying.\n"); + ret = sdap_dyndns_update_step(req); + if (ret == EOK) { + return; + } + } + } + + if (state->update_ptr == false) { + DEBUG(SSSDBG_TRACE_FUNC, "No PTR update requested, done\n"); + tevent_req_done(req); + return; + } + + talloc_free(state->update_msg); + + ret = sdap_dyndns_update_ptr_step(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + /* Execution will resume in sdap_dyndns_update_ptr_done */ +} + +static errno_t +sdap_dyndns_update_ptr_step(struct tevent_req *req) +{ + errno_t ret; + struct sdap_dyndns_update_state *state; + const char *servername; + const char *realm; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct sdap_dyndns_update_state); + + servername = NULL; + realm = NULL; + if (state->fallback_mode == true) { + servername = state->servername; + realm = state->realm; + } + + ret = be_nsupdate_create_ptr_msg(state, realm, servername, + state->hostname, + state->ttl, state->remove_af, + state->addresses, + state->update_per_family, + &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->auth_ptr_type, + state->update_msg, + dp_opt_get_bool(state->opts, + DP_OPT_DYNDNS_FORCE_TCP)); + if (subreq == NULL) { + return EIO; + } + + tevent_req_set_callback(subreq, sdap_dyndns_update_ptr_done, req); + return EOK; +} + +static void +sdap_dyndns_update_ptr_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->fallback_mode == false + && should_retry(ret, child_status)) { + state->fallback_mode = true; + DEBUG(SSSDBG_MINOR_FAILURE, "nsupdate failed, retrying\n"); + ret = sdap_dyndns_update_ptr_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 errno_t get_ifaces_addrs(TALLOC_CTX *mem_ctx, + const char *iface, + struct sss_iface_addr **_result) +{ + struct sss_iface_addr *result_addrs = NULL; + struct sss_iface_addr *intf_addrs; + TALLOC_CTX *tmp_ctx; + char **list_of_intfs; + int num_of_intfs; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, iface, ',', true, true, &list_of_intfs, + &num_of_intfs); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Parsing names of interfaces failed - %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; i < num_of_intfs; i++) { + ret = sss_iface_addr_list_get(tmp_ctx, list_of_intfs[i], &intf_addrs); + if (ret == EOK) { + if (result_addrs != NULL) { + /* If there is already an existing list, head of this existing + * list will be considered as parent talloc context for the + * new list. + */ + talloc_steal(result_addrs, intf_addrs); + } + sss_iface_addr_concatenate(&result_addrs, intf_addrs); + } else if (ret == ENOENT) { + /* non-critical failure */ + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot get interface %s or there are no addresses " + "bind to it.\n", list_of_intfs[i]); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get list of addresses from interface %s - %d:[%s]\n", + list_of_intfs[i], ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + *_result = talloc_steal(mem_ctx, result_addrs); + +done: + talloc_free(tmp_ctx); + return ret; +} + +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 = get_ifaces_addrs(state, iface, &state->addresses); + if (ret != EOK || state->addresses == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "get_ifaces_addrs() failed: %d:[%s]\n", + ret, sss_strerror(ret)); + } + /* 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->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 sockaddr_storage ss = {0}; + 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; + } + + if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Connection to LDAP is neither IPv4 nor IPv6\n"); + return EIO; + } + + ret = sss_get_dualstack_addresses(state, (struct sockaddr *) &ss, + &state->addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_get_dualstack_addresses failed: %d:[%s]\n", + ret, sss_strerror(ret)); + return ret; + } + + 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; +} |