/* SSSD ipa_dyndns.c Authors: Stephen Gallagher Copyright (C) 2010 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 "providers/ipa/ipa_common.h" #include "providers/ipa/ipa_dyndns.h" #include "providers/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 struct ipa_ipaddress { struct ipa_ipaddress *next; struct ipa_ipaddress *prev; struct sockaddr *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; }; static struct tevent_req * ipa_dyndns_update_send(struct ipa_options *ctx); static void ipa_dyndns_update_done(struct tevent_req *req); 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")); return; } tevent_req_set_callback(req, ipa_dyndns_update_done, req); } 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) { int ret; char *iface; struct ipa_dyndns_ctx *state; struct ifaddrs *ifaces; struct ifaddrs *ifa; struct ipa_ipaddress *address; struct tevent_req *req, *subreq; 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; 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) { /* Add this address to the IP address list */ address = talloc_zero(state, struct ipa_ipaddress); if (!address) { goto failed; } address->addr = talloc_memdup(address, ifa->ifa_addr, sizeof(struct sockaddr)); 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->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: 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 sa; socklen_t sa_len = sizeof(sa); 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; } ret = getsockname(fd, &sa, &sa_len); if (ret == -1) { DEBUG(0,("Failed to get socket name\n")); return errno; } switch(sa.sa_family) { case AF_INET: case AF_INET6: address = talloc(state, struct ipa_ipaddress); if (!address) { return ENOMEM; } address->addr = talloc_memdup(address, &sa, sizeof(struct sockaddr)); 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 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; /* 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; } /* In the future, it might be best to check that an update * needs to be run before running it, but this is such a * rare event that it's probably fine to just run an update * every time we come online. */ subreq = ipa_dyndns_gss_tsig_update_send(state); if(subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, ipa_dyndns_gss_tsig_update_done, req); 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, 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; req = tevent_req_create(ctx, &state, struct ipa_nsupdate_ctx); 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->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, bool use_server_with_nsupdate) { int ret, i; char *servername = NULL; char *realm; char *zone; char ip_addr[INET6_ADDRSTRLEN]; const char *ip; struct ipa_ipaddress *new_record; realm = dp_opt_get_string(ctx->dyndns_ctx->ipa_ctx->basic, IPA_KRB5_REALM); if (!realm) { return EIO; } zone = dp_opt_get_string(ctx->dyndns_ctx->ipa_ctx->basic, IPA_DOMAIN); if (!zone) { return EIO; } /* The DNS zone for IPA is the lower-case * version of hte IPA domain */ for(i = 0; zone[i] != '\0'; i++) { zone[i] = tolower(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")); return EIO; } servername = ctx->dyndns_ctx->ipa_ctx->service->sdap->uri + 7; if (!servername) { return EIO; } DEBUG(9, ("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\nrealm %s\nzone %s.\n", servername, realm, zone); } else { DEBUG(9, ("Creating update message for realm [%s] and zone [%s].\n", realm, zone)); /* Add the realm and zone headers */ ctx->update_msg = talloc_asprintf(ctx, "realm %s\nzone %s.\n", realm, zone); } if (ctx->update_msg == NULL) { ret = ENOMEM; goto done; } /* Remove any existing entries */ ctx->update_msg = talloc_asprintf_append(ctx->update_msg, "update delete %s. in A\nsend\n" "update delete %s. in AAAA\nsend\n", ctx->dyndns_ctx->hostname, 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->sa_family) { case AF_INET: ip = inet_ntop(new_record->addr->sa_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->sa_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. 86400 in %s %s\n", ctx->dyndns_ctx->hostname, new_record->addr->sa_family == AF_INET ? "A" : "AAAA", ip_addr); if (ctx->update_msg == NULL) { ret = ENOMEM; goto done; } } ctx->update_msg = talloc_asprintf_append(ctx->update_msg, "send\n"); if (ctx->update_msg == NULL) { ret = ENOMEM; goto done; } DEBUG(6, (" -- Begin nsupdate message -- \n%s", ctx->update_msg)); DEBUG(6, (" -- End nsupdate message -- \n")); ret = EOK; done: 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; ret = execv(NSUPDATE_PATH, args); if(ret == -1) { err = errno; DEBUG(1, ("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->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->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->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; } 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; } static void fork_nsupdate_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); ret = ipa_dyndns_child_recv(subreq, &state->child_status); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } static int fork_nsupdate_recv(struct tevent_req *req, int *child_status) { 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,("Updated DNS entry\n")); }