From 6fd6129e5b2a8df107f09f8d5ef607ab6f540309 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Tue, 28 Jun 2011 08:11:51 -0400
Subject: [PATCH 08/12] Update groups during rule download

---
 Makefile.am                          |    1 +
 src/providers/ipa/ipa_access.c       |   38 +++-
 src/providers/ipa/ipa_hbac_private.h |    8 +
 src/providers/ipa/ipa_hbac_users.c   |  389 ++++++++++++++++++++++++++++++++++
 4 files changed, 428 insertions(+), 8 deletions(-)
 create mode 100644 src/providers/ipa/ipa_hbac_users.c

diff --git a/Makefile.am b/Makefile.am
index f848e878db1d073ea9c8d0a2cafaec0d11a568d1..8eaa7e03e2e664439711727f82e42167413688fc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -887,6 +887,7 @@ libsss_ipa_la_SOURCES = \
     src/providers/ipa/ipa_hbac_private.h \
     src/providers/ipa/ipa_hbac_rules.c \
     src/providers/ipa/ipa_hbac_services.c \
+    src/providers/ipa/ipa_hbac_users.c \
     src/providers/ipa/ipa_hbac_common.c \
     src/providers/ldap/ldap_id.c \
     src/providers/ldap/ldap_id_enum.c \
diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c
index e9ca4c9787ef2c21881e6b4ae960b9fc6e7866f4..de7408df3d02f2f713e2d7dd2025d248543d452f 100644
--- a/src/providers/ipa/ipa_access.c
+++ b/src/providers/ipa/ipa_access.c
@@ -280,6 +280,7 @@ static bool hbac_check_step_result(struct hbac_ctx *hbac_ctx, int ret)
 
 static void hbac_get_service_info_step(struct tevent_req *req);
 static void hbac_get_rule_info_step(struct tevent_req *req);
+static void hbac_update_groups(struct tevent_req *req);
 static void hbac_sysdb_save (struct tevent_req *req);
 
 static int hbac_get_host_info_step(struct hbac_ctx *hbac_ctx)
@@ -397,25 +398,19 @@ static void hbac_get_rule_info_step(struct tevent_req *req)
         goto fail;
     }
 
-    tevent_req_set_callback(req, hbac_sysdb_save, hbac_ctx);
+    tevent_req_set_callback(req, hbac_update_groups, hbac_ctx);
     return;
 
 fail:
     ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR);
 }
 
-static void hbac_sysdb_save (struct tevent_req *req)
+static void hbac_update_groups(struct tevent_req *req)
 {
     errno_t ret;
-    bool in_transaction = false;
     struct hbac_ctx *hbac_ctx =
             tevent_req_callback_data(req, struct hbac_ctx);
-    struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx);
-    struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain;
-    struct be_ctx *be_ctx = hbac_ctx_be(hbac_ctx);
-    struct ipa_access_ctx *access_ctx =
-            talloc_get_type(be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
-                            struct ipa_access_ctx);
+
     ret = ipa_hbac_rule_info_recv(req, hbac_ctx,
                                   &hbac_ctx->rule_count,
                                   &hbac_ctx->rules);
@@ -424,6 +419,33 @@ static void hbac_sysdb_save (struct tevent_req *req)
         return;
     }
 
