From 28e137ab41b348052a676c99bc6e99385cd59c3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Tue, 9 Apr 2013 13:16:23 +0200
Subject: [PATCH] DNS sites support - add IPA SRV plugin

https://fedorahosted.org/sssd/ticket/1032
---
 Makefile.am                             |   2 +
 src/config/SSSDConfig/__init__.py.in    |   1 +
 src/config/etc/sssd.api.d/sssd-ipa.conf |   1 +
 src/man/sssd-ipa.5.xml                  |  21 +++
 src/providers/ipa/ipa_common.h          |   1 +
 src/providers/ipa/ipa_init.c            |  29 ++-
 src/providers/ipa/ipa_opts.h            |   1 +
 src/providers/ipa/ipa_srv.c             | 300 ++++++++++++++++++++++++++++++++
 src/providers/ipa/ipa_srv.h             |  47 +++++
 9 files changed, 398 insertions(+), 5 deletions(-)
 create mode 100644 src/providers/ipa/ipa_srv.c
 create mode 100644 src/providers/ipa/ipa_srv.h

diff --git a/Makefile.am b/Makefile.am
index dc9a7be59f41d4b667495d5ec89a3935e612ca33..01114aae9ff51cc041638a7b0567a39b4f07826a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -463,6 +463,7 @@ dist_noinst_HEADERS = \
     src/providers/ipa/ipa_id.h \
     src/providers/ipa/ipa_hostid.h \
     src/providers/ipa/ipa_opts.h \
+    src/providers/ipa/ipa_srv.h \
     src/providers/proxy/proxy.h \
     src/tools/tools_util.h \
     src/tools/sss_sync_ops.h \
@@ -1485,6 +1486,7 @@ libsss_ipa_la_SOURCES = \
     src/providers/ipa/ipa_selinux.c \
     src/providers/ipa/ipa_selinux_maps.c \
     src/providers/ipa/ipa_selinux_common.c \
