[SSSD] [PATCH] added access module of IPA provider

Sumit Bose sbose at redhat.com
Mon Nov 2 16:28:20 UTC 2009


Hi,

this is the first part of the access target of the IPA provider. It is
not complete but I thought it might be easier to review if the next
features are coming in smaller patches. Currently the service and user
data of the HBAC rules are evaluate.

If you want to test it you need a current IPA v2 server together with
some uncommited patches, namely
 - [PATCH] Make ldap2.convert_attr_synonyms more robust against schema
   lookup fails.
 - [Freeipa-devel] [PATCH] Handle ipaEnabledFlag as bool (TRUE/FALSE)
   instead of string (enabled/disabled).

and if you use 1.9.0
 - [Freeipa-devel] [PATCH] 303 proper syntax for fqdn

bye,
Sumit
-------------- next part --------------
>From a6ac513480234fa2b976c8f2205d95a4542f6d68 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 17 Sep 2009 11:11:54 +0200
Subject: [PATCH] added access module of IPA provider

---
 server/Makefile.am                |    6 +-
 server/db/sysdb.c                 |    8 +
 server/db/sysdb.h                 |    4 +
 server/providers/ipa/ipa_access.c | 1558 +++++++++++++++++++++++++++++++++++++
 server/providers/ipa/ipa_access.h |   55 ++
 server/providers/ipa/ipa_init.c   |   49 ++-
 6 files changed, 1676 insertions(+), 4 deletions(-)
 create mode 100644 server/providers/ipa/ipa_access.c
 create mode 100644 server/providers/ipa/ipa_access.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 81223f4..cbcd7f1 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -273,6 +273,7 @@ dist_noinst_HEADERS = \
     providers/ldap/sdap.h \
     providers/ldap/sdap_async.h \
     providers/ipa/ipa_common.h \
+    providers/ipa/ipa_access.h \
     tools/tools_util.h \
     tools/sss_sync_ops.h \
     resolv/async_resolv.h \
@@ -506,8 +507,9 @@ libsss_krb5_la_LDFLAGS = \
     -module
 
 libsss_ipa_la_SOURCES = \
-	providers/ipa/ipa_init.c \
-	providers/ipa/ipa_common.c \
+    providers/ipa/ipa_init.c \
+    providers/ipa/ipa_common.c \
+    providers/ipa/ipa_access.c \
     providers/ldap/ldap_id.c \
     providers/ldap/ldap_auth.c \
     providers/ldap/ldap_common.c \
diff --git a/server/db/sysdb.c b/server/db/sysdb.c
index a2ac3b2..99c02cc 100644
--- a/server/db/sysdb.c
+++ b/server/db/sysdb.c
@@ -24,6 +24,14 @@
 #include "confdb/confdb.h"
 #include <time.h>
 
+
+struct ldb_dn *sysdb_custom_subtree_dn(struct sysdb_ctx *ctx, void *memctx,
+                                       const char *domain,
+                                       const char *subtree_name)
+{
+    return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_CUSTOM_SUBTREE,
+                          subtree_name, domain);
+}
 struct ldb_dn *sysdb_custom_dn(struct sysdb_ctx *ctx, void *memctx,
                                 const char *domain, const char *object_name,
                                 const char *subtree_name)
diff --git a/server/db/sysdb.h b/server/db/sysdb.h
index 72f56db..35c95d4 100644
--- a/server/db/sysdb.h
+++ b/server/db/sysdb.h
@@ -136,6 +136,7 @@
 
 #define SYSDB_TMPL_USER SYSDB_NAME"=%s,"SYSDB_TMPL_USER_BASE
 #define SYSDB_TMPL_GROUP SYSDB_NAME"=%s,"SYSDB_TMPL_GROUP_BASE
+#define SYSDB_TMPL_CUSTOM_SUBTREE "cn=%s,"SYSDB_TMPL_CUSTOM_BASE
 #define SYSDB_TMPL_CUSTOM SYSDB_NAME"=%s,cn=%s,"SYSDB_TMPL_CUSTOM_BASE
 
 #define SYSDB_MOD_ADD LDB_FLAG_MOD_ADD
