summaryrefslogtreecommitdiffstats
path: root/src/responder/nss/nss_protocol_grent.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/nss/nss_protocol_grent.c')
-rw-r--r--src/responder/nss/nss_protocol_grent.c495
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;
+}