+    src/providers/ipa/ipa_srv.c \
     src/util/user_info_msg.c \
     src/util/find_uid.c \
     src/util/sss_ldap.c \
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index a1f223956b2d1a009c57ea5834e4d53c41d8c055..1f997f23813d3e6e3f7360ae8d9e0b8aea391345 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -142,6 +142,7 @@ option_strings = {
     'ipa_automount_location' : _("The automounter location this IPA client is using"),
     'ipa_master_domain_search_base': _("Search base for object containing info about IPA domain"),
     'ipa_ranges_search_base': _("Search base for objects containing info about ID ranges"),
+    'ipa_enable_dns_sites': _("Enable DNS sites - location based service discovery"),
 
     # [provider/ad]
     'ad_domain' : _('Active Directory domain'),
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
index a165bc23afdfb6e691acddbb07c74d4e4672bb14..04855d6cece3876e4fa191ae02747b6d88048f8d 100644
--- a/src/config/etc/sssd.api.d/sssd-ipa.conf
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -10,6 +10,7 @@ ipa_hbac_search_base = str, None, false
 ipa_host_search_base = str, None, false
 ipa_master_domain_search_base = str, None, false
 ipa_ranges_search_base = str, None, false
+ipa_enable_dns_sites = bool, None, false
 ldap_uri = str, None, false
 ldap_backup_uri = str, None, false
 ldap_search_base = str, None, false
diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml
index c4227da23c3c4adafc529919d930a2ea02796f82..758e7999370a5ba96afb444738af9fdbea357ca8 100644
--- a/src/man/sssd-ipa.5.xml
+++ b/src/man/sssd-ipa.5.xml
@@ -158,6 +158,27 @@
                         </para>
                     </listitem>
                 </varlistentry>
+                
+                <varlistentry>
+                    <term>ipa_enable_dns_sites (boolean)</term>
+                    <listitem>
+                        <para>
+                            Enables/disables DNS sites - location based
+                            service discovery.
+                        </para>
+                        <para>
+                            If true and _srv_ is present in ipa_server,
+                            SSSD will search for SRV records in
+                            (1) _location.hostname.example.com, (2) example.com.
+                            If (1) exists, it is expected to contain primary
+                            servers and (2) is treated as backup servers. If (1)
+                            does not exist, (2) is treated as primary servers.
+                        </para>
+                        <para>
+                            Default: true
+                        </para>
+                    </listitem>
+                </varlistentry>
 
                 <varlistentry>
                     <term>ipa_hbac_search_base (string)</term>
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index e3915bebc21d88459cf5574264b901e6bd3657ff..ae1c91731e9637c06dad555d1a87ef2e0cf53184 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -52,6 +52,7 @@ enum ipa_basic_opt {
     IPA_HBAC_SUPPORT_SRCHOST,
     IPA_AUTOMOUNT_LOCATION,
     IPA_RANGES_SEARCH_BASE,
+    IPA_ENABLE_DNS_SITES,
 
     IPA_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
index 7d97dc0c2b02c4f5f22ccdce47a2381bb8158333..b65a6cea1ddd05272a8ae522be5c8b070a93ac1e 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -39,6 +39,7 @@
 #include "providers/ipa/ipa_selinux.h"
 #include "providers/ldap/sdap_access.h"
 #include "providers/ipa/ipa_subdomains.h"
+#include "providers/ipa/ipa_srv.h"
 
 struct ipa_options *ipa_options = NULL;
 
@@ -111,6 +112,8 @@ int sssm_ipa_id_init(struct be_ctx *bectx,
     struct sdap_id_ctx *sdap_ctx;
     struct stat stat_buf;
     const char *hostname;
+    const char *ipa_domain;
+    struct ipa_srv_plugin_ctx *srv_ctx;
     errno_t err;
     int ret;
 
@@ -210,11 +213,27 @@ int sssm_ipa_id_init(struct be_ctx *bectx,
 
     /* setup SRV lookup plugin */
     hostname = dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME);
-    ret = be_fo_set_standard_srv_lookup_plugin(bectx, hostname);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to set SRV lookup plugin "
-                                    "[%d]: %s\n", ret, strerror(ret)));
-        goto done;
+    if (dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES)) {
+        /* use IPA plugin */
+        ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN);
+        srv_ctx = ipa_srv_plugin_ctx_init(bectx, bectx->be_res->resolv,
+                                          hostname, ipa_domain);
+        if (srv_ctx == NULL) {
+            DEBUG(SSSDBG_FATAL_FAILURE, ("Out of memory?\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+
+        be_fo_set_srv_lookup_plugin(bectx, ipa_srv_plugin_send,
+                                    ipa_srv_plugin_recv, srv_ctx, "IPA");
+    } else {
+        /* fall back to standard plugin */
+        ret = be_fo_set_dns_srv_lookup_plugin(bectx, hostname);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to set SRV lookup plugin "
+                                        "[%d]: %s\n", ret, strerror(ret)));
+            goto done;
+        }
     }
 
     *ops = &ipa_id_ops;
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 970d05b3921463f33c860f70c2a5b75f6b5f4e7d..f479d3d0abbc11cf42216312765ac61e06afe2ab 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -50,6 +50,7 @@ struct dp_option ipa_basic_opts[] = {
     { "ipa_hbac_support_srchost", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "ipa_automount_location", DP_OPT_STRING, { "default" }, NULL_STRING },
     { "ipa_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ipa_enable_dns_sites", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ipa/ipa_srv.c b/src/providers/ipa/ipa_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..3854bf94b030521634d727e34d433fdb6fa9ebec
--- /dev/null
+++ b/src/providers/ipa/ipa_srv.c
@@ -0,0 +1,300 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@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 <string.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "util/util.h"
+#include "resolv/async_resolv.h"
+#include "providers/fail_over_srv.h"
+
+#define IPA_DNS_LOCATION "_location"
+
+struct ipa_srv_plugin_ctx {
+    struct resolv_ctx *resolv_ctx;
+    const char *hostname;
+    const char *ipa_domain;
+};
+
+struct ipa_srv_plugin_ctx *
+ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx,
+                        struct resolv_ctx *resolv_ctx,
+                        const char *hostname,
+                        const char *ipa_domain)
+{
+    struct ipa_srv_plugin_ctx *ctx = NULL;
+
+    ctx = talloc_zero(mem_ctx, struct ipa_srv_plugin_ctx);
+    if (ctx == NULL) {
+        return NULL;
+    }
+
+    ctx->resolv_ctx = resolv_ctx;
+
+    ctx->hostname = talloc_strdup(ctx, hostname);
+    if (ctx->hostname == NULL) {
+        goto fail;
+    }
+
+    ctx->ipa_domain = talloc_strdup(ctx, ipa_domain);
+    if (ctx->ipa_domain == NULL) {
+        goto fail;
+    }
+
+    return ctx;
+
+fail:
+    talloc_free(ctx);
+    return NULL;
+}
+
+struct ipa_srv_plugin_state {
+    struct tevent_context *ev;
+    struct ipa_srv_plugin_ctx *ctx;
+    const char *service;
+    const char *protocol;
+    const char *discovery_domain;
+
+    char *dns_domain;
+    struct fo_server_info *primary_servers;
+    size_t num_primary_servers;
+    struct fo_server_info *backup_servers;
+    size_t num_backup_servers;
+};
+
+static void ipa_srv_plugin_primary_done(struct tevent_req *subreq);
+static void ipa_srv_plugin_backup_done(struct tevent_req *subreq);
+
+/* If IPA server supports sites, we will use
+ * _locations.hostname.discovery_domain for primary servers and
+ * discovery_domain for backup servers. If the server does not support sites or
+ * client's SRV record is not found, we will use the latter for primary
+ * servers, setting backup servers to NULL */
+struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       const char *service,
+                                       const char *protocol,
+                                       const char *discovery_domain,
+                                       void *pvt)
+{
+    struct ipa_srv_plugin_state *state = NULL;
+    struct ipa_srv_plugin_ctx *ctx = NULL;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    const char **domains = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct ipa_srv_plugin_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_req_create() failed\n"));
+        return NULL;
+    }
+
+    ctx = talloc_get_type(pvt, struct ipa_srv_plugin_ctx);
+    if (ctx == NULL) {
+        ret = EINVAL;
+        goto immediately;
+    }
+
+    state->ev = ev;
+    state->ctx = ctx;
+    state->service = service;
+    state->protocol = protocol;
+    if (state->protocol == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    if (discovery_domain != NULL) {
+        state->discovery_domain = discovery_domain;
+    } else {
+        state->discovery_domain = ctx->ipa_domain;
+    }
+    if (state->discovery_domain == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, ("Looking up primary servers\n"));
+
+    domains = talloc_zero_array(state, const char *, 3);
+    if (domains == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    if (strchr(ctx->hostname, '.') == NULL) {
+        /* not FQDN, append domain name */
+        domains[0] = talloc_asprintf(domains, IPA_DNS_LOCATION ".%s.%s",
+                                     ctx->hostname, state->discovery_domain);
+    } else {
+        domains[0] = talloc_asprintf(domains, IPA_DNS_LOCATION ".%s",
+                                     ctx->hostname);
+    }
+    if (domains[0] == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+    domains[1] = state->discovery_domain;
+
+    subreq = fo_discover_srv_send(state, ev, ctx->resolv_ctx,
+                                  state->service, state->protocol, domains);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    tevent_req_set_callback(subreq, ipa_srv_plugin_primary_done, req);
+
+    return req;
+
+immediately:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+
+    return req;
+}
+
+static void ipa_srv_plugin_primary_done(struct tevent_req *subreq)
+{
+    struct ipa_srv_plugin_state *state = NULL;
+    struct tevent_req *req = NULL;
+    const char **domains = NULL;
+    errno_t ret;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct ipa_srv_plugin_state);
+
+    ret = fo_discover_srv_recv(state, subreq,
+                               &state->dns_domain,
+                               &state->primary_servers,
+                               &state->num_primary_servers);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (strcmp(state->dns_domain, state->discovery_domain) == 0) {
+        /* IPA server does not support sites or this host is in default site */
+        state->backup_servers = NULL;
+        state->num_backup_servers = 0;
+
+        ret = EOK;
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, ("Looking up backup servers\n"));
+
+    domains = talloc_zero_array(state, const char *, 3);
+    if (domains == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    domains[0] = state->discovery_domain;
+
+    subreq = fo_discover_srv_send(state, state->ev, state->ctx->resolv_ctx,
+                                  state->service, state->protocol, domains);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    tevent_req_set_callback(subreq, ipa_srv_plugin_backup_done, req);
+
+    ret = EAGAIN;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+    }
+
+    return;
+}
+
+static void ipa_srv_plugin_backup_done(struct tevent_req *subreq)
+{
+    struct ipa_srv_plugin_state *state = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct ipa_srv_plugin_state);
+
+    ret = fo_discover_srv_recv(state, subreq, NULL,
+                               &state->backup_servers,
+                               &state->num_backup_servers);
+    talloc_zfree(subreq);
+    if (ret == ERR_SRV_NOT_FOUND || ret == ERR_SRV_LOOKUP_ERROR) {
+        /* we have successfully fetched primary servers, so we will
+         * finish the request normally */
+        DEBUG(SSSDBG_MINOR_FAILURE, ("Unable to retrieve backup servers "
+                                     "[%d]: %s\n", ret, sss_strerror(ret)));
+        ret = EOK;
+    }
+
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx,
+                            struct tevent_req *req,
+                            char **_dns_domain,
+                            struct fo_server_info **_primary_servers,
+                            size_t *_num_primary_servers,
+                            struct fo_server_info **_backup_servers,
+                            size_t *_num_backup_servers)
+{
+    struct ipa_srv_plugin_state *state = NULL;
+    state = tevent_req_data(req, struct ipa_srv_plugin_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_primary_servers) {
+        *_primary_servers = talloc_steal(mem_ctx, state->primary_servers);
+    }
+
+    if (_num_primary_servers) {
+        *_num_primary_servers = state->num_primary_servers;
+    }
+
+    if (_backup_servers) {
+        *_backup_servers = talloc_steal(mem_ctx, state->backup_servers);
+    }
+
+    if (_num_backup_servers) {
+        *_num_backup_servers = state->num_backup_servers;
+    }
+
+    if (_dns_domain) {
+        *_dns_domain = talloc_steal(mem_ctx, state->dns_domain);
+    }
+
+
+    return EOK;
+}
diff --git a/src/providers/ipa/ipa_srv.h b/src/providers/ipa/ipa_srv.h
new file mode 100644
index 0000000000000000000000000000000000000000..1bfd2eeaebec6ae9d2826aa76060746a32a634fc
--- /dev/null
+++ b/src/providers/ipa/ipa_srv.h
@@ -0,0 +1,47 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@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 __IPA_SRV_H__
+#define __IPA_SRV_H__
+
+struct ipa_srv_plugin_ctx;
+
+struct ipa_srv_plugin_ctx *
+ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx,
+                        struct resolv_ctx *resolv_ctx,
+                        const char *hostname,
+                        const char *ipa_domain);
+
+struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       const char *service,
+                                       const char *protocol,
+                                       const char *discovery_domain,
+                                       void *pvt);
+
+errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx,
+                            struct tevent_req *req,
+                            char **_dns_domain,
+                            struct fo_server_info **_primary_servers,
+                            size_t *_num_primary_servers,
+                            struct fo_server_info **_backup_servers,
+                            size_t *_num_backup_servers);
+
+#endif /* __IPA_SRV_H__ */
-- 
1.7.11.7

