diff options
Diffstat (limited to 'src/responder/nss/nss_protocol_grent.c')
-rw-r--r-- | src/responder/nss/nss_protocol_grent.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/src/responder/nss/nss_protocol_grent.c b/src/responder/nss/nss_protocol_grent.c new file mode 100644 index 0000000..887501a --- /dev/null +++ b/src/responder/nss/nss_protocol_grent.c @@ -0,0 +1,495 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "responder/nss/nss_protocol.h" +#include "util/sss_format.h" + +static errno_t +sss_nss_get_grent(TALLOC_CTX *mem_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + uint32_t *_gid, + struct sized_string **_name) +{ + const char *name; + uint32_t gid; + errno_t ret; + + /* Check object class. */ + if (!ldb_msg_check_string_attribute(msg, SYSDB_OBJECTCATEGORY, + SYSDB_GROUP_CLASS)) { + DEBUG(SSSDBG_MINOR_FAILURE, "Wrong object (%s) found on stack!\n", + ldb_dn_get_linearized(msg->dn)); + return ERR_INTERNAL; + } + + /* Get fields. */ + name = sss_get_name_from_msg(domain, msg); + gid = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, SYSDB_GIDNUM, 0); + + if (name == NULL || gid == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Incomplete group object for %s[%u]! Skipping\n", + name ? name : "<NULL>", gid); + return EINVAL; + } + + /* Convert to sized strings. */ + ret = sized_output_name(mem_ctx, nss_ctx->rctx, name, domain, _name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sized_output_name failed, skipping [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + *_gid = gid; + + return EOK; +} + +static struct ldb_message_element * +sss_nss_get_group_members(struct sss_domain_info *domain, + struct ldb_message *msg) +{ + struct ldb_message_element *el; + + if (domain->ignore_group_members) { + return NULL; + } + + /* Unconditionally prefer OVERRIDE_PREFIX SYSDB_MEMBERUID, it + * might contain override names from the default view. */ + el = ldb_msg_find_element(msg, OVERRIDE_PREFIX SYSDB_MEMBERUID); + if (el == NULL) { + el = ldb_msg_find_element(msg, SYSDB_MEMBERUID); + } + + return el; +} + +static struct ldb_message_element * +sss_nss_get_group_ghosts(struct sss_domain_info *domain, + struct ldb_message *msg, + const char *group_name) +{ + struct ldb_message_element *el; + + if (domain->ignore_group_members) { + return NULL; + } + + el = ldb_msg_find_element(msg, SYSDB_GHOST); + if (el == NULL) { + return NULL; + } + + if (DOM_HAS_VIEWS(domain) && !is_local_view(domain->view_name) + && el->num_values != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Domain has a view [%s] but group [%s] still has " + "ghost members.\n", domain->view_name, group_name); + return NULL; + } + + return el; +} + +static errno_t +sss_nss_protocol_fill_members(struct sss_packet *packet, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *group_name, + size_t *_rp, + uint32_t *_num_members) +{ + TALLOC_CTX *tmp_ctx; + struct resp_ctx *rctx = nss_ctx->rctx; + struct ldb_message_element *members[2]; + struct ldb_message_element *el; + struct sized_string *name; + const char *member_name; + uint32_t num_members = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + members[0] = sss_nss_get_group_members(domain, msg); + members[1] = sss_nss_get_group_ghosts(domain, msg, group_name); + + if (is_files_provider(domain) && members[1] != NULL) { + /* If there is a ghost member in files provider it means that we + * did not store the user on purpose (e.g. it has uid or gid 0). + * Therefore nss_files does handle the user and therefore we + * must let nss_files to also handle this group in order to + * provide correct membership. */ + DEBUG(SSSDBG_TRACE_FUNC, + "Unknown members found. nss_files will handle it.\n"); + + ret = sss_ncache_set_group(rctx->ncache, false, domain, group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_ncache_set_group failed.\n"); + } + + ret = ENOENT; + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + for (i = 0; i < sizeof(members) / sizeof(members[0]); i++) { + el = members[i]; + if (el == NULL) { + continue; + } + + for (j = 0; j < el->num_values; j++) { + member_name = (const char *)el->values[j].data; + + if (nss_ctx->filter_users_in_groups) { + ret = sss_ncache_check_user(rctx->ncache, domain, member_name); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group [%s] member [%s] filtered out! " + "(negative cache)\n", group_name, member_name); + continue; + } + } + + ret = sized_domain_name(tmp_ctx, rctx, member_name, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get sized name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_packet_grow(packet, name->len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_SET_STRING(&body[*_rp], name->str, name->len, _rp); + + num_members++; + } + } + + ret = EOK; + +done: + *_num_members = num_members; + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sss_nss_protocol_fill_grent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string *name; + struct sized_string pwfield; + uint32_t gid; + uint32_t num_results; + uint32_t num_members; + char *members; + size_t members_size; + size_t rp; + size_t rp_members; + size_t rp_num_members; + size_t body_len; + uint8_t *body; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + /* Password field content. */ + to_sized_string(&pwfield, sss_nss_get_pwfield(nss_ctx, result->domain)); + + ret = sss_nss_get_grent(tmp_ctx, nss_ctx, result->domain, msg, + &gid, &name); + if (ret != EOK) { + continue; + } + + /* Adjust packet size: gid, num_members + string fields. */ + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t) + + name->len + pwfield.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet. */ + + SAFEALIGN_SET_UINT32(&body[rp], gid, &rp); + + /* Remember pointer to number of members field. */ + rp_num_members = rp; + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); + SAFEALIGN_SET_STRING(&body[rp], name->str, name->len, &rp); + SAFEALIGN_SET_STRING(&body[rp], pwfield.str, pwfield.len, &rp); + rp_members = rp; + + /* Fill members. */ + ret = sss_nss_protocol_fill_members(packet, nss_ctx, result->domain, msg, + name->str, &rp, &num_members); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_SET_UINT32(&body[rp_num_members], num_members, NULL); + + num_results++; + + /* Do not store entry in memory cache during enumeration or when + * requested or if cache explicitly disabled. */ + if (!cmd_ctx->enumeration + && ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) == 0) + && (nss_ctx->grp_mc_ctx != NULL)) { + members = (char *)&body[rp_members]; + members_size = body_len - rp_members; + ret = sss_mmap_cache_gr_store(&nss_ctx->grp_mc_ctx, name, &pwfield, + gid, num_members, members, + members_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %s (%s) in mem-cache [%d]: %s!\n", + name->str, result->domain->name, ret, sss_strerror(ret)); + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} + +static bool is_group_filtered(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *grp_name, gid_t gid) +{ + int ret; + + if (grp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Group with gid [%"SPRIgid"] has no name, this should never " + "happen, trying to continue without.\n", gid); + } else { + ret = sss_ncache_check_group(ncache, domain, grp_name); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "Group [%s] is filtered out! " + "(negative cache)", grp_name); + return true; + } + } + ret = sss_ncache_check_gid(ncache, domain, gid); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "Group [%"SPRIgid"] is filtered out! " + "(negative cache)", gid); + return true; + } + + return false; +} + +errno_t +sss_nss_protocol_fill_initgr(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct sss_domain_info *domain; + struct sss_domain_info *grp_dom; + struct ldb_message *user; + struct ldb_message *msg; + struct ldb_message *primary_group_msg; + const char *posix; + struct sized_string rawname; + struct sized_string canonical_name; + uint32_t num_results; + uint8_t *body; + size_t body_len; + size_t rp; + gid_t gid; + const char *grp_name; + gid_t orig_gid; + errno_t ret; + int i; + + if (result->count == 0) { + return ENOENT; + } + + domain = result->domain; + + /* num_results, reserved + gids */ + ret = sss_packet_grow(packet, (2 + result->count) * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + sss_packet_get_body(packet, &body, &body_len); + rp = 2 * sizeof(uint32_t); + + user = result->msgs[0]; + gid = sss_view_ldb_msg_find_attr_as_uint64(domain, user, SYSDB_GIDNUM, 0); + orig_gid = sss_view_ldb_msg_find_attr_as_uint64(domain, user, + SYSDB_PRIMARY_GROUP_GIDNUM, + 0); + + /* Try to get the real gid in case the primary group's gid was overridden. */ + ret = sysdb_search_group_by_origgid(NULL, domain, orig_gid, NULL, + &primary_group_msg); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, + "There is no override for group %" SPRIgid "\n", + orig_gid); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to find the original group id attribute for %" SPRIgid + ". Assuming there is none. [%d] %s\n", + orig_gid, ret, sss_strerror(ret)); + } + /* Just continue with what we have. */ + } else { + orig_gid = ldb_msg_find_attr_as_uint64(primary_group_msg, SYSDB_GIDNUM, + orig_gid); + talloc_free(primary_group_msg); + } + + /* If the GID of the original primary group is available but equal to the + * current primary GID it must not be added. */ + orig_gid = orig_gid == gid ? 0 : orig_gid; + + /* First message is user, skip it. */ + num_results = 0; + for (i = 1; i < result->count; i++) { + msg = result->msgs[i]; + grp_dom = find_domain_by_msg(domain, msg); + gid = sss_view_ldb_msg_find_attr_as_uint64(grp_dom, msg, SYSDB_GIDNUM, + 0); + posix = ldb_msg_find_attr_as_string(msg, SYSDB_POSIX, NULL); + grp_name = sss_view_ldb_msg_find_attr_as_string(grp_dom, msg, SYSDB_NAME, + NULL); + + if (gid == 0) { + if (posix != NULL && strcmp(posix, "FALSE") == 0) { + continue; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Incomplete group object [%s] for initgroups! " + "Skipping.\n", ldb_dn_get_linearized(msg->dn)); + continue; + } + } + + if (is_group_filtered(nss_ctx->rctx->ncache, grp_dom, grp_name, gid)) { + continue; + } + + SAFEALIGN_COPY_UINT32(&body[rp], &gid, &rp); + num_results++; + + /* Do not add the GID of the original primary group if the user is + * already an explicit member of the group. */ + if (orig_gid == gid) { + orig_gid = 0; + } + } + + if (orig_gid == 0) { + /* Initialize allocated memory to be safe and make Valgrind happy. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); + } else { + /* Insert original primary group into the result. */ + SAFEALIGN_COPY_UINT32(&body[rp], &orig_gid, &rp); + num_results++; + } + + if (nss_ctx->initgr_mc_ctx + && ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) == 0) + && (nss_ctx->initgr_mc_ctx != NULL)) { + to_sized_string(&rawname, cmd_ctx->rawname); + to_sized_string(&canonical_name, sss_get_name_from_msg(domain, user)); + + ret = sss_mmap_cache_initgr_store(&nss_ctx->initgr_mc_ctx, &rawname, + &canonical_name, num_results, + body + 2 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store initgroups %s (%s) in mem-cache [%d]: %s!\n", + rawname.str, domain->name, ret, sss_strerror(ret)); + sss_packet_set_size(packet, 0); + return ret; + } + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} |