[sssd/f18] Fix CVE-2013-0287 sssd: simple access provider flaw prevents intended ACL use when client to an AD p

Jakub Hrozek jhrozek at fedoraproject.org
Wed Mar 20 16:56:43 UTC 2013


commit ee8348e70bcc0916f5056d3de3babe1387dfa155
Author: Jakub Hrozek <jhrozek at redhat.com>
Date:   Wed Mar 20 17:55:24 2013 +0100

    Fix CVE-2013-0287 sssd: simple access provider flaw prevents intended ACL use when client to an AD provider (#923838)

 ...esolve-GIDs-in-the-simple-access-provider.patch | 1622 ++++++++++++++++++++
 ...it-tests-for-simple-access-test-by-groups.patch |  412 +++++
 ...ile-main-in-DP-if-UNIT_TESTING-is-defined.patch |   40 +
 ...ovide-a-be_get_account_info_send-function.patch |  236 +++
 sssd.spec                                          |   10 +-
 5 files changed, 2319 insertions(+), 1 deletions(-)
---
diff --git a/0004-Resolve-GIDs-in-the-simple-access-provider.patch b/0004-Resolve-GIDs-in-the-simple-access-provider.patch
new file mode 100644
index 0000000..c06f900
--- /dev/null
+++ b/0004-Resolve-GIDs-in-the-simple-access-provider.patch
@@ -0,0 +1,1622 @@
+From 8b8019fe3dd1564fba657e219ec20ff816c7ffdb Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek at redhat.com>
+Date: Sat, 23 Feb 2013 10:44:54 +0100
+Subject: [PATCH 4/4] Resolve GIDs in the simple access provider
+
+Changes the simple access provider's interface to be asynchronous. When
+the simple access provider encounters a group that has gid, but no
+meaningful name, it attempts to resolve the name using the
+be_file_account_request function.
+
+Some providers (like the AD provider) might perform initgroups
+without resolving the group names. In order for the simple access
+provider to work correctly, we need to resolve the groups before
+performing the access check. In AD provider, the situation is
+even more tricky b/c the groups HAVE name, but their name
+attribute is set to SID and they are set as non-POSIX
+---
+ Makefile.am                                |  17 +-
+ src/providers/simple/simple_access.c       | 230 ++-------
+ src/providers/simple/simple_access.h       |  11 +-
+ src/providers/simple/simple_access_check.c | 723 +++++++++++++++++++++++++++++
+ src/tests/simple_access-tests.c            | 373 ++++++++++-----
+ 5 files changed, 1040 insertions(+), 314 deletions(-)
+ create mode 100644 src/providers/simple/simple_access_check.c
+
+diff --git a/Makefile.am b/Makefile.am
+index dc0465a8cb6b441cfc810a5da8c448b034cc4b7a..eea535e81ac572713810f8b97dacc58ed221b440 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -1008,14 +1008,22 @@ ad_ldap_opt_tests_LDADD = \
+ simple_access_tests_SOURCES = \
+     src/tests/simple_access-tests.c \
+     src/tests/common.c \
+-    src/providers/simple/simple_access.c
++    src/providers/simple/simple_access_check.c \
++    src/providers/data_provider_be.c \
++    src/providers/data_provider_fo.c \
++    src/providers/data_provider_callbacks.c \
++    $(SSSD_FAILOVER_OBJ)
+ simple_access_tests_CFLAGS = \
+     $(AM_CFLAGS) \
+-    $(CHECK_CFLAGS)
++    $(CHECK_CFLAGS) \
++    -DUNIT_TESTING
+ simple_access_tests_LDADD = \
+     $(SSSD_LIBS) \
++    $(CARES_LIBS) \
+     $(CHECK_LIBS) \
+-    libsss_util.la
++    $(PAM_LIBS) \
++    libsss_util.la \
++    libsss_test_common.la
+ 
+ util_tests_SOURCES = \
+     src/tests/util-tests.c
+@@ -1347,7 +1355,8 @@ libsss_proxy_la_LDFLAGS = \
+     -module
+ 
+ libsss_simple_la_SOURCES = \
+-    src/providers/simple/simple_access.c
++    src/providers/simple/simple_access.c \
++    src/providers/simple/simple_access_check.c
+ libsss_simple_la_CFLAGS = \
+     $(AM_CFLAGS)
+ libsss_simple_la_LIBADD = \
+diff --git a/src/providers/simple/simple_access.c b/src/providers/simple/simple_access.c
+index 70d1f07282d472089a2c6eac05479c39eb03c606..d53a04b4d73bfa4fae23b4522d01cf1e947e7d64 100644
+--- a/src/providers/simple/simple_access.c
++++ b/src/providers/simple/simple_access.c
+@@ -35,211 +35,13 @@
+ #define CONFDB_SIMPLE_ALLOW_GROUPS "simple_allow_groups"
+ #define CONFDB_SIMPLE_DENY_GROUPS "simple_deny_groups"
+ 
+-errno_t simple_access_check(struct simple_ctx *ctx, const char *username,
+-                            bool *access_granted)
+-{
+-    int i, j;
+-    errno_t ret;
+-    TALLOC_CTX *tmp_ctx = NULL;
+-    const char *user_attrs[] = { SYSDB_MEMBEROF,
+-                                 SYSDB_GIDNUM,
+-                                 NULL };
+-    const char *group_attrs[] = { SYSDB_NAME,
+-                                  NULL };
+-    struct ldb_message *msg;
+-    struct ldb_message_element *el;
+-    char **groups;
+-    const char *primary_group;
+-    gid_t gid;
+-    bool matched;
+-    bool cs = ctx->domain->case_sensitive;
+-
+-    *access_granted = false;
+-
+-    /* First, check whether the user is in the allowed users list */
+-    if (ctx->allow_users != NULL) {
+-        for(i = 0; ctx->allow_users[i] != NULL; i++) {
+-            if (sss_string_equal(cs, username, ctx->allow_users[i])) {
+-                DEBUG(9, ("User [%s] found in allow list, access granted.\n",
+-                      username));
+-
+-                /* Do not return immediately on explicit allow
+-                 * We need to make sure none of the user's groups
+-                 * are denied.
+-                 */
+-                *access_granted = true;
+-            }
+-        }
+-    } else if (!ctx->allow_groups) {
+-        /* If neither allow rule is in place, we'll assume allowed
+-         * unless a deny rule disables us below.
+-         */
+-        *access_granted = true;
+-    }
+-
+-    /* Next check whether this user has been specifically denied */
+-    if (ctx->deny_users != NULL) {
+-        for(i = 0; ctx->deny_users[i] != NULL; i++) {
+-            if (sss_string_equal(cs, username, ctx->deny_users[i])) {
+-                DEBUG(9, ("User [%s] found in deny list, access denied.\n",
+-                      username));
+-
+-                /* Return immediately on explicit denial */
+-                *access_granted = false;
+-                return EOK;
+-            }
+-        }
+-    }
+-
+-    if (!ctx->allow_groups && !ctx->deny_groups) {
+-        /* There are no group restrictions, so just return
+-         * here with whatever we've decided.
+-         */
+-        return EOK;
+-    }
+-
+-    /* Now get a list of this user's groups and check those against the
+-     * simple_allow_groups list.
+-     */
+-    tmp_ctx = talloc_new(NULL);
+-    if (!tmp_ctx) {
+-        ret = ENOMEM;
+-        goto done;
+-    }
+-
+-    ret = sysdb_search_user_by_name(tmp_ctx, ctx->sysdb,
+-                                    username, user_attrs, &msg);
+-    if (ret != EOK) {
+-        DEBUG(1, ("Could not look up username [%s]: [%d][%s]\n",
+-                  username, ret, strerror(ret)));
+-        goto done;
+-    }
+-
+-    /* Construct a list of the user's groups */
+-    el = ldb_msg_find_element(msg, SYSDB_MEMBEROF);
+-    if (el && el->num_values) {
+-        /* Get the groups from the memberOf entries
+-         * Allocate the array with room for both the NULL
+-         * terminator and the primary group
+-         */
+-        groups = talloc_array(tmp_ctx, char *, el->num_values + 2);
+-        if (!groups) {
+-            ret = ENOMEM;
+-            goto done;
+-        }
+-
+-        for (j = 0; j < el->num_values; j++) {
+-            ret = sysdb_group_dn_name(
+-                    ctx->sysdb, tmp_ctx,
+-                    (char *)el->values[j].data,
+-                    &groups[j]);
+-            if (ret != EOK) {
+-                goto done;
+-            }
+-        }
+-    } else {
+-        /* User is not a member of any groups except primary */
+-        groups = talloc_array(tmp_ctx, char *, 2);
+-        if (!groups) {
+-            ret = ENOMEM;
+-            goto done;
+-        }
+-        j = 0;
+-    }
+-
+-    /* Get the user's primary group */
+-    gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+-    if (!gid) {
+-        ret = EINVAL;
+-        goto done;
+-    }
+-    talloc_zfree(msg);
+-
+-    ret = sysdb_search_group_by_gid(tmp_ctx, ctx->sysdb,
+-                                    gid, group_attrs, &msg);
+-    if (ret != EOK) {
+-        DEBUG(1, ("Could not look up primary group [%lu]: [%d][%s]\n",
+-                  gid, ret, strerror(ret)));
+-        /* We have to treat this as non-fatal, because the primary
+-         * group may be local to the machine and not available in
+-         * our ID provider.
+-         */
+-    } else {
+-        primary_group = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+-        if (!primary_group) {
+-            ret = EINVAL;
+-            goto done;
+-        }
+-
+-        groups[j] = talloc_strdup(tmp_ctx, primary_group);
+-        if (!groups[j]) {
+-            ret = ENOMEM;
+-            goto done;
+-        }
+-        j++;
+-
+-        talloc_zfree(msg);
+-    }
+-
+-    groups[j] = NULL;
+-
+-    /* Now process allow and deny group rules
+-     * If access was already granted above, we'll skip
+-     * this redundant rule check
+-     */
+-    if (ctx->allow_groups && !*access_granted) {
+-        matched = false;
+-        for (i = 0; ctx->allow_groups[i]; i++) {
+-            for(j = 0; groups[j]; j++) {
+-                if (sss_string_equal(cs, groups[j], ctx->allow_groups[i])) {
+-                    matched = true;
+-                    break;
+-                }
+-            }
+-
+-            /* If any group has matched, we can skip out on the
+-             * processing early
+-             */
+-            if (matched) {
+-                *access_granted = true;
+-                break;
+-            }
+-        }
+-    }
+-
+-    /* Finally, process the deny group rules */
+-    if (ctx->deny_groups) {
+-        matched = false;
+-        for (i = 0; ctx->deny_groups[i]; i++) {
+-            for(j = 0; groups[j]; j++) {
+-                if (sss_string_equal(cs, groups[j], ctx->deny_groups[i])) {
+-                    matched = true;
+-                    break;
+-                }
+-            }
+-
+-            /* If any group has matched, we can skip out on the
+-             * processing early
+-             */
+-            if (matched) {
+-                *access_granted = false;
+-                break;
+-            }
+-        }
+-    }
+-
+-    ret = EOK;
+-
+-done:
+-    talloc_free(tmp_ctx);
+-    return ret;
+-}
++static void simple_access_check(struct tevent_req *req);
+ 
+ void simple_access_handler(struct be_req *be_req)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct be_ctx *be_ctx = be_req->be_ctx;
+     struct pam_data *pd;
++    struct tevent_req *req;
+     struct simple_ctx *ctx;
+ 
+     pd = talloc_get_type(be_req->req_data, struct pam_data);
+@@ -255,7 +57,30 @@ void simple_access_handler(struct be_req *be_req)
+     ctx = talloc_get_type(be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+                           struct simple_ctx);
+ 
+-    ret = simple_access_check(ctx, pd->user, &access_granted);
++    req = simple_access_check_send(be_req, be_ctx->ev, ctx, pd->user);
++    if (!req) {
++        pd->pam_status = PAM_SYSTEM_ERR;
++        goto done;
++    }
++    tevent_req_set_callback(req, simple_access_check, be_req);
++    return;
++
++done:
++    be_req->fn(be_req, DP_ERR_OK, pd->pam_status, NULL);
++}
++
++static void simple_access_check(struct tevent_req *req)
++{
++    bool access_granted = false;
++    errno_t ret;
++    struct pam_data *pd;
++    struct be_req *be_req;
++
++    be_req = tevent_req_callback_data(req, struct be_req);
++    pd = talloc_get_type(be_req->req_data, struct pam_data);
++
++    ret = simple_access_check_recv(req, &access_granted);
++    talloc_free(req);
+     if (ret != EOK) {
+         pd->pam_status = PAM_SYSTEM_ERR;
+         goto done;
+@@ -290,6 +115,7 @@ int sssm_simple_access_init(struct be_ctx *bectx, struct bet_ops **ops,
+ 
+     ctx->sysdb = bectx->sysdb;
+     ctx->domain = bectx->domain;
++    ctx->be_ctx = bectx;
+ 
+     /* Users */
+     ret = confdb_get_string_as_list(bectx->cdb, ctx, bectx->conf_path,
+diff --git a/src/providers/simple/simple_access.h b/src/providers/simple/simple_access.h
+index abcf61ac29f31b3e424dd12340759ee6cc8c9489..1de9d898bdc3a58bbce2e2c74a02796fa5c3c609 100644
+--- a/src/providers/simple/simple_access.h
++++ b/src/providers/simple/simple_access.h
+@@ -29,6 +29,7 @@
+ struct simple_ctx {
+     struct sysdb_ctx *sysdb;
+     struct sss_domain_info *domain;
++    struct be_ctx *be_ctx;
+ 
+     char **allow_users;
+     char **deny_users;
+@@ -36,6 +37,12 @@ struct simple_ctx {
+     char **deny_groups;
+ };
+ 
+-errno_t simple_access_check(struct simple_ctx *ctx, const char *username,
+-                            bool *access_granted);
++struct tevent_req *simple_access_check_send(TALLOC_CTX *mem_ctx,
++                                            struct tevent_context *ev,
++                                            struct simple_ctx *ctx,
++                                            const char *username);
++
++errno_t simple_access_check_recv(struct tevent_req *req,
++                                 bool *access_granted);
++
+ #endif /* __SIMPLE_ACCESS_H__ */
+diff --git a/src/providers/simple/simple_access_check.c b/src/providers/simple/simple_access_check.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..a9e8f632e8af6aae467571db6f093a5b07b5ed91
+--- /dev/null
++++ b/src/providers/simple/simple_access_check.c
+@@ -0,0 +1,723 @@
++/*
++   SSSD
++
++   Simple access control
++
++   Copyright (C) Sumit Bose <sbose at redhat.com> 2010
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; either version 3 of the License, or
++   (at your option) any later version.
++
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>.
++*/
++
++#include "providers/dp_backend.h"
++#include "providers/simple/simple_access.h"
++#include "util/sss_utf8.h"
++#include "db/sysdb.h"
++
++static bool
++is_posix(const struct ldb_message *group)
++{
++    const char *val;
++
++    val = ldb_msg_find_attr_as_string(group, SYSDB_POSIX, NULL);
++    if (!val || /* Groups are posix by default */
++        strcasecmp(val, "TRUE") == 0) {
++        return true;
++    }
++
++    return false;
++}
++
++/* Returns EOK if the result is definitive, EAGAIN if only partial result
++ */
++static errno_t
++simple_check_users(struct simple_ctx *ctx, const char *username,
++                   bool *access_granted)
++{
++    int i;
++    bool cs = ctx->domain->case_sensitive;
++
++    /* First, check whether the user is in the allowed users list */
++    if (ctx->allow_users != NULL) {
++        for(i = 0; ctx->allow_users[i] != NULL; i++) {
++            if (sss_string_equal(cs, username, ctx->allow_users[i])) {
++                DEBUG(SSSDBG_TRACE_LIBS,
++                      ("User [%s] found in allow list, access granted.\n",
++                      username));
++
++                /* Do not return immediately on explicit allow
++                 * We need to make sure none of the user's groups
++                 * are denied.
++                 */
++                *access_granted = true;
++            }
++        }
++    } else if (!ctx->allow_groups) {
++        /* If neither allow rule is in place, we'll assume allowed
++         * unless a deny rule disables us below.
++         */
++        DEBUG(SSSDBG_TRACE_LIBS,
++              ("No allow rule, assumuing allow unless explicitly denied\n"));
++        *access_granted = true;
++    }
++
++    /* Next check whether this user has been specifically denied */
++    if (ctx->deny_users != NULL) {
++        for(i = 0; ctx->deny_users[i] != NULL; i++) {
++            if (sss_string_equal(cs, username, ctx->deny_users[i])) {
++                DEBUG(SSSDBG_TRACE_LIBS,
++                      ("User [%s] found in deny list, access denied.\n",
++                      username));
++
++                /* Return immediately on explicit denial */
++                *access_granted = false;
++                return EOK;
++            }
++        }
++    }
++
++    return EAGAIN;
++}
++
++static errno_t
++simple_check_groups(struct simple_ctx *ctx, const char *username,
++                    const char **group_names, bool *access_granted)
++{
++    bool matched;
++    int i, j;
++    bool cs = ctx->domain->case_sensitive;
++
++    /* Now process allow and deny group rules
++     * If access was already granted above, we'll skip
++     * this redundant rule check
++     */
++    if (ctx->allow_groups && !*access_granted) {
++        matched = false;
++        for (i = 0; ctx->allow_groups[i]; i++) {
++            for(j = 0; group_names[j]; j++) {
++                if (sss_string_equal(cs, group_names[j], ctx->allow_groups[i])) {
++                    matched = true;
++                    break;
++                }
++            }
++
++            /* If any group has matched, we can skip out on the
++             * processing early
++             */
++            if (matched) {
++                DEBUG(SSSDBG_TRACE_LIBS,
++                      ("Group [%s] found in allow list, access granted.\n",
++                      group_names[j]));
++                *access_granted = true;
++                break;
++            }
++        }
++    }
++
++    /* Finally, process the deny group rules */
++    if (ctx->deny_groups) {
++        matched = false;
++        for (i = 0; ctx->deny_groups[i]; i++) {
++            for(j = 0; group_names[j]; j++) {
++                if (sss_string_equal(cs, group_names[j], ctx->deny_groups[i])) {
++                    matched = true;
++                    break;
++                }
++            }
++
++            /* If any group has matched, we can skip out on the
++             * processing early
++             */
++            if (matched) {
++                DEBUG(SSSDBG_TRACE_LIBS,
++                      ("Group [%s] found in deny list, access denied.\n",
++                      group_names[j]));
++                *access_granted = false;
++                break;
++            }
++        }
++    }
++
++    return EOK;
++}
++
++struct simple_resolve_group_state {
++    gid_t gid;
++    struct simple_ctx *ctx;
++
++    const char *name;
++};
++
++static errno_t
++simple_resolve_group_check(struct simple_resolve_group_state *state);
++static void simple_resolve_group_done(struct tevent_req *subreq);
++
++static struct tevent_req *
++simple_resolve_group_send(TALLOC_CTX *mem_ctx,
++                          struct tevent_context *ev,
++                          struct simple_ctx *ctx,
++                          gid_t gid)
++{
++    errno_t ret;
++    struct tevent_req *req;
++    struct tevent_req *subreq;
++    struct simple_resolve_group_state *state;
++    struct be_acct_req *ar;
++
++    req = tevent_req_create(mem_ctx, &state,
++                            struct simple_resolve_group_state);
++    if (!req) return NULL;
++
++    state->gid = gid;
++    state->ctx = ctx;
++
++    /* First check if the group was updated already. If it was (maybe its
++     * parent was updated first), then just shortcut */
++    ret = simple_resolve_group_check(state);
++    if (ret == EOK) {
++        DEBUG(SSSDBG_TRACE_LIBS, ("Group already updated\n"));
++        ret = EOK;
++        goto done;
++    } else if (ret != EAGAIN) {
++        DEBUG(SSSDBG_OP_FAILURE,
++              ("Cannot check if group was already updated\n"));
++        goto done;
++    }
++    /* EAGAIN - still needs update */
++
++    ar = talloc(state, struct be_acct_req);
++    if (!ar) {
++        ret = ENOMEM;
++        goto done;
++    }
++
++    ar->entry_type = BE_REQ_GROUP;
++    ar->attr_type = BE_ATTR_CORE;
++    ar->filter_type = BE_FILTER_IDNUM;
++    ar->filter_value = talloc_asprintf(ar, "%llu", (unsigned long long) gid);
++    ar->domain = talloc_strdup(ar, ctx->domain->name);
++    if (!ar->domain || !ar->filter_value) {
++        ret = ENOMEM;
++        goto done;
++    }
++
++    subreq = be_get_account_info_send(state, ev, NULL, ctx->be_ctx, ar);
++    if (!subreq) {
++        ret = ENOMEM;
++        goto done;
++    }
++    tevent_req_set_callback(subreq, simple_resolve_group_done, req);
++
++    return req;
++
++done:
++    if (ret == EOK) {
++        tevent_req_done(req);
++    } else {
++        tevent_req_error(req, ret);
++    }
++    tevent_req_post(req, ev);
++    return req;
++}
++
++static errno_t
++simple_resolve_group_check(struct simple_resolve_group_state *state)
++{
++    errno_t ret;
++    struct ldb_message *group;
++    const char *group_attrs[] = { SYSDB_NAME, SYSDB_POSIX,
++                                  SYSDB_GIDNUM, NULL };
++
++    /* Check the cache by GID again and fetch the name */
++    ret = sysdb_search_group_by_gid(state, state->ctx->domain->sysdb,
++                                    state->gid, group_attrs, &group);
++    if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE,
++               ("Could not look up group by gid [%lu]: [%d][%s]\n",
++               state->gid, ret, strerror(ret)));
++        return ret;
++    }
++
++    state->name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL);
++    if (!state->name) {
++        DEBUG(SSSDBG_OP_FAILURE, ("No group name\n"));
++        return ENOENT;
++    }
++
++    if (is_posix(group) == false) {
++        DEBUG(SSSDBG_TRACE_LIBS,
++              ("The group is still non-POSIX\n"));
++        return EAGAIN;
++    }
++
++    DEBUG(SSSDBG_TRACE_LIBS, ("Got POSIX group\n"));
++    return EOK;
++}
++
++static void simple_resolve_group_done(struct tevent_req *subreq)
++{
++    struct tevent_req *req;
++    struct simple_resolve_group_state *state;
++    int err_maj;
++    int err_min;
++    errno_t ret;
++    const char *err_msg;
++
++    req = tevent_req_callback_data(subreq, struct tevent_req);
++    state = tevent_req_data(req, struct simple_resolve_group_state);
++
++    ret = be_get_account_info_recv(subreq, state,
++                                   &err_maj, &err_min, &err_msg);
++    talloc_zfree(subreq);
++    if (ret) {
++        DEBUG(SSSDBG_OP_FAILURE, ("be_get_account_info_recv failed\n"));
++        tevent_req_error(req, ret);
++        return;
++    }
++
++    if (err_maj) {
++        DEBUG(SSSDBG_MINOR_FAILURE,
++              ("Cannot refresh data from DP: %u,%u: %s\n",
++              err_maj, err_min, err_msg));
++        tevent_req_error(req, EIO);
++        return;
++    }
++
++    /* Check the cache by GID again and fetch the name */
++    ret = simple_resolve_group_check(state);
++    if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE, ("Refresh failed\n"));
++        tevent_req_error(req, ret);
++        return;
++    }
++
++    tevent_req_done(req);
++}
++
++static errno_t
++simple_resolve_group_recv(struct tevent_req *req,
++                          TALLOC_CTX *mem_ctx,
++                          const char **name)
++{
++    struct simple_resolve_group_state *state;
++
++    state = tevent_req_data(req, struct simple_resolve_group_state);
++
++    TEVENT_REQ_RETURN_ON_ERROR(req);
++
++    *name = talloc_strdup(mem_ctx, state->name);
++    return EOK;
++}
++
++struct simple_check_groups_state {
++    struct tevent_context *ev;
++    struct simple_ctx *ctx;
++
++    gid_t *lookup_gids;
++    size_t num_gids;
++    size_t giter;
++
++    const char **group_names;
++    size_t num_names;
++};
++
++static void simple_check_get_groups_next(struct tevent_req *subreq);
++
++static errno_t
++simple_check_get_groups_primary(struct simple_check_groups_state *state,
++                                gid_t gid);
++static errno_t
++simple_check_process_group(struct simple_check_groups_state *state,
++                           struct ldb_message *group);
++
++static struct tevent_req *
++simple_check_get_groups_send(TALLOC_CTX *mem_ctx,
++                             struct tevent_context *ev,
++                             struct simple_ctx *ctx,
++                             const char *username)
++{
++    errno_t ret;
++    struct tevent_req *req;
++    struct tevent_req *subreq;
++    struct simple_check_groups_state *state;
++    const char *attrs[] = { SYSDB_NAME, SYSDB_POSIX, SYSDB_GIDNUM, NULL };
++    size_t group_count;
++    struct ldb_message *user;
++    struct ldb_message **groups;
++    int i;
++    gid_t gid;
++    char *cname;
++
++    req = tevent_req_create(mem_ctx, &state,
++                            struct simple_check_groups_state);
++    if (!req) return NULL;
++
++    state->ev = ev;
++    state->ctx = ctx;
++
++    cname = sss_get_cased_name(state, username, ctx->domain->case_sensitive);
++    if (!cname) {
++        ret = ENOMEM;
++        goto done;
++    }
++
++    DEBUG(SSSDBG_TRACE_LIBS, ("Looking up groups for user %s\n", cname));
++
++    ret = sysdb_search_user_by_name(state, ctx->domain->sysdb,
++                                    cname, attrs, &user);
++    if (ret == ENOENT) {
++        DEBUG(SSSDBG_MINOR_FAILURE, ("No such user %s\n", cname));
++        goto done;
++    } else if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE,
++              ("Could not look up username [%s]: [%d][%s]\n",
++              username, ret, strerror(ret)));
++        goto done;
++    }
++
++    ret = sysdb_asq_search(state, ctx->domain->sysdb,
++                           user->dn, NULL, SYSDB_MEMBEROF,
++                           attrs, &group_count, &groups);
++    if (ret != EOK) {
++        goto done;
++    }
++
++    DEBUG(SSSDBG_TRACE_FUNC,
++          ("User %s is a member of %d supplemental groups\n",
++           cname, group_count));
++
++    /* One extra space for terminator, one extra space for private group */
++    state->group_names = talloc_zero_array(state, const char *, group_count + 2);
++    state->lookup_gids = talloc_zero_array(state, gid_t, group_count + 2);
++    if (!state->group_names || !state->lookup_gids) {
++        ret = ENOMEM;
++        goto done;
++    }
++
++    for (i=0; i < group_count; i++) {
++        /* Some providers (like the AD provider) might perform initgroups
++         * without resolving the group names. In order for the simple access
++         * provider to work correctly, we need to resolve the groups before
++         * performing the access check. In AD provider, the situation is
++         * even more tricky b/c the groups HAVE name, but their name
++         * attribute is set to SID and they are set as non-POSIX
++         */
++        ret = simple_check_process_group(state, groups[i]);
++        if (ret != EOK) {
++            goto done;
++        }
++    }
++
++    gid = ldb_msg_find_attr_as_uint64(user, SYSDB_GIDNUM, 0);
++    if (!gid) {
++        DEBUG(SSSDBG_MINOR_FAILURE, ("User %s has no gid?\n", cname));
++        ret = EINVAL;
++        goto done;
++    }
++
++    ret = simple_check_get_groups_primary(state, gid);
++    if (ret != EOK) {
++        goto done;
++    }
++
++    if (state->num_gids == 0) {
++        /* If all groups could have been resolved by name, we are
++         * done
++         */
++        DEBUG(SSSDBG_TRACE_FUNC, ("All groups had name attribute\n"));
++        ret = EOK;
++        goto done;
++    }
++
++    DEBUG(SSSDBG_TRACE_FUNC, ("Need to resolve %d groups\n", state->num_gids));
++    state->giter = 0;
++    subreq = simple_resolve_group_send(req, state->ev, state->ctx,
++                                       state->lookup_gids[state->giter]);
++    if (!subreq) {
++        ret = ENOMEM;
++        goto done;
++    }
++    tevent_req_set_callback(subreq, simple_check_get_groups_next, req);
++
++    return req;
++
++done:
++    if (ret == EOK) {
++        tevent_req_done(req);
++    } else {
++        tevent_req_error(req, ret);
++    }
++    tevent_req_post(req, ev);
++    return req;
++}
++
++static void simple_check_get_groups_next(struct tevent_req *subreq)
++{
++    struct tevent_req *req =
++                        tevent_req_callback_data(subreq, struct tevent_req);
++    struct simple_check_groups_state *state =
++                        tevent_req_data(req, struct simple_check_groups_state);
++    errno_t ret;
++
++    ret = simple_resolve_group_recv(subreq, state->group_names,
++                                    &state->group_names[state->num_names]);
++    talloc_zfree(subreq);
++    if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE,
++              ("Could not resolve name of group with GID %llu\n",
++              state->lookup_gids[state->giter]));
++        tevent_req_error(req, ret);
++        return;
++    }
++
++    state->num_names++;
++    state->giter++;
++
++    if (state->giter < state->num_gids) {
++        subreq = simple_resolve_group_send(req, state->ev, state->ctx,
++                                           state->lookup_gids[state->giter]);
++        if (!subreq) {
++            tevent_req_error(req, ENOMEM);
++            return;
++        }
++        tevent_req_set_callback(subreq, simple_check_get_groups_next, req);
++        return;
++    }
++
++    DEBUG(SSSDBG_TRACE_INTERNAL, ("All groups resolved. Done.\n"));
++    tevent_req_done(req);
++}
++
++static errno_t
++simple_check_process_group(struct simple_check_groups_state *state,
++                           struct ldb_message *group)
++{
++    const char *name;
++    gid_t gid;
++    bool posix;
++
++    posix = is_posix(group);
++    name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL);
++    gid = ldb_msg_find_attr_as_uint64(group, SYSDB_GIDNUM, 0);
++
++    /* With the current sysdb layout, every group has a name */
++    if (name == NULL) {
++        return EINVAL;
++    }
++
++    if (gid == 0) {
++        if (posix == true) {
++            DEBUG(SSSDBG_CRIT_FAILURE, ("POSIX group without GID\n"));
++            return EINVAL;
++        }
++
++        /* Non-posix group with a name. Still can be used for access
++         * control as the name should point to the real name, no SID
++         */
++        state->group_names[state->num_names] = talloc_strdup(state->group_names,
++                                                             name);
++        if (!state->group_names[state->num_names]) {
++            return ENOMEM;
++        }
++        DEBUG(SSSDBG_TRACE_INTERNAL, ("Adding group %s\n", name));
++        state->num_names++;
++        return EOK;
++    }
++
++    /* Here are only groups with a name and gid. POSIX group can already
++     * be used, non-POSIX groups can be resolved */
++    if (posix) {
++        state->group_names[state->num_names] = talloc_strdup(state->group_names,
++                                                             name);
++        if (!state->group_names[state->num_names]) {
++            return ENOMEM;
++        }
++        DEBUG(SSSDBG_TRACE_INTERNAL, ("Adding group %s\n", name));
++        state->num_names++;
++        return EOK;
++    }
++
++    /* Non-posix group with a GID. Needs resolving */
++    state->lookup_gids[state->num_gids] = gid;
++    DEBUG(SSSDBG_TRACE_INTERNAL, ("Adding GID %llu\n", gid));
++    state->num_gids++;
++    return EOK;
++}
++
++static errno_t
++simple_check_get_groups_primary(struct simple_check_groups_state *state,
++                                gid_t gid)
++{
++    errno_t ret;
++    const char *group_attrs[] = { SYSDB_NAME, SYSDB_POSIX,
++                                  SYSDB_GIDNUM, NULL };
++    struct ldb_message *msg;
++
++    ret = sysdb_search_group_by_gid(state, state->ctx->domain->sysdb,
++                                    gid, group_attrs, &msg);
++    if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE,
++               ("Could not look up primary group [%lu]: [%d][%s]\n",
++               gid, ret, strerror(ret)));
++        /* We have to treat this as non-fatal, because the primary
++         * group may be local to the machine and not available in
++         * our ID provider.
++         */
++    } else {
++        ret = simple_check_process_group(state, msg);
++        if (ret != EOK) {
++            DEBUG(SSSDBG_OP_FAILURE, ("Cannot process primary group\n"));
++            return ret;
++        }
++    }
++
++    return EOK;
++}
++
++static errno_t
++simple_check_get_groups_recv(struct tevent_req *req,
++                             TALLOC_CTX *mem_ctx,
++                             const char ***_group_names)
++{
++    struct simple_check_groups_state *state;
++
++    state = tevent_req_data(req, struct simple_check_groups_state);
++
++    TEVENT_REQ_RETURN_ON_ERROR(req);
++
++    *_group_names = talloc_steal(mem_ctx, state->group_names);
++    return EOK;
++}
++
++struct simple_access_check_state {
++    bool access_granted;
++    struct simple_ctx *ctx;
++    const char *username;
++
++    const char **group_names;
++};
++
++static void simple_access_check_done(struct tevent_req *subreq);
++
++struct tevent_req *simple_access_check_send(TALLOC_CTX *mem_ctx,
++                                            struct tevent_context *ev,
++                                            struct simple_ctx *ctx,
++                                            const char *username)
++{
++    errno_t ret;
++    struct tevent_req *req;
++    struct tevent_req *subreq;
++    struct simple_access_check_state *state;
++
++    req = tevent_req_create(mem_ctx, &state,
++                            struct simple_access_check_state);
++    if (!req) return NULL;
++
++    state->access_granted = false;
++    state->ctx = ctx;
++    state->username = talloc_strdup(state, username);
++    if (!state->username) {
++        ret = ENOMEM;
++        goto immediate;
++    }
++
++    DEBUG(SSSDBG_FUNC_DATA, ("Simple access check for %s\n", username));
++
++    ret = simple_check_users(ctx, username, &state->access_granted);
++    if (ret != EAGAIN) {
++        /* Both access denied and an error */
++        goto immediate;
++    }
++
++    if (!ctx->allow_groups && !ctx->deny_groups) {
++        /* There are no group restrictions, so just return
++         * here with whatever we've decided.
++         */
++        DEBUG(SSSDBG_TRACE_LIBS, ("No group restrictions, end request\n"));
++        ret = EOK;
++        goto immediate;
++    }
++
++    /* The group names might not be available. Fire a request to
++     * gather them. In most cases, the request will just shortcut
++     */
++    subreq = simple_check_get_groups_send(state, ev, ctx, username);
++    if (!subreq) {
++        ret = EIO;
++        goto immediate;
++    }
++    tevent_req_set_callback(subreq, simple_access_check_done, req);
++
++    return req;
++
++immediate:
++    if (ret == EOK) {
++        tevent_req_done(req);
++    } else {
++        tevent_req_error(req, ret);
++    }
++    tevent_req_post(req, ev);
++    return req;
++}
++
++
++static void simple_access_check_done(struct tevent_req *subreq)
++{
++    struct tevent_req *req =
++                        tevent_req_callback_data(subreq, struct tevent_req);
++    struct simple_access_check_state *state =
++                        tevent_req_data(req, struct simple_access_check_state);
++    errno_t ret;
++
++    /* We know the names now. Run the check. */
++    ret = simple_check_get_groups_recv(subreq, state, &state->group_names);
++    talloc_zfree(subreq);
++    if (ret == ENOENT) {
++        /* If the user wasn't found, just shortcut */
++        state->access_granted = false;
++        tevent_req_done(req);
++        return;
++    } else if (ret != EOK) {
++        DEBUG(SSSDBG_OP_FAILURE,
++              ("Could not collect groups of user %s\n", state->username));
++        tevent_req_error(req, ret);
++        return;
++    }
++
++    ret = simple_check_groups(state->ctx, state->username,
++                              state->group_names, &state->access_granted);
++    if (ret != EOK) {
++        tevent_req_error(req, ret);
++        return;
++    }
++
++    /* Now just return whatever we decided */
++    DEBUG(SSSDBG_TRACE_INTERNAL, ("Group check done\n"));
++    tevent_req_done(req);
++}
++
++errno_t simple_access_check_recv(struct tevent_req *req, bool *access_granted)
++{
++    struct simple_access_check_state *state =
++                        tevent_req_data(req, struct simple_access_check_state);
++
++    TEVENT_REQ_RETURN_ON_ERROR(req);
++
++    DEBUG(SSSDBG_TRACE_LIBS,
++          ("Access %sgranted\n", state->access_granted ? "" : "not "));
++    if (access_granted) {
++        *access_granted = state->access_granted;
++    }
++
++    return EOK;
++}
+diff --git a/src/tests/simple_access-tests.c b/src/tests/simple_access-tests.c
+index 577c6d334edda513fd0f1e42a859ea333ba5ba23..ab2612db85b6cfe46c87a1fc8b17655dc7a795bb 100644
+--- a/src/tests/simple_access-tests.c
++++ b/src/tests/simple_access-tests.c
+@@ -27,6 +27,7 @@
+ #include <check.h>
+ 
+ #include "confdb/confdb.h"
++#include "db/sysdb_private.h"
+ #include "providers/simple/simple_access.h"
+ #include "tests/common.h"
+ 
+@@ -35,16 +36,40 @@
+ 
+ const char *ulist_1[] = {"u1", "u2", NULL};
+ const char *glist_1[] = {"g1", "g2", NULL};
++const char *glist_1_case[] = {"G1", "G2", NULL};
+ 
+ struct simple_test_ctx *test_ctx = NULL;
+ 
+ struct simple_test_ctx {
+     struct sysdb_ctx *sysdb;
+     struct confdb_ctx *confdb;
++    struct tevent_context *ev;
++    bool done;
++    int error;
+ 
++    bool access_granted;
+     struct simple_ctx *ctx;
+ };
+ 
++static int test_loop(struct simple_test_ctx *tctx)
++{
++    while (!tctx->done)
++        tevent_loop_once(tctx->ev);
++
++    return tctx->error;
++}
++
++static void simple_access_check_done(struct tevent_req *req)
++{
++    struct simple_test_ctx *tctx =
++                        tevent_req_callback_data(req, struct simple_test_ctx);
++
++
++    tctx->error = simple_access_check_recv(req, &tctx->access_granted);
++    talloc_free(req);
++    tctx->done = true;
++}
++
+ void setup_simple(void)
+ {
+     errno_t ret;
+@@ -52,19 +77,22 @@ void setup_simple(void)
+     const char *val[2];
+     val[1] = NULL;
+ 
+-    /* Create tests directory if it doesn't exist */
+-    /* (relative to current dir) */
+-    ret = mkdir(TESTS_PATH, 0775);
+-    fail_if(ret == -1 && errno != EEXIST,
+-            "Could not create %s directory", TESTS_PATH);
+-
+     fail_unless(test_ctx == NULL, "Simple context already initialized.");
+     test_ctx = talloc_zero(NULL, struct simple_test_ctx);
+     fail_unless(test_ctx != NULL, "Cannot create simple test context.");
+ 
++    test_ctx->ev = tevent_context_init(test_ctx);
++    fail_unless(test_ctx->ev != NULL, "Cannot create tevent context.");
++
+     test_ctx->ctx = talloc_zero(test_ctx, struct simple_ctx);
+     fail_unless(test_ctx->ctx != NULL, "Cannot create simple context.");
+ 
++    /* Create tests directory if it doesn't exist */
++    /* (relative to current dir) */
++    ret = mkdir(TESTS_PATH, 0775);
++    fail_if(ret == -1 && errno != EEXIST,
++            "Could not create %s directory", TESTS_PATH);
++
+     conf_db = talloc_asprintf(test_ctx, "%s/%s", TESTS_PATH, TEST_CONF_FILE);
+     fail_if(conf_db == NULL, "Out of memory, aborting!");
+     DEBUG(SSSDBG_TRACE_LIBS, ("CONFDB: %s\n", conf_db));
+@@ -98,6 +126,7 @@ void setup_simple(void)
+                                       &test_ctx->ctx->domain, &test_ctx->ctx->sysdb);
+     fail_if(ret != EOK, "Could not initialize connection to the sysdb (%d)", ret);
+     test_ctx->ctx->domain->case_sensitive = true;
++    test_ctx->ctx->sysdb->mpg = false; /* Simulate an LDAP domain better */
+ }
+ 
+ void teardown_simple(void)
+@@ -117,18 +146,22 @@ void setup_simple_group(void)
+ 
+     /* Add test users u1 and u2 that would be members of test groups
+      * g1 and g2 respectively */
++    ret = sysdb_add_group(test_ctx->ctx->sysdb,
++                          "pvt", 999, NULL, 0, 0);
++    fail_if(ret != EOK, "Could not add private group");
++
+     ret = sysdb_store_user(test_ctx->ctx->sysdb,
+-                           "u1", NULL, 123, 0, "u1", "/home/u1",
++                           "u1", NULL, 123, 999, "u1", "/home/u1",
+                            "/bin/bash", NULL, NULL, NULL, -1, 0);
+     fail_if(ret != EOK, "Could not add u1");
+ 
+     ret = sysdb_store_user(test_ctx->ctx->sysdb,
+-                           "u2", NULL, 456, 0, "u1", "/home/u1",
++                           "u2", NULL, 456, 999, "u1", "/home/u1",
+                            "/bin/bash", NULL, NULL, NULL, -1, 0);
+     fail_if(ret != EOK, "Could not add u2");
+ 
+     ret = sysdb_store_user(test_ctx->ctx->sysdb,
+-                           "u3", NULL, 789, 0, "u1", "/home/u1",
++                           "u3", NULL, 789, 999, "u1", "/home/u1",
+                            "/bin/bash", NULL, NULL, NULL, -1, 0);
+     fail_if(ret != EOK, "Could not add u3");
+ 
+@@ -163,190 +196,317 @@ void teardown_simple_group(void)
+     fail_if(ret != EOK, "Could not delete g1");
+     ret = sysdb_delete_group(test_ctx->ctx->sysdb, "g2", 0);
+     fail_if(ret != EOK, "Could not delete g2");
++    ret = sysdb_delete_group(test_ctx->ctx->sysdb, "pvt", 0);
++    fail_if(ret != EOK, "Could not delete pvt");
+ 
+     teardown_simple();
+ }
+ 
+ START_TEST(test_both_empty)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_users = NULL;
+     test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                        "while both lists are empty.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied while both lists are empty.");
+ }
+ END_TEST
+ 
+ START_TEST(test_allow_empty)
+ {
+-    int ret;
+-    bool access_granted = true;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_users = NULL;
+     test_ctx->ctx->deny_users = discard_const(ulist_1);
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "while user is in deny list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                         "while user is not in deny list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is in deny list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied while user is not in deny list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_deny_empty)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_users = discard_const(ulist_1);
+     test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                        "while user is in allow list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                        "while user is not in allow list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied while user is in allow list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is not in allow list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_both_set)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_users = discard_const(ulist_1);
+     test_ctx->ctx->deny_users = discard_const(ulist_1);
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "while user is in deny list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                        "while user is not in allow list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is in deny list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is not in allow list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_case)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_users = discard_const(ulist_1);
+     test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "for user with different case "
+-                                         "in case-sensitive domain");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "U1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted for user with different case "
++                "in case-sensitive domain");
+ 
+     test_ctx->ctx->domain->case_sensitive = false;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                        "for user with different case "
+-                                        "in case-insensitive domain");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "U1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied for user with different case "
++                "in case-sensitive domain");
+ }
+ END_TEST
+ 
++START_TEST(test_unknown_user)
++{
++    struct tevent_req *req;
++
++    test_ctx->ctx->allow_users = discard_const(ulist_1);
++    test_ctx->ctx->deny_users = NULL;
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "foo");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted for user not present in domain");
++}
++END_TEST
++
++
+ START_TEST(test_group_allow_empty)
+ {
+-    int ret;
+-    bool access_granted = true;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_groups = NULL;
+     test_ctx->ctx->deny_groups = discard_const(glist_1);
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "while group is in deny list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                         "while group is not in deny list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while group is in deny list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied while group is not in deny list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_group_deny_empty)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_groups = discard_const(glist_1);
+     test_ctx->ctx->deny_groups = NULL;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                        "while group is in allow list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                        "while group is not in allow list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied while user is in allow list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is not in allow list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_group_both_set)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+     test_ctx->ctx->allow_groups = discard_const(ulist_1);
+     test_ctx->ctx->deny_groups = discard_const(ulist_1);
+ 
+-    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "while group is in deny list.");
+-
+-    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                        "while group is not in allow list.");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is in deny list.");
++
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "u3");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted while user is not in allow list.");
+ }
+ END_TEST
+ 
+ START_TEST(test_group_case)
+ {
+-    int ret;
+-    bool access_granted = false;
++    struct tevent_req *req;
+ 
+-    test_ctx->ctx->allow_groups = discard_const(ulist_1);
++    test_ctx->ctx->allow_groups = discard_const(glist_1_case);
+     test_ctx->ctx->deny_groups = NULL;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == false, "Access granted "
+-                                         "for group with different case "
+-                                         "in case-sensitive domain");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "U1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == false,
++                "Access granted for user with different case "
++                "in case-sensitive domain");
+ 
+     test_ctx->ctx->domain->case_sensitive = false;
+ 
+-    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+-    fail_unless(ret == EOK, "access_simple_check failed.");
+-    fail_unless(access_granted == true, "Access denied "
+-                                        "for group with different case "
+-                                        "in case-insensitive domain");
++    req = simple_access_check_send(test_ctx, test_ctx->ev,
++                                   test_ctx->ctx, "U1");
++    fail_unless(test_ctx != NULL, "Cannot create request\n");
++    tevent_req_set_callback(req, simple_access_check_done, test_ctx);
++
++    test_loop(test_ctx);
++    test_ctx->done = false;
++
++    fail_unless(test_ctx->error == EOK, "access_simple_check failed.");
++    fail_unless(test_ctx->access_granted == true,
++                "Access denied for user with different case "
++                "in case-sensitive domain");
+ }
+ END_TEST
+ 
+@@ -361,6 +521,7 @@ Suite *access_simple_suite (void)
+     tcase_add_test(tc_allow_deny, test_deny_empty);
+     tcase_add_test(tc_allow_deny, test_both_set);
+     tcase_add_test(tc_allow_deny, test_case);
++    tcase_add_test(tc_allow_deny, test_unknown_user);
+     suite_add_tcase(s, tc_allow_deny);
+ 
+     TCase *tc_grp_allow_deny = tcase_create("group allow/deny");
+-- 
+1.8.1.4
+
diff --git a/0005-Add-unit-tests-for-simple-access-test-by-groups.patch b/0005-Add-unit-tests-for-simple-access-test-by-groups.patch
new file mode 100644
index 0000000..a219fb0
--- /dev/null
+++ b/0005-Add-unit-tests-for-simple-access-test-by-groups.patch
@@ -0,0 +1,412 @@
+From 754b09b5444e6da88ed58d6deaed8b815e268b6b Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek at redhat.com>
+Date: Sun, 3 Mar 2013 21:43:44 +0100
+Subject: [PATCH 2/4] Add unit tests for simple access test by groups
+
+I realized that the current unit tests for the simple access provider
+only tested the user directives. To have a baseline and be able to
+detect new bugs in the upcoming patch, I implemented unit tests for the
+group lists, too.
+---
+ src/tests/simple_access-tests.c | 283 +++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 252 insertions(+), 31 deletions(-)
+
+diff --git a/src/tests/simple_access-tests.c b/src/tests/simple_access-tests.c
+index c61814eb54c1aa5138a1b45653f9384228c5456a..577c6d334edda513fd0f1e42a859ea333ba5ba23 100644
+--- a/src/tests/simple_access-tests.c
++++ b/src/tests/simple_access-tests.c
+@@ -30,39 +30,152 @@
+ #include "providers/simple/simple_access.h"
+ #include "tests/common.h"
+ 
++#define TESTS_PATH "tests_simple_access"
++#define TEST_CONF_FILE "tests_conf.ldb"
++
+ const char *ulist_1[] = {"u1", "u2", NULL};
++const char *glist_1[] = {"g1", "g2", NULL};
+ 
+-struct simple_ctx *ctx = NULL;
++struct simple_test_ctx *test_ctx = NULL;
++
++struct simple_test_ctx {
++    struct sysdb_ctx *sysdb;
++    struct confdb_ctx *confdb;
++
++    struct simple_ctx *ctx;
++};
+ 
+ void setup_simple(void)
+ {
+-    fail_unless(ctx == NULL, "Simple context already initialized.");
+-    ctx = talloc_zero(NULL, struct simple_ctx);
+-    fail_unless(ctx != NULL, "Cannot create simple context.");
++    errno_t ret;
++    char *conf_db;
++    const char *val[2];
++    val[1] = NULL;
+ 
+-    ctx->domain = talloc_zero(ctx, struct sss_domain_info);
+-    fail_unless(ctx != NULL, "Cannot create domain in simple context.");
+-    ctx->domain->case_sensitive = true;
++    /* Create tests directory if it doesn't exist */
++    /* (relative to current dir) */
++    ret = mkdir(TESTS_PATH, 0775);
++    fail_if(ret == -1 && errno != EEXIST,
++            "Could not create %s directory", TESTS_PATH);
++
++    fail_unless(test_ctx == NULL, "Simple context already initialized.");
++    test_ctx = talloc_zero(NULL, struct simple_test_ctx);
++    fail_unless(test_ctx != NULL, "Cannot create simple test context.");
++
++    test_ctx->ctx = talloc_zero(test_ctx, struct simple_ctx);
++    fail_unless(test_ctx->ctx != NULL, "Cannot create simple context.");
++
++    conf_db = talloc_asprintf(test_ctx, "%s/%s", TESTS_PATH, TEST_CONF_FILE);
++    fail_if(conf_db == NULL, "Out of memory, aborting!");
++    DEBUG(SSSDBG_TRACE_LIBS, ("CONFDB: %s\n", conf_db));
++
++    /* Connect to the conf db */
++    ret = confdb_init(test_ctx, &test_ctx->confdb, conf_db);
++    fail_if(ret != EOK, "Could not initialize connection to the confdb");
++
++    val[0] = "LOCAL";
++    ret = confdb_add_param(test_ctx->confdb, true,
++                           "config/sssd", "domains", val);
++    fail_if(ret != EOK, "Could not initialize domains placeholder");
++
++    val[0] = "local";
++    ret = confdb_add_param(test_ctx->confdb, true,
++                           "config/domain/LOCAL", "id_provider", val);
++    fail_if(ret != EOK, "Could not initialize provider");
++
++    val[0] = "TRUE";
++    ret = confdb_add_param(test_ctx->confdb, true,
++                           "config/domain/LOCAL", "enumerate", val);
++    fail_if(ret != EOK, "Could not initialize LOCAL domain");
++
++    val[0] = "TRUE";
++    ret = confdb_add_param(test_ctx->confdb, true,
++                           "config/domain/LOCAL", "cache_credentials", val);
++    fail_if(ret != EOK, "Could not initialize LOCAL domain");
++
++    ret = sysdb_init_domain_and_sysdb(test_ctx, test_ctx->confdb, "local",
++                                      TESTS_PATH,
++                                      &test_ctx->ctx->domain, &test_ctx->ctx->sysdb);
++    fail_if(ret != EOK, "Could not initialize connection to the sysdb (%d)", ret);
++    test_ctx->ctx->domain->case_sensitive = true;
+ }
+ 
+ void teardown_simple(void)
+ {
+     int ret;
+-    fail_unless(ctx != NULL, "Simple context already freed.");
+-    ret = talloc_free(ctx);
+-    ctx = NULL;
++    fail_unless(test_ctx != NULL, "Simple context already freed.");
++    ret = talloc_free(test_ctx);
++    test_ctx = NULL;
+     fail_unless(ret == 0, "Connot free simple context.");
+ }
+ 
++void setup_simple_group(void)
++{
++    errno_t ret;
++
++    setup_simple();
++
++    /* Add test users u1 and u2 that would be members of test groups
++     * g1 and g2 respectively */
++    ret = sysdb_store_user(test_ctx->ctx->sysdb,
++                           "u1", NULL, 123, 0, "u1", "/home/u1",
++                           "/bin/bash", NULL, NULL, NULL, -1, 0);
++    fail_if(ret != EOK, "Could not add u1");
++
++    ret = sysdb_store_user(test_ctx->ctx->sysdb,
++                           "u2", NULL, 456, 0, "u1", "/home/u1",
++                           "/bin/bash", NULL, NULL, NULL, -1, 0);
++    fail_if(ret != EOK, "Could not add u2");
++
++    ret = sysdb_store_user(test_ctx->ctx->sysdb,
++                           "u3", NULL, 789, 0, "u1", "/home/u1",
++                           "/bin/bash", NULL, NULL, NULL, -1, 0);
++    fail_if(ret != EOK, "Could not add u3");
++
++    ret = sysdb_add_group(test_ctx->ctx->sysdb,
++                          "g1", 321, NULL, 0, 0);
++    fail_if(ret != EOK, "Could not add g1");
++
++    ret = sysdb_add_group(test_ctx->ctx->sysdb,
++                          "g2", 654, NULL, 0, 0);
++    fail_if(ret != EOK, "Could not add g2");
++
++    ret = sysdb_add_group_member(test_ctx->ctx->sysdb,
++                                 "g1", "u1", SYSDB_MEMBER_USER);
++    fail_if(ret != EOK, "Could not add u1 to g1");
++
++    ret = sysdb_add_group_member(test_ctx->ctx->sysdb,
++                                 "g2", "u2", SYSDB_MEMBER_USER);
++    fail_if(ret != EOK, "Could not add u2 to g2");
++}
++
++void teardown_simple_group(void)
++{
++    errno_t ret;
++
++    ret = sysdb_delete_user(test_ctx->ctx->sysdb, "u1", 0);
++    fail_if(ret != EOK, "Could not delete u1");
++    ret = sysdb_delete_user(test_ctx->ctx->sysdb, "u2", 0);
++    fail_if(ret != EOK, "Could not delete u2");
++    ret = sysdb_delete_user(test_ctx->ctx->sysdb, "u3", 0);
++    fail_if(ret != EOK, "Could not delete u3");
++    ret = sysdb_delete_group(test_ctx->ctx->sysdb, "g1", 0);
++    fail_if(ret != EOK, "Could not delete g1");
++    ret = sysdb_delete_group(test_ctx->ctx->sysdb, "g2", 0);
++    fail_if(ret != EOK, "Could not delete g2");
++
++    teardown_simple();
++}
++
+ START_TEST(test_both_empty)
+ {
+     int ret;
+     bool access_granted = false;
+ 
+-    ctx->allow_users = NULL;
+-    ctx->deny_users = NULL;
++    test_ctx->ctx->allow_users = NULL;
++    test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(ctx, "u1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == true, "Access denied "
+                                         "while both lists are empty.");
+@@ -74,15 +187,15 @@ START_TEST(test_allow_empty)
+     int ret;
+     bool access_granted = true;
+ 
+-    ctx->allow_users = NULL;
+-    ctx->deny_users = discard_const(ulist_1);
++    test_ctx->ctx->allow_users = NULL;
++    test_ctx->ctx->deny_users = discard_const(ulist_1);
+ 
+-    ret = simple_access_check(ctx, "u1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == false, "Access granted "
+                                          "while user is in deny list.");
+ 
+-    ret = simple_access_check(ctx, "u3", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == true, "Access denied "
+                                          "while user is not in deny list.");
+@@ -94,15 +207,15 @@ START_TEST(test_deny_empty)
+     int ret;
+     bool access_granted = false;
+ 
+-    ctx->allow_users = discard_const(ulist_1);
+-    ctx->deny_users = NULL;
++    test_ctx->ctx->allow_users = discard_const(ulist_1);
++    test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(ctx, "u1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == true, "Access denied "
+                                         "while user is in allow list.");
+ 
+-    ret = simple_access_check(ctx, "u3", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == false, "Access granted "
+                                         "while user is not in allow list.");
+@@ -114,15 +227,15 @@ START_TEST(test_both_set)
+     int ret;
+     bool access_granted = false;
+ 
+-    ctx->allow_users = discard_const(ulist_1);
+-    ctx->deny_users = discard_const(ulist_1);
++    test_ctx->ctx->allow_users = discard_const(ulist_1);
++    test_ctx->ctx->deny_users = discard_const(ulist_1);
+ 
+-    ret = simple_access_check(ctx, "u1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == false, "Access granted "
+                                          "while user is in deny list.");
+ 
+-    ret = simple_access_check(ctx, "u3", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == false, "Access granted "
+                                         "while user is not in allow list.");
+@@ -134,18 +247,18 @@ START_TEST(test_case)
+     int ret;
+     bool access_granted = false;
+ 
+-    ctx->allow_users = discard_const(ulist_1);
+-    ctx->deny_users = NULL;
++    test_ctx->ctx->allow_users = discard_const(ulist_1);
++    test_ctx->ctx->deny_users = NULL;
+ 
+-    ret = simple_access_check(ctx, "U1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == false, "Access granted "
+                                          "for user with different case "
+                                          "in case-sensitive domain");
+ 
+-    ctx->domain->case_sensitive = false;
++    test_ctx->ctx->domain->case_sensitive = false;
+ 
+-    ret = simple_access_check(ctx, "U1", &access_granted);
++    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
+     fail_unless(ret == EOK, "access_simple_check failed.");
+     fail_unless(access_granted == true, "Access denied "
+                                         "for user with different case "
+@@ -153,11 +266,95 @@ START_TEST(test_case)
+ }
+ END_TEST
+ 
++START_TEST(test_group_allow_empty)
++{
++    int ret;
++    bool access_granted = true;
++
++    test_ctx->ctx->allow_groups = NULL;
++    test_ctx->ctx->deny_groups = discard_const(glist_1);
++
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == false, "Access granted "
++                                         "while group is in deny list.");
++
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == true, "Access denied "
++                                         "while group is not in deny list.");
++}
++END_TEST
++
++START_TEST(test_group_deny_empty)
++{
++    int ret;
++    bool access_granted = false;
++
++    test_ctx->ctx->allow_groups = discard_const(glist_1);
++    test_ctx->ctx->deny_groups = NULL;
++
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == true, "Access denied "
++                                        "while group is in allow list.");
++
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == false, "Access granted "
++                                        "while group is not in allow list.");
++}
++END_TEST
++
++START_TEST(test_group_both_set)
++{
++    int ret;
++    bool access_granted = false;
++
++    test_ctx->ctx->allow_groups = discard_const(ulist_1);
++    test_ctx->ctx->deny_groups = discard_const(ulist_1);
++
++    ret = simple_access_check(test_ctx->ctx, "u1", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == false, "Access granted "
++                                         "while group is in deny list.");
++
++    ret = simple_access_check(test_ctx->ctx, "u3", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == false, "Access granted "
++                                        "while group is not in allow list.");
++}
++END_TEST
++
++START_TEST(test_group_case)
++{
++    int ret;
++    bool access_granted = false;
++
++    test_ctx->ctx->allow_groups = discard_const(ulist_1);
++    test_ctx->ctx->deny_groups = NULL;
++
++    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == false, "Access granted "
++                                         "for group with different case "
++                                         "in case-sensitive domain");
++
++    test_ctx->ctx->domain->case_sensitive = false;
++
++    ret = simple_access_check(test_ctx->ctx, "U1", &access_granted);
++    fail_unless(ret == EOK, "access_simple_check failed.");
++    fail_unless(access_granted == true, "Access denied "
++                                        "for group with different case "
++                                        "in case-insensitive domain");
++}
++END_TEST
++
+ Suite *access_simple_suite (void)
+ {
+     Suite *s = suite_create("access_simple");
+ 
+-    TCase *tc_allow_deny = tcase_create("allow/deny");
++    TCase *tc_allow_deny = tcase_create("user allow/deny");
+     tcase_add_checked_fixture(tc_allow_deny, setup_simple, teardown_simple);
+     tcase_add_test(tc_allow_deny, test_both_empty);
+     tcase_add_test(tc_allow_deny, test_allow_empty);
+@@ -166,6 +363,15 @@ Suite *access_simple_suite (void)
+     tcase_add_test(tc_allow_deny, test_case);
+     suite_add_tcase(s, tc_allow_deny);
+ 
++    TCase *tc_grp_allow_deny = tcase_create("group allow/deny");
++    tcase_add_checked_fixture(tc_grp_allow_deny,
++                              setup_simple_group, teardown_simple_group);
++    tcase_add_test(tc_grp_allow_deny, test_group_allow_empty);
++    tcase_add_test(tc_grp_allow_deny, test_group_deny_empty);
++    tcase_add_test(tc_grp_allow_deny, test_group_both_set);
++    tcase_add_test(tc_grp_allow_deny, test_group_case);
++    suite_add_tcase(s, tc_grp_allow_deny);
++
+     return s;
+ }
+ 
+@@ -174,6 +380,7 @@ int main(int argc, const char *argv[])
+     int opt;
+     poptContext pc;
+     int number_failed;
++    int ret;
+ 
+     struct poptOption long_options[] = {
+         POPT_AUTOHELP
+@@ -205,6 +412,20 @@ int main(int argc, const char *argv[])
+     srunner_run_all(sr, CK_ENV);
+     number_failed = srunner_ntests_failed(sr);
+     srunner_free(sr);
++
++    ret = unlink(TESTS_PATH"/"TEST_CONF_FILE);
++    if (ret != EOK) {
++        fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
++                errno, strerror(errno));
++        return EXIT_FAILURE;
++    }
++    ret = unlink(TESTS_PATH"/"LOCAL_SYSDB_FILE);
++    if (ret != EOK) {
++        fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
++                errno, strerror(errno));
++        return EXIT_FAILURE;
++    }
++
+     return (number_failed==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+ 
+-- 
+1.8.1.4
+
diff --git a/0006-Do-not-compile-main-in-DP-if-UNIT_TESTING-is-defined.patch b/0006-Do-not-compile-main-in-DP-if-UNIT_TESTING-is-defined.patch
new file mode 100644
index 0000000..73d5b84
--- /dev/null
+++ b/0006-Do-not-compile-main-in-DP-if-UNIT_TESTING-is-defined.patch
@@ -0,0 +1,40 @@
+From 26590d31f492dbbd36be6d0bde46a4bd3b221edb Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek at redhat.com>
+Date: Mon, 4 Mar 2013 16:37:04 +0100
+Subject: [PATCH 3/4] Do not compile main() in DP if UNIT_TESTING is defined
+
+The simple access provider unit tests now need to link against the Data
+Provider when they start using the be_file_account_request() function.
+But then we would start having conflicts as at least the main()
+functions would clash.
+
+If UNIT_TESTING is defined, then the data_provider_be.c module does not
+contain the main() function and can be linked against directly from
+another module that contains its own main() function
+---
+ src/providers/data_provider_be.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
+index f85a04d09b5b41b17be611c333324f7207242979..33590aeef0231427642916c6a2f9bc391c165c21 100644
+--- a/src/providers/data_provider_be.c
++++ b/src/providers/data_provider_be.c
+@@ -2651,6 +2651,7 @@ fail:
+     return ret;
+ }
+ 
++#ifndef UNIT_TESTING
+ int main(int argc, const char *argv[])
+ {
+     int opt;
+@@ -2732,6 +2733,7 @@ int main(int argc, const char *argv[])
+ 
+     return 0;
+ }
++#endif
+ 
+ static int data_provider_res_init(DBusMessage *message,
+                                   struct sbus_connection *conn)
+-- 
+1.8.1.4
+
diff --git a/0007-Provide-a-be_get_account_info_send-function.patch b/0007-Provide-a-be_get_account_info_send-function.patch
new file mode 100644
index 0000000..766e21c
--- /dev/null
+++ b/0007-Provide-a-be_get_account_info_send-function.patch
@@ -0,0 +1,236 @@
+From b63830b142053f99bfe954d4be5a2b0f68ce3a93 Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek at redhat.com>
+Date: Fri, 22 Feb 2013 11:01:38 +0100
+Subject: [PATCH 1/4] Provide a be_get_account_info_send function
+
+In order to resolve group names in the simple access provider we need to
+contact the Data Provider in a generic fashion from the access provider.
+We can't call any particular implementation (like sdap_generic_send())
+because we have no idea what kind of provider is configured as the
+id_provider.
+
+This patch splits introduces the be_file_account_request() function into
+the data_provider_be module and makes it public.
+
+A future patch should make the be_get_account_info function use the
+be_get_account_info_send function.
+---
+ src/providers/data_provider_be.c | 153 ++++++++++++++++++++++++++++++++++-----
+ src/providers/dp_backend.h       |  15 ++++
+ 2 files changed, 149 insertions(+), 19 deletions(-)
+
+diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
+index b261bf8d456829a513ec352c8290d2011bd3526a..f85a04d09b5b41b17be611c333324f7207242979 100644
+--- a/src/providers/data_provider_be.c
++++ b/src/providers/data_provider_be.c
+@@ -717,6 +717,34 @@ static errno_t be_initgroups_prereq(struct be_req *be_req)
+ }
+ 
+ static errno_t
++be_file_account_request(struct be_req *be_req, struct be_acct_req *ar)
++{
++    errno_t ret;
++    struct be_ctx *be_ctx = be_req->be_ctx;
++
++    be_req->req_data = ar;
++
++    /* see if we need a pre request call, only done for initgroups for now */
++    if ((ar->entry_type & 0xFF) == BE_REQ_INITGROUPS) {
++        ret = be_initgroups_prereq(be_req);
++        if (ret) {
++            DEBUG(SSSDBG_CRIT_FAILURE, ("Prerequest failed"));
++            return ret;
++        }
++    }
++
++    /* process request */
++    ret = be_file_request(be_ctx, be_req,
++                          be_ctx->bet_info[BET_ID].bet_ops->handler);
++    if (ret != EOK) {
++        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to file request"));
++        return ret;
++    }
++
++    return EOK;
++}
++
++static errno_t
+ split_name_extended(TALLOC_CTX *mem_ctx,
+                     const char *filter,
+                     char **name,
+@@ -742,6 +770,110 @@ split_name_extended(TALLOC_CTX *mem_ctx,
+     return EOK;
+ }
+ 
++static void
++be_get_account_info_done(struct be_req *be_req,
++                         int dp_err, int dp_ret,
++                         const char *errstr);
++
++struct be_get_account_info_state {
++    int err_maj;
++    int err_min;
++    const char *err_msg;
++};
++
++struct tevent_req *
++be_get_account_info_send(TALLOC_CTX *mem_ctx,
++                         struct tevent_context *ev,
++                         struct be_client *becli,
++                         struct be_ctx *be_ctx,
++                         struct be_acct_req *ar)
++{
++    struct tevent_req *req;
++    struct be_get_account_info_state *state;
++    struct be_req *be_req;
++    errno_t ret;
++
++    req = tevent_req_create(mem_ctx, &state,
++                            struct be_get_account_info_state);
++    if (!req) return NULL;
++
++    be_req = talloc_zero(mem_ctx, struct be_req);
++    if (be_req == NULL) {
++        ret = ENOMEM;
++        goto done;
++    }
++
++    be_req->becli = becli;
++    be_req->be_ctx = be_ctx;
++    be_req->fn = be_get_account_info_done;
++    be_req->pvt = req;
++
++    ret = be_file_account_request(be_req, ar);
++    if (ret != EOK) {
++        goto done;
++    }
++
++    return req;
++
++done:
++    tevent_req_error(req, ret);
++    tevent_req_post(req, ev);
++    return req;
++}
++
++static void
++be_get_account_info_done(struct be_req *be_req,
++                         int dp_err, int dp_ret,
++                         const char *errstr)
++{
++    struct tevent_req *req;
++    struct be_get_account_info_state *state;
++
++    req = talloc_get_type(be_req->pvt, struct tevent_req);
++    state = tevent_req_data(req, struct be_get_account_info_state);
++
++    state->err_maj = dp_err;
++    state->err_min = dp_ret;
++    if (errstr) {
++        state->err_msg = talloc_strdup(state, errstr);
++        if (state->err_msg == NULL) {
++            talloc_free(be_req);
++            tevent_req_error(req, ENOMEM);
++            return;
++        }
++    }
++
++    talloc_free(be_req);
++    tevent_req_done(req);
++}
++
++errno_t be_get_account_info_recv(struct tevent_req *req,
++                                 TALLOC_CTX *mem_ctx,
++                                 int *_err_maj,
++                                 int *_err_min,
++                                 const char **_err_msg)
++{
++    struct be_get_account_info_state *state;
++
++    state = tevent_req_data(req, struct be_get_account_info_state);
++
++    TEVENT_REQ_RETURN_ON_ERROR(req);
++
++    if (_err_maj) {
++        *_err_maj = state->err_maj;
++    }
++
++    if (_err_min) {
++        *_err_min = state->err_min;
++    }
++
++    if (_err_msg) {
++        *_err_msg = talloc_steal(mem_ctx, state->err_msg);
++    }
++
++    return EOK;
++}
++
+ static int be_get_account_info(DBusMessage *message, struct sbus_connection *conn)
+ {
+     struct be_acct_req *req;
+@@ -845,8 +977,6 @@ static int be_get_account_info(DBusMessage *message, struct sbus_connection *con
+         goto done;
+     }
+ 
+-    be_req->req_data = req;
+-
+     if ((attr_type != BE_ATTR_CORE) &&
+         (attr_type != BE_ATTR_MEM) &&
+         (attr_type != BE_ATTR_ALL)) {
+@@ -893,26 +1023,11 @@ static int be_get_account_info(DBusMessage *message, struct sbus_connection *con
+         goto done;
+     }
+ 
+-    /* see if we need a pre request call, only done for initgroups for now */
+-    if ((type & 0xFF) == BE_REQ_INITGROUPS) {
+-        ret = be_initgroups_prereq(be_req);
+-        if (ret) {
+-            err_maj = DP_ERR_FATAL;
+-            err_min = ret;
+-            err_msg = "Prerequest failed";
+-            goto done;
+-        }
+-    }
+-
+-    /* process request */
+-
+-    ret = be_file_request(becli->bectx->bet_info[BET_ID].pvt_bet_data,
+-                          be_req,
+-                          becli->bectx->bet_info[BET_ID].bet_ops->handler);
++    ret = be_file_account_request(be_req, req);
+     if (ret != EOK) {
+         err_maj = DP_ERR_FATAL;
+         err_min = ret;
+-        err_msg = "Failed to file request";
++        err_msg = "Cannot file account request";
+         goto done;
+     }
+ 
+diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
+index 58a9b7490df8aab06a2a15f8c0fed9ac5ed33600..743b6f4ffe73fe9ec7404218184a7133aba054c6 100644
+--- a/src/providers/dp_backend.h
++++ b/src/providers/dp_backend.h
+@@ -258,4 +258,19 @@ int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx,
+                                         const char *service_name);
+ 
+ void reset_fo(struct be_ctx *be_ctx);
++
++/* Request account information */
++struct tevent_req *
++be_get_account_info_send(TALLOC_CTX *mem_ctx,
++                         struct tevent_context *ev,
++                         struct be_client *becli,
++                         struct be_ctx *be_ctx,
++                         struct be_acct_req *ar);
++
++errno_t be_get_account_info_recv(struct tevent_req *req,
++                                 TALLOC_CTX *mem_ctx,
++                                 int *_err_maj,
++                                 int *_err_min,
++                                 const char **_err_msg);
++
+ #endif /* __DP_BACKEND_H___ */
+-- 
+1.8.1.4
+
diff --git a/sssd.spec b/sssd.spec
index 22c3fcd..3a57e2b 100644
--- a/sssd.spec
+++ b/sssd.spec
@@ -16,7 +16,7 @@
 
 Name: sssd
 Version: 1.9.4
-Release: 5%{?dist}
+Release: 6%{?dist}
 Group: Applications/System
 Summary: System Security Services Daemon
 License: GPLv3+
@@ -28,6 +28,10 @@ BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
 Patch0001:  0001-krb-recreate-ccache-if-it-was-deleted.patch
 Patch0002:  0002-subdomains-replace-invalid-characters-with-underscor.patch
 Patch0003:  0003-Fix-the-krb5-password-expiration-warning.patch
+Patch0004:  0004-Resolve-GIDs-in-the-simple-access-provider.patch
+Patch0005:  0005-Add-unit-tests-for-simple-access-test-by-groups.patch
+Patch0006:  0006-Do-not-compile-main-in-DP-if-UNIT_TESTING-is-defined.patch
+Patch0007:  0007-Provide-a-be_get_account_info_send-function.patch
 
 Patch0501:  0501-FEDORA-Switch-the-default-ccache-location.patch
 
@@ -524,6 +528,10 @@ fi
 %postun -n libsss_sudo -p /sbin/ldconfig
 
 %changelog
+* Wed Mar 20 2013 Jakub Hrozek <jhrozek at redhat.com> - 1.9.4-6
+- Fix CVE-2013-0287 sssd: simple access provider flaw prevents intended
+  ACL use when client to an AD provider (#923838)
+
 * Thu Feb 21 2013 Jakub Hrozek <jhrozek at redhat.com> - 1.9.4-5
 - Fix the Kerberos password expiration warning (#912223)
 


More information about the scm-commits mailing list