From 053acab2043248e2e4a4fb0d31dd5ef9972afdf9 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Wed, 18 May 2011 16:38:38 -0400
Subject: [PATCH 3/9] Add HBAC evaluator and tests

---
 Makefile.am                        |   28 ++-
 configure.ac                       |    3 +-
 contrib/sssd.spec.in               |   29 ++
 src/providers/ipa/hbac_evaluator.c |  217 +++++++++++++
 src/providers/ipa/ipa_hbac.h       |  158 +++++++++
 src/providers/ipa/ipa_hbac.pc.in   |   12 +
 src/tests/ipa_hbac-tests.c         |  618 ++++++++++++++++++++++++++++++++++++
 7 files changed, 1063 insertions(+), 2 deletions(-)
 create mode 100644 src/providers/ipa/hbac_evaluator.c
 create mode 100644 src/providers/ipa/ipa_hbac.h
 create mode 100644 src/providers/ipa/ipa_hbac.pc.in
 create mode 100644 src/tests/ipa_hbac-tests.c

diff --git a/Makefile.am b/Makefile.am
index b8423bab3d28dcd46a53f74a3a6ad5889a96107d..ee7c4685dc8fc269aa584b24ef1dee955e28366f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -33,6 +33,7 @@ initdir = @initdir@
 systemdunitdir = @systemdunitdir@
 logpath = @logpath@
 pubconfpath = @pubconfpath@
+pkgconfigdir = $(libdir)/pkgconfig
 
 AM_CFLAGS =
 if WANT_AUX_INFO
@@ -44,6 +45,8 @@ if HAVE_GCC
                  -Werror-implicit-function-declaration
 endif
 
+dist_pkgconfig_DATA =
+
 ACLOCAL_AMFLAGS = -I m4 -I .
 
 sbin_PROGRAMS = \
@@ -80,7 +83,8 @@ if HAVE_CHECK
         ipa_ldap_opt-tests \
         simple_access-tests \
         crypto-tests \
-        util-tests
+        util-tests \
+        ipa_hbac-tests
 endif
 
 check_PROGRAMS = \
@@ -365,6 +369,16 @@ libsss_util_la_LIBADD = \
     libsss_crypt.la \
     libsss_debug.la
 
+lib_LTLIBRARIES = libipa_hbac.la
+dist_pkgconfig_DATA += src/providers/ipa/ipa_hbac.pc
+libipa_hbac_la_SOURCES = \
+    src/providers/ipa/hbac_evaluator.c
+libipa_hbac_la_LDFLAGS = \
+    -version 0:0:0
+
+include_HEADERS = \
+    src/providers/ipa/ipa_hbac.h
+
 ####################
 # Program Binaries #
 ####################
@@ -711,6 +725,18 @@ crypto_tests_LDADD = \
     $(CHECK_LIBS) \
     libsss_test_common.la
 
+ipa_hbac_tests_SOURCES = \
+    src/tests/ipa_hbac-tests.c
+ipa_hbac_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(CHECK_CFLAGS)
+ipa_hbac_tests_LDADD = \
+    $(SSSD_LIBS) \
+    $(CHECK_LIBS) \
+    libsss_util.la \
+    libsss_test_common.la \
+    libipa_hbac.la
+
 endif
 
 stress_tests_SOURCES = \
diff --git a/configure.ac b/configure.ac
index 972c24c8ce46a26fe6d50f28a96437d6a70518f6..2cf3137f85f3f83597abf5167158b77ca769efdf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -192,6 +192,7 @@ AC_DEFINE_UNQUOTED([ABS_BUILD_DIR], ["$abs_build_dir"], [Absolute path to the bu
 AC_SUBST([abs_builddir], $abs_build_dir)
 
 AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config
-                 src/sysv/systemd/sssd.service po/Makefile.in src/man/Makefile])
+                 src/sysv/systemd/sssd.service po/Makefile.in src/man/Makefile
+                 src/providers/ipa/ipa_hbac.pc])
 AC_OUTPUT
 
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index b14818025817a6eb4659a8a71a1ea70d2decffb8..15ad9205571bf28cfb26fd77d14ba989e9951e3e 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -116,6 +116,23 @@ SSSD when using id_provider = local in /etc/sssd/sssd.conf.
 Also provides a userspace tool for generating an obfuscated LDAP password for
 use with ldap_default_authtok_type = obfuscated_password.
 