+    req = ipa_hbac_update_groups_send(hbac_ctx, hbac_ctx);
+    if (req == NULL) {
+        ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR);
+        return;
+    }
+    tevent_req_set_callback(req, hbac_sysdb_save, hbac_ctx);
+}
+
+static void hbac_sysdb_save(struct tevent_req *req)
+{
+    errno_t ret;
+    bool in_transaction = false;
+    struct hbac_ctx *hbac_ctx =
+            tevent_req_callback_data(req, struct hbac_ctx);
+    struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx);
+    struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain;
+    struct be_ctx *be_ctx = hbac_ctx_be(hbac_ctx);
+    struct ipa_access_ctx *access_ctx =
+            talloc_get_type(be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+                            struct ipa_access_ctx);
+
+    ret = ipa_hbac_update_groups_recv(req);
+    talloc_zfree(req);
+    if (!hbac_check_step_result(hbac_ctx, ret)) {
+        return;
+    }
+
     ret = sysdb_transaction_start(sysdb);
     if (ret != EOK) {
         DEBUG(0, ("Could not start transaction\n"));
diff --git a/src/providers/ipa/ipa_hbac_private.h b/src/providers/ipa/ipa_hbac_private.h
index 6b73d1b2d3ec445fbbcde71c4f3fe4c1bf866e0b..8fc47c0fe89728071fc2f25ff33503b3362894a1 100644
--- a/src/providers/ipa/ipa_hbac_private.h
+++ b/src/providers/ipa/ipa_hbac_private.h
@@ -174,4 +174,12 @@ ipa_hbac_rule_info_recv(struct tevent_req *req,
                         size_t *rule_count,
                         struct sysdb_attrs ***rules);
 
+/* From ipa_hbac_users.c */
+struct tevent_req *
+ipa_hbac_update_groups_send(TALLOC_CTX *mem_ctx,
+                            struct hbac_ctx *hbac_ctx);
+
+errno_t
+ipa_hbac_update_groups_recv(struct tevent_req *req);
+
 #endif /* IPA_HBAC_PRIVATE_H_ */
diff --git a/src/providers/ipa/ipa_hbac_users.c b/src/providers/ipa/ipa_hbac_users.c
new file mode 100644
index 0000000000000000000000000000000000000000..0908823feefe4c4e211d54dea8e7f60b77818b56
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_users.c
@@ -0,0 +1,389 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2011 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 "providers/ipa/ipa_hbac_private.h"
+#include "providers/ldap/sdap_async.h"
+
+struct hbac_update_groups_state {
+    struct hbac_ctx *hbac_ctx;
+    struct sysdb_ctx *sysdb;
+    struct sss_domain_info *domain;
+};
+
+
+/* Returns EOK and populates groupname if
+ * the group_dn is actually a group.
+ * Returns ENOENT if group_dn does not point
+ * at a a group.
+ * Returns EINVAL if there is a parsing error.
+ * Returns ENOMEM as appropriate
+ */
+static errno_t
+get_ipa_groupname(TALLOC_CTX *mem_ctx,
+                  struct sysdb_ctx *sysdb,
+                  const char *group_dn,
+                  char **groupname)
+{
+    errno_t ret;
+    struct ldb_dn *dn;
+    const char *rdn_name;
+    const char *group_comp_name;
+    const char *account_comp_name;
+    const struct ldb_val *rdn_val;
+    const struct ldb_val *group_comp_val;
+    const struct ldb_val *account_comp_val;
+
+    /* This is an IPA-specific hack. It may not
+     * work for non-IPA servers and will need to
+     * be changed if SSSD ever supports HBAC on
+     * a non-IPA server.
+     */
+    *groupname = NULL;
+
+    dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), group_dn);
+    if (dn == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (!ldb_dn_validate(dn)) {
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (ldb_dn_get_comp_num(dn) < 4) {
+        /* RDN, groups, accounts, and at least one DC= */
+        ret = EINVAL;
+        goto done;
+    }
+
+    /* If the RDN name is 'cn' */
+    rdn_name = ldb_dn_get_rdn_name(dn);
+    if (rdn_name == NULL) {
+        /* Shouldn't happen if ldb_dn_validate()
+         * passed, but we'll be careful.
+         */
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (strcasecmp("cn", rdn_name) != 0) {
+        /* RDN has the wrong attribute name.
+         * It's not a group.
+         */
+        ret = ENOENT;
+        goto done;
+    }
+
+    /* and the second component is "cn=groups" */
+    group_comp_name = ldb_dn_get_component_name(dn, 1);
+    if (strcasecmp("cn", group_comp_name) != 0) {
+        /* The second component name is not "cn" */
+        ret = ENOENT;
+        goto done;
+    }
+
+    group_comp_val = ldb_dn_get_component_val(dn, 1);
+    if (strncasecmp("groups",
+                    (const char *) group_comp_val->data,
+                    group_comp_val->length) != 0) {
+        /* The second component value is not "groups" */
+        ret = ENOENT;
+        goto done;
+    }
+
+    /* and the third component is "accounts" */
+    account_comp_name = ldb_dn_get_component_name(dn, 2);
+    if (strcasecmp("cn", account_comp_name) != 0) {
+        /* The third component name is not "cn" */
+        ret = ENOENT;
+        goto done;
+    }
+
+    account_comp_val = ldb_dn_get_component_val(dn, 2);
+    if (strncasecmp("accounts",
+                    (const char *) account_comp_val->data,
+                    account_comp_val->length) != 0) {
+        /* The third component value is not "accounts" */
+        ret = ENOENT;
+        goto done;
+    }
+
+    /* Then the value of the RDN is the group name */
+    rdn_val = ldb_dn_get_rdn_val(dn);
+    *groupname = talloc_strndup(mem_ctx,
+                                (const char *)rdn_val->data,
+                                rdn_val->length);
+    if (*groupname == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(dn);
+    return ret;
+}
+
+void
+ipa_hbac_update_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *
+ipa_hbac_update_groups_send(TALLOC_CTX *mem_ctx,
+                            struct hbac_ctx *hbac_ctx)
+{
+    errno_t ret;
+    size_t i, count;
+    unsigned int j;
+    struct tevent_context *ev = hbac_ctx_ev(hbac_ctx);
+    struct sdap_id_ctx *id_ctx = hbac_ctx_sdap_id_ctx(hbac_ctx);
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct hbac_update_groups_state *state;
+    struct ldb_message_element *el;
+    struct ldb_message **msgs;
+    time_t now;
+    uint64_t cache_expire;
+    char *filter;
+    char *base_filter;
+    char *ldap_filter;
+    bool check_ldap = false;
+    const char *orig_dn;
+    char *clean_dn;
+    char *ipa_group;
+    TALLOC_CTX *tmp_ctx = NULL;
+    const char *attrs[] = { SYSDB_DEFAULT_ATTRS, NULL };
+    const char **ldap_attrs;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct hbac_update_groups_state);
+    if (req == NULL) return NULL;
+
+    state->hbac_ctx = hbac_ctx;
+    state->sysdb = hbac_ctx_sysdb(hbac_ctx);
+    state->domain = hbac_ctx_be(hbac_ctx)->domain;
+
+    base_filter = talloc_asprintf(state,"(&(objectClass=%s)(|",
+                                 id_ctx->opts->group_map[SDAP_OC_GROUP].name);
+    if (base_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    now = time(NULL);
+
+    /* Scan through all the rules to find groups that need
+     * updating
+     */
+    for (i = 0; i < hbac_ctx->rule_count; i++) {
+        ret = sysdb_attrs_get_el(hbac_ctx->rules[i],
+                                 IPA_MEMBER_USER, &el);
+        if (ret != EOK && ret != ENOENT) goto immediate;
+        if (ret == ENOENT || el->num_values == 0) continue;
+
+        for (j = 0; j < el->num_values; j++) {
+            tmp_ctx = talloc_new(state);
+            if (tmp_ctx == NULL) {
+                ret = ENOMEM;
+                goto immediate;
+            }
+
+            orig_dn = (const char *)el->values[j].data;
+
+            /* This is a little bit of a hack and
+             * exploits an IPA-ism, namely that we can
+             * parse the orig DN to know if it is
+             * actually a group. We're doing it this way
+             * to reduce the LDAP lookup to a single
+             * request, since it's too expensive to do
+             * repeated BASE searches and we can't do a
+             * deref request because these values aren't
+             * coming from a single LDAP entry.
+             */
+            ret = get_ipa_groupname(tmp_ctx, state->sysdb,
+                                    orig_dn, &ipa_group);
+            if (ret != EOK && ret != ENOENT) {
+                DEBUG(3, ("Could not parse [%s]\n", orig_dn));
+                goto immediate;
+            } else if (ret == ENOENT) {
+                /* Not a group, skip it */
+                talloc_zfree(tmp_ctx);
+                continue;
+            }
+
+            ret = sss_filter_sanitize(tmp_ctx, orig_dn, &clean_dn);
+            if (ret != EOK) goto immediate;
+
+            filter = talloc_asprintf(tmp_ctx, "(%s=%s)",
+                                     SYSDB_ORIG_DN,
+                                     clean_dn);
+            if (filter == NULL) {
+                ret = ENOMEM;
+                goto immediate;
+            }
+            talloc_zfree(clean_dn);
+
+            ret = sysdb_search_groups(tmp_ctx,
+                                      state->sysdb,
+                                      state->domain,
+                                      filter, attrs,
+                                      &count, &msgs);
+            talloc_zfree(filter);
+            if (ret != EOK && ret != ENOENT) goto immediate;
+            if (count > 1) {
+                /* We got more than one reply for the originalDN.
+                 * This probably implies cache-corruption.
+                 */
+                DEBUG(3, ("Cache corruption? More %d groups "
+                          "match originalDN [%s]\n",
+                          count, orig_dn));
+                ret = EIO;
+                goto immediate;
+            } else if (count == 1) {
+                /* This is a group.
+                 * Check the cache expiration
+                 */
+
+                /* Setting the default value to -1 so that if
+                 * for some reason there's no expiration time
+                 * set, we'll always update it.
+                 */
+                cache_expire =
+                        ldb_msg_find_attr_as_uint64(msgs[0],
+                                                    SYSDB_CACHE_EXPIRE,
+                                                    (uint64_t)-1);
+                if (cache_expire < now) {
+                    /* Cache is still valid */
+                    talloc_zfree(tmp_ctx);
+                    continue;
+                }
+
+                /* Otherwise, fall through below
+                 * Also count == 0 will fall through
+                 */
+            }
+
+            /* Expired or not found. We'll add it to the
+             * list of groups to find. If it's a group,
+             * it will be updated in the cache. If it's
+             * a user, it will be ignored.
+             */
+            check_ldap = true;
+
+            base_filter = talloc_asprintf_append(
+                    base_filter, "(%s=%s)",
+                    id_ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+                    ipa_group);
+            if (base_filter == NULL) {
+                ret = ENOMEM;
+                goto immediate;
+            }
+
+            talloc_zfree(tmp_ctx);
+        }
+    }
+
+    if (!check_ldap) {
+        /* All of our groups are already up-to-date */
+        talloc_zfree(base_filter);
+        ret = EOK;
+        goto immediate;
+    }
+
+    DEBUG(5, ("Updating groups for HBAC rules\n"));
+
+    /* Close the ldap filter */
+    base_filter = talloc_asprintf_append(base_filter, "))");
+    if (base_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    ldap_filter = sdap_get_id_specific_filter(
+            state, base_filter,
+            dp_opt_get_string(id_ctx->opts->basic,
+                              SDAP_GROUP_SEARCH_FILTER));
+    talloc_zfree(base_filter);
+    if (ldap_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    /* Send the group update request */
+    ret = build_attrs_from_map(state, id_ctx->opts->group_map,
+                               SDAP_OPTS_GROUP, &ldap_attrs);
+    if (ret != EOK) goto immediate;
+
+    subreq = sdap_get_groups_send(state, ev, state->domain,
+                                  state->sysdb, id_ctx->opts,
+                                  sdap_id_op_handle(hbac_ctx->sdap_op),
+                                  ldap_attrs, ldap_filter,
+                                  dp_opt_get_int(id_ctx->opts->basic,
+                                                 SDAP_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_update_groups_done, req);
+
+    return req;
+
+immediate:
+    DEBUG(5, ("No group updates performed. [%s]\n",
+              strerror(ret)));
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+
+    talloc_free(tmp_ctx);
+    return req;
+}
+
+void
+ipa_hbac_update_groups_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+
+    /* At this point, everything is either up-to-date, or we've
+     * gone offline. Just return what we get back.
+     */
+    ret = sdap_get_groups_recv(subreq, NULL, NULL);
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        DEBUG(3, ("Received error [%s] from sdap_get_groups\n",
+                  strerror(ret)));
+        tevent_req_error(req, ret);
+    }
+}
+
+errno_t
+ipa_hbac_update_groups_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
-- 
1.7.5.4