@@ -184,6 +185,9 @@ struct ldb_dn *sysdb_domain_dn(struct sysdb_ctx *ctx, void *memctx,
 struct ldb_dn *sysdb_custom_dn(struct sysdb_ctx *ctx, void *memctx,
                                 const char *domain, const char *object_name,
                                 const char *subtree_name);
+struct ldb_dn *sysdb_custom_subtree_dn(struct sysdb_ctx *ctx, void *memctx,
+                                       const char *domain,
+                                       const char *subtree_name);
 
 
 
diff --git a/server/providers/ipa/ipa_access.c b/server/providers/ipa/ipa_access.c
new file mode 100644
index 0000000..8b8f1f4
--- /dev/null
+++ b/server/providers/ipa/ipa_access.c
@@ -0,0 +1,1558 @@
+/*
+    SSSD
+
+    IPA Backend Module -- Access control
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2009 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 <sys/param.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ipa/ipa_common.h"
+#include "providers/ipa/ipa_access.h"
+
+#define IPA_HOST_MEMBEROF "memberOf"
+#define IPA_HOST_SERVERHOSTNAME "serverHostName"
+#define IPA_HOST_FQDN "fqdn"
+#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_ACCESS_TIME "accessTime"
+#define IPA_UNIQUE_ID "ipauniqueid"
+#define IPA_ENABLED_FLAG "ipaenabledflag"
+#define IPA_MEMBER_HOST "memberHost"
+#define IPA_CN "cn"
+
+#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,dc=%s"
+#define IPA_HBAC_BASE_TMPL "cn=hbac,dc=%s"
+
+#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE
+
+#define HBAC_RULES_SUBDIR "hbac_rules"
+#define HBAC_HOSTS_SUBDIR "hbac_hosts"
+
+struct hbac_host_info {
+    const char *fqdn;
+    const char *serverhostname;
+    const char *dn;
+    const char **memberof;
+};
+
+
+static void ipa_access_reply(struct be_req *be_req, int pam_status)
+{
+    struct pam_data *pd;
+    pd = talloc_get_type(be_req->req_data, struct pam_data);
+    pd->pam_status = pam_status;
+
+    if (pam_status == PAM_SUCCESS) {
+        be_req->fn(be_req, DP_ERR_OK, pam_status, NULL);
+    } else {
+        be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL);
+    }
+}
+
+struct hbac_get_user_info_state {
+    struct tevent_context *ev;
+    struct be_ctx *be_ctx;;
+    struct sysdb_handle *handle;
+
+    const char *user;
+    struct ldb_message *user_msg;
+    const char *user_orig_dn;
+    struct ldb_dn *user_dn;
+    size_t groups_count;
+    const char **groups;
+};
+
+static void search_user_done(struct tevent_req *subreq);
+static void search_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *hbac_get_user_info_send(TALLOC_CTX *memctx,
+                                           struct tevent_context *ev,
+                                           struct be_ctx *be_ctx,
+                                           const char *user)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct hbac_get_user_info_state *state;
+    int ret;
+    const char **attrs;
+
+    req = tevent_req_create(memctx, &state, struct hbac_get_user_info_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->be_ctx = be_ctx;
+    state->handle = NULL;
+    state->user = user;
+    state->user_msg = NULL;
+    state->user_orig_dn = NULL;
+    state->user_dn = NULL;
+    state->groups_count = 0;
+    state->groups = NULL;
+
+    attrs = talloc_array(state, const char *, 2);
+    if (attrs == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    attrs[0] = SYSDB_ORIG_DN;
+    attrs[1] = NULL;
+
+    subreq = sysdb_search_user_by_name_send(state, ev, be_ctx->sysdb, NULL,
+                                            be_ctx->domain, user, attrs);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_search_user_by_name_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq, search_user_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void search_user_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_user_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_user_info_state);
+    int ret;
+    const char **attrs;
+
+
+    ret = sysdb_search_user_recv(subreq, state, &state->user_msg);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(9, ("Found user info for user [%s].\n", state->user));
+    state->user_dn = state->user_msg->dn;
+    state->user_orig_dn = ldb_msg_find_attr_as_string(state->user_msg,
+                                                      SYSDB_ORIG_DN, NULL);
+    if (state->user_dn == NULL) {
+        DEBUG(1, ("Original DN of user [%s] not available.\n", state->user));
+        ret = EINVAL;
+        goto failed;
+    }
+    DEBUG(9, ("Found original DN [%s] for user [%s].\n", state->user_orig_dn,
+                                                         state->user));
+
+    attrs = talloc_array(state, const char *, 2);
+    if (attrs == NULL) {
+        DEBUG(1, ("talloc_array failed.\n"));
+        ret = ENOMEM;
+        goto failed;
+    }
+    attrs[0] = SYSDB_ORIG_DN;
+    attrs[1] = NULL;
+
+    subreq = sysdb_asq_search_send(state, state->ev, state->be_ctx->sysdb, NULL,
+                                   state->be_ctx->domain, state->user_dn, NULL,
+                                   SYSDB_MEMBEROF, attrs);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_asq_search_send failed.\n"));
+        ret = ENOMEM;
+        goto failed;
+    }
+
+    tevent_req_set_callback(subreq, search_groups_done, req);
+    return;
+
+failed:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void search_groups_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_user_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_user_info_state);
+    int ret;
+    int i;
+    struct ldb_message **msg;
+
+    ret = sysdb_asq_search_recv(subreq, state, &state->groups_count, &msg);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->groups_count == 0) {
+        tevent_req_done(req);
+        return;
+    }
+
+    state->groups = talloc_array(state, const char *, state->groups_count);
+    if (state->groups == NULL) {
+        DEBUG(1, ("talloc_groups failed.\n"));
+        ret = ENOMEM;
+        goto failed;
+    }
+
+    for(i = 0; i < state->groups_count; i++) {
+        if (msg[i]->num_elements != 1) {
+            DEBUG(1, ("Unexpected number of elements.\n"));
+            ret = EINVAL;
+            goto failed;
+        }
+
+        if (msg[i]->elements[0].num_values != 1) {
+            DEBUG(1, ("Unexpected number of values.\n"));
+            ret = EINVAL;
+            goto failed;
+        }
+
+        state->groups[i] = talloc_strndup(state->groups,
+                                          (const char *) msg[i]->elements[0].values[0].data,
+                                          msg[i]->elements[0].values[0].length);
+        if (state->groups[i] == NULL) {
+            DEBUG(1, ("talloc_strndup failed.\n"));
+            ret = ENOMEM;
+            goto failed;
+        }
+
+        DEBUG(9, ("Found group [%s].\n", state->groups[i]));
+    }
+
+    tevent_req_done(req);
+    return;
+
+failed:
+    talloc_free(state->groups);
+    tevent_req_error(req, ret);
+    return;
+}
+
+static int hbac_get_user_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+                                   const char **user_dn, size_t *groups_count,
+                                   const char ***groups)
+{
+    struct hbac_get_user_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_user_info_state);
+    enum tevent_req_state tstate;
+    uint64_t err;
+    int i;
+
+    if (tevent_req_is_error(req, &tstate, &err)) {
+        if (err) return err;
+        return EIO;
+    }
+
+    *user_dn = talloc_steal(memctx, state->user_orig_dn);
+    *groups_count = state->groups_count;
+    for (i = 0; i < state->groups_count; i++) {
+        talloc_steal(memctx, state->groups[i]);
+    }
+    *groups = talloc_steal(memctx, state->groups);
+
+    return EOK;
+}
+
+
+struct hbac_get_host_info_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *sdap_ctx;
+    struct sysdb_ctx *sysdb;
+    struct sysdb_handle *handle;
+
+    const char *hostname;
+    char *host_filter;
+    char *host_search_base;
+    const char **host_attrs;
+
+    struct sysdb_attrs **host_reply_list;
+    size_t host_reply_count;
+    size_t current_item;
+    struct hbac_host_info **hbac_host_info;
+};
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq);
+static void host_get_host_memberof_done(struct tevent_req *subreq);
+static void hbac_get_host_info_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_get_host_info_store_prepare(struct tevent_req *req);
+static void hbac_get_host_info_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_host_info_send(TALLOC_CTX *memctx,
+                                                  struct tevent_context *ev,
+                                                  struct sdap_id_ctx *sdap_ctx,
+                                                  struct sysdb_ctx *sysdb,
+                                                  const char *ipa_domain,
+                                                  const char *hostname)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct hbac_get_host_info_state *state;
+    int ret;
+
+    if (hostname == NULL || ipa_domain == NULL) {
+        DEBUG(1, ("Missing fqdn or domain.\n"));
+        return NULL;
+    }
+
+    req = tevent_req_create(memctx, &state, struct hbac_get_host_info_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->sdap_ctx = sdap_ctx;
+    state->sysdb = sysdb;
+    state->handle = NULL;
+    state->hostname= hostname;
+
+    state->host_reply_list = NULL;
+    state->host_reply_count = 0;
+    state->current_item = 0;
+
+    state->host_filter = talloc_asprintf(state,
+                      "(&(|(fqdn=%s)(serverhostname=%s))(objectclass=ipaHost))",
+                      hostname, hostname);
+    if (state->host_filter == NULL) {
+        DEBUG(1, ("Failed to create filter.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    state->host_search_base = talloc_asprintf(state, IPA_HOST_BASE_TMPL,
+                                              ipa_domain);
+    if (state->host_search_base == NULL) {
+        DEBUG(1, ("Failed to create host search base.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    state->host_attrs = talloc_array(state, const char *, 5);
+    if (state->host_attrs == NULL) {
+        DEBUG(1, ("Failed to allocate host attribute list.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    state->host_attrs[0] = IPA_HOST_MEMBEROF;
+    state->host_attrs[1] = IPA_HOST_SERVERHOSTNAME;
+    state->host_attrs[2] = IPA_HOST_FQDN;
+    state->host_attrs[3] = "objectClass";
+    state->host_attrs[4] = NULL;
+
+    if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+        if (sdap_ctx->gsh != NULL) {
+            talloc_zfree(sdap_ctx->gsh);
+        }
+
+        subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts, NULL);
+        if (!subreq) {
+            DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, hbac_get_host_info_connect_done, req);
+
+        return req;
+    }
+
+    subreq = sdap_get_generic_send(state, state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   state->host_search_base,
+                                   LDAP_SCOPE_SUB,
+                                   state->host_filter,
+                                   state->host_attrs);
+
+    if (subreq == NULL) {
+        DEBUG(1, ("sdap_get_generic_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq, host_get_host_memberof_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    int ret;
+
+    ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+                                NULL);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sdap_get_generic_send(state, state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   state->host_search_base,
+                                   LDAP_SCOPE_SUB,
+                                   state->host_filter,
+                                   state->host_attrs);
+
+    if (subreq == NULL) {
+        DEBUG(1, ("sdap_get_generic_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq, host_get_host_memberof_done, req);
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void host_get_host_memberof_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    int ret;
+    int i;
+    int v;
+    struct ldb_message_element *el;
+    struct hbac_host_info **hhi;
+
+    ret = sdap_get_generic_recv(subreq, state, &state->host_reply_count,
+                                &state->host_reply_list);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (state->host_reply_list == NULL) {
+        DEBUG(1, ("Host [%s] not found in IPA server.\n", state->hostname));
+        ret = ENOENT;
+        goto fail;
+    }
+
+    hhi = talloc_array(state, struct hbac_host_info *, state->host_reply_count + 1);
+    if (hhi == NULL) {
+        DEBUG(1, ("talloc failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    memset(hhi, 0,
+           sizeof(struct hbac_host_info *) * (state->host_reply_count + 1));
+
+    for (i = 0; i < state->host_reply_count; i++) {
+        hhi[i] = talloc_zero(state, struct hbac_host_info);
+        if (hhi[i] == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        ret = sysdb_attrs_get_el(state->host_reply_list[i], SYSDB_ORIG_DN, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        DEBUG(9, ("OriginalDN: [%.*s].\n", el->values[0].length,
+                                           (char *)el->values[0].data));
+        hhi[i]->dn = talloc_strndup(hhi, (char *)el->values[0].data,
+                                   el->values[0].length);
+        if (hhi[i]->dn == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        ret = sysdb_attrs_get_el(state->host_reply_list[i],
+                                 IPA_HOST_SERVERHOSTNAME, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        DEBUG(9, ("ServerHostName: [%.*s].\n", el->values[0].length,
+                                               (char *)el->values[0].data));
+        hhi[i]->serverhostname = talloc_strndup(hhi, (char *)el->values[0].data,
+                                               el->values[0].length);
+        if (hhi[i]->serverhostname == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        ret = sysdb_attrs_get_el(state->host_reply_list[i],
+                                 IPA_HOST_FQDN, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        DEBUG(9, ("FQDN: [%.*s].\n", el->values[0].length,
+                                     (char *)el->values[0].data));
+        hhi[i]->fqdn = talloc_strndup(hhi, (char *)el->values[0].data,
+                                               el->values[0].length);
+        if (hhi[i]->fqdn == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        ret = sysdb_attrs_get_el(state->host_reply_list[i],
+                                 IPA_HOST_MEMBEROF, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+
+        hhi[i]->memberof = talloc_array(hhi, const char *, el->num_values + 1);
+        if (hhi[i]->memberof == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+        memset(hhi[i]->memberof, 0,
+               sizeof(const char *) * (el->num_values + 1));
+
+        for(v = 0; v < el->num_values; v++) {
+            DEBUG(9, ("%s: [%.*s].\n", IPA_HOST_MEMBEROF, el->values[v].length,
+                                     (const char *)el->values[v].data));
+            hhi[i]->memberof[v] = talloc_strndup(hhi,
+                                               (const char *)el->values[v].data,
+                                               el->values[v].length);
+            if (hhi[i]->memberof[v] == NULL) {
+                ret = ENOMEM;
+                goto fail;
+            }
+        }
+    }
+
+    state->hbac_host_info = hhi;
+
+    subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_transaction_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, hbac_get_host_info_sysdb_transaction_started, req);
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_get_host_info_sysdb_transaction_started(
+                                                      struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    int ret;
+
+    ret = sysdb_transaction_recv(subreq, state, &state->handle);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->current_item = 0;
+    hbac_get_host_info_store_prepare(req);
+    return;
+}
+
+static void hbac_get_host_info_store_prepare(struct tevent_req *req)
+{
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    int ret;
+    char *object_name;
+    struct ldb_message_element *el;
+    struct tevent_req *subreq;
+
+    if (state->current_item < state->host_reply_count) {
+        ret = sysdb_attrs_get_el(state->host_reply_list[state->current_item],
+                                 IPA_HOST_FQDN, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        object_name = talloc_strndup(state, (const char *)el->values[0].data,
+                                     el->values[0].length);
+        if (object_name == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+        DEBUG(9, ("Fqdn [%s].\n", object_name));
+
+        subreq = sysdb_store_custom_send(state, state->ev,
+                                         state->handle,
+                                         state->sdap_ctx->be->domain,
+                                         object_name,
+                                         HBAC_HOSTS_SUBDIR,
+                                         state->host_reply_list[state->current_item]);
+
+        if (subreq == NULL) {
+            DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, hbac_get_host_info_store_done, req);
+        return;
+    }
+
+    subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_get_host_info_store_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    int ret;
+
+    ret = sysdb_store_custom_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->current_item++;
+    hbac_get_host_info_store_prepare(req);
+}
+
+static int hbac_get_host_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+                                   struct hbac_host_info ***hhi)
+{
+    struct hbac_get_host_info_state *state = tevent_req_data(req,
+                                               struct hbac_get_host_info_state);
+    enum tevent_req_state tstate;
+    uint64_t err;
+
+    if (tevent_req_is_error(req, &tstate, &err)) {
+        if (err) return err;
+        return EIO;
+    }
+
+    *hhi = talloc_steal(memctx, state->hbac_host_info);
+    return EOK;
+}
+
+
+struct hbac_get_rules_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *sdap_ctx;
+    struct sysdb_ctx *sysdb;
+    struct sysdb_handle *handle;
+
+    const char *host_dn;
+    const char **memberof;
+    char *hbac_filter;
+    char *hbac_search_base;
+    const char **hbac_attrs;
+
+    struct ldb_message *old_rules;
+    struct sysdb_attrs **hbac_reply_list;
+    size_t hbac_reply_count;
+    int current_item;
+};
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq);
+static void hbac_rule_get_done(struct tevent_req *subreq);
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq);
+static void hbac_rule_store_prepare(struct tevent_req *req);
+static void hbac_rule_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_rules_send(TALLOC_CTX *memctx,
+                                             struct tevent_context *ev,
+                                             struct sdap_id_ctx *sdap_ctx,
+                                             struct sysdb_ctx *sysdb,
+                                             const char *ipa_domain,
+                                             const char *host_dn,
+                                             const char **memberof)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct hbac_get_rules_state *state;
+    int ret;
+    int i;
+
+    if (host_dn == NULL || ipa_domain == NULL) {
+        DEBUG(1, ("Missing host_dn or domain.\n"));
+        return NULL;
+    }
+
+    req = tevent_req_create(memctx, &state, struct hbac_get_rules_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->sdap_ctx = sdap_ctx;
+    state->sysdb = sysdb;
+    state->handle = NULL;
+    state->host_dn = host_dn;
+    state->memberof = memberof;
+
+    state->old_rules = NULL;
+    state->hbac_reply_list = NULL;
+    state->hbac_reply_count = 0;
+    state->current_item = 0;
+
+    state->hbac_search_base = talloc_asprintf(state, IPA_HBAC_BASE_TMPL,
+                                              ipa_domain);
+    if (state->hbac_search_base == NULL) {
+        DEBUG(1, ("Failed to create HBAC search base.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    state->hbac_attrs = talloc_array(state, const char *, 13);
+    if (state->hbac_attrs == NULL) {
+        DEBUG(1, ("Failed to allocate HBAC attribute list.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    state->hbac_attrs[0] = IPA_ACCESS_RULE_TYPE;
+    state->hbac_attrs[1] = IPA_MEMBER_USER;
+    state->hbac_attrs[2] = IPA_USER_CATEGORY;
+    state->hbac_attrs[3] = IPA_SERVICE_NAME;
+    state->hbac_attrs[4] = IPA_SOURCE_HOST;
+    state->hbac_attrs[5] = IPA_SOURCE_HOST_CATEGORY;
+    state->hbac_attrs[6] = IPA_EXTERNAL_HOST;
+    state->hbac_attrs[7] = IPA_ACCESS_TIME;
+    state->hbac_attrs[8] = IPA_UNIQUE_ID;
+    state->hbac_attrs[9] = IPA_ENABLED_FLAG;
+    state->hbac_attrs[10] = IPA_CN;
+    state->hbac_attrs[11] = "objectclass";
+    state->hbac_attrs[12] = NULL;
+
+    state->hbac_filter = talloc_asprintf(state,
+                                         "(&(objectclass=ipaHBACRule)(|(%s=%s)",
+                                         IPA_MEMBER_HOST, host_dn);
+    if (state->hbac_filter == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    for (i = 0; memberof[i] != NULL; i++) {
+        state->hbac_filter = talloc_asprintf_append(state->hbac_filter,
+                                                    "(%s=%s)",
+                                                    IPA_MEMBER_HOST,
+                                                    memberof[i]);
+        if (state->hbac_filter == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+    }
+    state->hbac_filter = talloc_asprintf_append(state->hbac_filter, "))");
+    if (state->hbac_filter == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    DEBUG(9, ("HBAC rule filter: [%s].\n", state->hbac_filter));
+
+    if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+        if (sdap_ctx->gsh != NULL) {
+            talloc_zfree(sdap_ctx->gsh);
+        }
+
+        subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts, NULL);
+        if (!subreq) {
+            DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, hbac_get_rules_connect_done, req);
+
+        return req;
+    }
+
+    subreq = sdap_get_generic_send(state, state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   state->hbac_search_base,
+                                   LDAP_SCOPE_SUB,
+                                   state->hbac_filter,
+                                   state->hbac_attrs);
+
+    if (subreq == NULL) {
+        DEBUG(1, ("sdap_get_generic_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+
+    ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+                                NULL);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sdap_get_generic_send(state, state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   state->hbac_search_base,
+                                   LDAP_SCOPE_SUB,
+                                   state->hbac_filter,
+                                   state->hbac_attrs);
+
+    if (subreq == NULL) {
+        DEBUG(1, ("sdap_get_generic_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_rule_get_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+    int i;
+    struct ldb_message_element *el;
+
+    ret = sdap_get_generic_recv(subreq, state, &state->hbac_reply_count,
+                                &state->hbac_reply_list);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    for (i = 0; i < state->hbac_reply_count; i++) {
+        ret = sysdb_attrs_get_el(state->hbac_reply_list[i], SYSDB_ORIG_DN, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        DEBUG(9, ("OriginalDN: [%s].\n", (const char *)el->values[0].data));
+    }
+
+    if (state->hbac_reply_count == 0) {
+        tevent_req_done(req);
+        return;
+    }
+
+    subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_transaction_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, hbac_rule_sysdb_transaction_started, req);
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+    struct ldb_dn *hbac_base_dn;
+
+    ret = sysdb_transaction_recv(subreq, state, &state->handle);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    hbac_base_dn = sysdb_custom_subtree_dn(state->sysdb, state,
+                                           state->sdap_ctx->be->domain->name,
+                                           HBAC_RULES_SUBDIR);
+    if (hbac_base_dn == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    subreq = sysdb_delete_recursive_send(state, state->ev, state->handle,
+                                         hbac_base_dn, true);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_delete_recursive_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, hbac_rule_sysdb_delete_done, req);
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+
+    ret = sysdb_delete_recursive_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->current_item = 0;
+    hbac_rule_store_prepare(req);
+}
+
+static void hbac_rule_store_prepare(struct tevent_req *req)
+{
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+    struct ldb_message_element *el;
+    struct tevent_req *subreq;
+    char *object_name;
+
+    if (state->current_item < state->hbac_reply_count) {
+
+        ret = sysdb_attrs_get_el(state->hbac_reply_list[state->current_item],
+                                 IPA_UNIQUE_ID, &el);
+        if (ret != EOK) {
+            DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+            goto fail;
+        }
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            goto fail;
+        }
+        object_name = talloc_strndup(state, (const char *)el->values[0].data,
+                                     el->values[0].length);
+        if (object_name == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+        DEBUG(9, ("IPAUniqueId: [%s].\n", object_name));
+
+        subreq = sysdb_store_custom_send(state, state->ev,
+                                         state->handle,
+                                         state->sdap_ctx->be->domain,
+                                         object_name,
+                                         HBAC_RULES_SUBDIR,
+                                         state->hbac_reply_list[state->current_item]);
+
+        if (subreq == NULL) {
+            DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, hbac_rule_store_done, req);
+        return;
+    }
+
+    subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+    if (subreq == NULL) {
+        DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+    return;
+
+fail:
+    tevent_req_error(req, ret);
+    return;
+}
+
+static void hbac_rule_store_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    int ret;
+
+    ret = sysdb_store_custom_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->current_item++;
+    hbac_rule_store_prepare(req);
+}
+
+static int hbac_get_rules_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+                               size_t *hbac_rule_count,
+                               struct sysdb_attrs ***hbac_rule_list)
+{
+    struct hbac_get_rules_state *state = tevent_req_data(req,
+                                                     struct hbac_get_rules_state);
+    enum tevent_req_state tstate;
+    uint64_t err;
+    int i;
+
+    if (tevent_req_is_error(req, &tstate, &err)) {
+        if (err) return err;
+        return EIO;
+    }
+
+    *hbac_rule_count = state->hbac_reply_count;
+    *hbac_rule_list = talloc_steal(memctx, state->hbac_reply_list);
+    for (i = 0; i < state->hbac_reply_count; i++) {
+        talloc_steal(memctx, state->hbac_reply_list[i]);
+    }
+    return EOK;
+}
+
+enum hbac_result {
+    HBAC_ALLOW = 1,
+    HBAC_DENY,
+    HBAC_NOT_APPLICABLE
+};
+
+enum check_result {
+    RULE_APPLICABLE = 0,
+    RULE_NOT_APPLICABLE,
+    RULE_ERROR
+};
+
+enum check_result check_service(struct pam_data *pd,
+                                struct sysdb_attrs *rule_attrs)
+{
+    int ret;
+    int i;
+    struct ldb_message_element *el;
+
+    if (pd->service == NULL) {
+        DEBUG(1, ("No service in pam data, assuming error.\n"));
+        return RULE_ERROR;
+    }
+
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_SERVICE_NAME, &el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return RULE_ERROR;
+    }
+    if (el->num_values == 0) {
+        DEBUG(9, ("No services in rule specified, assuming rule applies.\n"));
+        return RULE_APPLICABLE;
+    } else {
+        for (i = 0; i < el->num_values; i++) {
+            if (strncasecmp(pd->service, (const char *) el->values[i].data,
+                            el->values[i].length) == 0) {
+                DEBUG(9, ("Service [%s] found, rule applies.\n",
+                          pd->service));
+                return RULE_APPLICABLE;
+            }
+        }
+        DEBUG(9, ("No matching service found, rule does not apply.\n"));
+        return RULE_NOT_APPLICABLE;
+    }
+
+    return RULE_ERROR;
+}
+
+enum check_result check_user(struct hbac_ctx *hbac_ctx,
+                             struct sysdb_attrs *rule_attrs)
+{
+    int ret;
+    int i;
+    int g;
+    struct ldb_message_element *el;
+
+    if (hbac_ctx->user_dn == NULL) {
+        DEBUG(1, ("No user DN available, this should never happen.\n"));
+        return RULE_ERROR;
+    }
+
+/* TODO: add group and category checks */
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return RULE_ERROR;
+    }
+    if (el->num_values == 0) {
+        DEBUG(9, ("No user specified, assuming rule applies.\n"));
+        return RULE_APPLICABLE;
+    } else {
+        for (i = 0; i < el->num_values; i++) {
+            DEBUG(9, ("Searching matches for [%.*s].\n", el->values[i].length,
+                                           (const char *) el->values[i].data));
+            DEBUG(9, ("Checking user [%s].\n", hbac_ctx->user_dn));
+            if (strncmp(hbac_ctx->user_dn, (const char *) el->values[i].data,
+                       el->values[i].length) == 0) {
+                DEBUG(9, ("User [%s] found, rule applies.\n",
+                          hbac_ctx->user_dn));
+                return RULE_APPLICABLE;
+            }
+
+            for (g = 0; g < hbac_ctx->groups_count; g++) {
+                DEBUG(9, ("Checking group [%s].\n", hbac_ctx->groups[g]));
+                if (strncmp(hbac_ctx->groups[g],
+                           (const char *) el->values[i].data,
+                           el->values[i].length) == 0) {
+                    DEBUG(9, ("Group [%s] found, rule applies.\n",
+                              hbac_ctx->groups[g]));
+                    return RULE_APPLICABLE;
+                }
+            }
+        }
+        DEBUG(9, ("No matching user found, rule does not apply.\n"));
+        return RULE_NOT_APPLICABLE;
+    }
+
+    return RULE_ERROR;
+}
+
+enum check_result check_remote_hosts(struct pam_data *pd,
+                                     struct sysdb_attrs *rule_attrs)
+{
+    int ret;
+    int i;
+    struct ldb_message_element *src_el;
+    struct ldb_message_element *ext_el;
+    const char *remote_hostname;
+
+    if (pd->rhost == NULL && pd->tty == NULL) {
+        DEBUG(1, ("Neither remote host nor tty specified in pam data, "
+                  "assuming error.\n"));
+        return RULE_ERROR;
+    }
+
+    if (pd->rhost == NULL) {
+        DEBUG(5, ("No remote host specified, but tty is set [%s], "
+                  "assuming 'localhost'.\n", pd->tty));
+        remote_hostname = "localhost";
+    } else {
+        remote_hostname = pd->rhost;
+    }
+
+/* TODO: add group and category checks */
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST, &src_el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return RULE_ERROR;
+    }
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &ext_el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return RULE_ERROR;
+    }
+
+    if (src_el->num_values == 0 && ext_el->num_values == 0) {
+        DEBUG(9, ("No remote host specified, assuming rule applies.\n"));
+        return RULE_APPLICABLE;
+    } else {
+        for (i = 0; i < src_el->num_values; i++) {
+            if (strncasecmp(remote_hostname,
+                            (const char *) src_el->values[i].data,
+                            src_el->values[i].length) == 0) {
+                DEBUG(9, ("Source host [%s] found, rule applies.\n",
+                          remote_hostname));
+                return RULE_APPLICABLE;
+            }
+        }
+        for (i = 0; i < ext_el->num_values; i++) {
+            if (strncasecmp(remote_hostname,
+                            (const char *) ext_el->values[i].data,
+                            ext_el->values[i].length) == 0) {
+                DEBUG(9, ("External host [%s] found, rule applies.\n",
+                          remote_hostname));
+                return RULE_APPLICABLE;
+            }
+        }
+        DEBUG(9, ("No matching remote host found.\n"));
+        return RULE_NOT_APPLICABLE;
+    }
+
+    return RULE_ERROR;
+}
+
+static errno_t check_if_rule_applies(enum hbac_result *result,
+                                     struct hbac_ctx *hbac_ctx,
+                                     struct sysdb_attrs *rule_attrs) {
+    int ret;
+    struct ldb_message_element *el;
+    enum hbac_result rule_type;
+    char *rule_name;
+    struct pam_data *pd = hbac_ctx->pd;
+
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_CN, &el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return ret;
+    }
+    if (el->num_values == 0) {
+        DEBUG(4, ("rule has no name, assuming '(none)'.\n"));
+        rule_name = talloc_strdup(rule_attrs, "(none)");
+    } else {
+        rule_name = talloc_strndup(rule_attrs, (const char*) el->values[0].data,
+                                   el->values[0].length);
+    }
+    if (rule_name == NULL) {
+        DEBUG(1, ("talloc_strdup failed.\n"));
+        return ENOMEM;
+    }
+    DEBUG(9, ("Processsing rule [%s].\n", rule_name));
+
+    /* rule type */
+    ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_RULE_TYPE, &el);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+        return ret;
+    }
+    if (el->num_values == 0) {
+        DEBUG(4, ("rule has no type, assuming 'deny'.\n"));
+        rule_type = HBAC_DENY;
+    } else if (el->num_values == 1) {
+        if (strncasecmp((const char *) el->values[0].data, "allow",
+                        el->values[0].length) == 0) {
+            rule_type = HBAC_ALLOW;
+        } else {
+            rule_type = HBAC_DENY;
+        }
+    } else {
+        DEBUG(1, ("rule has an unsupported number of values [%d].\n",
+                  el->num_values));
+        return EINVAL;
+    }
+
+    ret = check_service(pd, rule_attrs);
+    if (ret != RULE_APPLICABLE) {
+        goto not_applicable;
+    }
+
+    ret = check_user(hbac_ctx, rule_attrs);
+    if (ret != RULE_APPLICABLE) {
+        goto not_applicable;
+    }
+
+    ret = check_remote_hosts(pd, rule_attrs);
+    if (ret != RULE_APPLICABLE) {
+        goto not_applicable;
+    }
+
+    *result = rule_type;
+
+    return EOK;
+
+not_applicable:
+    if (ret == RULE_NOT_APPLICABLE) {
+        *result = HBAC_NOT_APPLICABLE;
+    } else {
+        *result = HBAC_DENY;
+    }
+    return EOK;
+}
+
+static int evaluate_ipa_hbac_rules(bool *access_allowed,
+                                   struct hbac_ctx *hbac_ctx)
+{
+    bool allow_matched = false;
+    enum hbac_result result;
+    int ret;
+    int i;
+
+    *access_allowed = false;
+
+    for (i = 0; i < hbac_ctx->hbac_rule_count ; i++) {
+
+        ret = check_if_rule_applies(&result, hbac_ctx,
+                                    hbac_ctx->hbac_rule_list[i]);
+        if (ret != EOK) {
+            DEBUG(1, ("check_if_rule_applies failed.\n"));
+            return ret;
+        }
+
+        switch (result) {
+            case HBAC_DENY:
+                DEBUG(3, ("Access denied by single rule.\n"));
+                return EOK;
+                break;
+            case HBAC_ALLOW:
+                allow_matched = true;
+                DEBUG(9, ("Current rule allows access.\n"));
+                break;
+            default:
+                DEBUG(9, ("Current rule does not apply.\n"));
+        }
+
+    }
+
+    *access_allowed = allow_matched;
+
+    return EOK;
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req);
+static void hbac_get_rules_done(struct tevent_req *req);
+static void hbac_get_user_info_done(struct tevent_req *req);
+
+void ipa_access_handler(struct be_req *be_req)
+{
+    struct tevent_req *req;
+    struct pam_data *pd;
+    struct hbac_ctx *hbac_ctx;
+    int pam_status = PAM_SYSTEM_ERR;
+    struct ipa_access_ctx *ipa_access_ctx;
+
+    pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+    hbac_ctx = talloc_zero(be_req, struct hbac_ctx);
+    if (hbac_ctx == NULL) {
+        DEBUG(1, ("talloc failed.\n"));
+        goto fail;
+    }
+    hbac_ctx->be_req = be_req;
+    hbac_ctx->pd = pd;
+    ipa_access_ctx = talloc_get_type(
+                              be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+                              struct ipa_access_ctx);
+    hbac_ctx->sdap_ctx = ipa_access_ctx->sdap_ctx;
+    hbac_ctx->ipa_options = ipa_access_ctx->ipa_options;
+
+    req = hbac_get_host_info_send(hbac_ctx, be_req->be_ctx->ev,
+                                  hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb,
+                                  dp_opt_get_string(hbac_ctx->ipa_options,
+                                                    IPA_DOMAIN),
+                                  dp_opt_get_string(hbac_ctx->ipa_options,
+                                                    IPA_HOSTNAME));
+    if (req == NULL) {
+        DEBUG(1, ("hbac_get_host_info_send failed.\n"));
+        goto fail;
+    }
+
+    tevent_req_set_callback(req, hbac_get_host_info_done, hbac_ctx);
+    return;
+
+fail:
+    ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req)
+{
+    struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+    struct be_req *be_req = hbac_ctx->be_req;
+    int ret;
+    int pam_status = PAM_SYSTEM_ERR;
+    const char *ipa_hostname;
+    int i;
+
+    ret = hbac_get_host_info_recv(req, hbac_ctx, &hbac_ctx->hbac_host_info);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    ipa_hostname = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+    if (ipa_hostname == NULL) {
+        DEBUG(1, ("Missing ipa_hostname, this should never happen.\n"));
+        ret = EINVAL;
+        goto fail;
+    }
+
+    for (i = 0; hbac_ctx->hbac_host_info[i] != NULL; i++) {
+        if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn, ipa_hostname) == 0 ||
+            strcmp(hbac_ctx->hbac_host_info[i]->serverhostname,
+                   ipa_hostname) == 0) {
+            break;
+        }
+    }
+    if (hbac_ctx->hbac_host_info[i] == NULL) {
+        DEBUG(1, ("Missing host info for [%s].\n", ipa_hostname));
+        ret = EINVAL;
+        goto fail;
+    }
+    req = hbac_get_rules_send(hbac_ctx, be_req->be_ctx->ev, hbac_ctx->sdap_ctx,
+                              be_req->be_ctx->sysdb,
+                              dp_opt_get_string(hbac_ctx->ipa_options,
+                                                IPA_DOMAIN),
+                              hbac_ctx->hbac_host_info[i]->dn,
+                              hbac_ctx->hbac_host_info[i]->memberof);
+    if (req == NULL) {
+        DEBUG(1, ("hbac_get_rules_send failed.\n"));
+        goto fail;
+    }
+
+    tevent_req_set_callback(req, hbac_get_rules_done, hbac_ctx);
+    return;
+
+fail:
+    ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_rules_done(struct tevent_req *req)
+{
+    struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+    struct pam_data *pd = hbac_ctx->pd;
+    struct be_req *be_req = hbac_ctx->be_req;
+    int ret;
+    int pam_status = PAM_SYSTEM_ERR;
+
+    ret = hbac_get_rules_recv(req, hbac_ctx, &hbac_ctx->hbac_rule_count,
+                              &hbac_ctx->hbac_rule_list);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    req = hbac_get_user_info_send(hbac_ctx, be_req->be_ctx->ev, be_req->be_ctx,
+                                  pd->user);
+    if (req == NULL) {
+        DEBUG(1, ("hbac_get_user_info_send failed.\n"));
+        goto fail;
+    }
+
+    tevent_req_set_callback(req, hbac_get_user_info_done, hbac_ctx);
+    return;
+
+fail:
+    ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_user_info_done(struct tevent_req *req)
+{
+    struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+    struct be_req *be_req = hbac_ctx->be_req;
+    int ret;
+    int pam_status = PAM_SYSTEM_ERR;
+    bool access_allowed = false;
+
+    ret = hbac_get_user_info_recv(req, hbac_ctx, &hbac_ctx->user_dn,
+                                  &hbac_ctx->groups_count,
+                                  &hbac_ctx->groups);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        goto failed;
+    }
+
+    ret = evaluate_ipa_hbac_rules(&access_allowed, hbac_ctx);
+    if (ret != EOK) {
+        DEBUG(1, ("evaluate_ipa_hbac_rules failed.\n"));
+        goto failed;
+    }
+
+    if (access_allowed) {
+        pam_status = PAM_SUCCESS;
+        DEBUG(5, ("Access allowed.\n"));
+    } else {
+        pam_status = PAM_PERM_DENIED;
+        DEBUG(3, ("Access denied.\n"));
+    }
+
+failed:
+    ipa_access_reply(be_req, pam_status);
+}
diff --git a/server/providers/ipa/ipa_access.h b/server/providers/ipa/ipa_access.h
new file mode 100644
index 0000000..e4903cb
--- /dev/null
+++ b/server/providers/ipa/ipa_access.h
@@ -0,0 +1,55 @@
+/*
+    SSSD
+
+    IPA Backend Module -- Access control
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2009 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_ACCESS_H_
+#define _IPA_ACCESS_H_
+
+#include "providers/ldap/ldap_common.h"
+
+enum ipa_access_mode {
+    IPA_ACCESS_DENY = 0,
+    IPA_ACCESS_ALLOW
+};
+
+struct ipa_access_ctx {
+    struct sdap_id_ctx *sdap_ctx;
+    struct dp_option *ipa_options;
+};
+
+struct hbac_ctx {
+    struct sdap_id_ctx *sdap_ctx;
+    struct dp_option *ipa_options;
+    struct be_req *be_req;
+    struct pam_data *pd;
+    struct hbac_host_info **hbac_host_info;
+    struct sysdb_attrs **hbac_rule_list;
+    size_t hbac_rule_count;
+    const char *user_dn;
+    size_t groups_count;
+    const char **groups;
+};
+
+void ipa_access_handler(struct be_req *be_req);
+
+#endif /* _IPA_ACCESS_H_ */
diff --git a/server/providers/ipa/ipa_init.c b/server/providers/ipa/ipa_init.c
index d1439de..74e0067 100644
--- a/server/providers/ipa/ipa_init.c
+++ b/server/providers/ipa/ipa_init.c
@@ -29,6 +29,7 @@
 
 #include "providers/ipa/ipa_common.h"
 #include "providers/krb5/krb5_auth.h"
