From af95219169754476a905c762c5f0eb92e85efc01 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Mon, 6 Jun 2011 22:19:08 -0400
Subject: [PATCH 04/12] Add helper functions for looking up HBAC rule
 components

---
 Makefile.am                           |    5 +
 src/providers/ipa/ipa_hbac_common.c   | 1017 +++++++++++++++++++++++++++++++++
 src/providers/ipa/ipa_hbac_hosts.c    |  524 +++++++++++++++++
 src/providers/ipa/ipa_hbac_private.h  |  177 ++++++
 src/providers/ipa/ipa_hbac_rules.c    |  214 +++++++
 src/providers/ipa/ipa_hbac_services.c |  451 +++++++++++++++
 6 files changed, 2388 insertions(+), 0 deletions(-)
 create mode 100644 src/providers/ipa/ipa_hbac_common.c
 create mode 100644 src/providers/ipa/ipa_hbac_hosts.c
 create mode 100644 src/providers/ipa/ipa_hbac_private.h
 create mode 100644 src/providers/ipa/ipa_hbac_rules.c
 create mode 100644 src/providers/ipa/ipa_hbac_services.c

diff --git a/Makefile.am b/Makefile.am
index ee7c4685dc8fc269aa584b24ef1dee955e28366f..a6b03aa078b64a6c54e2658b62121ce8e3eb988d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -883,6 +883,11 @@ libsss_ipa_la_SOURCES = \
     src/providers/ipa/ipa_auth.c \
     src/providers/ipa/ipa_access.c \
     src/providers/ipa/ipa_dyndns.c \
+    src/providers/ipa/ipa_hbac_hosts.c \
+    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_common.c \
     src/providers/ldap/ldap_id.c \
     src/providers/ldap/ldap_id_enum.c \
     src/providers/ldap/ldap_id_cleanup.c \
