/* Authors: Pavel Březina 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 . */ #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 : "", 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; }