+#include "providers/ipa/ipa_access.h"
 
 struct ipa_options *ipa_options = NULL;
 
@@ -48,6 +49,11 @@ struct bet_ops ipa_chpass_ops = {
     .finalize = NULL,
 };
 
+struct bet_ops ipa_access_ops = {
+    .handler = ipa_access_handler,
+    .finalize = NULL
+};
+
 int sssm_ipa_init(struct be_ctx *bectx,
                   struct bet_ops **ops,
                   void **pvt_data)
@@ -186,7 +192,46 @@ done:
 
 int sssm_ipa_chpass_init(struct be_ctx *bectx,
                          struct bet_ops **ops,
-                         void **pvt_auth_data)
+                         void **pvt_data)
+{
+    int ret;
+    ret = sssm_ipa_auth_init(bectx, ops, pvt_data);
+    *ops = &ipa_chpass_ops;
+    return ret;
+}
+
+int sssm_ipa_access_init(struct be_ctx *bectx,
+                         struct bet_ops **ops,
+                         void **pvt_data)
 {
-    return sssm_ipa_auth_init(bectx, ops, pvt_auth_data);
+    int ret;
+    struct ipa_access_ctx *ipa_access_ctx;
+
+    ipa_access_ctx = talloc_zero(bectx, struct ipa_access_ctx);
+    if (ipa_access_ctx == NULL) {
+        DEBUG(1, ("talloc_zero failed.\n"));
+        return ENOMEM;
+    }
+
+    ret = sssm_ipa_init(bectx, ops, (void **) &ipa_access_ctx->sdap_ctx);
+    if (ret != EOK) {
+        DEBUG(1, ("sssm_ipa_init failed.\n"));
+        goto done;
+    }
+
+    ret = dp_copy_options(ipa_access_ctx, ipa_options->basic,
+                          IPA_OPTS_BASIC, &ipa_access_ctx->ipa_options);
+    if (ret != EOK) {
+        DEBUG(1, ("dp_copy_options failed.\n"));
+        goto done;
+    }
+
+    *ops = &ipa_access_ops;
+    *pvt_data = ipa_access_ctx;
+
+done:
+    if (ret != EOK) {
+        talloc_free(ipa_access_ctx);
+    }
+    return ret;
 }
-- 
1.6.2.5



More information about the sssd-devel mailing list