Blame SOURCES/0034-AD-Add-Global-Catalog-usability-check-in-subdomain-c.patch

ca1eb8
From 7f9567ba8d62536c4aeb68897316781e82116c21 Mon Sep 17 00:00:00 2001
ca1eb8
From: Jakub Hrozek <jhrozek@redhat.com>
ca1eb8
Date: Wed, 4 Jul 2018 13:14:40 +0200
ca1eb8
Subject: [PATCH] AD: Add Global Catalog usability check in subdomain code by
ca1eb8
 looking at the schema
ca1eb8
MIME-Version: 1.0
ca1eb8
Content-Type: text/plain; charset=UTF-8
ca1eb8
Content-Transfer-Encoding: 8bit
ca1eb8
ca1eb8
Addsa a new tevent request which checks for the presence of uidNumber
ca1eb8
and gidNumber under the schema naming context, which is typically
ca1eb8
cn=schema,cn=configuration,$BASEDN. For both objects representing each of
ca1eb8
the attributes, the isMemberOfPartialAttributeSet attribute is requested. If
ca1eb8
this attribute is set to TRUE, then the attribute corresponding to this
ca1eb8
schema object had been replicated to the Global Catalog.
ca1eb8
ca1eb8
Because the isMemberOfPartialAttributeSet is not replicated to the GC
ca1eb8
itself, we use the LDAP connection for the search.
ca1eb8
ca1eb8
Related:
ca1eb8
https://pagure.io/SSSD/sssd/issue/3755
ca1eb8
ca1eb8
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
ca1eb8
(cherry picked from commit ba96e7b839b875946f03787a3a57f259230a0fef)
ca1eb8
---
ca1eb8
 src/providers/ad/ad_subdomains.c | 308 +++++++++++++++++++++++++++++--
ca1eb8
 1 file changed, 288 insertions(+), 20 deletions(-)
ca1eb8
ca1eb8
diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c
ca1eb8
index 84886e920b37f8803d85ce0903b74e6c809a8904..549c2c1f76d4bbccdf03c6ab619fba5f9186358f 100644
ca1eb8
--- a/src/providers/ad/ad_subdomains.c
ca1eb8
+++ b/src/providers/ad/ad_subdomains.c
ca1eb8
@@ -54,9 +54,39 @@
ca1eb8
 #define SLAVE_DOMAIN_FILTER      "(&"SLAVE_DOMAIN_FILTER_BASE")"
ca1eb8
 #define FOREST_ROOT_FILTER_FMT   "(&"SLAVE_DOMAIN_FILTER_BASE"(cn=%s))"
ca1eb8
 
ca1eb8
+/* Attributes of schema objects. See e.g.
ca1eb8
+ * https://docs.microsoft.com/en-us/windows/desktop/AD/characteristics-of-attributes
ca1eb8
+ * for more details
ca1eb8
+ */
ca1eb8
+#define AD_SCHEMA_AT_OC         "attributeSchema"
ca1eb8
+#define AD_AT_SCHEMA_NAME       "cn"
ca1eb8
+#define AD_AT_SCHEMA_IS_REPL    "isMemberOfPartialAttributeSet"
ca1eb8
+
ca1eb8
 /* do not refresh more often than every 5 seconds for now */
ca1eb8
 #define AD_SUBDOMAIN_REFRESH_LIMIT 5
ca1eb8
 