+%package -n libipa_hbac
+Summary: FreeIPA HBAC Evaluator library
+Group: Development/Libraries
+License: LGPLv3+
+
+%description -n libipa_hbac
+Utility library to validate FreeIPA HBAC rules for authorization requests
+
+%package -n libipa_hbac-devel
+Summary: FreeIPA HBAC Evaluator library
+Group: Development/Libraries
+License: LGPLv3+
+Requires: libipa_hbac = %{version}-%{release}
+
+%description -n libipa_hbac-devel
+Utility library to validate FreeIPA HBAC rules for authorization requests
+
 %prep
 %setup -q
 
@@ -181,6 +198,7 @@ rm -f \
     $RPM_BUILD_ROOT/%{_libdir}/sssd/libsss_ipa.la \
     $RPM_BUILD_ROOT/%{_libdir}/sssd/libsss_simple.la \
     $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/libkrb5/sssd_krb5_locator_plugin.la \
+    $RPM_BUILD_ROOT/%{_libdir}/libipa_hbac.la \
     $RPM_BUILD_ROOT/%{python_sitearch}/pysss.la
 
 # Older versions of rpmbuild can only handle one -f option
@@ -268,6 +286,17 @@ rm -rf $RPM_BUILD_ROOT
 %{_mandir}/man8/sss_obfuscate.8*
 %{_mandir}/man8/sss_cache.8*
 
+%files -n libipa_hbac
+%defattr(-,root,root,-)
+%doc src/sss_client/COPYING src/sss_client/COPYING.LESSER
+%{_libdir}/libipa_hbac.so.*
+
+%files -n libipa_hbac-devel
+%defattr(-,root,root-)
+%{_includedir}/ipa_hbac.h
+%{_libdir}/libipa_hbac.so
+%{_libdir}/pkgconfig/ipa_hbac.pc
+
 %post
 /sbin/ldconfig
 /sbin/chkconfig --add %{servicename}
