diff options
-rw-r--r-- | Makefile.am | 15 | ||||
-rw-r--r-- | src/providers/simple/simple_access.c | 31 | ||||
-rw-r--r-- | src/providers/simple/simple_access.h | 11 | ||||
-rw-r--r-- | src/providers/simple/simple_access_check.c | 735 | ||||
-rw-r--r-- | src/tests/simple_access-tests.c | 361 |
5 files changed, 926 insertions, 227 deletions
diff --git a/Makefile.am b/Makefile.am index 265da9c79..211472195 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 3dcea8691..e617e93dc 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 2ddf27692..15dfaceb2 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 cb5f52825..6475e773e 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 <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" -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 19c72b66e..1c2d1a9ea 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"); |