ca1eb8
+static void
ca1eb8
+ad_disable_gc(struct ad_options *ad_options)
ca1eb8
+{
ca1eb8
+    errno_t ret;
ca1eb8
+
ca1eb8
+    if (dp_opt_get_bool(ad_options->basic, AD_ENABLE_GC) == false) {
ca1eb8
+        return;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    DEBUG(SSSDBG_IMPORTANT_INFO, "POSIX attributes were requested "
ca1eb8
+          "but are not present on the server side. Global Catalog "
ca1eb8
+          "lookups will be disabled\n");
ca1eb8
+
ca1eb8
+    ret = dp_opt_set_bool(ad_options->basic,
ca1eb8
+                          AD_ENABLE_GC, false);
ca1eb8
+    if (ret != EOK) {
ca1eb8
+        DEBUG(SSSDBG_MINOR_FAILURE,
ca1eb8
+                "Could not turn off GC support\n");
ca1eb8
+        /* Not fatal */
ca1eb8
+    }
ca1eb8
+}
ca1eb8
+
ca1eb8
 static struct sss_domain_info *
ca1eb8
 ads_get_root_domain(struct be_ctx *be_ctx, struct sysdb_attrs *attrs)
ca1eb8
 {
ca1eb8
@@ -1261,6 +1291,212 @@ static errno_t ad_get_root_domain_recv(TALLOC_CTX *mem_ctx,
ca1eb8
     return EOK;
ca1eb8
 }
ca1eb8
 
ca1eb8
+static void ad_check_gc_usability_search_done(struct tevent_req *subreq);
ca1eb8
+
ca1eb8
+struct ad_check_gc_usability_state {
ca1eb8
+    struct sdap_options *sdap_opts;
ca1eb8
+
ca1eb8
+    const char *attrs[3];
ca1eb8
+
ca1eb8
+    bool is_gc_usable;
ca1eb8
+};
ca1eb8
+
ca1eb8
+static struct tevent_req *
ca1eb8
+ad_check_gc_usability_send(TALLOC_CTX *mem_ctx,
ca1eb8
+                           struct tevent_context *ev,
ca1eb8
+                           struct ad_options *ad_options,
ca1eb8
+                           struct sdap_options *sdap_opts,
ca1eb8
+                           struct sdap_id_op *op,
ca1eb8
+                           const char *domain_name,
ca1eb8
+                           const char *domain_sid)
ca1eb8
+{
ca1eb8
+    struct ad_check_gc_usability_state *state = NULL;
ca1eb8
+    struct tevent_req *req = NULL;
ca1eb8
+    struct tevent_req *subreq = NULL;
ca1eb8
+    const char *filter = NULL;
ca1eb8
+    errno_t ret;
ca1eb8
+    bool uses_id_mapping;
ca1eb8
+
ca1eb8
+    req = tevent_req_create(mem_ctx, &state,
ca1eb8
+                            struct ad_check_gc_usability_state);
ca1eb8
+    if (req == NULL) {
ca1eb8
+        return NULL;
ca1eb8
+    }
ca1eb8
+    state->sdap_opts = sdap_opts;
ca1eb8
+    state->is_gc_usable = false;
ca1eb8
+
ca1eb8
+    if (dp_opt_get_bool(ad_options->basic, AD_ENABLE_GC) == false) {
ca1eb8
+        DEBUG(SSSDBG_TRACE_FUNC, "GC explicitly disabled\n");
ca1eb8
+        state->is_gc_usable = false;
ca1eb8
+        ret = EOK;
ca1eb8
+        goto immediately;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    uses_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
ca1eb8
+                                                        sdap_opts->idmap_ctx,
ca1eb8
+                                                        domain_name,
ca1eb8
+                                                        domain_sid);
ca1eb8
+    if (uses_id_mapping == true) {
ca1eb8
+        DEBUG(SSSDBG_TRACE_FUNC, "GC always usable while ID mapping\n");
ca1eb8
+        state->is_gc_usable = true;
ca1eb8
+        ret = EOK;
ca1eb8
+        goto immediately;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    /* The schema partition is replicated across all DCs in the forest, so
ca1eb8
+     * it's safe to use the baseDN even if e.g. joined to a child domain
ca1eb8
+     * even though the base DN "looks" like a part of the forest root
ca1eb8
+     * tree. On the other hand, it doesn't make sense to guess the value
ca1eb8
+     * if we can't detect it from the rootDSE.
ca1eb8
+     */
ca1eb8
+    if (state->sdap_opts->schema_basedn == NULL) {
ca1eb8
+        DEBUG(SSSDBG_TRACE_FUNC,
ca1eb8
+              "No idea where to look for the schema, disabling GC\n");
ca1eb8
+        state->is_gc_usable = false;
ca1eb8
+        ret = EOK;
ca1eb8
+        goto immediately;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    state->attrs[0] = AD_AT_SCHEMA_NAME;
ca1eb8
+    state->attrs[1] = AD_AT_SCHEMA_IS_REPL;
ca1eb8
+    state->attrs[2] = NULL;
ca1eb8
+
ca1eb8
+    DEBUG(SSSDBG_TRACE_FUNC, "Checking for POSIX attributes in GC\n");
ca1eb8
+
ca1eb8
+    filter = talloc_asprintf(
ca1eb8
+                        state,
ca1eb8
+                        "(&(objectclass=%s)(|(%s=%s)(%s=%s)))",
ca1eb8
+                        AD_SCHEMA_AT_OC,
ca1eb8
+                        AD_AT_SCHEMA_NAME,
ca1eb8
+                        state->sdap_opts->user_map[SDAP_AT_USER_UID].name,
ca1eb8
+                        AD_AT_SCHEMA_NAME,
ca1eb8
+                        state->sdap_opts->group_map[SDAP_AT_GROUP_GID].name);
ca1eb8
+    if (filter == NULL) {
ca1eb8
+        ret = ENOMEM;
ca1eb8
+        goto immediately;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    subreq = sdap_get_generic_send(state,
ca1eb8
+                                   ev,
ca1eb8
+                                   state->sdap_opts,
ca1eb8
+                                   sdap_id_op_handle(op),
ca1eb8
+                                   state->sdap_opts->schema_basedn,
ca1eb8
+                                   LDAP_SCOPE_SUBTREE,
ca1eb8
+                                   filter,
ca1eb8
+                                   state->attrs,
ca1eb8
+                                   NULL, 0,
ca1eb8
+                                   dp_opt_get_int(state->sdap_opts->basic,
ca1eb8
+                                                  SDAP_SEARCH_TIMEOUT),
ca1eb8
+                                   false);
ca1eb8
+    if (subreq == NULL) {
ca1eb8
+        ret = ENOMEM;
ca1eb8
+        goto immediately;
ca1eb8
+    }
ca1eb8
+    tevent_req_set_callback(subreq, ad_check_gc_usability_search_done, req);
ca1eb8
+
ca1eb8
+    return req;
ca1eb8
+
ca1eb8
+immediately:
ca1eb8
+    if (ret == EOK) {
ca1eb8
+        tevent_req_done(req);
ca1eb8
+    } else {
ca1eb8
+        tevent_req_error(req, ret);
ca1eb8
+    }
ca1eb8
+    tevent_req_post(req, ev);
ca1eb8
+
ca1eb8
+    return req;
ca1eb8
+}
ca1eb8
+
ca1eb8
+static void ad_check_gc_usability_search_done(struct tevent_req *subreq)
ca1eb8
+{
ca1eb8
+    struct tevent_req *req = tevent_req_callback_data(subreq,
ca1eb8
+                                                      struct tevent_req);
ca1eb8
+    struct ad_check_gc_usability_state *state = tevent_req_data(req,
ca1eb8
+                                          struct ad_check_gc_usability_state);
ca1eb8
+    errno_t ret;
ca1eb8
+    size_t reply_count;
ca1eb8
+    struct sysdb_attrs **reply = NULL;
ca1eb8
+    bool uid = false;
ca1eb8
+    bool gid = false;
ca1eb8
+
ca1eb8
+    ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply);
ca1eb8
+    talloc_zfree(subreq);
ca1eb8
+    if (ret != EOK) {
ca1eb8
+        DEBUG(SSSDBG_OP_FAILURE,
ca1eb8
+              "sdap_get_generic_recv failed [%d]: %s\n",
ca1eb8
+              ret, strerror(ret));
ca1eb8
+        /* We continue to finish sdap_id_op. */
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    if (reply_count == 0) {
ca1eb8
+        DEBUG(SSSDBG_TRACE_LIBS,
ca1eb8
+              "Nothing found, so no POSIX attrs can exist\n");
ca1eb8
+        state->is_gc_usable = false;
ca1eb8
+        tevent_req_done(req);
ca1eb8
+        return;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    for (size_t i = 0; i < reply_count; i++) {
ca1eb8
+        const char *name = NULL;
ca1eb8
+        const char *is_in_partial_set = NULL;
ca1eb8
+        bool *val = NULL;
ca1eb8
+
ca1eb8
+        ret = sysdb_attrs_get_string(reply[i], AD_AT_SCHEMA_NAME, &name);
ca1eb8
+        if (ret != EOK) {
ca1eb8
+            DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get "AD_AT_SCHEMA_NAME);
ca1eb8
+            continue;
ca1eb8
+        }
ca1eb8
+
ca1eb8
+        if (strcasecmp(name, state->sdap_opts->user_map[SDAP_AT_USER_UID].name) == 0) {
ca1eb8
+            val = &ui;;
ca1eb8
+        } else if (strcasecmp(name, state->sdap_opts->user_map[SDAP_AT_USER_GID].name) == 0) {
ca1eb8
+            val = &gid;
ca1eb8
+        } else {
ca1eb8
+            DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected attribute\n");
ca1eb8
+            continue;
ca1eb8
+        }
ca1eb8
+
ca1eb8
+        ret = sysdb_attrs_get_string(reply[i],
ca1eb8
+                                     AD_AT_SCHEMA_IS_REPL,
ca1eb8
+                                     &is_in_partial_set);
ca1eb8
+        if (ret != EOK) {
ca1eb8
+            DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get "AD_AT_SCHEMA_IS_REPL);
ca1eb8
+            continue;
ca1eb8
+        }
ca1eb8
+
ca1eb8
+        if (strcasecmp(is_in_partial_set, "true") == 0) {
ca1eb8
+            *val = true;
ca1eb8
+        }
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    if (uid == true && gid == true) {
ca1eb8
+        state->is_gc_usable = true;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    if (state->is_gc_usable == true) {
ca1eb8
+        DEBUG(SSSDBG_FUNC_DATA, "Server has POSIX attributes. Global Catalog will "
ca1eb8
+                                "be used for user and group lookups. Note that if "
ca1eb8
+                                "only a subset of POSIX attributes is present "
ca1eb8
+                                "in GC, the non-replicated attributes are "
ca1eb8
+                                "currently not read from the LDAP port\n");
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    tevent_req_done(req);
ca1eb8
+}
ca1eb8
+
ca1eb8
+static errno_t ad_check_gc_usability_recv(struct tevent_req *req,
ca1eb8
+                                          bool *_is_gc_usable)
ca1eb8
+{
ca1eb8
+    struct ad_check_gc_usability_state *state = NULL;
ca1eb8
+
ca1eb8
+    state = tevent_req_data(req, struct ad_check_gc_usability_state);
ca1eb8
+
ca1eb8
+    TEVENT_REQ_RETURN_ON_ERROR(req);
ca1eb8
+
ca1eb8
+    *_is_gc_usable = state->is_gc_usable;
ca1eb8
+    return EOK;
ca1eb8
+}
ca1eb8
+
ca1eb8
 struct ad_subdomains_refresh_state {
ca1eb8
     struct tevent_context *ev;
ca1eb8
     struct be_ctx *be_ctx;
ca1eb8
@@ -1268,11 +1504,14 @@ struct ad_subdomains_refresh_state {
ca1eb8
     struct sdap_id_op *sdap_op;
ca1eb8
     struct sdap_id_ctx *id_ctx;
ca1eb8
     struct ad_options *ad_options;
ca1eb8
+
ca1eb8
+    char *forest;
ca1eb8
 };
ca1eb8
 
ca1eb8
 static errno_t ad_subdomains_refresh_retry(struct tevent_req *req);
ca1eb8
 static void ad_subdomains_refresh_connect_done(struct tevent_req *subreq);
ca1eb8
 static void ad_subdomains_refresh_master_done(struct tevent_req *subreq);
ca1eb8
+static void ad_subdomains_refresh_gc_check_done(struct tevent_req *subreq);
ca1eb8
 static void ad_subdomains_refresh_root_done(struct tevent_req *subreq);
ca1eb8
 static void ad_subdomains_refresh_done(struct tevent_req *subreq);
ca1eb8
 
ca1eb8
@@ -1385,37 +1624,73 @@ static void ad_subdomains_refresh_master_done(struct tevent_req *subreq)
ca1eb8
     struct ad_subdomains_refresh_state *state;
ca1eb8
     struct tevent_req *req;
ca1eb8
     const char *realm;
ca1eb8
-    const char *ad_domain;
ca1eb8
     char *master_sid;
ca1eb8
     char *flat_name;
ca1eb8
-    char *forest;
ca1eb8
     errno_t ret;
ca1eb8
 
ca1eb8
     req = tevent_req_callback_data(subreq, struct tevent_req);
ca1eb8
     state = tevent_req_data(req, struct ad_subdomains_refresh_state);
ca1eb8
 
ca1eb8
     ret = ad_master_domain_recv(subreq, state, &flat_name, &master_sid,
ca1eb8
-                                NULL, &forest);
ca1eb8
+                                NULL, &state->forest);
ca1eb8
     talloc_zfree(subreq);
ca1eb8
     if (ret != EOK) {
ca1eb8
         DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get master domain information "
ca1eb8
               "[%d]: %s\n", ret, sss_strerror(ret));
ca1eb8
-        goto done;
ca1eb8
+        tevent_req_error(req, ret);
ca1eb8
+        return;
ca1eb8
     }
ca1eb8
 
ca1eb8
     realm = dp_opt_get_cstring(state->ad_options->basic, AD_KRB5_REALM);
ca1eb8
     if (realm == NULL) {
ca1eb8
         DEBUG(SSSDBG_CONF_SETTINGS, "Missing realm.\n");
ca1eb8
-        ret = EINVAL;
ca1eb8
-        goto done;
ca1eb8
+        tevent_req_error(req, EINVAL);
ca1eb8
+        return;
ca1eb8
     }
ca1eb8
 
ca1eb8
     ret = sysdb_master_domain_add_info(state->be_ctx->domain, realm,
ca1eb8
-                                       flat_name, master_sid, forest, NULL);
ca1eb8
+                                       flat_name, master_sid, state->forest, NULL);
ca1eb8
     if (ret != EOK) {
ca1eb8
         DEBUG(SSSDBG_OP_FAILURE, "Cannot save master domain info [%d]: %s\n",
ca1eb8
               ret, sss_strerror(ret));
ca1eb8
-        goto done;
ca1eb8
+        tevent_req_error(req, ret);
ca1eb8
+        return;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    subreq = ad_check_gc_usability_send(state,
ca1eb8
+                                        state->ev,
ca1eb8
+                                        state->ad_options,
ca1eb8
+                                        state->id_ctx->opts,
ca1eb8
+                                        state->sdap_op,
ca1eb8
+                                        state->be_ctx->domain->name,
ca1eb8
+                                        master_sid);
ca1eb8
+    if (subreq == NULL) {
ca1eb8
+        tevent_req_error(req, ENOMEM);
ca1eb8
+        return;
ca1eb8
+    }
ca1eb8
+    tevent_req_set_callback(subreq, ad_subdomains_refresh_gc_check_done, req);
ca1eb8
+}
ca1eb8
+
ca1eb8
+static void ad_subdomains_refresh_gc_check_done(struct tevent_req *subreq)
ca1eb8
+{
ca1eb8
+    struct ad_subdomains_refresh_state *state;
ca1eb8
+    struct tevent_req *req;
ca1eb8
+    const char *ad_domain;
ca1eb8
+    bool is_gc_usable;
ca1eb8
+    errno_t ret;
ca1eb8
+
ca1eb8
+    req = tevent_req_callback_data(subreq, struct tevent_req);
ca1eb8
+    state = tevent_req_data(req, struct ad_subdomains_refresh_state);
ca1eb8
+
ca1eb8
+    ret = ad_check_gc_usability_recv(subreq, &is_gc_usable);
ca1eb8
+    talloc_zfree(subreq);
ca1eb8
+    if (ret != EOK) {
ca1eb8
+        DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get GC usability status\n");
ca1eb8
+        is_gc_usable = false;
ca1eb8
+    }
ca1eb8
+
ca1eb8
+    if (is_gc_usable == false) {
ca1eb8
+        ad_disable_gc(state->ad_options);
ca1eb8
     }
ca1eb8
 
ca1eb8
     /*
ca1eb8
@@ -1428,7 +1703,8 @@ static void ad_subdomains_refresh_master_done(struct tevent_req *subreq)
ca1eb8
                            state->be_ctx->domain->name) == 0) {
ca1eb8
                 DEBUG(SSSDBG_TRACE_FUNC,
ca1eb8
                       "No other enabled domain than master.\n");
ca1eb8
-                goto done;
ca1eb8
+                tevent_req_done(req);
ca1eb8
+                return;
ca1eb8
             }
ca1eb8
         }
ca1eb8
     }
ca1eb8
@@ -1440,24 +1716,16 @@ static void ad_subdomains_refresh_master_done(struct tevent_req *subreq)
ca1eb8
         ad_domain = state->sd_ctx->be_ctx->domain->name;
ca1eb8
     }
ca1eb8
 
ca1eb8
-    subreq = ad_get_root_domain_send(state, state->ev, ad_domain, forest,
ca1eb8
+    subreq = ad_get_root_domain_send(state, state->ev, ad_domain, state->forest,
ca1eb8
                                      sdap_id_op_handle(state->sdap_op),
ca1eb8
                                      state->sd_ctx);
ca1eb8
     if (subreq == NULL) {
ca1eb8
-        ret = ENOMEM;
ca1eb8
-        goto done;
ca1eb8
+        tevent_req_error(req, ENOMEM);
ca1eb8
+        return;
ca1eb8
     }
ca1eb8
 
ca1eb8
     tevent_req_set_callback(subreq, ad_subdomains_refresh_root_done, req);
ca1eb8
     return;
ca1eb8
-
ca1eb8
-done:
ca1eb8
-    if (ret != EOK) {
ca1eb8
-        tevent_req_error(req, ret);
ca1eb8
-        return;
ca1eb8
-    }
ca1eb8
-
ca1eb8
-    tevent_req_done(req);
ca1eb8
 }
ca1eb8
 
ca1eb8
 static void ad_subdomains_refresh_root_done(struct tevent_req *subreq)
ca1eb8
-- 
ca1eb8
2.17.1
ca1eb8