[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