From c0bca1722d6f9dfb654ad78397be70f79ff39af1 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Sat, 23 Feb 2013 10:44:54 +0100 Subject: 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 | 15 +- src/providers/simple/simple_access.c | 31 +- src/providers/simple/simple_access.h | 11 +- src/providers/simple/simple_access_check.c | 735 ++++++++++++++++++++++++----- src/tests/simple_access-tests.c | 361 ++++++++++---- 5 files changed, 926 insertions(+), 227 deletions(-) diff --git a/Makefile.am b/Makefile.am index 265da9c7..21147219 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1032,15 +1032,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_check.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 diff --git a/src/providers/simple/simple_access.c b/src/providers/simple/simple_access.c index 3dcea869..e617e93d 100644 --- a/src/providers/simple/simple_access.c +++ b/src/providers/simple/simple_access.c @@ -32,12 +32,13 @@ #define CONFDB_SIMPLE_ALLOW_GROUPS "simple_allow_groups" #define CONFDB_SIMPLE_DENY_GROUPS "simple_deny_groups" +static void simple_access_check(struct tevent_req *req); + void simple_access_handler(struct be_req *be_req) { struct be_ctx *be_ctx = be_req_get_be_ctx(be_req); - int ret; - bool access_granted = false; struct pam_data *pd; + struct tevent_req *req; struct simple_ctx *ctx; pd = talloc_get_type(be_req_get_data(be_req), struct pam_data); @@ -53,7 +54,30 @@ void simple_access_handler(struct be_req *be_req) ctx = talloc_get_type(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_terminate(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_get_data(be_req), 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; @@ -87,6 +111,7 @@ int sssm_simple_access_init(struct be_ctx *bectx, struct bet_ops **ops, } 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 2ddf2769..15dfaceb 100644 --- a/src/providers/simple/simple_access.h +++ b/src/providers/simple/simple_access.h @@ -26,6 +26,7 @@ struct simple_ctx { struct sss_domain_info *domain; + struct be_ctx *be_ctx; char **allow_users; char **deny_users; @@ -33,6 +34,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 index cb5f5282..6475e773 100644 --- a/src/providers/simple/simple_access_check.c +++ b/src/providers/simple/simple_access_check.c @@ -19,36 +19,40 @@ along with this program. If not, see . */ +#include "providers/dp_backend.h" #include "providers/simple/simple_access.h" #include "util/sss_utf8.h" #include "db/sysdb.h" -errno_t simple_access_check(struct simple_ctx *ctx, const char *username, - bool *access_granted) +static bool +is_posix(const struct ldb_message *group) { - 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; + 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; +} - *access_granted = 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(9, ("User [%s] found in allow list, access granted.\n", + DEBUG(SSSDBG_TRACE_LIBS, + ("User [%s] found in allow list, access granted.\n", username)); /* Do not return immediately on explicit allow @@ -62,6 +66,8 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, /* 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; } @@ -69,7 +75,8 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, 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", + DEBUG(SSSDBG_TRACE_LIBS, + ("User [%s] found in deny list, access denied.\n", username)); /* Return immediately on explicit denial */ @@ -79,97 +86,16 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, } } - 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->domain->sysdb, ctx->domain, - 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->domain->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->domain->sysdb, ctx->domain, - 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); - } + return EAGAIN; +} - groups[j] = NULL; +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 @@ -178,8 +104,8 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, 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])) { + for(j = 0; group_names[j]; j++) { + if (sss_string_equal(cs, group_names[j], ctx->allow_groups[i])) { matched = true; break; } @@ -189,6 +115,9 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, * processing early */ if (matched) { + DEBUG(SSSDBG_TRACE_LIBS, + ("Group [%s] found in allow list, access granted.\n", + group_names[j])); *access_granted = true; break; } @@ -199,8 +128,8 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, 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])) { + for(j = 0; group_names[j]; j++) { + if (sss_string_equal(cs, group_names[j], ctx->deny_groups[i])) { matched = true; break; } @@ -210,15 +139,587 @@ errno_t simple_access_check(struct simple_ctx *ctx, const char *username, * processing early */ if (matched) { + DEBUG(SSSDBG_TRACE_LIBS, + ("Group [%s] found in deny list, access denied.\n", + group_names[j])); *access_granted = false; break; } } } - ret = EOK; + 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->ctx->domain, 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, ctx->domain, + 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: - talloc_free(tmp_ctx); - return ret; + 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, + state->ctx->domain, + 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 19c72b66..1c2d1a9e 100644 --- a/src/tests/simple_access-tests.c +++ b/src/tests/simple_access-tests.c @@ -35,16 +35,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 +76,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,7 +125,7 @@ void setup_simple(void) fail_if(ret != EOK, "Could not initialize connection to the sysdb (%d)", ret); test_ctx->sysdb = test_ctx->ctx->domain->sysdb; test_ctx->ctx->domain->case_sensitive = true; - + test_ctx->ctx->domain->mpg = false; /* Simulate an LDAP domain better */ } void teardown_simple(void) @@ -118,18 +145,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->sysdb, test_ctx->ctx->domain, + "pvt", 999, NULL, 0, 0); + fail_if(ret != EOK, "Could not add private group"); + ret = sysdb_store_user(test_ctx->sysdb, test_ctx->ctx->domain, - "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->sysdb, test_ctx->ctx->domain, - "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->sysdb, test_ctx->ctx->domain, - "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"); @@ -164,190 +195,317 @@ void teardown_simple_group(void) fail_if(ret != EOK, "Could not delete g1"); ret = sysdb_delete_group(test_ctx->sysdb, test_ctx->ctx->domain, "g2", 0); fail_if(ret != EOK, "Could not delete g2"); + ret = sysdb_delete_group(test_ctx->sysdb, test_ctx->ctx->domain, "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."); + 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; - 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."); + 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."); + 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; - 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."); + 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."); + 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; - 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."); + 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."); + 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); - 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."); + 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."); + 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); - 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."); + 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."); + 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); - 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."); + 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 @@ -362,6 +520,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"); -- cgit