diff --git a/src/providers/ipa/ipa_hbac_common.c b/src/providers/ipa/ipa_hbac_common.c
new file mode 100644
index 0000000000000000000000000000000000000000..67a11fb91d947400f4b9eee637f542bf544e0caa
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_common.c
@@ -0,0 +1,1017 @@
+/*
+    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 "providers/ipa/ipa_hbac_private.h"
+#include "providers/ipa/ipa_hbac.h"
+#include "providers/ipa/ipa_common.h"
+
+errno_t
+ipa_hbac_save_list(struct sysdb_ctx *sysdb, bool delete_subdir,
+                   const char *subdir, struct sss_domain_info *domain,
+                   const char *naming_attribute, size_t count,
+                   struct sysdb_attrs **list)
+{
+    int ret;
+    size_t c;
+    struct ldb_dn *base_dn;
+    const char *object_name;
+    struct ldb_message_element *el;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(1, ("talloc_new failed.\n"));
+        return ENOMEM;
+    }
+
+    if (delete_subdir) {
+        base_dn = sysdb_custom_subtree_dn(sysdb, tmp_ctx, domain->name, subdir);
+        if (base_dn == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        ret = sysdb_delete_recursive(tmp_ctx, sysdb, base_dn, true);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_delete_recursive failed.\n"));
+            goto done;
+        }
+    }
+
+    for (c = 0; c < count; c++) {
+        ret = sysdb_attrs_get_el(list[c], naming_attribute, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto done;
+        }
+        if (el->num_values == 0) {
+            DEBUG(1, ("[%s] not found.\n", naming_attribute));
+            ret = EINVAL;
+            goto done;
+        }
+        object_name = talloc_strndup(tmp_ctx, (const char *)el->values[0].data,
+                                     el->values[0].length);
+        if (object_name == NULL) {
+            DEBUG(1, ("talloc_strndup failed.\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        DEBUG(9, ("Object name: [%s].\n", object_name));
+
+        ret = sysdb_store_custom(tmp_ctx, sysdb, domain, object_name, subdir,
+                                 list[c]);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_store_custom failed.\n"));
+            goto done;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+ipa_hbac_sysdb_save(struct sysdb_ctx *sysdb, struct sss_domain_info *domain,
+                    const char *primary_subdir, const char *attr_name,
+                    size_t primary_count, struct sysdb_attrs **primary,
+                    const char *group_subdir, const char *groupattr_name,
+                    size_t group_count, struct sysdb_attrs **groups)
+{
+    int lret;
+    errno_t ret, sret;
+    bool in_transaction = false;
+    const char **orig_member_dns;
+    size_t i, j, member_count;
+    struct ldb_message **members;
+    TALLOC_CTX *tmp_ctx = NULL;
+    const char *member_dn;
+    const char *group_id;
+    struct ldb_message *msg;
+    char *member_filter;
+
+    if ((primary_count == 0 || primary == NULL)
+        || (group_count > 0 && groups == NULL)) {
+        /* There always has to be at least one
+         * primary entry.
+         */
+        return EINVAL;
+    }
+
+    /* Save the entries and groups to the cache */
+    ret = sysdb_transaction_start(sysdb);
+    if (ret != EOK) return ret;
+    in_transaction = true;
+
+    /* First, save the specific entries */
+    ret = ipa_hbac_save_list(sysdb, true,
+                             primary_subdir,
+                             domain,
+                             attr_name,
+                             primary_count,
+                             primary);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not save %s. [%d][%s]\n",
+                  primary_subdir, ret, strerror(ret)));
+        goto done;
+    }
+
+    /* Second, save the groups */
+    if (group_count > 0) {
+        ret = ipa_hbac_save_list(sysdb, true,
+                                 group_subdir,
+                                 domain,
+                                 groupattr_name,
+                                 group_count,
+                                 groups);
+        if (ret != EOK) {
+            DEBUG(1, ("Could not save %s. [%d][%s]\n",
+                      group_subdir, ret, strerror(ret)));
+            goto done;
+        }
+
+        /* Third, save the memberships */
+        for (i = 0; i < group_count; i++) {
+            if (!groups[i]) {
+                ret = EINVAL;
+                goto done;
+            }
+
+            talloc_free(tmp_ctx);
+            tmp_ctx = talloc_new(NULL);
+            if (tmp_ctx == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            ret = sysdb_attrs_get_string(groups[i],
+                                         groupattr_name,
+                                         &group_id);
+            if (ret != EOK) {
+                DEBUG(1, ("Could not determine group attribute name\n"));
+                goto done;
+            }
+
+            msg = ldb_msg_new(tmp_ctx);
+            if (msg == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            msg->dn = sysdb_custom_dn(sysdb, msg, domain->name,
+                                      group_id, group_subdir);
+            if (msg->dn == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            lret = ldb_msg_add_empty(msg, SYSDB_MEMBER, LDB_FLAG_MOD_ADD, NULL);
+            if (lret != LDB_SUCCESS) {
+                ret = sysdb_error_to_errno(lret);
+                goto done;
+            }
+
+            ret = sysdb_attrs_get_string_array(groups[i],
+                                               SYSDB_ORIG_MEMBER,
+                                               tmp_ctx,
+                                               &orig_member_dns);
+            if (ret != EOK) {
+                DEBUG(1, ("Could not determine original members\n"));
+                goto done;
+            }
+
+            for (j = 0; orig_member_dns[j]; j++) {
+                member_filter = talloc_asprintf(tmp_ctx, "%s=%s",
+                                                SYSDB_ORIG_DN,
+                                                orig_member_dns[j]);
+                if (member_filter == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+
+                ret = sysdb_search_custom(tmp_ctx, sysdb, domain,
+                                          member_filter, primary_subdir,
+                                          NULL, &member_count, &members);
+                talloc_zfree(member_filter);
+                if (ret != EOK && ret != ENOENT) {
+                    goto done;
+                } else if (ret == ENOENT || member_count == 0) {
+                    /* No member exists with this orig_dn. Skip it */
+                    DEBUG(6, ("[%s] does not exist\n", orig_member_dns[j]));
+                    continue;
+                } else if (member_count > 1) {
+                    /* This probably means corruption in the cache, but
+                     * we'll try to proceed anyway.
+                     */
+                    DEBUG(1, ("More than one result for DN [%s], skipping\n"));
+                    continue;
+                }
+
+                member_dn = ldb_dn_get_linearized(members[0]->dn);
+                if (!member_dn) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+                lret = ldb_msg_add_fmt(msg, SYSDB_MEMBER, "%s", member_dn);
+                if (lret != LDB_SUCCESS) {
+                    ret = sysdb_error_to_errno(lret);
+                    goto done;
+                }
+            }
+
+            lret = ldb_modify(sysdb_ctx_get_ldb(sysdb), msg);
+            if (lret != LDB_SUCCESS) {
+                ret = sysdb_error_to_errno(lret);
+                goto done;
+            }
+        }
+        talloc_zfree(tmp_ctx);
+    }
+
+    ret = sysdb_transaction_commit(sysdb);
+    if (ret != EOK) goto done;
+    in_transaction = false;
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(sysdb);
+        if (sret != EOK) {
+            DEBUG(0, ("Could not cancel sysdb transaction\n"));
+        }
+    }
+
+    if (ret != EOK) {
+        DEBUG(3, ("Error [%d][%s]\n", ret, strerror(ret)));
+    }
+    return ret;
+}
+
+errno_t
+replace_attribute_name(const char *old_name,
+                       const char *new_name, const size_t count,
+                       struct sysdb_attrs **list)
+{
+    int ret;
+    int i;
+
+    for (i = 0; i < count; i++) {
+        ret = sysdb_attrs_replace_name(list[i], old_name, new_name);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_replace_name failed.\n"));
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
+
+/********************************************
+ * Functions for handling conversion to the *
+ * HBAC evaluator format                    *
+ ********************************************/
+
+static errno_t
+hbac_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                   struct hbac_ctx *hbac_ctx,
+                   size_t index,
+                   struct hbac_rule **rule);
+
+static errno_t
+hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                        struct sysdb_ctx *sysdb,
+                        struct sss_domain_info *domain,
+                        const char *rule_name,
+                        struct sysdb_attrs *rule_attrs,
+                        struct hbac_rule_element **users);
+
+static errno_t
+hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx,
+                         struct hbac_ctx *hbac_ctx,
+                         struct hbac_eval_req **request);
+
+errno_t
+hbac_ctx_to_rules(TALLOC_CTX *mem_ctx,
+                  struct hbac_ctx *hbac_ctx,
+                  struct hbac_rule ***rules,
+                  struct hbac_eval_req **request)
+{
+    errno_t ret;
+    struct hbac_rule **new_rules;
+    struct hbac_eval_req *new_request;
+    size_t i;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    if (!rules || !request) return EINVAL;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    /* First create an array of rules */
+    new_rules = talloc_array(tmp_ctx, struct hbac_rule *,
+                             hbac_ctx->rule_count + 1);
+    if (new_rules == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Create each rule one at a time */
+    for (i = 0; i < hbac_ctx->rule_count ; i++) {
+        ret = hbac_attrs_to_rule(new_rules, hbac_ctx, i, &(new_rules[i]));
+        if (ret != EOK) {
+            DEBUG(1, ("Could not construct rules\n"))
+            goto done;
+        }
+    }
+    new_rules[i] = NULL;
+
+    /* Create the eval request */
+    ret = hbac_ctx_to_eval_request(tmp_ctx, hbac_ctx, &new_request);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not construct eval request\n"));
+        goto done;
+    }
+
+    *rules = talloc_steal(mem_ctx, new_rules);
+    *request = talloc_steal(mem_ctx, new_request);
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+hbac_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                   struct hbac_ctx *hbac_ctx,
+                   size_t idx,
+                   struct hbac_rule **rule)
+{
+    errno_t ret;
+    struct hbac_rule *new_rule;
+    struct ldb_message_element *el;
+
+    new_rule = talloc_zero(mem_ctx, struct hbac_rule);
+    if (new_rule == NULL) return ENOMEM;
+
+    ret = sysdb_attrs_get_el(hbac_ctx->rules[idx],
+                             IPA_CN, &el);
+    if (el->num_values == 0) {
+        DEBUG(4, ("rule has no name, assuming '(none)'.\n"));
+        new_rule->name = talloc_strdup(new_rule, "(none)");
+    } else {
+        new_rule->name = talloc_strndup(new_rule,
+                                        (const char*) el->values[0].data,
+                                        el->values[0].length);
+    }
+
+    DEBUG(7, ("Processing rule [%s]\n", new_rule->name));
+
+    ret = sysdb_attrs_get_bool(hbac_ctx->rules[idx], IPA_ENABLED_FLAG,
+                               &new_rule->enabled);
+    if (ret != EOK) goto done;
+
+    /* Get the users */
+    ret = hbac_user_attrs_to_rule(new_rule,
+                                  hbac_ctx_sysdb(hbac_ctx),
+                                  hbac_ctx_be(hbac_ctx)->domain,
+                                  new_rule->name,
+                                  hbac_ctx->rules[idx],
+                                  &new_rule->users);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not parse users for rule [%s]\n",
+                  new_rule->name));
+        goto done;
+    }
+
+    /* Get the services */
+    ret = hbac_service_attrs_to_rule(new_rule,
+                                     hbac_ctx_sysdb(hbac_ctx),
+                                     hbac_ctx_be(hbac_ctx)->domain,
+                                     new_rule->name,
+                                     hbac_ctx->rules[idx],
+                                     &new_rule->services);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not parse services for rule [%s]\n",
+                  new_rule->name));
+        goto done;
+    }
+
+    /* Get the target hosts */
+    ret = hbac_thost_attrs_to_rule(new_rule,
+                                   hbac_ctx_sysdb(hbac_ctx),
+                                   hbac_ctx_be(hbac_ctx)->domain,
+                                   new_rule->name,
+                                   hbac_ctx->rules[idx],
+                                   &new_rule->targethosts);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not parse target hosts for rule [%s]\n",
+                  new_rule->name));
+        goto done;
+    }
+
+    /* Get the source hosts */
+    ret = hbac_shost_attrs_to_rule(new_rule,
+                                   hbac_ctx_sysdb(hbac_ctx),
+                                   hbac_ctx_be(hbac_ctx)->domain,
+                                   new_rule->name,
+                                   hbac_ctx->rules[idx],
+                                   &new_rule->srchosts);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not parse source hosts for rule [%s]\n",
+                  new_rule->name));
+        goto done;
+    }
+
+    *rule = new_rule;
+    ret = EOK;
+
+done:
+    if (ret != EOK) talloc_free(new_rule);
+    return ret;
+}
+
+errno_t
+hbac_get_category(struct sysdb_attrs *attrs,
+                  const char *category_attr,
+                  uint32_t *_categories)
+{
+    errno_t ret;
+    size_t i;
+    uint32_t cats = HBAC_CATEGORY_NULL;
+    const char **categories;
+
+    TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    ret = sysdb_attrs_get_string_array(attrs, category_attr,
+                                       tmp_ctx, &categories);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    if (ret != ENOENT) {
+        for (i = 0; categories[i]; i++) {
+            if (strcasecmp("all", categories[i]) == 0) {
+                DEBUG(5, ("Category is set to 'all'.\n"));
+                cats |= HBAC_CATEGORY_ALL;
+                continue;
+            }
+            DEBUG(9, ("Unsupported user category [%s].\n",
+                      categories[i]));
+        }
+    }
+
+    *_categories = cats;
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                                       struct sysdb_ctx *sysdb,
+                                       struct sss_domain_info *domain,
+                                       const char *rule_name,
+                                       struct sysdb_attrs *rule_attrs,
+                                       struct hbac_rule_element **users)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct hbac_rule_element *new_users = NULL;
+    struct ldb_message_element *el = NULL;
+    struct ldb_message **msgs = NULL;
+    char *filter;
+    char *member_dn;
+    const char *attrs[] = { SYSDB_NAME, NULL };
+    size_t num_users = 0;
+    size_t num_groups = 0;
+    const char *name;
+
+    size_t count;
+    size_t i;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    new_users = talloc_zero(tmp_ctx, struct hbac_rule_element);
+    if (new_users == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    DEBUG(7, ("Processing users for rule [%s]\n", rule_name));
+
+    ret = hbac_get_category(rule_attrs, IPA_USER_CATEGORY,
+                            &new_users->category);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not identify user categories\n"));
+        goto done;
+    }
+    if (new_users->category & HBAC_CATEGORY_ALL) {
+        /* Short-cut to the exit */
+        ret = EOK;
+        goto done;
+    }
+
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        goto done;
+    }
+    if (ret == ENOENT || el->num_values == 0) {
+        el->num_values = 0;
+        DEBUG(4, ("No user specified, rule will never apply.\n"));
+    }
+
+    new_users->names = talloc_array(new_users,
+                                    const char *,
+                                    el->num_values + 1);
+    if (new_users->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_users->groups = talloc_array(new_users,
+                                     const char *,
+                                     el->num_values + 1);
+    if (new_users->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < el->num_values; i++) {
+        ret = sss_filter_sanitize(tmp_ctx,
+                                  (const char *)el->values[i].data,
+                                  &member_dn);
+        if (ret != EOK) goto done;
+
+        filter = talloc_asprintf(member_dn, "(%s=%s)",
+                                 SYSDB_ORIG_DN, member_dn);
+        if (filter == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        /* First check if this is a user */
+        ret = sysdb_search_users(tmp_ctx, sysdb, domain,
+                                 filter, attrs, &count, &msgs);
+        if (ret != EOK && ret != ENOENT) goto done;
+        if (ret == EOK && count == 0) {
+            ret = ENOENT;
+        }
+
+        if (ret == EOK) {
+            if (count > 1) {
+                DEBUG(1, ("Original DN matched multiple users. Skipping \n"));
+                talloc_zfree(member_dn);
+                continue;
+            }
+
+            /* Original DN matched a single user. Get the username */
+            name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
+            if (name == NULL) {
+                DEBUG(1, ("Attribute is missing!\n"));
+                ret = EFAULT;
+                goto done;
+            }
+
+            new_users->names[num_users] = talloc_strdup(new_users->names,
+                                                        name);
+            if (new_users->names[num_users] == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            DEBUG(8, ("Added user [%s] to rule [%s]\n",
+                      name, rule_name));
+            num_users++;
+        } else {
+            /* Check if it is a group instead */
+            ret = sysdb_search_groups(tmp_ctx, sysdb, domain,
+                                      filter, attrs, &count, &msgs);
+            if (ret != EOK && ret != ENOENT) goto done;
+            if (ret == EOK && count == 0) {
+                ret = ENOENT;
+            }
+
+            if (ret == EOK) {
+                if (count > 1) {
+                    DEBUG(1, ("Original DN matched multiple groups. "
+                              "Skipping\n"));
+                    talloc_zfree(member_dn);
+                    continue;
+                }
+
+                /* Original DN matched a single group. Get the groupname */
+                name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
+                if (name == NULL) {
+                    DEBUG(1, ("Attribute is missing!\n"));
+                    ret = EFAULT;
+                    goto done;
+                }
+
+                new_users->groups[num_groups] =
+                        talloc_strdup(new_users->groups, name);
+                if (new_users->groups[num_groups] == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+                DEBUG(8, ("Added group [%s] to rule [%s]\n",
+                          name, rule_name));
+                num_groups++;
+            } else {
+                /* Neither a user nor a group? */
+                DEBUG(1, ("[%s] does not map to either a user or group. "
+                          "Skipping\n", member_dn));
+            }
+        }
+        talloc_zfree(member_dn);
+    }
+    new_users->names[num_users] = NULL;
+    new_users->groups[num_groups] = NULL;
+
+    /* Shrink the arrays down to their real sizes */
+    new_users->names = talloc_realloc(new_users, new_users->names,
+                                      const char *, num_users + 1);
+    if (new_users->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_users->groups = talloc_realloc(new_users, new_users->groups,
+                                      const char *, num_groups + 1);
+    if (new_users->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    if (ret == EOK) {
+        *users = talloc_steal(mem_ctx, new_users);
+    }
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
+static errno_t
+hbac_eval_user_element(TALLOC_CTX *mem_ctx,
+                       struct sysdb_ctx *sysdb,
+                       struct sss_domain_info *domain,
+                       const char *username,
+                       struct hbac_request_element **user_element);
+
+static errno_t
+hbac_eval_service_element(TALLOC_CTX *mem_ctx,
+                          struct sysdb_ctx *sysdb,
+                          struct sss_domain_info *domain,
+                          const char *hostname,
+                          struct hbac_request_element **svc_element);
+
+static errno_t
+hbac_eval_host_element(TALLOC_CTX *mem_ctx,
+                       struct sysdb_ctx *sysdb,
+                       struct sss_domain_info *domain,
+                       const char *hostname,
+                       struct hbac_request_element **host_element);
+
+static errno_t
+hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx,
+                         struct hbac_ctx *hbac_ctx,
+                         struct hbac_eval_req **request)
+{
+    errno_t ret;
+    struct pam_data *pd = hbac_ctx->pd;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_eval_req *eval_req;
+    struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx);
+    struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain;
+    const char *rhost;
+    const char *thost;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    eval_req = talloc_zero(tmp_ctx, struct hbac_eval_req);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    eval_req->request_time = time(NULL);
+
+    /* Get user the user name and groups */
+    ret = hbac_eval_user_element(eval_req, sysdb, domain,
+                                 pd->user, &eval_req->user);
+    if (ret != EOK) goto done;
+
+    /* Get the PAM service and service groups */
+    ret = hbac_eval_service_element(eval_req, sysdb, domain,
+                                    pd->service, &eval_req->service);
+    if (ret != EOK) goto done;
+
+    /* Get the source host */
+    if (pd->rhost == NULL || pd->rhost[0] == '\0') {
+            /* If we haven't been passed an rhost, we
+             * have to assume it's coming from the
+             * target host
+             */
+        rhost = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+    } else {
+        rhost = pd->rhost;
+    }
+    if (rhost == NULL) {
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = hbac_eval_host_element(eval_req, sysdb, domain,
+                                 rhost, &eval_req->srchost);
+    if (ret != EOK) goto done;
+
+    /* The target host is always the current machine */
+    thost = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+    if (thost == NULL) {
+        DEBUG(1, ("Missing ipa_hostname, this should never happen.\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = hbac_eval_host_element(eval_req, sysdb, domain,
+                                 thost, &eval_req->targethost);
+    if (ret != EOK) goto done;
+
+    *request = talloc_steal(mem_ctx, eval_req);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+hbac_eval_user_element(TALLOC_CTX *mem_ctx,
+                       struct sysdb_ctx *sysdb,
+                       struct sss_domain_info *domain,
+                       const char *username,
+                       struct hbac_request_element **user_element)
+{
+    errno_t ret;
+    size_t i;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_request_element *users;
+    struct ldb_result *res;
+    const char *group_name;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    users = talloc_zero(tmp_ctx, struct hbac_request_element);
+    if (users == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    users->name = username;
+
+    ret = sysdb_initgroups(tmp_ctx, sysdb, domain,
+                           users->name, &res);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("Could not determine user memberships\n"));
+        goto done;
+    } if (ret == ENOENT) {
+        res->count = 0;
+    }
+
+    users->groups = talloc_array(username,
+                                 const char *,
+                                 res->count + 1);
+    if (users->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < res->count; i++) {
+        group_name = ldb_msg_find_attr_as_string(res->msgs[i],
+                                                 SYSDB_NAME,
+                                                 NULL);
+        if (group_name == NULL) {
+            DEBUG(1, ("Group with no name?\n"));
+            ret = EINVAL;
+            goto done;
+        }
+
+        users->groups[i] = talloc_strdup(users->groups,
+                                         group_name);
+        if (users->groups[i] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+    users->groups[i] = NULL;
+
+    *user_element = talloc_steal(mem_ctx, users);
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+hbac_eval_service_element(TALLOC_CTX *mem_ctx,
+                          struct sysdb_ctx *sysdb,
+                          struct sss_domain_info *domain,
+                          const char *hostname,
+                          struct hbac_request_element **svc_element)
+{
+    errno_t ret;
+    size_t i, count;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_request_element *svc;
+    struct ldb_message **msgs;
+    const char *group_name;
+    struct ldb_dn *svc_dn;
+    const char *attrs[] = { IPA_CN, NULL };
+    const char *service_filter;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    svc = talloc_zero(tmp_ctx, struct hbac_request_element);
+    if (svc == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    svc->name = hostname;
+
+    service_filter = talloc_asprintf(tmp_ctx,
+                                     "(objectClass=%s)",
+                                     IPA_HBAC_SERVICE_GROUP);
+    if (service_filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    svc_dn = sysdb_custom_dn(sysdb, tmp_ctx, domain->name,
+                             svc->name, HBAC_SERVICES_SUBDIR);
+    if (svc_dn == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Find the service groups */
+    ret = sysdb_asq_search(tmp_ctx, sysdb, domain, svc_dn,
+                           service_filter, SYSDB_MEMBEROF,
+                           attrs, &count, &msgs);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("Could not look up servicegroups\n"));
+        goto done;
+    } else if (ret == ENOENT) {
+        count = 0;
+    }
+
+    svc->groups = talloc_array(svc, const char *, count + 1);
+    if (svc->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < count; i++) {
+        group_name = ldb_msg_find_attr_as_string(msgs[i], IPA_CN, NULL);
+        if (group_name == NULL) {
+            DEBUG(1, ("Group with no name?\n"));
+            ret = EINVAL;
+            goto done;
+        }
+        svc->groups[i] = talloc_strdup(svc->groups,
+                                       group_name);
+        if (svc->groups[i] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        DEBUG(6, ("Added service group [%s] to the eval request\n",
+                  svc->groups[i]));
+    }
+    svc->groups[i] = NULL;
+
+    *svc_element = talloc_steal(mem_ctx, svc);
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+hbac_eval_host_element(TALLOC_CTX *mem_ctx,
+                       struct sysdb_ctx *sysdb,
+                       struct sss_domain_info *domain,
+                       const char *hostname,
+                       struct hbac_request_element **host_element)
+{
+    errno_t ret;
+    size_t i, count;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_request_element *host;
+    struct ldb_message **msgs;
+    const char *group_name;
+    struct ldb_dn *host_dn;
+    const char *attrs[] = { IPA_HOST_FQDN, NULL };
+    const char *host_filter;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    host = talloc_zero(tmp_ctx, struct hbac_request_element);
+    if (host == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    host->name = hostname;
+
+
+    host_filter = talloc_asprintf(tmp_ctx,
+                                  "(objectClass=%s)",
+                                  IPA_HOSTGROUP);
+    if (host_filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    host_dn = sysdb_custom_dn(sysdb, tmp_ctx, domain->name,
+                             host->name, HBAC_SERVICES_SUBDIR);
+    if (host_dn == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Find the host groups */
+    ret = sysdb_asq_search(tmp_ctx, sysdb, domain, host_dn,
+                           host_filter, SYSDB_MEMBEROF,
+                           attrs, &count, &msgs);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("Could not look up host groups\n"));
+        goto done;
+    } else if (ret == ENOENT) {
+        count = 0;
+    }
+
+    host->groups = talloc_array(host, const char *, count + 1);
+    if (host->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < count; i++) {
+        group_name = ldb_msg_find_attr_as_string(msgs[i],
+                                                 IPA_HOST_FQDN,
+                                                 NULL);
+        if (group_name == NULL) {
+            DEBUG(1, ("Group with no name?\n"));
+            ret = EINVAL;
+            goto done;
+        }
+        host->groups[i] = talloc_strdup(host->groups,
+                                       group_name);
+        if (host->groups[i] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        DEBUG(6, ("Added host group [%s] to the eval request\n",
+                  host->groups[i]));
+    }
+    host->groups[i] = NULL;
+
+    *host_element = talloc_steal(mem_ctx, host);
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
diff --git a/src/providers/ipa/ipa_hbac_hosts.c b/src/providers/ipa/ipa_hbac_hosts.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e753f3744ee22dc5f065b7c4f5ea7ef960cdc2b
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_hosts.c
@@ -0,0 +1,524 @@
+/*
+    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 "db/sysdb.h"
+#include "providers/ipa/ipa_hbac_private.h"
+#include "providers/ldap/sdap_async.h"
+
+struct ipa_hbac_host_state {
+    struct tevent_context *ev;
+    struct sysdb_ctx *sysdb;
+    struct sss_domain_info *dom;
+    struct sdap_handle *sh;
+    struct sdap_options *opts;
+    const char *search_base;
+    const char **attrs;
+
+    /* Return values */
+    size_t host_count;
+    struct sysdb_attrs **hosts;
+
+    size_t hostgroup_count;
+    struct sysdb_attrs **hostgroups;
+};
+
+static void
+ipa_hbac_host_info_done(struct tevent_req *subreq);
+
+static void
+ipa_hbac_hostgroup_info_done(struct tevent_req *subreq);
+
+struct tevent_req *
+ipa_hbac_host_info_send(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        struct sysdb_ctx *sysdb,
+                        struct sss_domain_info *dom,
+                        struct sdap_handle *sh,
+                        struct sdap_options *opts,
+                        const char *search_base)
+{
+    errno_t ret;
+    struct ipa_hbac_host_state *state;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    char *host_filter;
+
+    req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_host_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->sysdb = sysdb;
+    state->dom = dom;
+    state->sh = sh;
+    state->opts = opts;
+    state->search_base = search_base;
+
+    host_filter = talloc_asprintf(state, "(objectClass=%s)", IPA_HOST);
+    if (host_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    state->attrs = talloc_array(state, const char *, 8);
+    if (state->attrs == NULL) {
+        DEBUG(1, ("Failed to allocate host attribute list.\n"));
+        ret = ENOMEM;
+        goto immediate;
+    }
+    state->attrs[0] = "objectClass";
+    state->attrs[1] = IPA_HOST_SERVERHOSTNAME;
+    state->attrs[2] = IPA_HOST_FQDN;
+    state->attrs[3] = IPA_UNIQUE_ID;
+    state->attrs[4] = IPA_MEMBER;
+    state->attrs[5] = IPA_MEMBEROF;
+    state->attrs[6] = IPA_CN;
+    state->attrs[7] = NULL;
+
+    subreq = sdap_get_generic_send(state, ev, opts, sh, search_base,
+                                   LDAP_SCOPE_SUB, host_filter,
+                                   state->attrs, NULL, 0,
+                                   dp_opt_get_int(opts->basic,
+                                                  SDAP_ENUM_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        DEBUG(1, ("Error requesting host info\n"));
+        ret = EIO;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_host_info_done, req);
+
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void
+ipa_hbac_host_info_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct ipa_hbac_host_state *state =
+            tevent_req_data(req, struct ipa_hbac_host_state);
+    char *hostgroup_filter;
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &state->host_count,
+                                &state->hosts);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->host_count == 0) {
+        tevent_req_error(req, ENOENT);
+        return;
+    }
+
+    ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF,
+                                 state->host_count,
+                                 state->hosts);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    hostgroup_filter = talloc_asprintf(state, "(objectClass=%s)",
+                                              IPA_HOSTGROUP);
+    if (hostgroup_filter == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    /* Look up host groups */
+    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+                                   state->search_base, LDAP_SCOPE_SUB,
+                                   hostgroup_filter, state->attrs, NULL, 0,
+                                   dp_opt_get_int(state->opts->basic,
+                                                  SDAP_ENUM_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        DEBUG(1, ("Error requesting host info\n"));
+        tevent_req_error(req, EIO);
+        return;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_hostgroup_info_done, req);
+}
+
+static void
+ipa_hbac_hostgroup_info_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct ipa_hbac_host_state *state =
+            tevent_req_data(req, struct ipa_hbac_host_state);
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &state->hostgroup_count,
+                                &state->hostgroups);
+    talloc_zfree(subreq);
+    if (ret != EOK) goto done;
+
+    ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER,
+                                 state->hostgroup_count,
+                                 state->hostgroups);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        goto done;
+    }
+
+    ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF,
+                                 state->hostgroup_count,
+                                 state->hostgroups);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        goto done;
+    }
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        DEBUG(3, ("Error [%d][%s]\n", ret, strerror(ret)));
+        tevent_req_error(req, ret);
+    }
+}
+
+errno_t
+ipa_hbac_host_info_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx,
+                        size_t *host_count,
+                        struct sysdb_attrs ***hosts,
+                        size_t *hostgroup_count,
+                        struct sysdb_attrs ***hostgroups)
+{
+    size_t c;
+    struct ipa_hbac_host_state *state =
+            tevent_req_data(req, struct ipa_hbac_host_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *host_count = state->host_count;
+    *hosts = talloc_steal(mem_ctx, state->hosts);
+    for (c = 0; c < state->host_count; c++) {
+        /* Guarantee the memory heirarchy of the list */
+        talloc_steal(state->hosts, state->hosts[c]);
+    }
+
+    *hostgroup_count = state->hostgroup_count;
+    *hostgroups = talloc_steal(mem_ctx, state->hostgroups);
+
+    return EOK;
+}
+
+/*
+ * Functions to convert sysdb_attrs to the hbac_rule format
+ */
+static errno_t hbac_host_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                                       struct sysdb_ctx *sysdb,
+                                       struct sss_domain_info *domain,
+                                       const char *rule_name,
+                                       struct sysdb_attrs *rule_attrs,
+                                       const char *category_attr,
+                                       const char *member_attr,
+                                       size_t *host_count,
+                                       struct hbac_rule_element **hosts)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_rule_element *new_hosts;
+    const char *attrs[] = { IPA_HOST_FQDN, NULL };
+    struct ldb_message_element *el;
+    size_t num_hosts = 0;
+    size_t num_hostgroups = 0;
+    size_t i;
+    char *member_dn;
+    char *filter;
+    size_t count;
+    struct ldb_message **msgs;
+    const char *name;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    new_hosts = talloc_zero(tmp_ctx, struct hbac_rule_element);
+    if (new_hosts == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* First check for host category */
+    ret = hbac_get_category(rule_attrs, category_attr, &new_hosts->category);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not identify host categories\n"));
+        goto done;
+    }
+    if (new_hosts->category & HBAC_CATEGORY_ALL) {
+        /* Short-cut to the exit */
+        ret = EOK;
+        goto done;
+    }
+
+    /* Get the list of DNs from the member_attr */
+    ret = sysdb_attrs_get_el(rule_attrs, member_attr, &el);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        goto done;
+    }
+    if (ret == ENOENT || el->num_values == 0) {
+        el->num_values = 0;
+        DEBUG(4, ("No host specified, rule will never apply.\n"));
+    }
+
+    /* Assume maximum size; We'll trim it later */
+    new_hosts->names = talloc_array(new_hosts,
+                                    const char *,
+                                    el->num_values +1);
+    if (new_hosts->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_hosts->groups = talloc_array(new_hosts,
+                                     const char *,
+                                     el->num_values + 1);
+    if (new_hosts->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < el->num_values; i++) {
+        ret = sss_filter_sanitize(tmp_ctx,
+                                  (const char *)el->values[i].data,
+                                  &member_dn);
+        if (ret != EOK) goto done;
+
+        filter = talloc_asprintf(member_dn, "(%s=%s)",
+                                 SYSDB_ORIG_DN, member_dn);
+        if (filter == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        /* First check if this is a specific host */
+        ret = sysdb_search_custom(tmp_ctx, sysdb, domain, filter,
+                                  HBAC_HOSTS_SUBDIR, attrs,
+                                  &count, &msgs);
+        if (ret != EOK && ret != ENOENT) goto done;
+        if (ret == EOK && count == 0) {
+            ret = ENOENT;
+        }
+
+        if (ret == EOK) {
+            if (count > 1) {
+                DEBUG(1, ("Original DN matched multiple hosts. Skipping \n"));
+                talloc_zfree(member_dn);
+                continue;
+            }
+
+            /* Original DN matched a single host. Get the hostname */
+            name = ldb_msg_find_attr_as_string(msgs[0],
+                                               IPA_HOST_FQDN,
+                                               NULL);
+            if (name == NULL) {
+                DEBUG(1, ("Attribute is missing!\n"));
+                ret = EFAULT;
+                goto done;
+            }
+
+            new_hosts->names[num_hosts] = talloc_strdup(new_hosts->names,
+                                                        name);
+            if (new_hosts->names[num_hosts] == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            DEBUG(8, ("Added host [%s] to rule [%s]\n",
+                      name, rule_name));
+            num_hosts++;
+        } else { /* ret == ENOENT */
+            /* Check if this is a hostgroup */
+            ret = sysdb_search_custom(tmp_ctx, sysdb, domain, filter,
+                                      HBAC_HOSTGROUPS_SUBDIR, attrs,
+                                      &count, &msgs);
+            if (ret != EOK && ret != ENOENT) goto done;
+            if (ret == EOK && count == 0) {
+                ret = ENOENT;
+            }
+
+            if (ret == EOK) {
+                if (count > 1) {
+                    DEBUG(1, ("Original DN matched multiple hostgroups. "
+                              "Skipping\n"));
+                    talloc_zfree(member_dn);
+                    continue;
+                }
+
+                /* Original DN matched a single group. Get the groupname */
+                name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
+                if (name == NULL) {
+                    DEBUG(1, ("Attribute is missing!\n"));
+                    ret = EFAULT;
+                    goto done;
+                }
+
+                new_hosts->groups[num_hostgroups] =
+                        talloc_strdup(new_hosts->groups, name);
+                if (new_hosts->groups[num_hostgroups] == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+
+                DEBUG(8, ("Added hostgroup [%s] to rule [%s]\n",
+                          name, rule_name));
+                num_hostgroups++;
+            } else { /* ret == ENOENT */
+                /* Neither a host nor a hostgroup? Skip it */
+                DEBUG(1, ("[%s] does not map to either a host or hostgroup. "
+                          "Skipping\n", member_dn));
+            }
+        }
+        talloc_zfree(member_dn);
+    }
+    new_hosts->names[num_hosts] = NULL;
+    new_hosts->groups[num_hostgroups] = NULL;
+
+    /* Shrink the arrays down to their real sizes */
+    new_hosts->names = talloc_realloc(new_hosts, new_hosts->names,
+                                      const char *, num_hosts + 1);
+    if (new_hosts->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_hosts->groups = talloc_realloc(new_hosts, new_hosts->groups,
+                                       const char *, num_hostgroups + 1);
+    if (new_hosts->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *hosts = talloc_steal(mem_ctx, new_hosts);
+        if (host_count) *host_count = num_hosts;
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                         struct sysdb_ctx *sysdb,
+                         struct sss_domain_info *domain,
+                         const char *rule_name,
+                         struct sysdb_attrs *rule_attrs,
+                         struct hbac_rule_element **thosts)
+{
+    DEBUG(7, ("Processing target hosts for rule [%s]\n", rule_name));
+
+    return hbac_host_attrs_to_rule(mem_ctx, sysdb, domain,
+                                   rule_name, rule_attrs,
+                                   IPA_HOST_CATEGORY, IPA_MEMBER_HOST,
+                                   NULL, thosts);
+}
+
+errno_t
+hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                         struct sysdb_ctx *sysdb,
+                         struct sss_domain_info *domain,
+                         const char *rule_name,
+                         struct sysdb_attrs *rule_attrs,
+                         struct hbac_rule_element **source_hosts)
+{
+    errno_t ret;
+    size_t host_count;
+    TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+    size_t idx;
+    struct ldb_message_element *el;
+    struct hbac_rule_element *shosts;
+
+    DEBUG(7, ("Processing source hosts for rule [%s]\n", rule_name));
+
+    ret = hbac_host_attrs_to_rule(tmp_ctx, sysdb, domain,
+                                  rule_name, rule_attrs,
+                                  IPA_SOURCE_HOST_CATEGORY, IPA_SOURCE_HOST,
+                                  &host_count, &shosts);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (shosts->category & HBAC_CATEGORY_ALL) {
+        /* All hosts (including external) are
+         * allowed.
+         */
+        goto done;
+    }
+
+    /* Include external (non-IPA-managed) source hosts */
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &el);
+    if (ret != EOK && ret != ENOENT) goto done;
+    if (ret == EOK && el->num_values == 0) ret = ENOENT;
+
+    if (ret != ENOENT) {
+        shosts->names = talloc_realloc(shosts, shosts->names, const char *,
+                                       host_count + el->num_values + 1);
+        if (shosts->names == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        for (idx = host_count; idx <= host_count + el->num_values; idx++) {
+            shosts->names[idx] =
+                    talloc_strdup(shosts->names,
+                                  (const char *)el->values[idx].data);
+            if (shosts->names[idx] == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            DEBUG(8, ("Added external source host [%s] to rule [%s]\n",
+                      shosts->names[idx], rule_name));
+        }
+        shosts->names[idx] = NULL;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *source_hosts = talloc_steal(mem_ctx, shosts);
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
diff --git a/src/providers/ipa/ipa_hbac_private.h b/src/providers/ipa/ipa_hbac_private.h
new file mode 100644
index 0000000000000000000000000000000000000000..6b73d1b2d3ec445fbbcde71c4f3fe4c1bf866e0b
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_private.h
@@ -0,0 +1,177 @@
+/*
+    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/>.
+*/
+
+#ifndef IPA_HBAC_PRIVATE_H_
+#define IPA_HBAC_PRIVATE_H_
+
+#include "providers/ipa/ipa_access.h"
+#include "providers/ipa/ipa_hbac.h"
+
+#define IPA_HBAC_RULE "ipaHBACRule"
+
+#define IPA_HOST "ipaHost"
+#define IPA_HOSTGROUP "ipaHostGroup"
+
+#define IPA_HBAC_SERVICE "ipaHBACService"
+#define IPA_HBAC_SERVICE_GROUP "ipaHBACServiceGroup"
+
+#define IPA_HOST_SERVERHOSTNAME "serverHostName"
+#define IPA_HOST_FQDN "fqdn"
+#define IPA_UNIQUE_ID "ipauniqueid"
+
+#define IPA_MEMBER "member"
+#define SYSDB_ORIG_MEMBER "orig_member"
+#define HBAC_HOSTS_SUBDIR "hbac_hosts"
+#define HBAC_HOSTGROUPS_SUBDIR "hbac_hostgroups"
+
+#define OBJECTCLASS "objectclass"
+#define IPA_MEMBEROF "memberOf"
+#define IPA_ACCESS_RULE_TYPE "accessRuleType"
+#define IPA_MEMBER_USER "memberUser"
+#define IPA_USER_CATEGORY "userCategory"
+#define IPA_SERVICE_NAME "serviceName"
+#define IPA_SOURCE_HOST "sourceHost"
+#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory"
+#define IPA_EXTERNAL_HOST "externalHost"
+#define IPA_ENABLED_FLAG "ipaenabledflag"
+#define IPA_MEMBER_HOST "memberHost"
+#define IPA_HOST_CATEGORY "hostCategory"
+#define IPA_CN "cn"
+#define IPA_MEMBER_SERVICE "memberService"
+#define IPA_SERVICE_CATEGORY "serviceCategory"
+#define IPA_TRUE_VALUE "TRUE"
+
+#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,%s"
+#define IPA_HBAC_BASE_TMPL "cn=hbac,%s"
+#define IPA_SERVICES_BASE_TMPL "cn=hbacservices,cn=accounts,%s"
+
+#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE
+
+#define HBAC_RULES_SUBDIR "hbac_rules"
+#define HBAC_SERVICES_SUBDIR "hbac_services"
+#define HBAC_SERVICEGROUPS_SUBDIR "hbac_servicegroups"
+
+/* From ipa_hbac_common.c */
+errno_t ipa_hbac_save_list(struct sysdb_ctx *sysdb, bool delete_subdir,
+                           const char *subdir, struct sss_domain_info *domain,
+                           const char *naming_attribute, size_t count,
+                           struct sysdb_attrs **list);
+errno_t
+ipa_hbac_sysdb_save(struct sysdb_ctx *sysdb, struct sss_domain_info *domain,
+                    const char *primary_subdir, const char *attr_name,
+                    size_t primary_count, struct sysdb_attrs **primary,
+                    const char *group_subdir, const char *groupattr_name,
+                    size_t group_count, struct sysdb_attrs **groups);
+
+errno_t
+replace_attribute_name(const char *old_name,
+                       const char *new_name, const size_t count,
+                       struct sysdb_attrs **list);
+
+errno_t hbac_ctx_to_rules(TALLOC_CTX *mem_ctx,
+                          struct hbac_ctx *hbac_ctx,
+                          struct hbac_rule ***rules,
+                          struct hbac_eval_req **request);
+
+errno_t
+hbac_get_category(struct sysdb_attrs *attrs,
+                  const char *category_attr,
+                  uint32_t *_categories);
+
+/* From ipa_hbac_hosts.c */
+struct tevent_req *
+ipa_hbac_host_info_send(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        struct sysdb_ctx *sysdb,
+                        struct sss_domain_info *dom,
+                        struct sdap_handle *sh,
+                        struct sdap_options *opts,
+                        const char *search_base);
+
+errno_t
+ipa_hbac_host_info_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx,
+                        size_t *host_count,
+                        struct sysdb_attrs ***hosts,
+                        size_t *hostgroup_count,
+                        struct sysdb_attrs ***hostgroups);
+
+errno_t
+hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                         struct sysdb_ctx *sysdb,
+                         struct sss_domain_info *domain,
+                         const char *rule_name,
+                         struct sysdb_attrs *rule_attrs,
+                         struct hbac_rule_element **thosts);
+
+errno_t
+hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                         struct sysdb_ctx *sysdb,
+                         struct sss_domain_info *domain,
+                         const char *rule_name,
+                         struct sysdb_attrs *rule_attrs,
+                         struct hbac_rule_element **source_hosts);
+
+/* From ipa_hbac_services.c */
+struct tevent_req *
+ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx,
+                           struct tevent_context *ev,
+                           struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *dom,
+                           struct sdap_handle *sh,
+                           struct sdap_options *opts,
+                           const char *search_base);
+
+errno_t
+ipa_hbac_service_info_recv(struct tevent_req *req,
+                           TALLOC_CTX *mem_ctx,
+                           size_t *service_count,
+                           struct sysdb_attrs ***services,
+                           size_t *servicegroup_count,
+                           struct sysdb_attrs ***servicegroups);
+
+errno_t
+hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                           struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *domain,
+                           const char *rule_name,
+                           struct sysdb_attrs *rule_attrs,
+                           struct hbac_rule_element **services);
+
+/* From ipa_hbac_rules.c */
+struct tevent_req *
+ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        struct sysdb_ctx *sysdb,
+                        struct sss_domain_info *dom,
+                        struct sdap_handle *sh,
+                        struct sdap_options *opts,
+                        const char *search_base,
+                        struct sysdb_attrs *ipa_host);
+
+errno_t
+ipa_hbac_rule_info_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx,
+                        size_t *rule_count,
+                        struct sysdb_attrs ***rules);
+
+#endif /* IPA_HBAC_PRIVATE_H_ */
diff --git a/src/providers/ipa/ipa_hbac_rules.c b/src/providers/ipa/ipa_hbac_rules.c
new file mode 100644
index 0000000000000000000000000000000000000000..6cbf83a801d1606dce5aea67c4393cd1ffe8758a
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_rules.c
@@ -0,0 +1,214 @@
+/*
+    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 ipa_hbac_rule_state {
+    struct sdap_options *opts;
+
+    size_t rule_count;
+    struct sysdb_attrs **rules;
+};
+
+static void
+ipa_hbac_rule_info_done(struct tevent_req *subreq);
+
+struct tevent_req *
+ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        struct sysdb_ctx *sysdb,
+                        struct sss_domain_info *dom,
+                        struct sdap_handle *sh,
+                        struct sdap_options *opts,
+                        const char *search_base,
+                        struct sysdb_attrs *ipa_host)
+{
+    errno_t ret;
+    size_t i;
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq;
+    struct ipa_hbac_rule_state *state;
+    TALLOC_CTX *tmp_ctx;
+    const char *host_dn;
+    char *host_dn_clean;
+    char *host_group_clean;
+    char *rule_filter;
+    const char **memberof_list;
+    const char *rule_attrs[] = { OBJECTCLASS,
+                                 IPA_CN,
+                                 IPA_UNIQUE_ID,
+                                 IPA_ENABLED_FLAG,
+                                 IPA_ACCESS_RULE_TYPE,
+                                 IPA_MEMBER_USER,
+                                 IPA_USER_CATEGORY,
+                                 IPA_MEMBER_SERVICE,
+                                 IPA_SERVICE_CATEGORY,
+                                 IPA_SOURCE_HOST,
+                                 IPA_SOURCE_HOST_CATEGORY,
+                                 IPA_EXTERNAL_HOST,
+                                 IPA_MEMBER_HOST,
+                                 IPA_HOST_CATEGORY,
+                                 NULL };
+
+    if (ipa_host == NULL) {
+        DEBUG(1, ("Missing host\n"));
+        return NULL;
+    }
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return NULL;
+
+    ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not identify IPA hostname\n"));
+        goto error;
+    }
+
+    ret = sss_filter_sanitize(tmp_ctx, host_dn, &host_dn_clean);
+    if (ret != EOK) goto error;
+
+    req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_rule_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->opts = opts;
+
+    rule_filter = talloc_asprintf(tmp_ctx,
+                                  "(&(objectclass=%s)"
+                                  "(%s=%s)(|(%s=%s)(%s=%s)",
+                                  IPA_HBAC_RULE,
+                                  IPA_ENABLED_FLAG, IPA_TRUE_VALUE,
+                                  IPA_HOST_CATEGORY, "all",
+                                  IPA_MEMBER_HOST, host_dn_clean);
+    if (rule_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    /* Add all parent groups of ipa_hostname to the filter */
+    ret = sysdb_attrs_get_string_array(ipa_host, SYSDB_ORIG_MEMBEROF,
+                                       tmp_ctx, &memberof_list);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("Could not identify "))
+    } if (ret == ENOENT) {
+        /* This host is not a member of any hostgroups */
+        memberof_list = talloc_array(tmp_ctx, const char *, 1);
+        if (memberof_list == NULL) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+        memberof_list[0] = NULL;
+    }
+
+    for (i = 0; memberof_list[i]; i++) {
+        ret = sss_filter_sanitize(tmp_ctx,
+                                  memberof_list[i],
+                                  &host_group_clean);
+        if (ret != EOK) goto immediate;
+
+        rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)",
+                                             IPA_MEMBER_HOST,
+                                             host_group_clean);
+        if (rule_filter == NULL) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+    }
+
+    rule_filter = talloc_asprintf_append(rule_filter, "))");
+    if (rule_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    talloc_steal(state, rule_filter);
+
+    subreq = sdap_get_generic_send(state, ev, opts, sh, search_base,
+                                   LDAP_SCOPE_SUB, rule_filter, rule_attrs,
+                                   NULL, 0,
+                                   dp_opt_get_int(state->opts->basic,
+                                                  SDAP_ENUM_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        DEBUG(1, ("sdap_get_generic_send failed.\n"));
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_rule_info_done, req);
+
+    talloc_free(tmp_ctx);
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    talloc_free(tmp_ctx);
+    return req;
+
+error:
+    talloc_free(tmp_ctx);
+    return NULL;
+}
+
+static void
+ipa_hbac_rule_info_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct ipa_hbac_rule_state *state =
+            tevent_req_data(req, struct ipa_hbac_rule_state);
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &state->rule_count,
+                                &state->rules);
+    if (ret != EOK) {
+        DEBUG(3, ("Could not retrieve HBAC rules\n"));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t
+ipa_hbac_rule_info_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx,
+                        size_t *rule_count,
+                        struct sysdb_attrs ***rules)
+{
+    struct ipa_hbac_rule_state *state =
+            tevent_req_data(req, struct ipa_hbac_rule_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *rule_count = state->rule_count;
+    *rules = talloc_steal(mem_ctx, state->rules);
+
+    return EOK;
+}
diff --git a/src/providers/ipa/ipa_hbac_services.c b/src/providers/ipa/ipa_hbac_services.c
new file mode 100644
index 0000000000000000000000000000000000000000..df276b86c427d4976bca1dfd0648c43dc91d4a51
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac_services.c
@@ -0,0 +1,451 @@
+/*
+    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 ipa_hbac_service_state {
+    struct tevent_context *ev;
+    struct sysdb_ctx *sysdb;
+    struct sss_domain_info *dom;
+    struct sdap_handle *sh;
+    struct sdap_options *opts;
+    const char *search_base;
+    const char **attrs;
+
+    /* Return values */
+    size_t service_count;
+    struct sysdb_attrs **services;
+
+    size_t servicegroup_count;
+    struct sysdb_attrs **servicegroups;
+};
+
+static void
+ipa_hbac_service_info_done(struct tevent_req *subreq);
+static void
+ipa_hbac_servicegroup_info_done(struct tevent_req *subreq);
+
+struct tevent_req *
+ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx,
+                           struct tevent_context *ev,
+                           struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *dom,
+                           struct sdap_handle *sh,
+                           struct sdap_options *opts,
+                           const char *search_base)
+{
+    errno_t ret;
+    struct ipa_hbac_service_state *state;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    char *service_filter;
+
+    req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_service_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->sysdb = sysdb;
+    state->dom = dom;
+    state->sh = sh;
+    state->opts = opts;
+    state->search_base = search_base;
+
+    service_filter = talloc_asprintf(state, "(objectClass=%s)",
+                                     IPA_HBAC_SERVICE);
+    if (service_filter == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    state->attrs = talloc_array(state, const char *, 6);
+    if (state->attrs == NULL) {
+        DEBUG(1, ("Failed to allocate service attribute list.\n"));
+        ret = ENOMEM;
+        goto immediate;
+    }
+    state->attrs[0] = OBJECTCLASS;
+    state->attrs[1] = IPA_CN;
+    state->attrs[2] = IPA_UNIQUE_ID;
+    state->attrs[3] = IPA_MEMBER;
+    state->attrs[4] = IPA_MEMBEROF;
+    state->attrs[5] = NULL;
+
+    subreq = sdap_get_generic_send(state, ev, opts, sh, search_base,
+                                   LDAP_SCOPE_SUB, service_filter,
+                                   state->attrs, NULL, 0,
+                                   dp_opt_get_int(opts->basic,
+                                                  SDAP_ENUM_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        DEBUG(1, ("Error requesting service info\n"));
+        ret = EIO;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_service_info_done, req);
+
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void
+ipa_hbac_service_info_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct ipa_hbac_service_state *state =
+            tevent_req_data(req, struct ipa_hbac_service_state);
+    char *servicegroup_filter;
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &state->service_count,
+                                &state->services);
+    talloc_zfree(subreq);
+    if (ret != EOK && ret != ENOENT) {
+        goto done;
+    }
+
+    if (ret == ENOENT || state->service_count == 0) {
+        /* If there are no services, we'll shortcut out
+         * This is still valid, as rules can apply to
+         * all services
+         *
+         * There's no reason to try to process groups
+         */
+
+        state->service_count = 0;
+        state->services = NULL;
+        ret = EOK;
+        goto done;
+    }
+
+    ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF,
+                                 state->service_count,
+                                 state->services);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        goto done;
+    }
+
+    servicegroup_filter = talloc_asprintf(state, "(objectClass=%s)",
+                                          IPA_HBAC_SERVICE_GROUP);
+    if (servicegroup_filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Look up service groups */
+    subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+                                   state->search_base, LDAP_SCOPE_SUB,
+                                   servicegroup_filter, state->attrs, NULL, 0,
+                                   dp_opt_get_int(state->opts->basic,
+                                                  SDAP_ENUM_SEARCH_TIMEOUT));
+    if (subreq == NULL) {
+        DEBUG(1, ("Error requesting host info\n"));
+        ret = EIO;
+        goto done;
+    }
+    tevent_req_set_callback(subreq, ipa_hbac_servicegroup_info_done, req);
+
+    return;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+}
+
+static void
+ipa_hbac_servicegroup_info_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct ipa_hbac_service_state *state =
+            tevent_req_data(req, struct ipa_hbac_service_state);
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &state->servicegroup_count,
+                                &state->servicegroups);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER,
+                                 state->servicegroup_count,
+                                 state->servicegroups);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        goto done;
+    }
+
+    ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF,
+                                 state->servicegroup_count,
+                                 state->servicegroups);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not replace attribute names\n"));
+        goto done;
+    }
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        DEBUG(3, ("Error [%d][%s]\n", ret, strerror(ret)));
+        tevent_req_error(req, ret);
+    }
+}
+
+errno_t
+ipa_hbac_service_info_recv(struct tevent_req *req,
+                           TALLOC_CTX *mem_ctx,
+                           size_t *service_count,
+                           struct sysdb_attrs ***services,
+                           size_t *servicegroup_count,
+                           struct sysdb_attrs ***servicegroups)
+{
+    size_t c;
+    struct ipa_hbac_service_state *state =
+            tevent_req_data(req, struct ipa_hbac_service_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *service_count = state->service_count;
+    *services = talloc_steal(mem_ctx, state->services);
+    for (c = 0; c < state->service_count; c++) {
+        /* Guarantee the memory heirarchy of the list */
+        talloc_steal(state->services, state->services[c]);
+    }
+
+    *servicegroup_count = state->servicegroup_count;
+    *servicegroups = talloc_steal(mem_ctx, state->servicegroups);
+
+    return EOK;
+}
+
+errno_t
+hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx,
+                           struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *domain,
+                           const char *rule_name,
+                           struct sysdb_attrs *rule_attrs,
+                           struct hbac_rule_element **services)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct hbac_rule_element *new_services;
+    const char *attrs[] = { IPA_CN, NULL };
+    struct ldb_message_element *el;
+    size_t num_services = 0;
+    size_t num_servicegroups = 0;
+    size_t i;
+    char *member_dn;
+    char *filter;
+    size_t count;
+    struct ldb_message **msgs;
+    const char *name;
+
+    DEBUG(7, ("Processing PAM services for rule [%s]\n", rule_name));
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    new_services = talloc_zero(tmp_ctx, struct hbac_rule_element);
+    if (new_services == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* First check for service category */
+    ret = hbac_get_category(rule_attrs, IPA_SERVICE_CATEGORY,
+                            &new_services->category);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not identify service categories\n"));
+        goto done;
+    }
+    if (new_services->category & HBAC_CATEGORY_ALL) {
+        /* Short-cut to the exit */
+        ret = EOK;
+        goto done;
+    }
+
+    /* Get the list of DNs from the member attr */
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_SERVICE, &el);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        goto done;
+    }
+    if (ret == ENOENT || el->num_values == 0) {
+        el->num_values = 0;
+        DEBUG(4, ("No services specified, rule will never apply.\n"));
+    }
+
+    /* Assume maximum size; We'll trim it later */
+    new_services->names = talloc_array(new_services,
+                                       const char *,
+                                       el->num_values +1);
+    if (new_services->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_services->groups = talloc_array(new_services,
+                                        const char *,
+                                        el->num_values + 1);
+    if (new_services->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < el->num_values; i++) {
+        ret = sss_filter_sanitize(tmp_ctx,
+                                  (const char *)el->values[i].data,
+                                  &member_dn);
+        if (ret != EOK) goto done;
+
+        filter = talloc_asprintf(member_dn, "(%s=%s)",
+                                 SYSDB_ORIG_DN, member_dn);
+        if (filter == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        /* First check if this is a specific service */
+        ret = sysdb_search_custom(tmp_ctx, sysdb, domain, filter,
+                                  HBAC_SERVICES_SUBDIR, attrs,
+                                  &count, &msgs);
+        if (ret != EOK && ret != ENOENT) goto done;
+        if (ret == EOK && count == 0) {
+            ret = ENOENT;
+        }
+
+        if (ret == EOK) {
+            if (count > 1) {
+                DEBUG(1, ("Original DN matched multiple services. "
+                          "Skipping \n"));
+                talloc_zfree(member_dn);
+                continue;
+            }
+
+            /* Original DN matched a single service. Get the service name */
+            name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL);
+            if (name == NULL) {
+                DEBUG(1, ("Attribute is missing!\n"));
+                ret = EFAULT;
+                goto done;
+            }
+
+            new_services->names[num_services] =
+                    talloc_strdup(new_services->names, name);
+            if (new_services->names[num_services] == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            DEBUG(8, ("Added service [%s] to rule [%s]\n",
+                      name, rule_name));
+            num_services++;
+        } else { /* ret == ENOENT */
+            /* Check if this is a service group */
+            ret = sysdb_search_custom(tmp_ctx, sysdb, domain, filter,
+                                      HBAC_SERVICEGROUPS_SUBDIR, attrs,
+                                      &count, &msgs);
+            if (ret != EOK && ret != ENOENT) goto done;
+            if (ret == EOK && count == 0) {
+                ret = ENOENT;
+            }
+
+            if (ret == EOK) {
+                if (count > 1) {
+                    DEBUG(1, ("Original DN matched multiple service groups. "
+                              "Skipping\n"));
+                    talloc_zfree(member_dn);
+                    continue;
+                }
+
+                /* Original DN matched a single group. Get the groupname */
+                name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL);
+                if (name == NULL) {
+                    DEBUG(1, ("Attribute is missing!\n"));
+                    ret = EFAULT;
+                    goto done;
+                }
+
+                new_services->groups[num_servicegroups] =
+                        talloc_strdup(new_services->groups, name);
+                if (new_services->groups[num_servicegroups] == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+
+                DEBUG(8, ("Added service group [%s] to rule [%s]\n",
+                          name, rule_name));
+                num_servicegroups++;
+            } else { /* ret == ENOENT */
+                /* Neither a service nor a service group? Skip it */
+                DEBUG(1, ("[%s] does not map to either a service or "
+                          "service group. Skipping\n", member_dn));
+            }
+        }
+        talloc_zfree(member_dn);
+    }
+    new_services->names[num_services] = NULL;
+    new_services->groups[num_servicegroups] = NULL;
+
+    /* Shrink the arrays down to their real sizes */
+    new_services->names = talloc_realloc(new_services, new_services->names,
+                                         const char *, num_services + 1);
+    if (new_services->names == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    new_services->groups = talloc_realloc(new_services, new_services->groups,
+                                          const char *, num_servicegroups + 1);
+    if (new_services->groups == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *services = talloc_steal(mem_ctx, new_services);
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
-- 
1.7.5.4