diff --git a/src/providers/ipa/hbac_evaluator.c b/src/providers/ipa/hbac_evaluator.c
new file mode 100644
index 0000000000000000000000000000000000000000..1869597e149885e54c6920c5f59f75f424c03f1f
--- /dev/null
+++ b/src/providers/ipa/hbac_evaluator.c
@@ -0,0 +1,217 @@
+/*
+    SSSD
+
+    IPA Backend Module -- Access control
+
+    Authors:
+        Sumit Bose <sbose@redhat.com>
+        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 <stdlib.h>
+#include <string.h>
+#include "providers/ipa/ipa_hbac.h"
+
+/* Placeholder structure for future HBAC time-based
+ * evaluation rules
+ */
+struct hbac_time_rules {
+    int not_yet_implemented;
+};
+
+enum hbac_eval_result hbac_evaluate_rule(struct hbac_rule *rule,
+                                         struct hbac_eval_req *hbac_req,
+                                         enum hbac_error_code *error);
+
+enum hbac_eval_result hbac_evaluate(struct hbac_rule **rules,
+                                    struct hbac_eval_req *hbac_req,
+                                    struct hbac_error **error)
+{
+    enum hbac_error_code ret;
+    enum hbac_eval_result result = HBAC_EVAL_DENY;
+    enum hbac_eval_result intermediate_result;
+    *error = NULL;
+    uint32_t i;
+
+    for (i = 0; rules[i]; i++) {
+        intermediate_result = hbac_evaluate_rule(rules[i], hbac_req, &ret);
+        if (intermediate_result == HBAC_EVAL_DENY) {
+            /* This request matched a DENY rule.
+             * Any rule returning DENY is definitive. Do not continue
+             * processing.
+             */
+            result = HBAC_EVAL_DENY;
+            goto done;
+        } else if (intermediate_result == HBAC_EVAL_UNMATCHED) {
+            /* This rule did not match at all. Skip it */
+            continue;
+        } else if (intermediate_result == HBAC_EVAL_ALLOW) {
+            /* This request matched an ALLOW rule
+             * Set the result to ALLOW but continue checking
+             * the other rules in case a DENY rule trumps it.
+             */
+            result = HBAC_EVAL_ALLOW;
+        } else {
+            /* An error occurred processing this rule */
+            result = HBAC_EVAL_ERROR;
+            *error = malloc(sizeof(struct hbac_error));
+            if (*error == NULL) {
+                /* We're out of memory, just return NULL for the error */
+            } else {
+                (*error)->code = ret;
+                (*error)->rule_name = strdup(rules[i]->name);
+                /* Explicitly not checking the result of strdup(), since if
+                 * it's NULL, we can't do anything anyway.
+                 */
+            }
+            goto done;
+        }
+    }
+
+    /* If we've reached the end of the loop, we have either set the
+     * result to ALLOW explicitly or we'll stick with the default DENY.
+     */
+done:
+
+    return result;
+}
+
+static bool hbac_evaluate_element(struct hbac_rule_element *rule_el,
+                                  struct hbac_request_element *req_el);
+
+enum hbac_eval_result hbac_evaluate_rule(struct hbac_rule *rule,
+                                         struct hbac_eval_req *hbac_req,
+                                         enum hbac_error_code *error)
+{
+    enum hbac_eval_result matching_result;
+
+    /* Make sure we have all elements */
+    if (!rule->users
+     || !rule->services
+     || !rule->targethosts
+     || !rule->srchosts) {
+        *error = HBAC_ERROR_UNPARSEABLE_RULE;
+        return HBAC_EVAL_ERROR;
+    }
+
+    if (!rule->enabled) return HBAC_EVAL_UNMATCHED;
+
+    /* Is this rule an ALLOW or DENY rule? */
+    if (rule->type == HBAC_RULE_ALLOW) {
+        matching_result = HBAC_EVAL_ALLOW;
+    } else { /* HBAC_RULE_DENY */
+        matching_result = HBAC_EVAL_DENY;
+    }
+
+    /* Check users */
+    if (!hbac_evaluate_element(rule->users, hbac_req->user)) {
+        return HBAC_EVAL_UNMATCHED;
+    }
+
+    /* Check services */
+    if (!hbac_evaluate_element(rule->services, hbac_req->service)) {
+        return HBAC_EVAL_UNMATCHED;
+    }
+
+    /* Check target hosts */
+    if (!hbac_evaluate_element(rule->targethosts, hbac_req->targethost)) {
+        return HBAC_EVAL_UNMATCHED;
+    }
+
+    /* Check source hosts */
+    if (!hbac_evaluate_element(rule->srchosts, hbac_req->srchost)) {
+        return HBAC_EVAL_UNMATCHED;
+    }
+
+    return matching_result;
+}
+
+static bool hbac_evaluate_element(struct hbac_rule_element *rule_el,
+                                  struct hbac_request_element *req_el)
+{
+    size_t i, j;
+
+    if (rule_el->category & HBAC_CATEGORY_ALL) {
+        return true;
+    }
+
+    /* First check the name list */
+    if (rule_el->names) {
+        for (i = 0; rule_el->names[i]; i++) {
+            if (strcmp(rule_el->names[i], req_el->name) == 0) {
+                return true;
+            }
+        }
+    }
+
+    if (rule_el->groups) {
+        /* Not found in the name list
+         * Check for group membership
+         */
+        for (i = 0; rule_el->groups[i]; i++) {
+            for (j = 0; req_el->groups[j]; j++) {
+                if (strcmp(rule_el->groups[i],
+                           req_el->groups[j]) == 0) {
+                    return true;
+                }
+            }
+        }
+    }
+
+    /* Not found in groups either */
+    return false;
+}
+
+const char *hbac_result_string(enum hbac_eval_result result)
+{
+    switch(result) {
+    case HBAC_EVAL_ALLOW:
+        return "HBAC_EVAL_ALLOW";
+    case HBAC_EVAL_DENY:
+        return "HBAC_EVAL_DENY";
+    case HBAC_EVAL_ERROR:
+        return "HBAC_EVAL_ERROR";
+    case HBAC_EVAL_UNMATCHED:
+        return "Rule did not match for this request";
+    }
+    return "HBAC_EVAL_ERROR";
+}
+
+void hbac_free_error(struct hbac_error *error)
+{
+    free(error->rule_name);
+    free(error);
+    error = NULL;
+}
+
+const char *hbac_error_string(enum hbac_error_code code)
+{
+    switch(code) {
+    case HBAC_SUCCESS:
+        return "Success";
+    case HBAC_ERROR_NOT_IMPLEMENTED:
+        return "Function is not yet implemented";
+    case HBAC_ERROR_OUT_OF_MEMORY:
+        return "Out of memory";
+    case HBAC_ERROR_UNPARSEABLE_RULE:
+        return "Rule could not be evaluated";
+    case HBAC_ERROR_UNKNOWN:
+    default:
+        return "Unknown error code";
+    }
+}
diff --git a/src/providers/ipa/ipa_hbac.h b/src/providers/ipa/ipa_hbac.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ce8e2402187cc26a3d14c2dcf077f63a47e5b2d
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac.h
@@ -0,0 +1,158 @@
+/*
+    SSSD
+
+    IPA Backend Module -- Access control
+
+    Authors:
+        Sumit Bose <sbose@redhat.com>
+        Stephen Gallagher <sgallagh@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_HBAC_H_
+#define IPA_HBAC_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+enum hbac_eval_result {
+    HBAC_EVAL_ALLOW,
+    HBAC_EVAL_DENY,
+    HBAC_EVAL_UNMATCHED,
+    HBAC_EVAL_ERROR
+};
+
+enum hbac_rule_type {
+    HBAC_RULE_ALLOW, /* Rule grants access if it matches */
+    HBAC_RULE_DENY   /* Rule denies access if it matches */
+};
+
+#define HBAC_CATEGORY_NULL 0x0000 /* No service category specified */
+#define HBAC_CATEGORY_ALL  0x0001 /* Rule should apply to all */
+
+/* Opaque type contained in hbac_evaluator.c */
+struct hbac_time_rules;
+
+struct hbac_rule_element {
+    uint32_t category;
+    const char **names;
+    const char **groups;
+};
+
+struct hbac_rule {
+    const char *name;
+    bool enabled;
+
+    enum hbac_rule_type type;
+
+    /* Services and service groups
+     * for which this rule applies
+     */
+    struct hbac_rule_element *services;
+
+    /* Users and groups for which this
+     * rule applies
+     */
+    struct hbac_rule_element *users;
+
+    /* Target hosts for which this rule apples */
+    struct hbac_rule_element *targethosts;
+
+    /* Source hosts for which this rule applies */
+    struct hbac_rule_element *srchosts;
+
+    /* For future use */
+    struct hbac_time_rules *timerules;
+};
+
+struct hbac_request_element {
+    const char *name;
+    const char **groups;
+};
+
+struct hbac_eval_req {
+    /* This is a list of service DNs to check,
+     * it must consist of the actual service
+     * requested, as well as all parent groups
+     * containing that service.
+     */
+    struct hbac_request_element *service;
+
+    /* This is a list of user DNs to check,
+     * it must consist of the actual user
+     * requested, as well as all parent groups
+     * containing that user.
+     */
+    struct hbac_request_element *user;
+
+    /* This is a list of target hosts to check,
+     * it must consist of the actual target host
+     * requested, as well as all parent groups
+     * containing that target host.
+     */
+    struct hbac_request_element *targethost;
+
+    /* This is a list of source hosts to check,
+     * it must consist of the actual source host
+     * requested, as well as all parent groups
+     * containing that source host.
+     */
+    struct hbac_request_element *srchost;
+
+    /* For future use */
+    time_t request_time;
+};
+
+enum hbac_error_code {
+    HBAC_ERROR_UNKNOWN = -1,
+    HBAC_SUCCESS,
+    HBAC_ERROR_NOT_IMPLEMENTED,
+    HBAC_ERROR_OUT_OF_MEMORY,
+    HBAC_ERROR_UNPARSEABLE_RULE
+};
+
+/* Extended error information */
+struct hbac_error {
+    /* Type of error encountered */
+    enum hbac_error_code code;
+
+    /* If the error was encountered while processing
+     * a particular rule, the name of that rule
+     */
+    char *rule_name;
+};
+
+
+/**
+ * @brief Evaluate an authorization request against a set of HBAC rules
+ *
+ * @param[in] rules    A NULL-terminated list of rules to evaluate against
+ * @param[in] hbac_req A user authorization request
+ * @param[out] error   Extended information returned in the event of an error
+ * @return
+ */
+enum hbac_eval_result hbac_evaluate(struct hbac_rule **rules,
+                                    struct hbac_eval_req *hbac_req,
+                                    struct hbac_error **error);
+
+const char *hbac_result_string(enum hbac_eval_result result);
+const char *hbac_error_string(enum hbac_error_code code);
+
+void hbac_free_error(struct hbac_error *error);
+
+
+#endif /* IPA_HBAC_H_ */
diff --git a/src/providers/ipa/ipa_hbac.pc.in b/src/providers/ipa/ipa_hbac.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..8764f13ebe0a46dea1b18b6f4c1c40a859483199
--- /dev/null
+++ b/src/providers/ipa/ipa_hbac.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: ipa_hbac
+Description: FreeIPA HBAC Evaluator library
+Version: @VERSION@
+Libs: -L$(libdir) -lipa_hbac
+Cflags:
+URL: http://fedorahosted.org/sssd/
+
diff --git a/src/tests/ipa_hbac-tests.c b/src/tests/ipa_hbac-tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..624184a0678e212714b02bfd695753ad9c5ecb71
--- /dev/null
+++ b/src/tests/ipa_hbac-tests.c
@@ -0,0 +1,618 @@
+/*
+    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 <stdlib.h>
+#include <check.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <talloc.h>
+
+#include "tests/common.h"
+#include "providers/ipa/ipa_hbac.h"
+
+#define HBAC_TEST_USER "testuser"
+#define HBAC_TEST_INVALID_USER "nosuchuser"
+
+#define HBAC_TEST_GROUP1 "testgroup1"
+#define HBAC_TEST_GROUP2 "testgroup2"
+#define HBAC_TEST_INVALID_GROUP "nosuchgroup"
+
+#define HBAC_TEST_SERVICE "testservice"
+#define HBAC_TEST_INVALID_SERVICE "nosuchservice"
+
+#define HBAC_TEST_SERVICEGROUP1 "login_services"
+#define HBAC_TEST_SERVICEGROUP2 "all_services"
+#define HBAC_TEST_INVALID_SERVICEGROUP "nosuchservicegroup"
+
+#define HBAC_TEST_SRCHOST "client.example.com"
+#define HBAC_TEST_INVALID_SRCHOST "nosuchsrchost"
+
+#define HBAC_TEST_SRCHOSTGROUP1 "site_hosts"
+#define HBAC_TEST_SRCHOSTGROUP2 "corp_hosts"
+#define HBAC_TEST_INVALID_SRCHOSTGROUP "nosuchsrchostgroup"
+
+static void get_allow_all_rule(TALLOC_CTX *mem_ctx,
+                               struct hbac_rule **allow_rule)
+{
+    struct hbac_rule *rule;
+    /* Create a rule that ALLOWs all services, users and
+     * remote hosts.
+     */
+    rule = talloc_zero(mem_ctx, struct hbac_rule);
+    fail_if (rule == NULL);
+
+    rule->enabled = true;
+
+    rule->services = talloc_zero(rule, struct hbac_rule_element);
+    fail_if (rule->services == NULL);
+    rule->services->category = HBAC_CATEGORY_ALL;
+    rule->services->names = NULL;
+    rule->services->groups = NULL;
+
+    rule->users = talloc_zero(rule, struct hbac_rule_element);
+    fail_if (rule->users == NULL);
+    rule->users->category = HBAC_CATEGORY_ALL;
+    rule->users->names = NULL;
+    rule->users->groups = NULL;
+
+    rule->targethosts = talloc_zero(rule, struct hbac_rule_element);
+    fail_if (rule->targethosts == NULL);
+    rule->targethosts->category = HBAC_CATEGORY_ALL;
+    rule->targethosts->names = NULL;
+    rule->targethosts->groups = NULL;
+
+    rule->srchosts = talloc_zero(rule, struct hbac_rule_element);
+    fail_if (rule->srchosts == NULL);
+    rule->srchosts->category = HBAC_CATEGORY_ALL;
+    rule->srchosts->names = NULL;
+    rule->srchosts->groups = NULL;
+
+    *allow_rule = rule;
+}
+
+static void get_test_user(TALLOC_CTX *mem_ctx,
+                          struct hbac_request_element **user)
+{
+    struct hbac_request_element *new_user;
+
+    new_user = talloc_zero(mem_ctx, struct hbac_request_element);
+    fail_if (new_user == NULL);
+
+    new_user->name = talloc_strdup(new_user, HBAC_TEST_USER);
+    fail_if(new_user->name == NULL);
+
+    new_user->groups = talloc_array(new_user, const char *, 3);
+    fail_if(new_user->groups == NULL);
+
+    new_user->groups[0] = talloc_strdup(new_user->groups, HBAC_TEST_GROUP1);
+    fail_if(new_user->groups[0] == NULL);
+
+    new_user->groups[1] = talloc_strdup(new_user->groups, HBAC_TEST_GROUP2);
+    fail_if(new_user->groups[1] == NULL);
+
+    new_user->groups[2] = NULL;
+
+    *user = new_user;
+}
+
+static void get_test_service(TALLOC_CTX *mem_ctx,
+                             struct hbac_request_element **service)
+{
+    struct hbac_request_element *new_service;
+
+    new_service = talloc_zero(mem_ctx, struct hbac_request_element);
+    fail_if (new_service == NULL);
+
+    new_service->name = talloc_strdup(new_service, HBAC_TEST_SERVICE);
+    fail_if(new_service->name == NULL);
+
+    new_service->groups = talloc_array(new_service, const char *, 3);
+    fail_if(new_service->groups == NULL);
+
+    new_service->groups[0] = talloc_strdup(new_service->groups, HBAC_TEST_SERVICEGROUP1);
+    fail_if(new_service->groups[0] == NULL);
+
+    new_service->groups[1] = talloc_strdup(new_service->groups, HBAC_TEST_SERVICEGROUP2);
+    fail_if(new_service->groups[1] == NULL);
+
+    new_service->groups[2] = NULL;
+
+    *service = new_service;
+}
+
+static void get_test_srchost(TALLOC_CTX *mem_ctx,
+                             struct hbac_request_element **srchost)
+{
+    struct hbac_request_element *new_srchost;
+
+    new_srchost = talloc_zero(mem_ctx, struct hbac_request_element);
+    fail_if (new_srchost == NULL);
+
+    new_srchost->name = talloc_strdup(new_srchost, "client.example.com");
+    fail_if(new_srchost->name == NULL);
+
+    new_srchost->groups = talloc_array(new_srchost, const char *, 3);
+    fail_if(new_srchost->groups == NULL);
+
+    new_srchost->groups[0] = talloc_strdup(new_srchost->groups, "site_hosts");
+    fail_if(new_srchost->groups[0] == NULL);
+
+    new_srchost->groups[1] = talloc_strdup(new_srchost->groups, "corp_hosts");
+    fail_if(new_srchost->groups[1] == NULL);
+
+    new_srchost->groups[2] = NULL;
+
+    *srchost = new_srchost;
+}
+
+START_TEST(ipa_hbac_test_allow_all)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+    rules[0]->name = talloc_strdup(rules[0], "Allow All");
+    fail_if(rules[0]->name == NULL);
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_user)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a specific user */
+    rules[0]->name = talloc_strdup(rules[0], "Allow user");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->users->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->users->names = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->users->names == NULL);
+
+    rules[0]->users->names[0] = HBAC_TEST_USER;
+    rules[0]->users->names[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->users->names[0] = HBAC_TEST_INVALID_USER;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_group)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a group of users */
+    rules[0]->name = talloc_strdup(rules[0], "Allow group");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->users->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->users->names = NULL;
+    rules[0]->users->groups = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->users->groups == NULL);
+
+    rules[0]->users->groups[0] = HBAC_TEST_GROUP1;
+    rules[0]->users->groups[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->users->groups[0] = HBAC_TEST_INVALID_GROUP;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_svc)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a specific service */
+    rules[0]->name = talloc_strdup(rules[0], "Allow service");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->services->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->services->names = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->services->names == NULL);
+
+    rules[0]->services->names[0] = HBAC_TEST_SERVICE;
+    rules[0]->services->names[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->services->names[0] = HBAC_TEST_INVALID_SERVICE;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_svcgroup)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a group of users */
+    rules[0]->name = talloc_strdup(rules[0], "Allow servicegroup");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->services->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->services->names = NULL;
+    rules[0]->services->groups = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->services->groups == NULL);
+
+    rules[0]->services->groups[0] = HBAC_TEST_SERVICEGROUP1;
+    rules[0]->services->groups[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->services->groups[0] = HBAC_TEST_INVALID_SERVICEGROUP;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_srchost)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a specific service */
+    rules[0]->name = talloc_strdup(rules[0], "Allow srchost");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->srchosts->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->srchosts->names = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->srchosts->names == NULL);
+
+    rules[0]->srchosts->names[0] = HBAC_TEST_SRCHOST;
+    rules[0]->srchosts->names[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->srchosts->names[0] = HBAC_TEST_INVALID_SRCHOST;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s](%s)",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(ipa_hbac_test_allow_srchostgroup)
+{
+    enum hbac_eval_result result;
+    TALLOC_CTX *test_ctx;
+    struct hbac_rule **rules;
+    struct hbac_eval_req *eval_req;
+    struct hbac_error *error;
+
+    test_ctx = talloc_new(global_talloc_context);
+
+    /* Create a request */
+    eval_req = talloc_zero(test_ctx, struct hbac_eval_req);
+    fail_if (eval_req == NULL);
+
+    get_test_user(eval_req, &eval_req->user);
+    get_test_service(eval_req, &eval_req->service);
+    get_test_srchost(eval_req, &eval_req->srchost);
+
+    /* Create the rules to evaluate against */
+    rules = talloc_array(test_ctx, struct hbac_rule *, 2);
+    fail_if (rules == NULL);
+
+    get_allow_all_rule(rules, &rules[0]);
+
+    /* Modify the rule to allow only a group of users */
+    rules[0]->name = talloc_strdup(rules[0], "Allow srchostgroup");
+    fail_if(rules[0]->name == NULL);
+    rules[0]->srchosts->category = HBAC_CATEGORY_NULL;
+
+    rules[0]->srchosts->names = NULL;
+    rules[0]->srchosts->groups = talloc_array(rules[0], const char *, 2);
+    fail_if(rules[0]->srchosts->groups == NULL);
+
+    rules[0]->srchosts->groups[0] = HBAC_TEST_SRCHOSTGROUP1;
+    rules[0]->srchosts->groups[1] = NULL;
+
+    rules[1] = NULL;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_ALLOW,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_ALLOW),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    /* Negative test */
+    rules[0]->srchosts->groups[0] = HBAC_TEST_INVALID_SRCHOSTGROUP;
+
+    /* Evaluate the rules */
+    result = hbac_evaluate(rules, eval_req, &error);
+    fail_unless(result == HBAC_EVAL_DENY,
+                "Expected [%s], got [%s]; "
+                "Error: [%s]",
+                hbac_result_string(HBAC_EVAL_DENY),
+                hbac_result_string(result),
+                error ? hbac_error_string(error->code):"Unknown");
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+Suite *hbac_test_suite (void)
+{
+    Suite *s = suite_create ("HBAC");
+
+    TCase *tc_hbac = tcase_create("HBAC_rules");
+    tcase_add_checked_fixture(tc_hbac,
+                              leak_check_setup,
+                              leak_check_teardown);
+
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_all);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_user);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_group);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_svc);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_svcgroup);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_srchost);
+    tcase_add_test(tc_hbac, ipa_hbac_test_allow_srchostgroup);
+
+    suite_add_tcase(s, tc_hbac);
+    return s;
+}
+
+int main(int argc, const char *argv[])
+{
+    int number_failed;
+
+    tests_set_cwd();
+
+    Suite *s = hbac_test_suite();
+    SRunner *sr = srunner_create(s);
+
+    /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+    srunner_run_all(sr, CK_ENV);
+    number_failed = srunner_ntests_failed (sr);
+    srunner_free (sr);
+
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
-- 
1.7.5.4

