summaryrefslogtreecommitdiffstats
path: root/src/responder/nss
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/nss')
-rw-r--r--src/responder/nss/nss_cmd.c1439
-rw-r--r--src/responder/nss/nss_enum.c361
-rw-r--r--src/responder/nss/nss_get_object.c546
-rw-r--r--src/responder/nss/nss_iface.c242
-rw-r--r--src/responder/nss/nss_iface.h31
-rw-r--r--src/responder/nss/nss_private.h155
-rw-r--r--src/responder/nss/nss_protocol.c487
-rw-r--r--src/responder/nss/nss_protocol.h217
-rw-r--r--src/responder/nss/nss_protocol_grent.c495
-rw-r--r--src/responder/nss/nss_protocol_hostent.c299
-rw-r--r--src/responder/nss/nss_protocol_netent.c243
-rw-r--r--src/responder/nss/nss_protocol_netgr.c181
-rw-r--r--src/responder/nss/nss_protocol_pwent.c338
-rw-r--r--src/responder/nss/nss_protocol_sid.c704
-rw-r--r--src/responder/nss/nss_protocol_subid.c60
-rw-r--r--src/responder/nss/nss_protocol_svcent.c270
-rw-r--r--src/responder/nss/nss_utils.c38
-rw-r--r--src/responder/nss/nsssrv.c740
-rw-r--r--src/responder/nss/nsssrv_mmap_cache.c1626
-rw-r--r--src/responder/nss/nsssrv_mmap_cache.h86
20 files changed, 8558 insertions, 0 deletions
diff --git a/src/responder/nss/nss_cmd.c b/src/responder/nss/nss_cmd.c
new file mode 100644
index 0000000..dd80113
--- /dev/null
+++ b/src/responder/nss/nss_cmd.c
@@ -0,0 +1,1439 @@
+/*
+ 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 <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "db/sysdb.h"
+#include "responder/nss/nss_private.h"
+#include "responder/nss/nss_protocol.h"
+
+static struct sss_nss_cmd_ctx *
+sss_nss_cmd_ctx_create(TALLOC_CTX *mem_ctx,
+ struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct sss_nss_cmd_ctx *cmd_ctx;
+
+ cmd_ctx = talloc_zero(mem_ctx, struct sss_nss_cmd_ctx);
+ if (cmd_ctx == NULL) {
+ return NULL;
+ }
+
+ cmd_ctx->cli_ctx = cli_ctx;
+ cmd_ctx->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ cmd_ctx->state_ctx = talloc_get_type(cli_ctx->state_ctx,
+ struct sss_nss_state_ctx);
+ cmd_ctx->type = type;
+ cmd_ctx->fill_fn = fill_fn;
+
+ return cmd_ctx;
+}
+
+static errno_t eval_flags(struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_data *data)
+{
+ if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0
+ && (cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Flags SSS_NSS_EX_FLAG_NO_CACHE and "
+ "SSS_NSS_EX_FLAG_INVALIDATE_CACHE are "
+ "mutually exclusive.\n");
+ return EINVAL;
+ }
+
+ if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0) {
+ cache_req_data_set_bypass_cache(data, true);
+ } else if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) {
+ cache_req_data_set_bypass_dp(data, true);
+ }
+
+ return EOK;
+}
+
+static void sss_nss_getby_done(struct tevent_req *subreq);
+static void sss_nss_getlistby_done(struct tevent_req *subreq);
+
+static errno_t sss_nss_getby_name(struct cli_ctx *cli_ctx,
+ bool ex_version,
+ enum cache_req_type type,
+ const char **attrs,
+ enum sss_mc_type memcache,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ const char *rawname;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cmd_ctx->flags = 0;
+ if (ex_version) {
+ ret = sss_nss_protocol_parse_name_ex(cli_ctx, &rawname, &cmd_ctx->flags);
+ } else {
+ ret = sss_nss_protocol_parse_name(cli_ctx, &rawname);
+ }
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input name: %s\n", rawname);
+
+ data = cache_req_data_name_attrs(cmd_ctx, type, rawname, attrs);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = eval_flags(cmd_ctx, data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "eval_flags failed.\n");
+ goto done;
+ }
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, memcache, rawname, 0);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t sss_nss_getby_id(struct cli_ctx *cli_ctx,
+ bool ex_version,
+ enum cache_req_type type,
+ const char **attrs,
+ enum sss_mc_type memcache,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ uint32_t id;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (ex_version) {
+ ret = sss_nss_protocol_parse_id_ex(cli_ctx, &id, &cmd_ctx->flags);
+ } else {
+ ret = sss_nss_protocol_parse_id(cli_ctx, &id);
+ }
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input ID: %u (looking up '%s')\n", id,
+ (fill_fn == sss_nss_protocol_fill_sid) ? "SID" : "POSIX data");
+
+ data = cache_req_data_id_attrs(cmd_ctx, type, id, attrs);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = eval_flags(cmd_ctx, data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "eval_flags failed.\n");
+ goto done;
+ }
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, memcache, NULL, id);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t sss_nss_getby_svc(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ const char *protocol,
+ const char *name,
+ uint16_t port,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cmd_ctx->svc_protocol = protocol;
+
+ data = cache_req_data_svc(cmd_ctx, type, name, protocol, port);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input name: %s, protocol: %s, port: %u\n",
+ (name == NULL ? "<none>" : name),
+ (protocol == NULL ? "<none>" : protocol),
+ port);
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, SSS_MC_NONE, NULL, 0);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t sss_nss_getlistby_cert(struct cli_ctx *cli_ctx,
+ enum cache_req_type type)
+{
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ const char *cert;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, NULL);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cmd_ctx->sid_id_type = SSS_ID_TYPE_UID;
+
+ ret = sss_nss_protocol_parse_cert(cli_ctx, &cert);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input cert: %s\n", get_last_x_chars(cert, 10));
+
+ subreq = cache_req_user_by_cert_send(cmd_ctx, cli_ctx->ev, cli_ctx->rctx,
+ cli_ctx->rctx->ncache, 0,
+ CACHE_REQ_ANY_DOM, NULL,
+ cert);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sss_nss_getlistby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static void sss_nss_getlistby_done(struct tevent_req *subreq)
+{
+ struct cache_req_result **results;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ errno_t ret;
+ struct cli_protocol *pctx;
+
+ cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx);
+
+ ret = cache_req_recv(cmd_ctx, subreq, &results);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n");
+ goto done;
+ }
+
+ pctx = talloc_get_type(cmd_ctx->cli_ctx->protocol_ctx, struct cli_protocol);
+
+ ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in),
+ &pctx->creq->out);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sss_nss_protocol_fill_name_list_all_domains(cmd_ctx->nss_ctx, cmd_ctx,
+ pctx->creq->out, results);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_set_error(pctx->creq->out, EOK);
+
+done:
+ sss_nss_protocol_done(cmd_ctx->cli_ctx, ret);
+ talloc_free(cmd_ctx);
+}
+
+static errno_t sss_nss_getby_cert(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ const char *cert;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cmd_ctx->sid_id_type = SSS_ID_TYPE_UID;
+
+ ret = sss_nss_protocol_parse_cert(cli_ctx, &cert);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ data = cache_req_data_cert(cmd_ctx, type, cert);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input cert: %s\n", get_last_x_chars(cert, 10));
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, SSS_MC_NONE, NULL, 0);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t sss_nss_getby_sid(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ const char *sid;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* It will be detected when constructing output packet. */
+ cmd_ctx->sid_id_type = SSS_ID_TYPE_NOT_SPECIFIED;
+
+ ret = sss_nss_protocol_parse_sid(cli_ctx, &sid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Input SID: %s (looking up '%s')\n", sid,
+ (fill_fn == sss_nss_protocol_fill_name) ? "name"
+ : ((fill_fn == sss_nss_protocol_fill_id) ? "id" : ""));
+
+ data = cache_req_data_sid(cmd_ctx, type, sid, NULL);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, SSS_MC_NONE, NULL, 0);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t sss_nss_getby_addr(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ enum sss_mc_type memcache,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cache_req_data *data;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ uint8_t *addr;
+ uint32_t addrlen;
+ uint32_t af;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cmd_ctx->flags = 0;
+ ret = sss_nss_protocol_parse_addr(cli_ctx, &af, &addrlen, &addr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse address: %s\n",
+ strerror(ret));
+ goto done;
+ }
+
+ data = cache_req_data_addr(cmd_ctx, type, af, addrlen, addr);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set cache request data!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx,
+ data, memcache, NULL, 0);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static errno_t invalidate_cache(struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_result *result)
+{
+ int ret;
+ enum sss_mc_type memcache_type;
+ const char *name;
+ bool is_user;
+ struct sysdb_attrs *attrs = NULL;
+
+ switch (cmd_ctx->type) {
+ case CACHE_REQ_INITGROUPS:
+ case CACHE_REQ_INITGROUPS_BY_UPN:
+ memcache_type = SSS_MC_INITGROUPS;
+ is_user = true;
+ break;
+ case CACHE_REQ_USER_BY_NAME:
+ case CACHE_REQ_USER_BY_ID:
+ memcache_type = SSS_MC_PASSWD;
+ is_user = true;
+ break;
+ case CACHE_REQ_GROUP_BY_NAME:
+ case CACHE_REQ_GROUP_BY_ID:
+ memcache_type = SSS_MC_GROUP;
+ is_user = false;
+ break;
+ default:
+ /* nothing to do -
+ * other requests don't support SSS_NSS_EX_FLAG_INVALIDATE_CACHE
+ */
+ return EOK;
+ }
+
+ /* Find output name to invalidate memory cache entry */
+ name = sss_get_name_from_msg(result->domain, result->msgs[0]);
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Found object has no name.\n");
+ return EINVAL;
+ }
+
+ memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, NULL,
+ name, 0, memcache_type);
+ if (memcache_type == SSS_MC_INITGROUPS) {
+ /* Invalidate the passwd data as well */
+ memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx,
+ result->domain, name, 0, SSS_MC_PASSWD);
+ }
+
+ /* Use sysdb name to invalidate disk cache entry */
+ name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL);
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Found object has no name.\n");
+ return EINVAL;
+ }
+
+ if (memcache_type == SSS_MC_INITGROUPS) {
+ attrs = sysdb_new_attrs(cmd_ctx);
+ if (attrs == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sysdb_attrs_add_time_t(attrs, SYSDB_INITGR_EXPIRE, 1);
+ if (ret != EOK) {
+ talloc_free(attrs);
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_time_t failed.\n");
+ return ret;
+ }
+
+ ret = sysdb_set_user_attr(result->domain, name, attrs, SYSDB_MOD_REP);
+ talloc_free(attrs);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n");
+ return ret;
+ }
+ }
+
+ ret = sysdb_invalidate_cache_entry(result->domain, name, is_user);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sysdb_invalidate_cache_entry failed.\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void sss_nss_getby_done(struct tevent_req *subreq)
+{
+ struct cache_req_result *result;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ errno_t ret;
+
+ cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx);
+
+ ret = sss_nss_get_object_recv(cmd_ctx, subreq, &result, &cmd_ctx->rawname);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ sss_nss_protocol_done(cmd_ctx->cli_ctx, ret);
+ goto done;
+ }
+
+ if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) {
+ ret = invalidate_cache(cmd_ctx, result);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to invalidate cache for [%s].\n",
+ cmd_ctx->rawname);
+ sss_nss_protocol_done(cmd_ctx->cli_ctx, ret);
+ goto done;
+ }
+ }
+
+ sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx,
+ result, cmd_ctx->fill_fn);
+
+done:
+ talloc_free(cmd_ctx);
+}
+
+static void sss_nss_setent_done(struct tevent_req *subreq);
+
+static errno_t sss_nss_setent(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ struct sss_nss_enum_ctx *enum_ctx)
+{
+ struct tevent_req *subreq;
+
+ subreq = sss_nss_setent_send(cli_ctx, cli_ctx->ev, cli_ctx, type, enum_ctx);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setent_send() failed\n");
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_setent_done, cli_ctx);
+
+ return EOK;
+}
+
+static void sss_nss_setent_done(struct tevent_req *subreq)
+{
+ struct cli_ctx *cli_ctx;
+ errno_t ret;
+
+ cli_ctx = tevent_req_callback_data(subreq, struct cli_ctx);
+
+ ret = sss_nss_setent_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK && ret != ENOENT) {
+ sss_nss_protocol_done(cli_ctx, ret);
+ return;
+ }
+
+ /* Both EOK and ENOENT means that setent was successful. */
+ sss_nss_protocol_done(cli_ctx, EOK);
+}
+
+static void sss_nss_getent_done(struct tevent_req *subreq);
+
+static errno_t sss_nss_getent(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ struct sss_nss_enum_index *idx,
+ sss_nss_protocol_fill_packet_fn fill_fn,
+ struct sss_nss_enum_ctx *enum_ctx)
+{
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sss_nss_protocol_parse_limit(cli_ctx, &cmd_ctx->enum_limit);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ cmd_ctx->enumeration = true;
+ cmd_ctx->enum_ctx = enum_ctx;
+ cmd_ctx->enum_index = idx;
+
+ subreq = sss_nss_setent_send(cli_ctx, cli_ctx->ev, cli_ctx, type, enum_ctx);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setent_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_getent_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return ret;
+}
+
+static struct cache_req_result *
+sss_nss_getent_get_result(struct sss_nss_enum_ctx *enum_ctx,
+ struct sss_nss_enum_index *idx)
+{
+ struct cache_req_result *result;
+
+ if (enum_ctx->result == NULL) {
+ /* Nothing was found. */
+ return NULL;
+ }
+
+ result = enum_ctx->result[idx->domain];
+
+ if (result != NULL && idx->result >= result->count) {
+ /* Switch to next domain. */
+ idx->result = 0;
+ idx->domain++;
+
+ result = enum_ctx->result[idx->domain];
+ }
+
+ return result;
+}
+
+static void sss_nss_getent_done(struct tevent_req *subreq)
+{
+ struct cache_req_result *limited;
+ struct cache_req_result *result;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ errno_t ret;
+
+ cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx);
+
+ ret = sss_nss_setent_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ result = sss_nss_getent_get_result(cmd_ctx->enum_ctx, cmd_ctx->enum_index);
+ if (result == NULL) {
+ /* No more records to return. */
+ ret = ENOENT;
+ goto done;
+ }
+
+ /* Create copy of the result with limited number of records. */
+ limited = cache_req_copy_limited_result(cmd_ctx, result,
+ cmd_ctx->enum_index->result,
+ cmd_ctx->enum_limit);
+ if (limited == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ cmd_ctx->enum_index->result += result->count;
+
+ /* Reply with limited result. */
+ sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx,
+ result, cmd_ctx->fill_fn);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ sss_nss_protocol_done(cmd_ctx->cli_ctx, ret);
+ }
+
+ talloc_free(cmd_ctx);
+}
+
+static void sss_nss_setnetgrent_done(struct tevent_req *subreq);
+
+/* This function's name started to collide with external nss symbol,
+ * so it has additional sss_* prefix unlike other functions here. */
+static errno_t sss_nss_setnetgrent(struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ struct tevent_req *subreq;
+ const char *netgroup;
+ errno_t ret;
+
+ cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn);
+ if (cmd_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ nss_ctx = cmd_ctx->nss_ctx;
+ state_ctx = cmd_ctx->state_ctx;
+
+ ret = sss_nss_protocol_parse_name(cli_ctx, &netgroup);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n");
+ goto done;
+ }
+
+ state_ctx->netgrent.domain = 0;
+ state_ctx->netgrent.result = 0;
+
+ talloc_zfree(state_ctx->netgroup);
+ state_ctx->netgroup = talloc_strdup(state_ctx, netgroup);
+ if (state_ctx->netgroup == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* enum_limit is not used for setnetgrent, all results will be returned at
+ * once to allow innetgr() to be implemented in the thread-safe way. */
+ cmd_ctx->enum_limit = 0;
+ cmd_ctx->enumeration = true;
+ cmd_ctx->enum_ctx = NULL; /* We will determine it later. */
+ cmd_ctx->enum_index = &cmd_ctx->state_ctx->netgrent;
+
+ subreq = sss_nss_setnetgrent_send(cli_ctx, cli_ctx->ev, cli_ctx, type,
+ nss_ctx->netgrent, state_ctx->netgroup);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setnetgrent_send() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_setnetgrent_done, cmd_ctx);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(cmd_ctx);
+ return sss_nss_protocol_done(cli_ctx, ret);
+ }
+
+ return EOK;
+}
+
+static void sss_nss_setnetgrent_done(struct tevent_req *subreq)
+{
+ struct sss_nss_enum_ctx *enum_ctx;
+ struct sss_nss_cmd_ctx *cmd_ctx;
+ errno_t ret;
+
+ cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx);
+
+ ret = sss_nss_setent_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ enum_ctx = sss_ptr_hash_lookup(cmd_ctx->nss_ctx->netgrent,
+ cmd_ctx->state_ctx->netgroup,
+ struct sss_nss_enum_ctx);
+ if (enum_ctx == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ cmd_ctx->enum_ctx = enum_ctx;
+
+ /* Reply with result. */
+ sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx,
+ NULL, cmd_ctx->fill_fn);
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ sss_nss_protocol_done(cmd_ctx->cli_ctx, ret);
+ }
+
+ cmd_ctx->state_ctx->netgrent.domain = 0;
+ cmd_ctx->state_ctx->netgrent.result = 0;
+
+ talloc_free(cmd_ctx);
+}
+
+static errno_t sss_nss_endent(struct cli_ctx *cli_ctx,
+ struct sss_nss_enum_index *idx)
+{
+ DEBUG(SSSDBG_CONF_SETTINGS, "Resetting enumeration state\n");
+
+ idx->domain = 0;
+ idx->result = 0;
+
+ sss_nss_protocol_done(cli_ctx, EOK);
+
+ return EOK;
+}
+
+static errno_t sss_nss_cmd_getpwnam(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_USER_BY_NAME, NULL,
+ SSS_MC_PASSWD, sss_nss_protocol_fill_pwent);
+}
+
+static errno_t sss_nss_cmd_getpwuid(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_USER_BY_ID, NULL,
+ SSS_MC_PASSWD, sss_nss_protocol_fill_pwent);
+}
+
+static errno_t sss_nss_cmd_getpwnam_ex(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_USER_BY_NAME, NULL,
+ SSS_MC_PASSWD, sss_nss_protocol_fill_pwent);
+}
+
+static errno_t sss_nss_cmd_getpwuid_ex(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_id(cli_ctx, true, CACHE_REQ_USER_BY_ID, NULL,
+ SSS_MC_PASSWD, sss_nss_protocol_fill_pwent);
+}
+
+static errno_t sss_nss_cmd_setpwent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->pwent.domain = 0;
+ state_ctx->pwent.result = 0;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_USERS, nss_ctx->pwent);
+}
+
+static errno_t sss_nss_cmd_getpwent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_USERS,
+ &state_ctx->pwent, sss_nss_protocol_fill_pwent,
+ nss_ctx->pwent);
+}
+
+static errno_t sss_nss_cmd_endpwent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_endent(cli_ctx, &state_ctx->pwent);
+}
+
+static errno_t sss_nss_cmd_getgrnam(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_GROUP_BY_NAME, NULL,
+ SSS_MC_GROUP, sss_nss_protocol_fill_grent);
+}
+
+static errno_t sss_nss_cmd_getgrgid(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_GROUP_BY_ID, NULL,
+ SSS_MC_GROUP, sss_nss_protocol_fill_grent);
+}
+
+static errno_t sss_nss_cmd_getgrnam_ex(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_GROUP_BY_NAME, NULL,
+ SSS_MC_GROUP, sss_nss_protocol_fill_grent);
+}
+
+static errno_t sss_nss_cmd_getgrgid_ex(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_id(cli_ctx, true, CACHE_REQ_GROUP_BY_ID, NULL,
+ SSS_MC_GROUP, sss_nss_protocol_fill_grent);
+}
+
+
+static errno_t sss_nss_cmd_setgrent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->grent.domain = 0;
+ state_ctx->grent.result = 0;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_GROUPS, nss_ctx->grent);
+}
+
+static errno_t sss_nss_cmd_getgrent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_GROUPS,
+ &state_ctx->grent, sss_nss_protocol_fill_grent,
+ nss_ctx->grent);
+}
+
+static errno_t sss_nss_cmd_endgrent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_endent(cli_ctx, &state_ctx->grent);
+}
+
+static errno_t sss_nss_cmd_initgroups(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_INITGROUPS, NULL,
+ SSS_MC_INITGROUPS, sss_nss_protocol_fill_initgr);
+}
+
+static errno_t sss_nss_cmd_initgroups_ex(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_INITGROUPS, NULL,
+ SSS_MC_INITGROUPS, sss_nss_protocol_fill_initgr);
+}
+
+static errno_t sss_nss_cmd_subid_ranges(struct cli_ctx *cli_ctx)
+{
+#ifdef BUILD_SUBID
+ const char *attrs[] =
+ {
+ SYSDB_SUBID_UID_COUND,
+ SYSDB_SUBID_GID_COUNT,
+ SYSDB_SUBID_UID_NUMBER,
+ SYSDB_SUBID_GID_NUMBER,
+ NULL
+ };
+
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_SUBID_RANGES_BY_NAME, attrs,
+ SSS_MC_NONE, sss_nss_protocol_fill_subid_ranges);
+#else
+ return ENOTSUP;
+#endif
+}
+
+static errno_t sss_nss_cmd_setnetgrent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->netgrent.domain = 0;
+ state_ctx->netgrent.result = 0;
+
+ return sss_nss_setnetgrent(cli_ctx, CACHE_REQ_NETGROUP_BY_NAME,
+ sss_nss_protocol_fill_netgrent);
+}
+
+static errno_t sss_nss_cmd_getservbyname(struct cli_ctx *cli_ctx)
+{
+ const char *name;
+ const char *protocol;
+ errno_t ret;
+
+ ret = sss_nss_protocol_parse_svc_name(cli_ctx, &name, &protocol);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ return sss_nss_getby_svc(cli_ctx, CACHE_REQ_SVC_BY_NAME, protocol, name, 0,
+ sss_nss_protocol_fill_svcent);
+}
+
+static errno_t sss_nss_cmd_getservbyport(struct cli_ctx *cli_ctx)
+{
+ const char *protocol;
+ uint16_t port;
+ errno_t ret;
+
+ ret = sss_nss_protocol_parse_svc_port(cli_ctx, &port, &protocol);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ return sss_nss_getby_svc(cli_ctx, CACHE_REQ_SVC_BY_PORT, protocol, NULL, port,
+ sss_nss_protocol_fill_svcent);
+}
+
+static errno_t sss_nss_cmd_setservent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->svcent.domain = 0;
+ state_ctx->svcent.result = 0;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_SVC, nss_ctx->svcent);
+}
+
+static errno_t sss_nss_cmd_getservent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_SVC,
+ &state_ctx->svcent, sss_nss_protocol_fill_svcent,
+ nss_ctx->svcent);
+}
+
+static errno_t sss_nss_cmd_endservent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_endent(cli_ctx, &state_ctx->svcent);
+}
+
+static errno_t sss_nss_cmd_getsidbyname(struct cli_ctx *cli_ctx)
+{
+ /* The attributes besides SYSDB_SID_STR are needed to handle some corner
+ * cases with respect to user-private-groups */
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_OBJECT_BY_NAME, attrs,
+ SSS_MC_NONE, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getsidbyusername(struct cli_ctx *cli_ctx)
+{
+ /* The attributes besides SYSDB_SID_STR are needed to handle some corner
+ * cases with respect to user-private-groups */
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_USER_BY_NAME, attrs,
+ SSS_MC_NONE, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getsidbygroupname(struct cli_ctx *cli_ctx)
+{
+ /* The attributes besides SYSDB_SID_STR are needed to handle some corner
+ * cases with respect to user-private-groups */
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_GROUP_BY_NAME, attrs,
+ SSS_MC_NONE, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getsidbyid(struct cli_ctx *cli_ctx)
+{
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_OBJECT_BY_ID, attrs,
+ SSS_MC_SID, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getsidbyuid(struct cli_ctx *cli_ctx)
+{
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_USER_BY_ID, attrs,
+ SSS_MC_SID, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getsidbygid(struct cli_ctx *cli_ctx)
+{
+ const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM,
+ SYSDB_OBJECTCATEGORY, NULL };
+
+ return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_GROUP_BY_ID, attrs,
+ SSS_MC_SID, sss_nss_protocol_fill_sid);
+}
+
+static errno_t sss_nss_cmd_getnamebysid(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_sid(cli_ctx, CACHE_REQ_OBJECT_BY_SID,
+ sss_nss_protocol_fill_name);
+}
+
+static errno_t sss_nss_cmd_getidbysid(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_sid(cli_ctx, CACHE_REQ_OBJECT_BY_SID,
+ sss_nss_protocol_fill_id);
+}
+
+static errno_t sss_nss_cmd_getorigbyname_common(struct cli_ctx *cli_ctx,
+ enum cache_req_type type)
+{
+ errno_t ret;
+ struct sss_nss_ctx *nss_ctx;
+ const char **attrs;
+ static const char *cache_attrs[] = { SYSDB_NAME,
+ SYSDB_OBJECTCATEGORY,
+ SYSDB_SID_STR,
+ SYSDB_DEFAULT_ATTRS,
+ NULL };
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ ret = add_strings_lists_ex(cli_ctx, cache_attrs, nss_ctx->full_attribute_list,
+ false, true, &attrs);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to concatenate attributes [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ENOMEM;
+ }
+
+ return sss_nss_getby_name(cli_ctx, false, type, attrs,
+ SSS_MC_NONE, sss_nss_protocol_fill_orig);
+}
+
+static errno_t sss_nss_cmd_getorigbyname(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_OBJECT_BY_NAME);
+}
+
+static errno_t sss_nss_cmd_getorigbyusername(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_USER_BY_NAME);
+}
+
+static errno_t sss_nss_cmd_getorigbygroupname(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_GROUP_BY_NAME);
+}
+
+
+static errno_t sss_nss_cmd_getnamebycert(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_cert(cli_ctx, CACHE_REQ_USER_BY_CERT,
+ sss_nss_protocol_fill_single_name);
+}
+
+static errno_t sss_nss_cmd_getlistbycert(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getlistby_cert(cli_ctx, CACHE_REQ_USER_BY_CERT);
+}
+
+static errno_t sss_nss_cmd_gethostbyname(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_IP_HOST_BY_NAME, NULL,
+ SSS_MC_NONE, sss_nss_protocol_fill_hostent);
+}
+
+static errno_t sss_nss_cmd_gethostbyaddr(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_addr(cli_ctx, CACHE_REQ_IP_HOST_BY_ADDR,
+ SSS_MC_NONE, sss_nss_protocol_fill_hostent);
+}
+
+static errno_t sss_nss_cmd_sethostent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->hostent.domain = 0;
+ state_ctx->hostent.result = 0;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_HOST, nss_ctx->hostent);
+}
+
+static errno_t sss_nss_cmd_gethostent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_HOST,
+ &state_ctx->hostent, sss_nss_protocol_fill_hostent,
+ nss_ctx->hostent);
+}
+
+static errno_t sss_nss_cmd_endhostent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_endent(cli_ctx, &state_ctx->hostent);
+}
+
+static errno_t sss_nss_cmd_getnetbyname(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_IP_NETWORK_BY_NAME, NULL,
+ SSS_MC_NONE, sss_nss_protocol_fill_netent);
+}
+
+static errno_t sss_nss_cmd_getnetbyaddr(struct cli_ctx *cli_ctx)
+{
+ return sss_nss_getby_addr(cli_ctx, CACHE_REQ_IP_NETWORK_BY_ADDR,
+ SSS_MC_NONE, sss_nss_protocol_fill_netent);
+}
+
+
+static errno_t sss_nss_cmd_setnetent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+ state_ctx->netent.domain = 0;
+ state_ctx->netent.result = 0;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_IP_NETWORK, nss_ctx->netent);
+}
+
+static errno_t sss_nss_cmd_getnetent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_IP_NETWORK,
+ &state_ctx->netent, sss_nss_protocol_fill_netent,
+ nss_ctx->netent);
+}
+
+static errno_t sss_nss_cmd_endnetent(struct cli_ctx *cli_ctx)
+{
+ struct sss_nss_state_ctx *state_ctx;
+
+ state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx);
+
+ return sss_nss_endent(cli_ctx, &state_ctx->netent);
+}
+
+struct sss_cmd_table *get_sss_nss_cmds(void)
+{
+ static struct sss_cmd_table nss_cmds[] = {
+ { SSS_GET_VERSION, sss_cmd_get_version },
+ { SSS_NSS_GETPWNAM, sss_nss_cmd_getpwnam },
+ { SSS_NSS_GETPWUID, sss_nss_cmd_getpwuid },
+ { SSS_NSS_SETPWENT, sss_nss_cmd_setpwent },
+ { SSS_NSS_GETPWENT, sss_nss_cmd_getpwent },
+ { SSS_NSS_ENDPWENT, sss_nss_cmd_endpwent },
+ { SSS_NSS_GETGRNAM, sss_nss_cmd_getgrnam },
+ { SSS_NSS_GETGRGID, sss_nss_cmd_getgrgid },
+ { SSS_NSS_SETGRENT, sss_nss_cmd_setgrent },
+ { SSS_NSS_GETGRENT, sss_nss_cmd_getgrent },
+ { SSS_NSS_ENDGRENT, sss_nss_cmd_endgrent },
+ { SSS_NSS_INITGR, sss_nss_cmd_initgroups },
+ { SSS_NSS_GET_SUBID_RANGES, sss_nss_cmd_subid_ranges },
+ { SSS_NSS_SETNETGRENT, sss_nss_cmd_setnetgrent },
+ /* { SSS_NSS_GETNETGRENT, "not needed" }, */
+ /* { SSS_NSS_ENDNETGRENT, "not needed" }, */
+ { SSS_NSS_GETSERVBYNAME, sss_nss_cmd_getservbyname },
+ { SSS_NSS_GETSERVBYPORT, sss_nss_cmd_getservbyport },
+ { SSS_NSS_SETSERVENT, sss_nss_cmd_setservent },
+ { SSS_NSS_GETSERVENT, sss_nss_cmd_getservent },
+ { SSS_NSS_ENDSERVENT, sss_nss_cmd_endservent },
+ { SSS_NSS_GETSIDBYNAME, sss_nss_cmd_getsidbyname },
+ { SSS_NSS_GETSIDBYUSERNAME, sss_nss_cmd_getsidbyusername },
+ { SSS_NSS_GETSIDBYGROUPNAME, sss_nss_cmd_getsidbygroupname },
+ { SSS_NSS_GETSIDBYID, sss_nss_cmd_getsidbyid },
+ { SSS_NSS_GETSIDBYUID, sss_nss_cmd_getsidbyuid },
+ { SSS_NSS_GETSIDBYGID, sss_nss_cmd_getsidbygid },
+ { SSS_NSS_GETNAMEBYSID, sss_nss_cmd_getnamebysid },
+ { SSS_NSS_GETIDBYSID, sss_nss_cmd_getidbysid },
+ { SSS_NSS_GETORIGBYNAME, sss_nss_cmd_getorigbyname },
+ { SSS_NSS_GETORIGBYUSERNAME, sss_nss_cmd_getorigbyusername },
+ { SSS_NSS_GETORIGBYGROUPNAME, sss_nss_cmd_getorigbygroupname },
+ { SSS_NSS_GETNAMEBYCERT, sss_nss_cmd_getnamebycert },
+ { SSS_NSS_GETLISTBYCERT, sss_nss_cmd_getlistbycert },
+ { SSS_NSS_GETPWNAM_EX, sss_nss_cmd_getpwnam_ex },
+ { SSS_NSS_GETPWUID_EX, sss_nss_cmd_getpwuid_ex },
+ { SSS_NSS_GETGRNAM_EX, sss_nss_cmd_getgrnam_ex },
+ { SSS_NSS_GETGRGID_EX, sss_nss_cmd_getgrgid_ex },
+ { SSS_NSS_INITGR_EX, sss_nss_cmd_initgroups_ex },
+ { SSS_NSS_GETHOSTBYNAME, sss_nss_cmd_gethostbyname },
+ { SSS_NSS_GETHOSTBYNAME2, sss_nss_cmd_gethostbyname },
+ { SSS_NSS_GETHOSTBYADDR, sss_nss_cmd_gethostbyaddr },
+ { SSS_NSS_SETHOSTENT, sss_nss_cmd_sethostent },
+ { SSS_NSS_GETHOSTENT, sss_nss_cmd_gethostent },
+ { SSS_NSS_ENDHOSTENT, sss_nss_cmd_endhostent },
+ { SSS_NSS_GETNETBYNAME, sss_nss_cmd_getnetbyname },
+ { SSS_NSS_GETNETBYADDR, sss_nss_cmd_getnetbyaddr },
+ { SSS_NSS_SETNETENT, sss_nss_cmd_setnetent },
+ { SSS_NSS_GETNETENT, sss_nss_cmd_getnetent },
+ { SSS_NSS_ENDNETENT, sss_nss_cmd_endnetent },
+ { SSS_CLI_NULL, NULL }
+ };
+
+ return nss_cmds;
+}
+
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+ static struct cli_protocol_version nss_cli_protocol_version[] = {
+ { 1, "2008-09-05", "initial version, \\0 terminated strings" },
+ { 0, NULL, NULL }
+ };
+
+ return nss_cli_protocol_version;
+}
+
+int sss_nss_connection_setup(struct cli_ctx *cli_ctx)
+{
+ int ret;
+
+ ret = sss_connection_setup(cli_ctx);
+ if (ret != EOK) return ret;
+
+ cli_ctx->state_ctx = talloc_zero(cli_ctx, struct sss_nss_state_ctx);
+ if (cli_ctx->state_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ return EOK;
+}
diff --git a/src/responder/nss/nss_enum.c b/src/responder/nss/nss_enum.c
new file mode 100644
index 0000000..2b4fb60
--- /dev/null
+++ b/src/responder/nss/nss_enum.c
@@ -0,0 +1,361 @@
+/*
+ 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 <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "responder/nss/nss_private.h"
+
+typedef errno_t (*sss_nss_setent_set_timeout_fn)(struct tevent_context *ev,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_enum_ctx *enum_ctx);
+
+struct sss_nss_setent_internal_state {
+ struct tevent_context *ev;
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_enum_ctx *enum_ctx;
+ sss_nss_setent_set_timeout_fn timeout_handler;
+ enum cache_req_type type;
+};
+
+static void sss_nss_setent_internal_done(struct tevent_req *subreq);
+
+/* Cache request data is stealed on internal state. */
+static struct tevent_req *
+sss_nss_setent_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ struct cache_req_data *data,
+ enum cache_req_type type,
+ struct sss_nss_enum_ctx *enum_ctx,
+ sss_nss_setent_set_timeout_fn timeout_handler)
+{
+ struct sss_nss_setent_internal_state *state;
+ struct tevent_req *subreq;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sss_nss_setent_internal_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n");
+ return NULL;
+ }
+
+ talloc_steal(state, data);
+
+ state->ev = ev;
+ state->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state->enum_ctx = enum_ctx;
+ state->type = type;
+ state->timeout_handler = timeout_handler;
+
+ if (state->enum_ctx->is_ready) {
+ /* Object is already constructed, just return here. */
+ talloc_free(data);
+ ret = EOK;
+ goto done;
+ }
+
+ if (state->enum_ctx->ongoing != NULL) {
+ /* Object is being constructed. Register ourselves for
+ * notification when it is finished. */
+ ret = setent_add_ref(state, &state->enum_ctx->notify_list, req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to register setent reference [%d]: %s!\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EAGAIN;
+ goto done;
+ }
+
+ /* Create new object. */
+ state->enum_ctx->is_ready = false;
+ subreq = cache_req_send(req, ev, cli_ctx->rctx, cli_ctx->rctx->ncache,
+ state->nss_ctx->cache_refresh_percent,
+ CACHE_REQ_POSIX_DOM, NULL, data);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sss_nss_setent_internal_done, req);
+ state->enum_ctx->ongoing = subreq;
+
+ ret = EAGAIN;
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void sss_nss_setent_internal_done(struct tevent_req *subreq)
+{
+ struct cache_req_result **result;
+ struct sss_nss_setent_internal_state *state;
+ struct setent_req_list **notify_list;
+ struct tevent_req *req;
+ errno_t ret;
+ errno_t tret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sss_nss_setent_internal_state);
+
+ /* This is the ongoing request and it is finished. Remove it. */
+ state->enum_ctx->ongoing = NULL;
+
+ ret = cache_req_recv(state, subreq, &result);
+ talloc_zfree(subreq);
+
+ switch (ret) {
+ case EOK:
+ talloc_zfree(state->enum_ctx->result);
+ state->enum_ctx->result = talloc_steal(state->enum_ctx, result);
+
+ if (state->type == CACHE_REQ_NETGROUP_BY_NAME) {
+ /* We need to expand the netgroup into triples and members. */
+ ret = sysdb_netgr_to_entries(state->enum_ctx,
+ result[0]->ldb_result,
+ &state->enum_ctx->netgroup,
+ &state->enum_ctx->netgroup_count);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+ break;
+ case ENOENT:
+ /* Reset the result but build it again next time setent is called. */
+ talloc_zfree(state->enum_ctx->result);
+ talloc_zfree(state->enum_ctx->netgroup);
+ goto done;
+ default:
+ /* In case of an error, we do not touch the enumeration context. */
+ goto done;
+ }
+
+ /* Expire the result object after its timeout is reached. */
+ tret = state->timeout_handler(state->ev, state->nss_ctx, state->enum_ctx);
+ if (tret != EOK) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* The object is ready now. */
+ state->enum_ctx->is_ready = true;
+
+ ret = EOK;
+
+done:
+ /* We want to finish the requests in correct order, this was the
+ * first request, notify_list contain the subsequent request.
+ *
+ * Because callback invoked from tevent_req_done will free state,
+ * we must remember notify_list explicitly to avoid segfault.
+ */
+ notify_list = &state->enum_ctx->notify_list;
+
+ if (ret == EOK) {
+ tevent_req_done(req);
+ setent_notify_done(notify_list);
+ } else {
+ tevent_req_error(req, ret);
+ setent_notify(notify_list, ret);
+ }
+}
+
+static errno_t
+sss_nss_setent_internal_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+sss_nss_setent_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *pvt)
+{
+ struct sss_nss_enum_ctx *enum_ctx = pvt;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Enumeration result object has expired.\n");
+
+ /* Reset enumeration context. */
+ talloc_zfree(enum_ctx->result);
+ enum_ctx->is_ready = false;
+}
+
+static errno_t
+sss_nss_setent_set_timeout(struct tevent_context *ev,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_enum_ctx *enum_ctx)
+{
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ tv = tevent_timeval_current_ofs(nss_ctx->enum_cache_timeout, 0);
+ te = tevent_add_timer(ev, nss_ctx, tv, sss_nss_setent_timeout, enum_ctx);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not set up life timer for enumeration object.\n");
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+struct tevent_req *
+sss_nss_setent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ struct sss_nss_enum_ctx *enum_ctx)
+{
+ struct cache_req_data *data;
+
+ data = cache_req_data_enum(mem_ctx, type);
+ if (data == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n");
+ return NULL;
+ }
+
+ return sss_nss_setent_internal_send(mem_ctx, ev, cli_ctx, data, type, enum_ctx,
+ sss_nss_setent_set_timeout);
+}
+
+errno_t sss_nss_setent_recv(struct tevent_req *req)
+{
+ return sss_nss_setent_internal_recv(req);
+}
+
+static void
+sss_nss_setnetgrent_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *pvt)
+{
+ struct sss_nss_enum_ctx *enum_ctx;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Enumeration result object has expired.\n");
+
+ /* Free enumeration context. This will also remove it from the table. */
+ enum_ctx = talloc_get_type(pvt, struct sss_nss_enum_ctx);
+ talloc_free(enum_ctx);
+}
+
+static errno_t
+sss_nss_setnetgrent_set_timeout(struct tevent_context *ev,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_enum_ctx *enum_ctx)
+{
+ struct tevent_timer *te;
+ struct timeval tv;
+ uint32_t timeout;
+
+ if (nss_ctx->cache_refresh_percent) {
+ timeout = enum_ctx->result[0]->domain->netgroup_timeout *
+ (nss_ctx->cache_refresh_percent / 100.0);
+ } else {
+ timeout = enum_ctx->result[0]->domain->netgroup_timeout;
+ }
+
+ /* In order to not trash the cache between setnetgrent()/getnetgrent()
+ * calls with too low timeout values, we only allow 10 seconds as
+ * the minimal timeout
+ */
+ if (timeout < 10) timeout = 10;
+
+ tv = tevent_timeval_current_ofs(timeout, 0);
+ te = tevent_add_timer(ev, enum_ctx, tv, sss_nss_setnetgrent_timeout, enum_ctx);
+ if (te == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not set up life timer for enumeration object.\n");
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static struct sss_nss_enum_ctx *
+sss_nss_setnetgrent_set_enum_ctx(hash_table_t *table,
+ const char *netgroup)
+{
+ struct sss_nss_enum_ctx *enum_ctx;
+ errno_t ret;
+
+ enum_ctx = sss_ptr_hash_lookup(table, netgroup, struct sss_nss_enum_ctx);
+ if (enum_ctx != NULL) {
+ return enum_ctx;
+ }
+
+ enum_ctx = talloc_zero(table, struct sss_nss_enum_ctx);
+ if (enum_ctx == NULL) {
+ return NULL;
+ }
+
+ ret = sss_ptr_hash_add(table, netgroup, enum_ctx, struct sss_nss_enum_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to add enumeration context into table [%d]: %s\n",
+ ret, sss_strerror(ret));
+ talloc_free(enum_ctx);
+ return NULL;
+ }
+
+ return enum_ctx;
+}
+
+struct tevent_req *
+sss_nss_setnetgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ hash_table_t *table,
+ const char *netgroup)
+{
+ struct sss_nss_enum_ctx *enum_ctx;
+ struct cache_req_data *data;
+
+ enum_ctx = sss_nss_setnetgrent_set_enum_ctx(table, netgroup);
+ if (enum_ctx == NULL) {
+ return NULL;
+ }
+
+ data = cache_req_data_name(mem_ctx, type, netgroup);
+ if (data == NULL) {
+ return NULL;
+ }
+
+ return sss_nss_setent_internal_send(mem_ctx, ev, cli_ctx, data, type, enum_ctx,
+ sss_nss_setnetgrent_set_timeout);
+}
diff --git a/src/responder/nss/nss_get_object.c b/src/responder/nss/nss_get_object.c
new file mode 100644
index 0000000..29f9cb5
--- /dev/null
+++ b/src/responder/nss/nss_get_object.c
@@ -0,0 +1,546 @@
+/*
+ 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 <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "responder/nss/nss_private.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
+
+static errno_t
+memcache_delete_entry_by_name(struct sss_nss_ctx *nss_ctx,
+ struct sized_string *name,
+ enum sss_mc_type type)
+{
+ errno_t ret;
+
+ switch (type) {
+ case SSS_MC_PASSWD:
+ ret = sss_mmap_cache_pw_invalidate(&nss_ctx->pwd_mc_ctx, name);
+ break;
+ case SSS_MC_GROUP:
+ ret = sss_mmap_cache_gr_invalidate(&nss_ctx->grp_mc_ctx, name);
+ break;
+ case SSS_MC_INITGROUPS:
+ ret = sss_mmap_cache_initgr_invalidate(&nss_ctx->initgr_mc_ctx, name);
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (ret == EOK || ret == ENOENT) {
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Internal failure in memory cache code: %d [%s]\n",
+ ret, sss_strerror(ret));
+
+ return ret;
+}
+
+static errno_t
+memcache_delete_entry_by_id(struct sss_nss_ctx *nss_ctx,
+ uint32_t id,
+ enum sss_mc_type type)
+{
+ errno_t ret;
+
+ switch (type) {
+ case SSS_MC_PASSWD:
+ ret = sss_mmap_cache_pw_invalidate_uid(&nss_ctx->pwd_mc_ctx, (uid_t)id);
+ break;
+ case SSS_MC_GROUP:
+ ret = sss_mmap_cache_gr_invalidate_gid(&nss_ctx->grp_mc_ctx, (gid_t)id);
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (ret == EOK || ret == ENOENT) {
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Internal failure in memory cache code: %d [%s]\n",
+ ret, sss_strerror(ret));
+
+ return ret;
+}
+
+errno_t
+memcache_delete_entry(struct sss_nss_ctx *nss_ctx,
+ struct resp_ctx *rctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ uint32_t id,
+ enum sss_mc_type type)
+{
+ struct sss_domain_info *dom;
+ struct sized_string *sized_name;
+ errno_t ret;
+
+ for (dom = rctx->domains;
+ dom != NULL;
+ dom = get_next_domain(dom, SSS_GND_DESCEND)) {
+
+ if (domain == dom) {
+ /* We found entry in this domain so we don't
+ * wont to invalidate it here. */
+ continue;
+ }
+
+ if (name != NULL) {
+ ret = sized_output_name(NULL, rctx, name, dom, &sized_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to create sized name [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ ret = memcache_delete_entry_by_name(nss_ctx, sized_name, type);
+ talloc_zfree(sized_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to delete '%s' from domain '%s' memory cache!\n",
+ name, dom->name);
+ continue;
+ }
+ } else if (id == 0) {
+ /*
+ * As "root" is not handled by SSSD, let's just return EOK here
+ * instead of erroring out.
+ */
+ return EOK;
+ } else if (id != 0) {
+ ret = memcache_delete_entry_by_id(nss_ctx, id, type);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to delete '%u' from domain '%s' memory cache!\n",
+ id, dom->name);
+ continue;
+ }
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE, "Bug: invalid input!");
+ return ERR_INTERNAL;
+ }
+ }
+
+ return EOK;
+}
+
+static struct cache_req_data *
+hybrid_domain_retry_data(TALLOC_CTX *mem_ctx,
+ struct cache_req_data *orig,
+ const char *input_name,
+ uint32_t input_id)
+{
+ enum cache_req_type cr_type = cache_req_data_get_type(orig);
+ struct cache_req_data *hybrid_data = NULL;
+
+ if (cr_type == CACHE_REQ_GROUP_BY_ID) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Retrying group-by-ID lookup in user space\n");
+ hybrid_data = cache_req_data_id(mem_ctx,
+ CACHE_REQ_USER_BY_ID,
+ input_id);
+ } else if (cr_type == CACHE_REQ_GROUP_BY_NAME) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Retrying group-by-name lookup in user space\n");
+ hybrid_data = cache_req_data_name(mem_ctx,
+ CACHE_REQ_USER_BY_NAME,
+ input_name);
+ }
+
+ if (hybrid_data != NULL) {
+ cache_req_data_set_hybrid_lookup(hybrid_data, true);
+ }
+
+ return hybrid_data;
+}
+
+static struct cache_req_data *
+hybrid_domain_verify_gid_data(TALLOC_CTX *mem_ctx,
+ struct cache_req_result *user_group)
+{
+ gid_t gid;
+
+ /* read the GID of this 'group' and use it to construct
+ * a cache_req_data struct
+ */
+ gid = sss_view_ldb_msg_find_attr_as_uint64(user_group->domain,
+ user_group->msgs[0],
+ SYSDB_GIDNUM,
+ 0);
+ if (gid == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "A user with no GID?\n");
+ return NULL;
+ }
+
+ return cache_req_data_id(mem_ctx,
+ CACHE_REQ_GROUP_BY_ID,
+ gid);
+}
+
+static int
+hybrid_domain_user_to_group(struct cache_req_result *result)
+{
+ errno_t ret;
+ uid_t uid;
+ gid_t gid;
+
+ /* There must be exactly one entry.. */
+ if (result == NULL || result->count != 1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No result or wrong number of entries, expected 1 entry\n");
+ return ENOENT;
+ }
+
+ /* ...which has uidNumber equal to gidNumber */
+ uid = sss_view_ldb_msg_find_attr_as_uint64(result->domain,
+ result->msgs[0],
+ SYSDB_UIDNUM,
+ 0);
+
+ gid = sss_view_ldb_msg_find_attr_as_uint64(result->domain,
+ result->msgs[0],
+ SYSDB_GIDNUM,
+ 0);
+
+ if (uid == 0 || uid != gid) {
+ DEBUG(SSSDBG_TRACE_INTERNAL, "UID and GID differ\n");
+ return ENOENT;
+ }
+
+ /* OK, we have a user with uid == gid; let's pretend this is a group */
+ ret = ldb_msg_add_string(result->msgs[0],
+ SYSDB_OBJECTCATEGORY,
+ SYSDB_GROUP_CLASS);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add group class\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
+struct sss_nss_get_object_state {
+ struct sss_nss_ctx *nss_ctx;
+ struct resp_ctx *rctx;
+ struct tevent_context *ev;
+ struct cli_ctx *cli_ctx;
+ struct cache_req_data *data;
+
+ /* We delete object from memory cache if it is not found */
+ enum sss_mc_type memcache;
+ const char *input_name;
+ uint32_t input_id;
+
+ struct cache_req_result *result;
+};
+
+static void sss_nss_get_object_done(struct tevent_req *subreq);
+static bool sss_nss_is_hybrid_object_enabled(struct sss_domain_info *domains);
+static errno_t sss_nss_get_hybrid_object_step(struct tevent_req *req);
+static void sss_nss_get_hybrid_object_done(struct tevent_req *subreq);
+static void sss_nss_get_hybrid_gid_verify_done(struct tevent_req *subreq);
+static void sss_nss_get_object_finish_req(struct tevent_req *req,
+ errno_t ret);
+
+/* Cache request data memory context is stolen to internal state. */
+struct tevent_req *
+sss_nss_get_object_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ struct cache_req_data *data,
+ enum sss_mc_type memcache,
+ const char *input_name,
+ uint32_t input_id)
+{
+ struct sss_nss_get_object_state *state;
+ struct tevent_req *subreq;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sss_nss_get_object_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n");
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli_ctx = cli_ctx;
+ state->data = talloc_steal(state, data);
+
+ state->rctx = cli_ctx->rctx;
+ state->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+ state->memcache = memcache;
+ state->input_id = input_id;
+ state->input_name = talloc_strdup(state, input_name);
+ if (input_name != NULL && state->input_name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ subreq = cache_req_send(req, ev, cli_ctx->rctx, cli_ctx->rctx->ncache,
+ state->nss_ctx->cache_refresh_percent,
+ CACHE_REQ_POSIX_DOM, NULL, data);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Client [%p][%d]: unable to send cache request!\n",
+ cli_ctx, cli_ctx->cfd);
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Client [%p][%d]: sent cache request #%u\n",
+ cli_ctx, cli_ctx->cfd, cache_req_get_reqid(subreq));
+
+ tevent_req_set_callback(subreq, sss_nss_get_object_done, req);
+
+ ret = EAGAIN;
+
+done:
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void sss_nss_get_object_done(struct tevent_req *subreq)
+{
+ struct sss_nss_get_object_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+ errno_t retry_ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ ret = cache_req_single_domain_recv(state, subreq, &state->result);
+ talloc_zfree(subreq);
+
+ /* Try to process hybrid object if any domain enables it. This will issue a
+ * cache_req that will iterate only over domains with MPG_HYBRID. */
+ if (ret == ENOENT
+ && sss_nss_is_hybrid_object_enabled(state->nss_ctx->rctx->domains)) {
+ retry_ret = sss_nss_get_hybrid_object_step(req);
+ if (retry_ret == EAGAIN) {
+ /* Retrying hybrid search */
+ return;
+ }
+ /* Otherwise return the value of ret as returned from
+ * cache_req_single_domain_recv
+ */
+ }
+
+ sss_nss_get_object_finish_req(req, ret);
+ return;
+}
+
+static void sss_nss_get_object_finish_req(struct tevent_req *req,
+ errno_t ret)
+{
+ struct sss_nss_get_object_state *state;
+
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ switch (ret) {
+ case EOK:
+ tevent_req_done(req);
+ break;
+ case ENOENT:
+ if ((state->memcache != SSS_MC_NONE) && (state->memcache != SSS_MC_SID)) {
+ /* Delete entry from all domains. */
+ memcache_delete_entry(state->nss_ctx, state->rctx, NULL,
+ state->input_name, state->input_id,
+ state->memcache);
+ }
+
+ tevent_req_error(req, ENOENT);
+ break;
+ default:
+ tevent_req_error(req, ret);
+ break;
+ }
+}
+
+static bool sss_nss_is_hybrid_object_enabled(struct sss_domain_info *domains)
+{
+ struct sss_domain_info *dom;
+
+ for (dom = domains; dom != NULL;
+ dom = get_next_domain(dom, SSS_GND_DESCEND)) {
+ if (dom->mpg_mode == MPG_HYBRID) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static errno_t sss_nss_get_hybrid_object_step(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sss_nss_get_object_state *state;
+
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ state->data = hybrid_domain_retry_data(state,
+ state->data,
+ state->input_name,
+ state->input_id);
+ if (state->data == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC, "This request cannot be retried\n");
+ return EOK;
+ }
+
+ subreq = cache_req_send(req,
+ state->ev,
+ state->cli_ctx->rctx,
+ state->cli_ctx->rctx->ncache,
+ state->nss_ctx->cache_refresh_percent,
+ CACHE_REQ_POSIX_DOM,
+ NULL,
+ state->data);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n");
+ return ENOMEM;
+ }
+ tevent_req_set_callback(subreq, sss_nss_get_hybrid_object_done, req);
+
+ return EAGAIN;
+}
+
+static void sss_nss_get_hybrid_object_done(struct tevent_req *subreq)
+{
+ struct sss_nss_get_object_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ ret = cache_req_single_domain_recv(state, subreq, &state->result);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Converting user object to a group\n");
+ ret = hybrid_domain_user_to_group(state->result);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* If the "group" was requested by name, we also must verify that
+ * no other group with this ID exists in any domain, otherwise
+ * we would have returned a private group that should be shadowed,
+ * this record would have been inserted into the memcache and then
+ * even getgrgid() would return this unexpected group
+ */
+ if (cache_req_data_get_type(state->data) == CACHE_REQ_USER_BY_NAME) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Will verify if MPG group is shadowed\n");
+ talloc_zfree(state->data);
+ state->data = hybrid_domain_verify_gid_data(state, state->result);
+ if (state->data == NULL) {
+ sss_nss_get_object_finish_req(req, EINVAL);
+ return;
+ }
+
+ subreq = cache_req_send(req,
+ state->ev,
+ state->cli_ctx->rctx,
+ state->cli_ctx->rctx->ncache,
+ state->nss_ctx->cache_refresh_percent,
+ CACHE_REQ_POSIX_DOM,
+ NULL,
+ state->data);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n");
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+ tevent_req_set_callback(subreq, sss_nss_get_hybrid_gid_verify_done, req);
+ return;
+ }
+
+done:
+ sss_nss_get_object_finish_req(req, ret);
+ return;
+}
+
+static void sss_nss_get_hybrid_gid_verify_done(struct tevent_req *subreq)
+{
+ struct sss_nss_get_object_state *state;
+ struct cache_req_result *real_gr_result;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ ret = cache_req_single_domain_recv(state, subreq, &real_gr_result);
+ talloc_zfree(subreq);
+ if (ret == ENOENT) {
+ /* There is no real group with the same GID as the autogenerated
+ * one we were checking, so let's return the autogenerated one
+ */
+ ret = EOK;
+ goto done;
+ } else if (ret == EOK) {
+ /* The autogenerated group is shadowed by a real one. Don't return
+ * anything.
+ */
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "A real entry would be shadowed by MPG entry\n");
+ ret = ENOENT;
+ goto done;
+ }
+
+done:
+ sss_nss_get_object_finish_req(req, ret);
+ return;
+}
+
+errno_t
+sss_nss_get_object_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct cache_req_result **_result,
+ const char **_rawname)
+{
+ struct sss_nss_get_object_state *state;
+ state = tevent_req_data(req, struct sss_nss_get_object_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (_result != NULL) {
+ *_result = talloc_steal(mem_ctx, state->result);
+ }
+
+ if (_rawname != NULL) {
+ *_rawname = talloc_steal(mem_ctx, state->input_name);
+ }
+
+ return EOK;
+}
diff --git a/src/responder/nss/nss_iface.c b/src/responder/nss/nss_iface.c
new file mode 100644
index 0000000..db743f8
--- /dev/null
+++ b/src/responder/nss/nss_iface.c
@@ -0,0 +1,242 @@
+/*
+ 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_private.h"
+#include "responder/nss/nss_iface.h"
+#include "sss_iface/sss_iface_async.h"
+
+static void
+sss_nss_update_initgr_memcache(struct sss_nss_ctx *nctx,
+ const char *fq_name, const char *domain,
+ int gnum, uint32_t *groups)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct sss_domain_info *dom;
+ struct ldb_result *res;
+ struct sized_string *delete_name;
+ bool changed = false;
+ uint32_t id;
+ uint32_t gids[gnum];
+ int ret;
+ int i, j;
+
+ for (dom = nctx->rctx->domains;
+ dom;
+ dom = get_next_domain(dom, SSS_GND_DESCEND)) {
+ if (strcasecmp(dom->name, domain) == 0) {
+ break;
+ }
+ }
+
+ if (dom == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unknown domain (%s) requested by provider\n", domain);
+ return;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ ret = sized_output_name(tmp_ctx, nctx->rctx, fq_name, dom, &delete_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sized_output_name failed for '%s': %d [%s]\n",
+ fq_name, ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = sysdb_initgroups(tmp_ctx, dom, fq_name, &res);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sysdb_initgroups() failed [%d][%s]\n",
+ ret, strerror(ret));
+ goto done;
+ }
+
+ /* copy, we need the original intact in case we need to invalidate
+ * all the original groups */
+ memcpy(gids, groups, gnum * sizeof(uint32_t));
+
+ if (ret == ENOENT || res->count == 0) {
+ /* The user is gone. Invalidate the mc record */
+ ret = sss_mmap_cache_pw_invalidate(&nctx->pwd_mc_ctx, delete_name);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Internal failure in memory cache code: %d [%s]\n",
+ ret, strerror(ret));
+ }
+
+ /* Also invalidate his groups */
+ changed = true;
+ } else {
+ /* we skip the first entry, it's the user itself */
+ for (i = 0; i < res->count; i++) {
+ id = ldb_msg_find_attr_as_uint(res->msgs[i], SYSDB_GIDNUM, 0);
+ if (id == 0) {
+ /* probably non-POSIX group, skip */
+ continue;
+ }
+ for (j = 0; j < gnum; j++) {
+ if (gids[j] == id) {
+ gids[j] = 0;
+ break;
+ }
+ }
+ if (j >= gnum) {
+ /* we couldn't find a match, this means the groups have
+ * changed after the refresh */
+ changed = true;
+ break;
+ }
+ }
+
+ if (!changed) {
+ for (j = 0; j < gnum; j++) {
+ if (gids[j] != 0) {
+ /* we found an un-cleared groups, this means the groups
+ * have changed after the refresh (some got deleted) */
+ changed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (changed) {
+ for (i = 0; i < gnum; i++) {
+ id = groups[i];
+
+ ret = sss_mmap_cache_gr_invalidate_gid(&nctx->grp_mc_ctx, id);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Internal failure in memory cache code: %d [%s]\n",
+ ret, strerror(ret));
+ }
+ }
+
+ to_sized_string(delete_name, fq_name);
+ ret = sss_mmap_cache_initgr_invalidate(&nctx->initgr_mc_ctx,
+ delete_name);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Internal failure in memory cache code: %d [%s]\n",
+ ret, strerror(ret));
+ }
+ }
+
+done:
+ talloc_free(tmp_ctx);
+}
+
+static errno_t
+sss_nss_memorycache_invalidate_users(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx)
+{
+ DEBUG(SSSDBG_TRACE_LIBS, "Invalidating all users in memory cache\n");
+ sss_mmap_cache_reset(nctx->pwd_mc_ctx);
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_memorycache_invalidate_groups(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx)
+{
+ DEBUG(SSSDBG_TRACE_LIBS, "Invalidating all groups in memory cache\n");
+ sss_mmap_cache_reset(nctx->grp_mc_ctx);
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_memorycache_invalidate_initgroups(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx)
+{
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Invalidating all initgroup records in memory cache\n");
+ sss_mmap_cache_reset(nctx->initgr_mc_ctx);
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_memorycache_update_initgroups(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx,
+ const char *user,
+ const char *domain,
+ uint32_t *groups)
+{
+ DEBUG(SSSDBG_TRACE_LIBS, "Updating initgroups memory cache of [%s@%s]\n",
+ user, domain);
+
+ sss_nss_update_initgr_memcache(nctx, user, domain,
+ talloc_array_length(groups), groups);
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_memorycache_invalidate_group_by_id(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx,
+ uint32_t gid)
+{
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Invalidating group %u from memory cache\n", gid);
+
+ sss_mmap_cache_gr_invalidate_gid(&nctx->grp_mc_ctx, gid);
+
+ return EOK;
+}
+
+errno_t
+sss_nss_register_backend_iface(struct sbus_connection *conn,
+ struct sss_nss_ctx *nss_ctx)
+{
+ errno_t ret;
+
+ SBUS_INTERFACE(iface,
+ sssd_nss_MemoryCache,
+ SBUS_METHODS(
+ SBUS_SYNC(METHOD, sssd_nss_MemoryCache, UpdateInitgroups, sss_nss_memorycache_update_initgroups, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllUsers, sss_nss_memorycache_invalidate_users, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllGroups, sss_nss_memorycache_invalidate_groups, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllInitgroups, sss_nss_memorycache_invalidate_initgroups, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateGroupById, sss_nss_memorycache_invalidate_group_by_id, nss_ctx)
+ ),
+ SBUS_SIGNALS(SBUS_NO_SIGNALS),
+ SBUS_PROPERTIES(SBUS_NO_PROPERTIES)
+ );
+
+ ret = sbus_connection_add_path(conn, SSS_BUS_PATH, &iface);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface"
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ }
+
+ return ret;
+}
diff --git a/src/responder/nss/nss_iface.h b/src/responder/nss/nss_iface.h
new file mode 100644
index 0000000..8aabddb
--- /dev/null
+++ b/src/responder/nss/nss_iface.h
@@ -0,0 +1,31 @@
+/*
+ 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/>.
+*/
+
+#ifndef _NSS_IFACE_H_
+#define _NSS_IFACE_H_
+
+#include "sss_iface/sss_iface_async.h"
+#include "responder/nss/nss_private.h"
+
+errno_t
+sss_nss_register_backend_iface(struct sbus_connection *conn,
+ struct sss_nss_ctx *nss_ctx);
+
+#endif /* _NSS_IFACE_H_ */
diff --git a/src/responder/nss/nss_private.h b/src/responder/nss/nss_private.h
new file mode 100644
index 0000000..e2f5a3e
--- /dev/null
+++ b/src/responder/nss/nss_private.h
@@ -0,0 +1,155 @@
+/*
+ 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/>.
+*/
+
+#ifndef _NSS_PRIVATE_H_
+#define _NSS_PRIVATE_H_
+
+#include <talloc.h>
+#include <tevent.h>
+#include <dhash.h>
+#include <ldb.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "responder/common/responder.h"
+#include "responder/common/cache_req/cache_req.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
+#include "lib/idmap/sss_idmap.h"
+
+struct sss_nss_enum_index {
+ unsigned int domain;
+ unsigned int result;
+};
+
+struct sss_nss_enum_ctx {
+ struct cache_req_result **result;
+ struct sysdb_netgroup_ctx **netgroup;
+ size_t netgroup_count;
+
+ /* Ongoing cache request that is constructing enumeration result. */
+ struct tevent_req *ongoing;
+
+ /* If true, the object is already constructed. */
+ bool is_ready;
+
+ /* List of setent requests awaiting the result. We finish
+ * them when the ongoing cache request is completed. */
+ struct setent_req_list *notify_list;
+};
+
+struct sss_nss_state_ctx {
+ struct sss_nss_enum_index pwent;
+ struct sss_nss_enum_index grent;
+ struct sss_nss_enum_index svcent;
+ struct sss_nss_enum_index netgrent;
+ struct sss_nss_enum_index hostent;
+ struct sss_nss_enum_index netent;
+
+ const char *netgroup;
+};
+
+struct sss_nss_ctx {
+ struct resp_ctx *rctx;
+ struct sss_idmap_ctx *idmap_ctx;
+
+ /* Options. */
+ int cache_refresh_percent;
+ int enum_cache_timeout;
+ bool filter_users_in_groups;
+ char *pwfield;
+ char *override_homedir;
+ char *fallback_homedir;
+ char *homedir_substr;
+ const char **extra_attributes;
+ const char **full_attribute_list;
+
+ /* Enumeration. */
+ struct sss_nss_enum_ctx *pwent;
+ struct sss_nss_enum_ctx *grent;
+ struct sss_nss_enum_ctx *svcent;
+ struct sss_nss_enum_ctx *hostent;
+ struct sss_nss_enum_ctx *netent;
+ hash_table_t *netgrent;
+
+ /* Memory cache. */
+ struct sss_mc_ctx *pwd_mc_ctx;
+ struct sss_mc_ctx *grp_mc_ctx;
+ struct sss_mc_ctx *initgr_mc_ctx;
+ struct sss_mc_ctx *sid_mc_ctx;
+ uid_t mc_uid;
+ gid_t mc_gid;
+};
+
+struct sss_cmd_table *get_sss_nss_cmds(void);
+
+int sss_nss_connection_setup(struct cli_ctx *cli_ctx);
+
+errno_t
+memcache_delete_entry(struct sss_nss_ctx *nss_ctx,
+ struct resp_ctx *rctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ uint32_t id,
+ enum sss_mc_type type);
+
+struct tevent_req *
+sss_nss_get_object_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ struct cache_req_data *data,
+ enum sss_mc_type memcache,
+ const char *input_name,
+ uint32_t input_id);
+
+errno_t
+sss_nss_get_object_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct cache_req_result **_result,
+ const char **_rawname);
+
+struct tevent_req *
+sss_nss_setent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ struct sss_nss_enum_ctx *enum_ctx);
+
+errno_t
+sss_nss_setent_recv(struct tevent_req *req);
+
+struct tevent_req *
+sss_nss_setnetgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_ctx *cli_ctx,
+ enum cache_req_type type,
+ hash_table_t *table,
+ const char *netgroup);
+
+/* Utils. */
+
+const char *
+sss_nss_get_name_from_msg(struct sss_domain_info *domain,
+ struct ldb_message *msg);
+
+const char *
+sss_nss_get_pwfield(struct sss_nss_ctx *nctx,
+ struct sss_domain_info *dom);
+
+#endif /* _NSS_PRIVATE_H_ */
diff --git a/src/responder/nss/nss_protocol.c b/src/responder/nss/nss_protocol.c
new file mode 100644
index 0000000..e6dc702
--- /dev/null
+++ b/src/responder/nss/nss_protocol.c
@@ -0,0 +1,487 @@
+/*
+ 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 "util/util.h"
+#include "util/cert.h"
+#include "lib/idmap/sss_idmap.h"
+#include "responder/nss/nss_protocol.h"
+#include <arpa/inet.h>
+
+errno_t
+sss_nss_protocol_done(struct cli_ctx *cli_ctx, errno_t error)
+{
+ struct cli_protocol *pctx;
+ errno_t ret;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ switch (error) {
+ case EOK:
+ /* Create empty packet if none was provided. */
+ if (pctx->creq->out == NULL) {
+ ret = sss_packet_new(pctx->creq, 0,
+ sss_packet_get_cmd(pctx->creq->in),
+ &pctx->creq->out);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_set_error(pctx->creq->out, EOK);
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Sending reply: success\n");
+ ret = EOK;
+ goto done;
+ case ENOENT:
+ DEBUG(SSSDBG_TRACE_ALL, "Sending reply: not found\n");
+ ret = sss_cmd_send_empty(cli_ctx);
+ goto done;
+ default:
+ DEBUG(SSSDBG_TRACE_ALL, "Sending reply: error [%d]: %s\n",
+ error, sss_strerror(error));
+ ret = sss_cmd_send_error(cli_ctx, error);
+ goto done;
+ }
+
+done:
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send reply [%d]: %s!\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ sss_cmd_done(cli_ctx, NULL);
+ return EOK;
+}
+
+void sss_nss_protocol_reply(struct cli_ctx *cli_ctx,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_result *result,
+ sss_nss_protocol_fill_packet_fn fill_fn)
+{
+ struct cli_protocol *pctx;
+ errno_t ret;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in),
+ &pctx->creq->out);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = fill_fn(nss_ctx, cmd_ctx, pctx->creq->out, result);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_set_error(pctx->creq->out, EOK);
+
+done:
+ sss_nss_protocol_done(cli_ctx, ret);
+}
+
+errno_t
+sss_nss_protocol_parse_name(struct cli_ctx *cli_ctx, const char **_rawname)
+{
+ struct cli_protocol *pctx;
+ const char *rawname;
+ uint8_t *body;
+ size_t blen;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ /* If not terminated fail. */
+ if (body[blen - 1] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated!\n");
+ return EINVAL;
+ }
+
+ /* If the body isn't valid UTF-8, fail */
+ if (!sss_utf8_check(body, blen - 1)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n");
+ return EINVAL;
+ }
+
+ rawname = (const char *)body;
+ if (rawname[0] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "An empty name was provided!\n");
+ return EINVAL;
+ }
+
+ *_rawname = rawname;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_name_ex(struct cli_ctx *cli_ctx, const char **_rawname,
+ uint32_t *_flags)
+{
+ struct cli_protocol *pctx;
+ const char *rawname;
+ uint8_t *body;
+ size_t blen;
+ uint8_t *p;
+ uint32_t flags;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ if (blen < 1 + sizeof(uint32_t)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body too short!\n");
+ return EINVAL;
+ }
+
+ /* If first argument not terminated fail. */
+ if (body[blen - 1 - sizeof(uint32_t)] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated!\n");
+ return EINVAL;
+ }
+
+ p = memchr(body, '\0', blen);
+ /* Although body for sure is null terminated, let's add this check here
+ * so static analyzers are happier. */
+ if (p == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "memchr() returned NULL, body is not null terminated!\n");
+ return EINVAL;
+ }
+
+ /* If the body isn't valid UTF-8, fail */
+ if (!sss_utf8_check(body, (p - body))) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "First argument is not UTF-8 string!\n");
+ return EINVAL;
+ }
+
+ rawname = (const char *)body;
+ if (rawname[0] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "An empty name was provided!\n");
+ return EINVAL;
+ }
+
+ p++;
+ if ((p - body) + sizeof(uint32_t) != blen) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body has unexpected size!\n");
+ return EINVAL;
+ }
+
+ SAFEALIGN_COPY_UINT32(&flags, p, NULL);
+
+ *_rawname = rawname;
+ *_flags = flags;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_id(struct cli_ctx *cli_ctx, uint32_t *_id)
+{
+ struct cli_protocol *pctx;
+ uint8_t *body;
+ size_t blen;
+ uint32_t id;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ if (blen != sizeof(uint32_t)) {
+ return EINVAL;
+ }
+
+ SAFEALIGN_COPY_UINT32(&id, body, NULL);
+
+ *_id = id;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_id_ex(struct cli_ctx *cli_ctx, uint32_t *_id,
+ uint32_t *_flags)
+{
+ struct cli_protocol *pctx;
+ uint8_t *body;
+ size_t blen;
+ uint32_t id;
+ uint32_t flags;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ if (blen != 2 * sizeof(uint32_t)) {
+ return EINVAL;
+ }
+
+ SAFEALIGN_COPY_UINT32(&id, body, NULL);
+ SAFEALIGN_COPY_UINT32(&flags, body + sizeof(uint32_t), NULL);
+
+ *_id = id;
+ *_flags = flags;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_limit(struct cli_ctx *cli_ctx, uint32_t *_limit)
+{
+ return sss_nss_protocol_parse_id(cli_ctx, _limit);
+}
+
+errno_t
+sss_nss_protocol_parse_svc_name(struct cli_ctx *cli_ctx,
+ const char **_name,
+ const char **_protocol)
+{
+ struct cli_protocol *pctx;
+ const char *protocol;
+ const char *name;
+ size_t protocol_len;
+ size_t name_len;
+ uint8_t *body;
+ size_t blen;
+ int i;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ /* If not terminated fail. */
+ if (body[blen - 1] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n");
+ return EINVAL;
+ }
+
+ /* Calculate service name length. */
+ for (i = 0, name_len = 0; body[i] != '\0'; i++) {
+ name_len++;
+ }
+
+ /* Calculate protocol name length, use index from previous cycle. */
+ for (protocol_len = 0; body[i + 1] != '\0'; i++) {
+ protocol_len++;
+ }
+
+ if (name_len == 0) {
+ return EINVAL;
+ }
+
+ name = (const char *)body;
+ protocol = protocol_len == 0 ? NULL : (const char *)(body + name_len + 1);
+
+ if (!sss_utf8_check((const uint8_t *)name, name_len)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Name is not UTF-8 string\n");
+ return EINVAL;
+ }
+
+ if (!sss_utf8_check((const uint8_t *)protocol, protocol_len)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Protocol is not UTF-8 string\n");
+ return EINVAL;
+ }
+
+ *_name = name;
+ *_protocol = protocol;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_svc_port(struct cli_ctx *cli_ctx,
+ uint16_t *_port,
+ const char **_protocol)
+{
+ struct cli_protocol *pctx;
+ const char *protocol;
+ size_t protocol_len;
+ uint16_t port;
+ uint8_t *body;
+ size_t blen;
+ int i;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ /* If not terminated fail. */
+ if (body[blen - 1] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n");
+ return EINVAL;
+ }
+
+ SAFEALIGN_COPY_UINT16(&port, body, NULL);
+ port = ntohs(port);
+
+ /* Move behind the port and padding to get the protocol. */
+ body = body + 2 * sizeof(uint16_t) + sizeof(uint32_t);
+
+ /* Calculate protocol name length. */
+ for (protocol_len = 0, i = 0; body[i] != '\0'; i++) {
+ protocol_len++;
+ }
+
+ protocol = protocol_len == 0 ? NULL : (const char *)body;
+
+ if (!sss_utf8_check((const uint8_t *)protocol, protocol_len)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Protocol is not UTF-8 string\n");
+ return EINVAL;
+ }
+
+ *_port = port;
+ *_protocol = protocol;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_cert(struct cli_ctx *cli_ctx,
+ const char **_derb64)
+{
+ struct cli_protocol *pctx;
+ const char *derb64;
+ size_t pem_size;
+ char *pem_cert;
+ uint8_t *body;
+ size_t blen;
+ errno_t ret;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ /* If not terminated fail. */
+ if (body[blen - 1] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n");
+ return EINVAL;
+ }
+
+ derb64 = (const char *)body;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Input certificate [%s]\n", derb64);
+
+ /* Check input. */
+ ret = sss_cert_derb64_to_pem(cli_ctx, derb64, &pem_cert, &pem_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to convert certificate to pem [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ talloc_free(pem_cert);
+
+ *_derb64 = derb64;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_sid(struct cli_ctx *cli_ctx,
+ const char **_sid)
+{
+ struct cli_protocol *pctx;
+ struct sss_nss_ctx *nss_ctx;
+ const char *sid;
+ uint8_t *bin_sid;
+ size_t bin_len;
+ uint8_t *body;
+ size_t blen;
+ enum idmap_error_code err;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+ nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ /* If not terminated fail. */
+ if (body[blen - 1] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n");
+ return EINVAL;
+ }
+
+ sid = (const char *)body;
+
+ /* If the body isn't a SID, fail */
+ err = sss_idmap_sid_to_bin_sid(nss_ctx->idmap_ctx, sid, &bin_sid,
+ &bin_len);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to convert SID to binary [%s].\n", sid);
+ return EINVAL;
+ }
+
+ sss_idmap_free_bin_sid(nss_ctx->idmap_ctx, bin_sid);
+
+ DEBUG(SSSDBG_TRACE_ALL, "Input SID [%s]\n", sid);
+
+ *_sid = sid;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_parse_addr(struct cli_ctx *cli_ctx,
+ uint32_t *_af,
+ uint32_t *_addrlen,
+ uint8_t **_addr)
+{
+ struct cli_protocol *pctx;
+ uint8_t *body;
+ size_t blen;
+ uint32_t af;
+ uint8_t *addr;
+ socklen_t addrlen;
+ char buf[INET6_ADDRSTRLEN];
+ const char *addrstr = NULL;
+
+ pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol);
+
+ sss_packet_get_body(pctx->creq->in, &body, &blen);
+
+ if (blen < sizeof(uint32_t) * 2) {
+ return EINVAL;
+ }
+
+ SAFEALIGN_COPY_UINT32(&af, body, NULL);
+ SAFEALIGN_COPY_UINT32(&addrlen, body + sizeof(uint32_t), NULL);
+
+ addr = body + sizeof(uint32_t) * 2;
+
+ /* If the body isn't a addr, fail */
+ addrstr = inet_ntop(af, addr, buf, INET6_ADDRSTRLEN);
+ if (addrstr == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to parse address: %s\n", strerror(errno));
+ return EINVAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Input address [%s]\n", addrstr);
+
+ *_af = af;
+ *_addr = addr;
+ *_addrlen = addrlen;
+
+ return EOK;
+}
diff --git a/src/responder/nss/nss_protocol.h b/src/responder/nss/nss_protocol.h
new file mode 100644
index 0000000..13ff870
--- /dev/null
+++ b/src/responder/nss/nss_protocol.h
@@ -0,0 +1,217 @@
+/*
+ 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/>.
+*/
+
+#ifndef _NSS_PROTOCOL_H_
+#define _NSS_PROTOCOL_H_
+
+#include <stdint.h>
+
+#include "util/util.h"
+#include "responder/common/responder.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/cache_req/cache_req.h"
+#include "responder/nss/nss_private.h"
+#include "sss_client/idmap/sss_nss_idmap.h"
+
+struct sss_nss_cmd_ctx;
+
+/**
+ * Fill SSSD response packet.
+ *
+ * @return EOK If packet is successfully created and should be sent to client.
+ * @return Other errno code on error, an error reply will be sent to client.
+ */
+typedef errno_t
+(*sss_nss_protocol_fill_packet_fn)(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+struct sss_nss_cmd_ctx {
+ enum cache_req_type type;
+ struct cli_ctx *cli_ctx;
+ struct sss_nss_ctx *nss_ctx;
+ struct sss_nss_state_ctx *state_ctx;
+ sss_nss_protocol_fill_packet_fn fill_fn;
+ uint32_t flags;
+
+ /* For initgroups- */
+ const char *rawname;
+
+ /* For enumeration. */
+ bool enumeration;
+ struct sss_nss_enum_ctx *enum_ctx;
+ struct sss_nss_enum_index *enum_index;
+ uint32_t enum_limit;
+
+ /* For services. */
+ const char *svc_protocol;
+
+ /* For SID lookups. */
+ enum sss_id_type sid_id_type;
+};
+
+/**
+ * If error is EOK, send existing reply packet to the client.
+ * If error is ENOENT, create and send empty response.
+ * On other error code, create and send an error.
+ */
+errno_t sss_nss_protocol_done(struct cli_ctx *cli_ctx, errno_t error);
+
+/**
+ * Create and send SSSD response packet to the client.
+ */
+void sss_nss_protocol_reply(struct cli_ctx *cli_ctx,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_result *result,
+ sss_nss_protocol_fill_packet_fn fill_fn);
+
+/* Parse input packet. */
+
+errno_t
+sss_nss_protocol_parse_name(struct cli_ctx *cli_ctx, const char **_rawname);
+
+errno_t
+sss_nss_protocol_parse_name_ex(struct cli_ctx *cli_ctx, const char **_rawname,
+ uint32_t *_flags);
+
+errno_t
+sss_nss_protocol_parse_id(struct cli_ctx *cli_ctx, uint32_t *_id);
+
+errno_t
+sss_nss_protocol_parse_id_ex(struct cli_ctx *cli_ctx, uint32_t *_id,
+ uint32_t *_flags);
+
+errno_t
+sss_nss_protocol_parse_limit(struct cli_ctx *cli_ctx, uint32_t *_limit);
+
+errno_t
+sss_nss_protocol_parse_svc_name(struct cli_ctx *cli_ctx,
+ const char **_name,
+ const char **_protocol);
+
+errno_t
+sss_nss_protocol_parse_svc_port(struct cli_ctx *cli_ctx,
+ uint16_t *_port,
+ const char **_protocol);
+
+errno_t
+sss_nss_protocol_parse_cert(struct cli_ctx *cli_ctx,
+ const char **_derb64);
+
+errno_t
+sss_nss_protocol_parse_sid(struct cli_ctx *cli_ctx,
+ const char **_sid);
+
+errno_t
+sss_nss_protocol_parse_addr(struct cli_ctx *cli_ctx,
+ uint32_t *_af,
+ uint32_t *_addrlen,
+ uint8_t **_addr);
+
+/* Create response packet. */
+
+errno_t
+sss_nss_protocol_fill_pwent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+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);
+
+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);
+
+#ifdef BUILD_SUBID
+errno_t
+sss_nss_protocol_fill_subid_ranges(struct sss_nss_ctx *sss_nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+#endif
+
+errno_t
+sss_nss_protocol_fill_netgrent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_svcent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_sid(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_orig(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_name(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_single_name(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_name_list_all_domains(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result **results);
+
+errno_t
+sss_nss_protocol_fill_id(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+errno_t
+sss_nss_protocol_fill_hostent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+errno_t
+sss_nss_protocol_fill_netent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result);
+
+#endif /* _NSS_PROTOCOL_H_ */
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;
+}
diff --git a/src/responder/nss/nss_protocol_hostent.c b/src/responder/nss/nss_protocol_hostent.c
new file mode 100644
index 0000000..687baad
--- /dev/null
+++ b/src/responder/nss/nss_protocol_hostent.c
@@ -0,0 +1,299 @@
+/*
+ SSSD
+
+ Authors:
+ Samuel Cabrero <scabrero@suse.com>
+
+ Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+
+ 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 "db/sysdb.h"
+#include "db/sysdb_iphosts.h"
+#include "responder/nss/nss_protocol.h"
+
+static errno_t
+sss_nss_get_hostent(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ struct sized_string *_name)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *name;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Get name */
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ if (name == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve);
+ if (name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Set output variables */
+
+ talloc_steal(mem_ctx, name);
+
+ to_sized_string(_name, name);
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static errno_t
+sss_nss_get_host_aliases(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *name,
+ struct sized_string **_aliases,
+ uint32_t *_num_aliases)
+{
+ struct ldb_message_element *el;
+ struct sized_string *aliases = NULL;
+ uint32_t num_aliases;
+ const char *alias;
+ errno_t ret;
+ int i;
+
+ el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS);
+ if (el == NULL) {
+ *_num_aliases = 0;
+ *_aliases = NULL;
+ ret = EOK;
+ goto done;
+ }
+
+ aliases = talloc_zero_array(mem_ctx, struct sized_string,
+ el->num_values + 1);
+ if (aliases == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ num_aliases = 0;
+ for (i = 0; i < el->num_values; i++) {
+ alias = (const char *)el->values[i].data;
+
+ if (sss_string_equal(domain->case_sensitive, alias, name)) {
+ continue;
+ }
+
+ /* Element value remains in the message, we don't need to strdup it. */
+ to_sized_string(&aliases[num_aliases], alias);
+ num_aliases++;
+ }
+
+ *_aliases = aliases;
+ *_num_aliases = num_aliases;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(aliases);
+ }
+
+ return ret;
+}
+
+static errno_t
+sss_nss_get_host_addresses(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *name,
+ struct sized_string **_addresses,
+ uint32_t *_num_addresses)
+{
+ struct ldb_message_element *el;
+ struct sized_string *addresses = NULL;
+ uint32_t num_addresses;
+ const char *addr;
+ errno_t ret;
+ int i;
+
+ el = ldb_msg_find_element(msg, SYSDB_IP_HOST_ATTR_ADDRESS);
+ if (el == NULL) {
+ *_num_addresses = 0;
+ *_addresses = NULL;
+ ret = EOK;
+ goto done;
+ }
+
+ addresses = talloc_zero_array(mem_ctx, struct sized_string,
+ el->num_values + 1);
+ if (addresses == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ num_addresses = 0;
+ for (i = 0; i < el->num_values; i++) {
+ addr = (const char *)el->values[i].data;
+
+ /* Element value remains in the message, we don't need to strdup it. */
+ to_sized_string(&addresses[num_addresses], addr);
+ num_addresses++;
+ }
+
+ *_addresses = addresses;
+ *_num_addresses = num_addresses;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(addresses);
+ }
+
+ return ret;
+}
+
+errno_t
+sss_nss_protocol_fill_hostent(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 *aliases;
+ struct sized_string *addresses;
+ uint32_t num_aliases;
+ uint32_t num_addresses;
+ uint32_t num_results;
+ size_t rp;
+ size_t body_len;
+ uint8_t *body;
+ int i;
+ int j;
+ 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];
+
+ ret = sss_nss_get_hostent(tmp_ctx, result->domain, msg, &name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get host information, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ ret = sss_nss_get_host_aliases(tmp_ctx, result->domain, msg, name.str,
+ &aliases, &num_aliases);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get host aliases, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ ret = sss_nss_get_host_addresses(tmp_ctx, result->domain, msg, name.str,
+ &addresses, &num_addresses);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get host addresses, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ /* Adjust packet size */
+
+ ret = sss_packet_grow(packet, 2 * sizeof(uint32_t) + name.len);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ /* Fill packet */
+
+ SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp);
+ SAFEALIGN_SET_UINT32(&body[rp], num_addresses, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp);
+
+ /* Store aliases */
+ for (j = 0; j < num_aliases; j++) {
+ ret = sss_packet_grow(packet, aliases[j].len);
+ if (ret != EOK) {
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len,
+ &rp);
+ }
+
+ /* Store addresses */
+ for (j = 0; j < num_addresses; j++) {
+ ret = sss_packet_grow(packet, addresses[j].len);
+ if (ret != EOK) {
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_STRING(&body[rp], addresses[j].str, addresses[j].len,
+ &rp);
+ }
+
+ num_results++;
+ }
+
+ 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;
+}
diff --git a/src/responder/nss/nss_protocol_netent.c b/src/responder/nss/nss_protocol_netent.c
new file mode 100644
index 0000000..0517726
--- /dev/null
+++ b/src/responder/nss/nss_protocol_netent.c
@@ -0,0 +1,243 @@
+/*
+ SSSD
+
+ Authors:
+ Samuel Cabrero <scabrero@suse.com>
+
+ Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+
+ 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 "db/sysdb.h"
+#include "db/sysdb_ipnetworks.h"
+#include "responder/nss/nss_protocol.h"
+
+static errno_t
+sss_nss_get_netent(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ struct sized_string *_name,
+ struct sized_string *_addr)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *name;
+ const char *addr;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Get name */
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ if (name == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve);
+ if (name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Get address */
+ addr = ldb_msg_find_attr_as_string(msg, SYSDB_IP_NETWORK_ATTR_NUMBER,
+ NULL);
+ if (addr == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ addr = sss_get_cased_name(tmp_ctx, addr, domain->case_preserve);
+ if (addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Set output variables */
+
+ talloc_steal(mem_ctx, name);
+ talloc_steal(mem_ctx, addr);
+
+ to_sized_string(_name, name);
+ to_sized_string(_addr, addr);
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static errno_t
+sss_nss_get_network_aliases(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *name,
+ struct sized_string **_aliases,
+ uint32_t *_num_aliases)
+{
+ struct ldb_message_element *el;
+ struct sized_string *aliases = NULL;
+ uint32_t num_aliases;
+ const char *alias;
+ errno_t ret;
+ int i;
+
+ el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS);
+ if (el == NULL) {
+ *_num_aliases = 0;
+ *_aliases = NULL;
+ ret = EOK;
+ goto done;
+ }
+
+ aliases = talloc_zero_array(mem_ctx, struct sized_string,
+ el->num_values + 1);
+ if (aliases == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ num_aliases = 0;
+ for (i = 0; i < el->num_values; i++) {
+ alias = (const char *)el->values[i].data;
+
+ if (sss_string_equal(domain->case_sensitive, alias, name)) {
+ continue;
+ }
+
+ /* Element value remains in the message, we don't need to strdup it. */
+ to_sized_string(&aliases[num_aliases], alias);
+ num_aliases++;
+ }
+
+ *_aliases = aliases;
+ *_num_aliases = num_aliases;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(aliases);
+ }
+
+ return ret;
+}
+
+errno_t
+sss_nss_protocol_fill_netent(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 addr;
+ struct sized_string *aliases;
+ uint32_t num_aliases;
+ uint32_t num_results;
+ size_t rp;
+ size_t body_len;
+ uint8_t *body;
+ int i;
+ int j;
+ 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];
+
+ ret = sss_nss_get_netent(tmp_ctx, result->domain, msg, &name, &addr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get network information, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ ret = sss_nss_get_network_aliases(tmp_ctx, result->domain, msg, name.str,
+ &aliases, &num_aliases);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get network aliases, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ /* Adjust packet size */
+
+ ret = sss_packet_grow(packet, sizeof(uint32_t) + name.len + addr.len);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ /* Fill packet */
+
+ SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], addr.str, addr.len, &rp);
+
+ /* Store aliases */
+ for (j = 0; j < num_aliases; j++) {
+ ret = sss_packet_grow(packet, aliases[j].len);
+ if (ret != EOK) {
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len,
+ &rp);
+ }
+
+ num_results++;
+ }
+
+ 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;
+}
diff --git a/src/responder/nss/nss_protocol_netgr.c b/src/responder/nss/nss_protocol_netgr.c
new file mode 100644
index 0000000..be3380b
--- /dev/null
+++ b/src/responder/nss/nss_protocol_netgr.c
@@ -0,0 +1,181 @@
+/*
+ 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 "db/sysdb.h"
+#include "db/sysdb_services.h"
+#include "responder/nss/nss_protocol.h"
+
+static errno_t
+sss_nss_protocol_fill_netgr_triple(struct sss_packet *packet,
+ struct sysdb_netgroup_ctx *entry,
+ size_t *_rp)
+{
+ struct sized_string host;
+ struct sized_string user;
+ struct sized_string domain;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ to_sized_string(&host, entry->value.triple.hostname);
+ to_sized_string(&user, entry->value.triple.username);
+ to_sized_string(&domain, entry->value.triple.domainname);
+
+ if (host.len == 0) {
+ host.len = 1;
+ host.str = "";
+ }
+
+ if (user.len == 0) {
+ user.len = 1;
+ user.str = "";
+ }
+
+ if (domain.len == 0) {
+ domain.len = 1;
+ domain.str = "";
+ }
+
+ ret = sss_packet_grow(packet, sizeof(uint32_t)
+ + host.len + user.len + domain.len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to grow packet!\n");
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[*_rp], SSS_NETGR_REP_TRIPLE, _rp);
+ SAFEALIGN_SET_STRING(&body[*_rp], host.str, host.len, _rp);
+ SAFEALIGN_SET_STRING(&body[*_rp], user.str, user.len, _rp);
+ SAFEALIGN_SET_STRING(&body[*_rp], domain.str, domain.len, _rp);
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_protocol_fill_netgr_member(struct sss_packet *packet,
+ struct sysdb_netgroup_ctx *entry,
+ size_t *_rp)
+{
+ struct sized_string group;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ if (entry->value.groupname == NULL || entry->value.groupname[0] == '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Empty netgroup member!\n");
+ return EINVAL;
+ }
+
+ to_sized_string(&group, entry->value.groupname);
+
+ ret = sss_packet_grow(packet, sizeof(uint32_t) + group.len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to grow packet!\n");
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[*_rp], SSS_NETGR_REP_GROUP, _rp);
+ SAFEALIGN_SET_STRING(&body[*_rp], group.str, group.len, _rp);
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_netgrent(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ struct sysdb_netgroup_ctx **entries;
+ struct sysdb_netgroup_ctx *entry;
+ struct sss_nss_enum_index *idx;
+ uint32_t num_results;
+ size_t rp;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ idx = cmd_ctx->enum_index;
+ entries = cmd_ctx->enum_ctx->netgroup;
+
+ if (idx->result > cmd_ctx->enum_ctx->netgroup_count) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Inconsistent state while processing netgroups.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* 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);
+
+ if (entries == NULL) {
+ num_results = 0;
+ ret = EOK;
+ goto done;
+ }
+
+ num_results = 1; /* group was found */
+ for (; entries[idx->result] != NULL; idx->result++) {
+
+ entry = entries[idx->result];
+
+ switch (entry->type) {
+ case SYSDB_NETGROUP_TRIPLE_VAL:
+ ret = sss_nss_protocol_fill_netgr_triple(packet, entry, &rp);
+ break;
+ case SYSDB_NETGROUP_GROUP_VAL:
+ ret = sss_nss_protocol_fill_netgr_member(packet, entry, &rp);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected value type %d!\n", entry->type);
+ ret = ERR_INTERNAL;
+ break;
+ }
+
+ if (ret != EOK) {
+ goto done;
+ }
+
+ num_results++;
+ }
+
+ ret = EOK;
+
+done:
+ 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;
+}
diff --git a/src/responder/nss/nss_protocol_pwent.c b/src/responder/nss/nss_protocol_pwent.c
new file mode 100644
index 0000000..3e1f31f
--- /dev/null
+++ b/src/responder/nss/nss_protocol_pwent.c
@@ -0,0 +1,338 @@
+/*
+ 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_nss.h"
+
+static uint32_t
+sss_nss_get_gid(struct sss_domain_info *domain,
+ struct ldb_message *msg)
+{
+ uint32_t gid;
+
+ /* First, try to return overridden gid. */
+ if (DOM_HAS_VIEWS(domain)) {
+ gid = ldb_msg_find_attr_as_uint64(msg, OVERRIDE_PREFIX SYSDB_GIDNUM,
+ 0);
+ if (gid != 0) {
+ return gid;
+ }
+ }
+
+ /* Try to return domain gid override. */
+ if (domain->override_gid != 0) {
+ return domain->override_gid;
+ }
+
+ /* Return original gid. */
+ return ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+}
+
+static const char *
+sss_nss_get_homedir_override(TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg,
+ struct sss_nss_ctx *nctx,
+ struct sss_domain_info *dom,
+ struct sss_nss_homedir_ctx *homedir_ctx)
+{
+ const char *homedir;
+ bool is_override = false;
+
+ homedir = sss_view_ldb_msg_find_attr_as_string_ex(dom, msg, SYSDB_HOMEDIR,
+ NULL, &is_override);
+ homedir_ctx->original = homedir;
+
+ /* Check to see which homedir_prefix to use. */
+ if (dom->homedir_substr != NULL) {
+ homedir_ctx->config_homedir_substr = dom->homedir_substr;
+ } else if (nctx->homedir_substr != NULL) {
+ homedir_ctx->config_homedir_substr = nctx->homedir_substr;
+ }
+
+ /* Individual overrides have the highest priority, only templates will be
+ * expanded and no further options will be evaluated. */
+ if (is_override) {
+ return expand_homedir_template(mem_ctx, homedir,
+ dom->case_preserve, homedir_ctx);
+ }
+
+ /* Here we skip the files provider as it should always return *only*
+ * what's in the files and nothing else.
+ */
+ if (!is_files_provider(dom)) {
+ /* Check whether we are unconditionally overriding the server
+ * for home directory locations.
+ */
+ if (dom->override_homedir) {
+ return expand_homedir_template(mem_ctx, dom->override_homedir,
+ dom->case_preserve, homedir_ctx);
+ } else if (nctx->override_homedir) {
+ return expand_homedir_template(mem_ctx, nctx->override_homedir,
+ dom->case_preserve, homedir_ctx);
+ }
+ }
+
+ if (!homedir || *homedir == '\0') {
+ /* In the case of a NULL or empty homedir, check to see if
+ * we have a fallback homedir to use.
+ */
+ if (dom->fallback_homedir) {
+ return expand_homedir_template(mem_ctx, dom->fallback_homedir,
+ dom->case_preserve, homedir_ctx);
+ } else if (nctx->fallback_homedir) {
+ return expand_homedir_template(mem_ctx, nctx->fallback_homedir,
+ dom->case_preserve, homedir_ctx);
+ }
+ }
+
+ /* Provider can also return template, try to expand it.*/
+ return expand_homedir_template(mem_ctx, homedir,
+ dom->case_preserve, homedir_ctx);
+}
+
+static const char *
+sss_nss_get_homedir(TALLOC_CTX *mem_ctx,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *orig_name,
+ const char *upn,
+ uid_t uid)
+{
+ struct sss_nss_homedir_ctx hd_ctx = { 0 };
+ const char *homedir;
+
+ hd_ctx.username = orig_name;
+ hd_ctx.uid = uid;
+ hd_ctx.domain = domain->name;
+ hd_ctx.upn = upn;
+
+ homedir = sss_nss_get_homedir_override(mem_ctx, msg, nss_ctx, domain, &hd_ctx);
+ if (homedir == NULL) {
+ return "";
+ }
+
+ return homedir;
+}
+
+static errno_t
+sss_nss_get_shell(struct sss_nss_ctx *nss_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *name,
+ uint32_t uid,
+ const char **_shell)
+{
+ const char *shell = NULL;
+
+ if (nss_ctx->rctx->sr_conf.scope != SESSION_RECORDING_SCOPE_NONE) {
+ const char *sr_enabled;
+ sr_enabled = ldb_msg_find_attr_as_string(
+ msg, SYSDB_SESSION_RECORDING, NULL);
+ if (sr_enabled == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "%s attribute not found for %s[%u]! Skipping\n",
+ SYSDB_SESSION_RECORDING, name, uid);
+ return EINVAL;
+ } else if (strcmp(sr_enabled, "TRUE") == 0) {
+ shell = SESSION_RECORDING_SHELL;
+ } else if (strcmp(sr_enabled, "FALSE") != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Skipping %s[%u] "
+ "because its %s attribute value is invalid: %s\n",
+ name, uid, SYSDB_SESSION_RECORDING, sr_enabled);
+ return EINVAL;
+ }
+ }
+ if (shell == NULL) {
+ shell = sss_resp_get_shell_override(msg, nss_ctx->rctx, domain);
+ }
+
+ *_shell = shell;
+ return EOK;
+}
+
+static errno_t
+sss_nss_get_pwent(TALLOC_CTX *mem_ctx,
+ struct sss_nss_ctx *nss_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ uint32_t *_uid,
+ uint32_t *_gid,
+ struct sized_string **_name,
+ struct sized_string *_gecos,
+ struct sized_string *_homedir,
+ struct sized_string *_shell)
+{
+ const char *upn;
+ const char *name;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ uint32_t gid;
+ uint32_t uid;
+ errno_t ret;
+
+ /* Get fields. */
+ upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL);
+ name = sss_get_name_from_msg(domain, msg);
+ gid = sss_nss_get_gid(domain, msg);
+ uid = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, SYSDB_UIDNUM, 0);
+
+ if (name == NULL || uid == 0 || gid == 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Incomplete user object for %s[%u]! Skipping\n",
+ name ? name : "<NULL>", uid);
+ return EINVAL;
+ }
+
+ gecos = sss_view_ldb_msg_find_attr_as_string(domain, msg, SYSDB_GECOS,
+ NULL);
+ homedir = sss_nss_get_homedir(mem_ctx, nss_ctx, domain, msg, name, upn, uid);
+ ret = sss_nss_get_shell(nss_ctx, domain, msg, name, uid, &shell);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "failed retrieving shell for %s[%u], skipping [%d]: %s\n",
+ name, uid, ret, sss_strerror(ret));
+ return ret;
+ }
+
+ /* 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;
+ }
+
+ to_sized_string(_gecos, gecos == NULL ? "" : gecos);
+ to_sized_string(_shell, shell);
+ to_sized_string(_homedir, homedir);
+
+ *_gid = gid;
+ *_uid = uid;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_pwent(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 pwfield;
+ struct sized_string *name;
+ struct sized_string gecos;
+ struct sized_string homedir;
+ struct sized_string shell;
+ uint32_t gid;
+ uint32_t uid;
+ uint32_t num_results;
+ size_t rp;
+ 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_pwent(tmp_ctx, nss_ctx, result->domain, msg, &uid, &gid,
+ &name, &gecos, &homedir, &shell);
+ if (ret != EOK) {
+ continue;
+ }
+
+ /* Adjust packet size: uid, gid + string fields. */
+
+ ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)
+ + name->len + gecos.len + homedir.len
+ + shell.len + pwfield.len);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ /* Fill packet. */
+
+ SAFEALIGN_SET_UINT32(&body[rp], uid, &rp);
+ SAFEALIGN_SET_UINT32(&body[rp], gid, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], name->str, name->len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], pwfield.str, pwfield.len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], gecos.str, gecos.len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], homedir.str, homedir.len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], shell.str, shell.len, &rp);
+
+ 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->pwd_mc_ctx != NULL)) {
+ ret = sss_mmap_cache_pw_store(&nss_ctx->pwd_mc_ctx, name, &pwfield,
+ uid, gid, &gecos, &homedir, &shell);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to store user %s (%s) in mmap 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;
+}
diff --git a/src/responder/nss/nss_protocol_sid.c b/src/responder/nss/nss_protocol_sid.c
new file mode 100644
index 0000000..69d61bb
--- /dev/null
+++ b/src/responder/nss/nss_protocol_sid.c
@@ -0,0 +1,704 @@
+/*
+ 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 "util/crypto/sss_crypto.h"
+#include "responder/nss/nss_protocol.h"
+
+static errno_t
+find_sss_id_type(struct ldb_message *msg,
+ bool mpg,
+ enum sss_id_type *id_type)
+{
+ size_t c;
+ struct ldb_message_element *el;
+ struct ldb_val *val = NULL;
+
+ el = ldb_msg_find_element(msg, SYSDB_OBJECTCATEGORY);
+ if (el == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Objectcategory attribute not found.\n");
+ return EINVAL;
+ }
+
+ for (c = 0; c < el->num_values; c++) {
+ val = &(el->values[c]);
+ if (strncasecmp(SYSDB_USER_CLASS,
+ (char *)val->data, val->length) == 0) {
+ break;
+ }
+ }
+
+ if (c == el->num_values) {
+ *id_type = SSS_ID_TYPE_GID;
+ } else {
+ if (mpg) {
+ *id_type = SSS_ID_TYPE_BOTH;
+ } else {
+ *id_type = SSS_ID_TYPE_UID;
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_get_id_type(struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_result *result,
+ enum sss_id_type *_type)
+{
+ errno_t ret;
+ bool mpg;
+
+ /* Well known objects are always groups. */
+ if (result->well_known_object) {
+ *_type = SSS_ID_TYPE_GID;
+ return EOK;
+ }
+
+ mpg = sss_domain_is_mpg(result->domain) || sss_domain_is_hybrid(result->domain);
+ ret = find_sss_id_type(result->msgs[0], mpg, _type);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to find ID type [%d]: %s\n", ret, sss_strerror(ret));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_get_sid_id_type(struct sss_nss_cmd_ctx *cmd_ctx,
+ struct cache_req_result *result,
+ const char **_sid,
+ uint64_t *_id,
+ enum sss_id_type *_type)
+{
+ errno_t ret;
+ size_t c;
+ const char *tmp;
+ const char *user_sid = NULL;
+ const char *group_sid = NULL;
+ uint64_t user_uid = 0;
+ uint64_t user_gid = 0;
+ uint64_t group_gid = 0;
+ enum sss_id_type ltype;
+
+ if (result->count == 1) {
+ *_sid = ldb_msg_find_attr_as_string(result->msgs[0],
+ SYSDB_SID_STR, NULL);
+ if (*_sid == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID.\n");
+ return EINVAL;
+ }
+ ret = sss_nss_get_id_type(cmd_ctx, result, _type);
+ if (ret == EOK ) {
+ if (*_type == SSS_ID_TYPE_GID) {
+ *_id = ldb_msg_find_attr_as_uint64(result->msgs[0],
+ SYSDB_GIDNUM, 0);
+ } else {
+ *_id = ldb_msg_find_attr_as_uint64(result->msgs[0],
+ SYSDB_UIDNUM, 0);
+ }
+ }
+ return ret;
+ }
+
+ for (c = 0; c < result->count; c++) {
+ ret = find_sss_id_type(result->msgs[c],
+ false /* we are only interested in the type
+ * of the object, so mpg setting can
+ * be ignored */,
+ &ltype);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to find ID type, ignored [%d][%s].\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(result->msgs[c],
+ SYSDB_SID_STR, NULL);
+ if (tmp == NULL) {
+ continue;
+ }
+
+ if (ltype == SSS_ID_TYPE_GID) {
+ if (tmp != NULL && group_sid != NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Search for SID found multiple groups with SIDs: %s, %s;"
+ " request failed.\n", tmp, group_sid);
+ return EINVAL;
+ }
+ group_sid = tmp;
+ group_gid = ldb_msg_find_attr_as_uint64(result->msgs[c],
+ SYSDB_GIDNUM, 0);
+ } else {
+ if (tmp != NULL && user_sid != NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Search for SID found multiple users with SIDs: %s, %s; "
+ "request failed.\n", tmp, user_sid);
+ return EINVAL;
+ }
+ user_sid = tmp;
+ user_uid = ldb_msg_find_attr_as_uint64(result->msgs[c],
+ SYSDB_UIDNUM, 0);
+ user_gid = ldb_msg_find_attr_as_uint64(result->msgs[c],
+ SYSDB_GIDNUM, 0);
+ }
+ }
+
+ if (user_sid == NULL && group_sid == NULL) {
+ /* No SID in the results */
+ return ENOENT;
+ } else if (user_sid != NULL && group_sid == NULL) {
+ /* There is only one user with a SID in the results */
+ *_sid = user_sid;
+ *_id = user_uid;
+ *_type = SSS_ID_TYPE_UID;
+ } else if (user_sid == NULL && group_sid != NULL) {
+ /* There is only one group with a SID in the results */
+ *_sid = group_sid;
+ *_id = group_gid;
+ *_type = SSS_ID_TYPE_GID;
+ } else if (user_sid != NULL && group_sid != NULL && user_uid != 0
+ && user_uid == user_gid && user_gid == group_gid) {
+ /* Manually created user-private-group */
+ *_sid = user_sid;
+ *_id = user_uid;
+ *_type = SSS_ID_TYPE_UID;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Found user with SID [%s] and group with SID [%s] during a "
+ "single request, cannot handle this case.\n",
+ user_sid, group_sid);
+ /* Unrelated user and group both with SIDs are returned, we cannot
+ * handle this case. */
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_sid(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ struct sized_string sz_sid;
+ enum sss_id_type id_type;
+ const char *sid;
+ uint64_t id;
+ size_t rp = 0;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ ret = sss_nss_get_sid_id_type(cmd_ctx, result, &sid, &id, &id_type);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ to_sized_string(&sz_sid, sid);
+
+ ret = sss_packet_grow(packet, sz_sid.len + 3 * sizeof(uint32_t));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */
+ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */
+ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], sz_sid.str, sz_sid.len, &rp);
+
+ if (nss_ctx->sid_mc_ctx != NULL) {
+ /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since
+ * SID related requests don't support 'flags'
+ */
+ if (id == 0 || id >= UINT32_MAX) {
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid POSIX ID %lu\n", id);
+ return EOK;
+ }
+ ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sz_sid,
+ (uint32_t)id, id_type,
+ cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to store SID='%s' / ID=%lu in mmap cache [%d]: %s!\n",
+ sz_sid.str, id, ret, sss_strerror(ret));
+ }
+ }
+
+ return EOK;
+}
+
+static errno_t process_attr_list(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ const char **attr_list,
+ struct sized_string **_keys,
+ struct sized_string **_vals,
+ size_t *array_size, size_t *sum,
+ size_t *found)
+{
+ size_t c;
+ size_t d;
+ struct sized_string *keys;
+ struct sized_string *vals;
+ struct ldb_val val;
+ struct ldb_message_element *el;
+ bool use_base64;
+
+ keys = *_keys;
+ vals = *_vals;
+
+ for (c = 0; attr_list[c] != NULL; c++) {
+ el = ldb_msg_find_element(msg, attr_list[c]);
+ if (el != NULL && el->num_values > 0) {
+ if (el->num_values > 1) {
+ *array_size += el->num_values;
+ keys = talloc_realloc(mem_ctx, keys, struct sized_string,
+ *array_size);
+ vals = talloc_realloc(mem_ctx, vals, struct sized_string,
+ *array_size);
+ if (keys == NULL || vals == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ return ENOMEM;
+ }
+ }
+
+ use_base64 = false;
+ if (strcmp(attr_list[c], SYSDB_USER_CERT) == 0) {
+ use_base64 = true;
+ }
+
+ for (d = 0; d < el->num_values; d++) {
+ to_sized_string(&keys[*found], attr_list[c]);
+ *sum += keys[*found].len;
+ if (use_base64) {
+ val.data = (uint8_t *)sss_base64_encode(vals,
+ el->values[d].data,
+ el->values[d].length);
+ if (val.data != NULL) {
+ val.length = strlen((char *)val.data);
+ }
+ } else {
+ val = el->values[d];
+ }
+
+ if (val.data == NULL || val.data[val.length] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected attribute value found for [%s].\n",
+ attr_list[c]);
+ return EINVAL;
+ }
+ to_sized_string(&vals[*found], (const char *)val.data);
+ *sum += vals[*found].len;
+
+ (*found)++;
+ }
+ }
+ }
+
+ *_keys = keys;
+ *_vals = vals;
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_orig(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 = result->msgs[0];
+ const char **full_attrs = NULL;
+ enum sss_id_type id_type;
+ struct sized_string *keys;
+ struct sized_string *vals;
+ size_t full_attrs_count = 0;
+ size_t array_size;
+ size_t sum;
+ size_t found;
+ size_t i;
+ size_t rp = 0;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ if (result->count != 1) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unexpected number of results [%u], expected [1].\n",
+ result->count);
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ret = sss_nss_get_id_type(cmd_ctx, result, &id_type);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ if (nss_ctx->full_attribute_list != NULL) {
+ full_attrs = nss_ctx->full_attribute_list;
+ for (full_attrs_count = 0;
+ full_attrs[full_attrs_count] != NULL;
+ full_attrs_count++);
+ }
+
+ array_size = full_attrs_count;
+ keys = talloc_array(tmp_ctx, struct sized_string, array_size);
+ vals = talloc_array(tmp_ctx, struct sized_string, array_size);
+ if (keys == NULL || vals == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ sum = 0;
+ found = 0;
+
+ if (full_attrs_count != 0) {
+ ret = process_attr_list(tmp_ctx, msg, full_attrs, &keys, &vals,
+ &array_size, &sum, &found);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "process_attr_list failed.\n");
+ goto done;
+ }
+ }
+
+ ret = sss_packet_grow(packet, sum + 3 * sizeof(uint32_t));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
+ goto done;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+ SAFEALIGN_SETMEM_UINT32(&body[rp], 1, &rp); /* Num results */
+ SAFEALIGN_SETMEM_UINT32(&body[rp], 0, &rp); /* reserved */
+ SAFEALIGN_COPY_UINT32(&body[rp], &id_type, &rp);
+ for (i = 0; i < found; i++) {
+ SAFEALIGN_SET_STRING(&body[rp], keys[i].str, keys[i].len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], vals[i].str, vals[i].len, &rp);
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t
+sss_nss_get_well_known_name(TALLOC_CTX *mem_ctx,
+ struct resp_ctx *rctx,
+ struct cache_req_result *result,
+ struct sized_string **_sz_name)
+{
+ struct sized_string *sz_name;
+ const char *fq_name = NULL;
+ const char *domname;
+ const char *name;
+
+ name = result->lookup_name;
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n");
+ return EINVAL;
+ }
+
+ sz_name = talloc_zero(mem_ctx, struct sized_string);
+ if (sz_name == NULL) {
+ return ENOMEM;
+ }
+
+ domname = result->domain != NULL
+ ? result->domain->name
+ : result->well_known_domain;
+
+ if (domname != NULL) {
+ fq_name = sss_tc_fqname2(sz_name, rctx->global_names,
+ domname, domname, name);
+ if (fq_name == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "Conversion to fqname failed.\n");
+ talloc_free(sz_name);
+ return ENOMEM;
+ }
+
+ name = fq_name;
+ }
+
+ to_sized_string(sz_name, name);
+
+ *_sz_name = sz_name;
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_get_ad_name(TALLOC_CTX *mem_ctx,
+ struct resp_ctx *rctx,
+ struct cache_req_result *result,
+ struct sized_string **_sz_name)
+{
+ struct ldb_message *msg = result->msgs[0];
+ const char *name;
+ errno_t ret;
+
+ if (result->well_known_object) {
+ return sss_nss_get_well_known_name(mem_ctx, rctx, result, _sz_name);
+ }
+
+ name = ldb_msg_find_attr_as_string(msg, ORIGINALAD_PREFIX SYSDB_NAME,
+ NULL);
+ if (name == NULL) {
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ }
+
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n");
+ return EINVAL;
+ }
+
+ ret = sized_output_name(mem_ctx, rctx, name, result->domain, _sz_name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to create sized name [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_single_name(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ if (result->ldb_result->count > 1) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Lookup returned more than one result "
+ "but only one was expected.\n");
+ return EEXIST;
+ }
+
+ return sss_nss_protocol_fill_name(nss_ctx, cmd_ctx, packet, result);
+}
+
+errno_t
+sss_nss_protocol_fill_name(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ struct sized_string *sz_name;
+ enum sss_id_type id_type;
+ size_t rp = 0;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ ret = sss_nss_get_id_type(cmd_ctx, result, &id_type);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = sss_nss_get_ad_name(cmd_ctx, nss_ctx->rctx, result, &sz_name);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = sss_packet_grow(packet, sz_name->len + 3 * sizeof(uint32_t));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
+ talloc_free(sz_name);
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */
+ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */
+ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], sz_name->str, sz_name->len, &rp);
+
+ talloc_free(sz_name);
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_id(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ struct ldb_message *msg = result->msgs[0];
+ enum sss_id_type id_type;
+ uint64_t id64;
+ uint32_t id;
+ const char *sid = NULL;
+ struct sized_string sid_key;
+ size_t rp = 0;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+
+ if (result->ldb_result == NULL) {
+ /* This was a well known SID. This is currently unsupported with id. */
+ return EINVAL;
+ }
+
+ ret = sss_nss_get_id_type(cmd_ctx, result, &id_type);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ if (id_type == SSS_ID_TYPE_GID) {
+ id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+ } else {
+ id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+ }
+
+ if (id64 == 0 || id64 >= UINT32_MAX) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid POSIX ID.\n");
+ return EINVAL;
+ }
+
+ id = (uint32_t)id64;
+
+ ret = sss_packet_grow(packet, 4 * sizeof(uint32_t));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */
+ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */
+ SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp);
+ SAFEALIGN_SET_UINT32(&body[rp], id, &rp);
+
+ if (nss_ctx->sid_mc_ctx != NULL) {
+ /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since
+ * SID related requests don't support 'flags'
+ */
+ sid = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL);
+ if (!sid) {
+ DEBUG(SSSDBG_OP_FAILURE, "Missing SID?!\n");
+ return EOK;
+ }
+ to_sized_string(&sid_key, sid);
+ ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sid_key, id, id_type,
+ cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to store SID='%s' / ID=%d in mmap cache [%d]: %s!\n",
+ sid, id, ret, sss_strerror(ret));
+ }
+ }
+
+ return EOK;
+}
+
+errno_t
+sss_nss_protocol_fill_name_list_all_domains(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result **results)
+{
+ enum sss_id_type *id_types;
+ size_t rp = 0;
+ size_t body_len;
+ uint8_t *body;
+ errno_t ret;
+ struct sized_string *sz_names;
+ size_t len;
+ size_t c;
+ const char *tmp_str;
+ size_t d;
+ size_t total = 0;
+ size_t iter = 0;
+
+ if (results == NULL) {
+ return EINVAL;
+ }
+
+ for (d = 0; results[d] != NULL; d++) {
+ total += results[d]->count;
+ }
+
+ sz_names = talloc_array(cmd_ctx, struct sized_string, total);
+ if (sz_names == NULL) {
+ return ENOMEM;
+ }
+
+ id_types = talloc_array(cmd_ctx, enum sss_id_type, total);
+ if (id_types == NULL) {
+ return ENOMEM;
+ }
+
+ len = 0;
+ for (d = 0; results[d] != NULL; d++) {
+ for (c = 0; c < results[d]->count; c++) {
+ ret = sss_nss_get_id_type(cmd_ctx, results[d], &(id_types[iter]));
+ if (ret != EOK) {
+ return ret;
+ }
+
+ tmp_str = sss_get_name_from_msg(results[d]->domain,
+ results[d]->msgs[c]);
+ if (tmp_str == NULL) {
+ return EINVAL;
+ }
+ to_sized_string(&(sz_names[iter]), tmp_str);
+
+ len += sz_names[iter].len;
+ iter++;
+ }
+ }
+
+ len += (2 + total) * sizeof(uint32_t);
+
+ ret = sss_packet_grow(packet, len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_UINT32(&body[rp], total, &rp); /* Num results. */
+ SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */
+ for (c = 0; c < total; c++) {
+ SAFEALIGN_SET_UINT32(&body[rp], id_types[c], &rp);
+ SAFEALIGN_SET_STRING(&body[rp], sz_names[c].str, sz_names[c].len,
+ &rp);
+ }
+
+ return EOK;
+}
diff --git a/src/responder/nss/nss_protocol_subid.c b/src/responder/nss/nss_protocol_subid.c
new file mode 100644
index 0000000..c06ab0b
--- /dev/null
+++ b/src/responder/nss/nss_protocol_subid.c
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2021 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"
+
+errno_t
+sss_nss_protocol_fill_subid_ranges(struct sss_nss_ctx *nss_ctx,
+ struct sss_nss_cmd_ctx *cmd_ctx,
+ struct sss_packet *packet,
+ struct cache_req_result *result)
+{
+ static const uint32_t one = 1;
+ errno_t ret;
+ uint8_t *body;
+ size_t body_len;
+ size_t rp = 0;
+ uint32_t gid, uid, gidCount, uidCount;
+
+ if (!result->count || !result->msgs) {
+ return ENOENT;
+ }
+
+ uid = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_NUMBER, 0);
+ uidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_COUND, 0);
+ gid = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_NUMBER, 0);
+ gidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_COUNT, 0);
+ if (!uid || !gid || !gidCount || !uidCount) {
+ return ENOENT;
+ }
+
+ /* only single uid & gid range is expected currently */
+ ret = sss_packet_grow(packet, (2 + 2*2) * sizeof(uint32_t));
+ if (ret != EOK) {
+ return ret;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+ SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp);
+ SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp);
+ SAFEALIGN_COPY_UINT32(&body[rp], &uid, &rp);
+ SAFEALIGN_COPY_UINT32(&body[rp], &uidCount, &rp);
+ SAFEALIGN_COPY_UINT32(&body[rp], &gid, &rp);
+ SAFEALIGN_COPY_UINT32(&body[rp], &gidCount, &rp);
+
+ return EOK;
+}
diff --git a/src/responder/nss/nss_protocol_svcent.c b/src/responder/nss/nss_protocol_svcent.c
new file mode 100644
index 0000000..68e20af
--- /dev/null
+++ b/src/responder/nss/nss_protocol_svcent.c
@@ -0,0 +1,270 @@
+/*
+ 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 "db/sysdb.h"
+#include "db/sysdb_services.h"
+#include "responder/nss/nss_protocol.h"
+
+static errno_t
+sss_nss_get_svcent(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *requested_protocol,
+ struct sized_string *_name,
+ struct sized_string *_protocol,
+ uint16_t *_port)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message_element *el;
+ const char *protocol;
+ const char *name;
+ uint16_t port;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ /* Get name. */
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ if (name == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve);
+ if (name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Get port. */
+ port = (uint16_t)ldb_msg_find_attr_as_uint(msg, SYSDB_SVC_PORT, 0);
+ if (port == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No port for service [%s]\n", name);
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Get protocol.
+ *
+ * Use the requested protocol if present, otherwise take the
+ * first protocol returned by the sysdb. */
+ if (requested_protocol != NULL) {
+ protocol = requested_protocol;
+ } else {
+ el = ldb_msg_find_element(msg, SYSDB_SVC_PROTO);
+ if (el == NULL || el->num_values == 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ protocol = (const char *)el->values[0].data;
+ if (protocol == NULL) {
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+ }
+
+ protocol = sss_get_cased_name(tmp_ctx, protocol, domain->case_preserve);
+ if (protocol == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Set output variables. */
+
+ talloc_steal(mem_ctx, name);
+ talloc_steal(mem_ctx, protocol);
+
+ to_sized_string(_name, name);
+ to_sized_string(_protocol, protocol);
+ *_port = port;
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static errno_t
+sss_nss_get_svc_aliases(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct ldb_message *msg,
+ const char *name,
+ struct sized_string **_aliases,
+ uint32_t *_num_aliases)
+{
+ struct ldb_message_element *el;
+ struct sized_string *aliases = NULL;
+ uint32_t num_aliases;
+ const char *alias;
+ errno_t ret;
+ int i;
+
+ el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS);
+ if (el == NULL) {
+ *_num_aliases = 0;
+ *_aliases = NULL;
+ ret = EOK;
+ goto done;
+ }
+
+ aliases = talloc_zero_array(mem_ctx, struct sized_string,
+ el->num_values + 1);
+ if (aliases == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ num_aliases = 0;
+ for (i = 0; i < el->num_values; i++) {
+ alias = (const char *)el->values[i].data;
+
+ if (sss_string_equal(domain->case_sensitive, alias, name)) {
+ continue;
+ }
+
+ /* Element value remains in the message, we don't need to strdup it. */
+ to_sized_string(&aliases[num_aliases], alias);
+ num_aliases++;
+ }
+
+ *_aliases = aliases;
+ *_num_aliases = num_aliases;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(aliases);
+ }
+
+ return ret;
+}
+
+errno_t
+sss_nss_protocol_fill_svcent(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 protocol;
+ struct sized_string *aliases;
+ uint32_t num_aliases;
+ uint16_t port;
+ uint32_t num_results;
+ size_t rp;
+ size_t body_len;
+ uint8_t *body;
+ int i;
+ int j;
+ 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];
+
+ ret = sss_nss_get_svcent(tmp_ctx, result->domain, msg,
+ cmd_ctx->svc_protocol, &name, &protocol, &port);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get service information, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ ret = sss_nss_get_svc_aliases(tmp_ctx, result->domain, msg, name.str,
+ &aliases, &num_aliases);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Unable to get service aliases, skipping... [%d]: %s\n",
+ ret, sss_strerror(ret));
+ continue;
+ }
+
+ /* Adjust packet size. */
+
+ ret = sss_packet_grow(packet, 2 * sizeof(uint16_t) + sizeof(uint32_t)
+ + name.len + protocol.len);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_get_body(packet, &body, &body_len);
+
+ /* Fill packet. */
+
+ SAFEALIGN_SET_UINT32(&body[rp], (uint32_t)htons(port), &rp);
+ SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp);
+ SAFEALIGN_SET_STRING(&body[rp], protocol.str, protocol.len, &rp);
+
+ /* Store aliases. */
+ for (j = 0; j < num_aliases; j++) {
+ ret = sss_packet_grow(packet, aliases[j].len);
+ if (ret != EOK) {
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &body_len);
+
+ SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len,
+ &rp);
+ }
+
+ num_results++;
+ }
+
+ 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;
+}
diff --git a/src/responder/nss/nss_utils.c b/src/responder/nss/nss_utils.c
new file mode 100644
index 0000000..cec55e6
--- /dev/null
+++ b/src/responder/nss/nss_utils.c
@@ -0,0 +1,38 @@
+/*
+ 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 <talloc.h>
+#include <ldb.h>
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "responder/common/responder.h"
+#include "responder/nss/nss_private.h"
+
+const char *
+sss_nss_get_pwfield(struct sss_nss_ctx *nctx,
+ struct sss_domain_info *dom)
+{
+ if (dom->pwfield != NULL) {
+ return dom->pwfield;
+ }
+
+ return nctx->pwfield;
+}
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
new file mode 100644
index 0000000..4673a64
--- /dev/null
+++ b/src/responder/nss/nsssrv.c
@@ -0,0 +1,740 @@
+/*
+ SSSD
+
+ NSS Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ 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 <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <popt.h>
+#include <dbus/dbus.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "util/mmap_cache.h"
+#include "responder/nss/nss_private.h"
+#include "responder/nss/nss_iface.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
+#include "responder/common/negcache.h"
+#include "db/sysdb.h"
+#include "confdb/confdb.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/responder.h"
+#include "providers/data_provider.h"
+#include "util/util_sss_idmap.h"
+#include "sss_iface/sss_iface_async.h"
+
+#define DEFAULT_PWFIELD "*"
+#define DEFAULT_NSS_FD_LIMIT 8192
+
+static errno_t
+sss_nss_clear_memcache(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx)
+{
+ int memcache_timeout;
+ errno_t ret;
+
+ if (access(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG, F_OK) < 0) {
+ ret = errno;
+ if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "CLEAR_MC_FLAG not found. Nothing to do.\n");
+ return EOK; /* Most probably log rotation SIGHUP to monitor */
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to check existence of "CLEAR_MC_FLAG": %s.\n",
+ strerror(ret));
+ return ret;
+ }
+ }
+
+ /*
+ * CLEAR_MC_FLAG flag file found.
+ * This file existance indicates that SIGHUP was called by sss_cache
+ * as trigger for the memory cache cleanup.
+ * sss_cache is waiting for CLEAR_MC_FLAG file deletion
+ * as confirmation that memory cache cleaning has finished.
+ */
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_MEMCACHE_TIMEOUT,
+ 300, &memcache_timeout);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Unable to get memory cache entry timeout [%s].\n",
+ CONFDB_MEMCACHE_TIMEOUT);
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Clearing memory caches.\n");
+ ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid,
+ -1, /* keep current size */
+ (time_t) memcache_timeout,
+ &nctx->pwd_mc_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "passwd mmap cache invalidation failed\n");
+ goto done;
+ }
+
+ ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid,
+ -1, /* keep current size */
+ (time_t) memcache_timeout,
+ &nctx->grp_mc_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "group mmap cache invalidation failed\n");
+ goto done;
+ }
+
+ ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid,
+ -1, /* keep current size */
+ (time_t)memcache_timeout,
+ &nctx->initgr_mc_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "initgroups mmap cache invalidation failed\n");
+ goto done;
+ }
+
+done:
+ if (unlink(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG) != 0) {
+ if (errno != ENOENT)
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unlink file: %s.\n",
+ strerror(errno));
+ }
+ return ret;
+}
+
+static errno_t
+sss_nss_clear_negcache(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nctx)
+{
+ errno_t ret;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Clearing negative cache non-permament entries\n");
+
+ ret = sss_ncache_reset_users(nctx->rctx->ncache);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Negative cache clearing users failed\n");
+ goto done;
+ }
+
+ ret = sss_ncache_reset_groups(nctx->rctx->ncache);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Negative cache clearing groups failed\n");
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
+static errno_t
+sss_nss_clear_netgroup_hash_table(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sss_nss_ctx *nss_ctx)
+{
+ DEBUG(SSSDBG_TRACE_FUNC, "Invalidating netgroup hash table\n");
+
+ sss_ptr_hash_delete_all(nss_ctx->netgrent, false);
+
+ return EOK;
+}
+
+static int sss_nss_get_config(struct sss_nss_ctx *nctx,
+ struct confdb_ctx *cdb)
+{
+ int ret;
+ char *tmp_str;
+ static const char *orig_attrs[] = { SYSDB_SID_STR,
+ ORIGINALAD_PREFIX SYSDB_NAME,
+ ORIGINALAD_PREFIX SYSDB_UIDNUM,
+ ORIGINALAD_PREFIX SYSDB_GIDNUM,
+ ORIGINALAD_PREFIX SYSDB_HOMEDIR,
+ ORIGINALAD_PREFIX SYSDB_GECOS,
+ ORIGINALAD_PREFIX SYSDB_SHELL,
+ SYSDB_UPN,
+ SYSDB_DEFAULT_OVERRIDE_NAME,
+ SYSDB_AD_ACCOUNT_EXPIRES,
+ SYSDB_AD_USER_ACCOUNT_CONTROL,
+ SYSDB_SSH_PUBKEY,
+ SYSDB_USER_CERT,
+ SYSDB_USER_EMAIL,
+ SYSDB_ORIG_DN,
+ SYSDB_ORIG_MEMBEROF,
+ NULL };
+
+ ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_ENUM_CACHE_TIMEOUT, 120,
+ &nctx->enum_cache_timeout);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_bool(cdb, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_FILTER_USERS_IN_GROUPS, true,
+ &nctx->filter_users_in_groups);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE, 50,
+ &nctx->cache_refresh_percent);
+ if (ret != EOK) goto done;
+ if (nctx->cache_refresh_percent < 0 ||
+ nctx->cache_refresh_percent > 99) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Configuration error: entry_cache_nowait_percentage is "
+ "invalid. Disabling feature.\n");
+ nctx->cache_refresh_percent = 0;
+ }
+
+ ret = sss_ncache_prepopulate(nctx->rctx->ncache, cdb, nctx->rctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_PWFIELD, DEFAULT_PWFIELD,
+ &nctx->pwfield);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_OVERRIDE_HOMEDIR, NULL,
+ &nctx->override_homedir);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_FALLBACK_HOMEDIR, NULL,
+ &nctx->fallback_homedir);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_HOMEDIR_SUBSTRING,
+ CONFDB_DEFAULT_HOMEDIR_SUBSTRING,
+ &nctx->homedir_substr);
+ if (ret != EOK) goto done;
+
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_IFP_USER_ATTR_LIST, NULL, &tmp_str);
+ if (ret != EOK) goto done;
+
+ if (tmp_str == NULL) {
+ ret = confdb_get_string(cdb, nctx, CONFDB_IFP_CONF_ENTRY,
+ CONFDB_IFP_USER_ATTR_LIST, NULL, &tmp_str);
+ if (ret != EOK) goto done;
+ }
+
+ if (tmp_str != NULL) {
+ nctx->extra_attributes = parse_attr_list_ex(nctx, tmp_str, NULL);
+ if (nctx->extra_attributes == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ ret = add_strings_lists_ex(nctx, nctx->extra_attributes, orig_attrs, false,
+ true, &nctx->full_attribute_list);
+ if (ret != EOK) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+done:
+ return ret;
+}
+
+static int setup_memcaches(struct sss_nss_ctx *nctx)
+{
+ /* Default memcache sizes */
+ static const size_t SSS_MC_CACHE_SLOTS_PER_MB = 1024*1024/MC_SLOT_SIZE;
+ static const size_t SSS_MC_CACHE_PASSWD_SIZE = 8;
+ static const size_t SSS_MC_CACHE_GROUP_SIZE = 6;
+ static const size_t SSS_MC_CACHE_INITGROUP_SIZE = 10;
+ static const size_t SSS_MC_CACHE_SID_SIZE = 6;
+
+ int ret;
+ int memcache_timeout;
+ int mc_size_passwd;
+ int mc_size_group;
+ int mc_size_initgroups;
+ int mc_size_sid;
+
+ /* Remove the CLEAR_MC_FLAG file if exists. */
+ ret = unlink(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG);
+ if (ret != 0 && errno != ENOENT) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to unlink file [%s]. This can cause memory cache to "
+ "be purged when next log rotation is requested. %d: %s\n",
+ SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG, ret, strerror(ret));
+ }
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_MEMCACHE_TIMEOUT,
+ 300, &memcache_timeout);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get 'memcache_timeout' option from confdb.\n");
+ return ret;
+ }
+
+ /* Get all memcache sizes from confdb (pwd, grp, initgr, sid) */
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_MEMCACHE_SIZE_PASSWD,
+ SSS_MC_CACHE_PASSWD_SIZE,
+ &mc_size_passwd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_PASSWD
+ "' option from confdb.\n");
+ return ret;
+ }
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_MEMCACHE_SIZE_GROUP,
+ SSS_MC_CACHE_GROUP_SIZE,
+ &mc_size_group);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_GROUP
+ "' option from confdb.\n");
+ return ret;
+ }
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_MEMCACHE_SIZE_INITGROUPS,
+ SSS_MC_CACHE_INITGROUP_SIZE,
+ &mc_size_initgroups);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_INITGROUPS
+ "' option from confdb.\n");
+ return ret;
+ }
+
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_MEMCACHE_SIZE_SID,
+ SSS_MC_CACHE_SID_SIZE,
+ &mc_size_sid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_SID
+ "' option from confdb.\n");
+ return ret;
+ }
+
+ /* Initialize the fast in-memory caches if they were not disabled */
+
+ ret = sss_mmap_cache_init(nctx, "passwd",
+ nctx->mc_uid, nctx->mc_gid,
+ SSS_MC_PASSWD,
+ mc_size_passwd * SSS_MC_CACHE_SLOTS_PER_MB,
+ (time_t)memcache_timeout,
+ &nctx->pwd_mc_ctx);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize passwd mmap cache: '%s'\n",
+ sss_strerror(ret));
+ }
+
+ ret = sss_mmap_cache_init(nctx, "group",
+ nctx->mc_uid, nctx->mc_gid,
+ SSS_MC_GROUP,
+ mc_size_group * SSS_MC_CACHE_SLOTS_PER_MB,
+ (time_t)memcache_timeout,
+ &nctx->grp_mc_ctx);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize group mmap cache: '%s'\n",
+ sss_strerror(ret));
+ }
+
+ ret = sss_mmap_cache_init(nctx, "initgroups",
+ nctx->mc_uid, nctx->mc_gid,
+ SSS_MC_INITGROUPS,
+ mc_size_initgroups * SSS_MC_CACHE_SLOTS_PER_MB,
+ (time_t)memcache_timeout,
+ &nctx->initgr_mc_ctx);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize initgroups mmap cache: '%s'\n",
+ sss_strerror(ret));
+ }
+
+ ret = sss_mmap_cache_init(nctx, "sid",
+ nctx->mc_uid, nctx->mc_gid,
+ SSS_MC_SID,
+ mc_size_sid * SSS_MC_CACHE_SLOTS_PER_MB,
+ (time_t)memcache_timeout,
+ &nctx->sid_mc_ctx);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize sid mmap cache: '%s'\n",
+ sss_strerror(ret));
+ }
+
+ return EOK;
+}
+
+static errno_t
+sss_nss_register_service_iface(struct sss_nss_ctx *nss_ctx,
+ struct resp_ctx *rctx)
+{
+ errno_t ret;
+
+ SBUS_INTERFACE(iface_svc,
+ sssd_service,
+ SBUS_METHODS(
+ SBUS_SYNC(METHOD, sssd_service, rotateLogs, responder_logrotate, rctx),
+ SBUS_SYNC(METHOD, sssd_service, clearEnumCache, sss_nss_clear_netgroup_hash_table, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_service, clearMemcache, sss_nss_clear_memcache, nss_ctx),
+ SBUS_SYNC(METHOD, sssd_service, clearNegcache, sss_nss_clear_negcache, nss_ctx)
+ ),
+ SBUS_SIGNALS(SBUS_NO_SIGNALS),
+ SBUS_PROPERTIES(
+ SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL),
+ SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL)
+ )
+ );
+
+ ret = sbus_connection_add_path(rctx->mon_conn, SSS_BUS_PATH, &iface_svc);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface"
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ }
+
+ return ret;
+}
+
+static int sssd_supplementary_group(struct sss_nss_ctx *nss_ctx)
+{
+ errno_t ret;
+ int size;
+ gid_t *supp_gids = NULL;
+
+ /*
+ * We explicitly read the IDs of the SSSD user even though the server
+ * receives --uid and --gid by parameters to account for the case where
+ * the SSSD is compiled --with-sssd-user=sssd but the default of the
+ * user option is root (this is what RHEL does)
+ */
+ ret = sss_user_by_name_or_uid(SSSD_USER,
+ &nss_ctx->mc_uid,
+ &nss_ctx->mc_gid);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get info on "SSSD_USER);
+ return ret;
+ }
+
+ if (getgid() == nss_ctx->mc_gid) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Already running as the sssd group\n");
+ return EOK;
+ }
+
+ size = getgroups(0, NULL);
+ if (size == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ if (size > 0) {
+ supp_gids = talloc_zero_array(NULL, gid_t, size);
+ if (supp_gids == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Allocation failed!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ size = getgroups(size, supp_gids);
+ if (size == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ for (int i = 0; i < size; i++) {
+ if (supp_gids[i] == nss_ctx->mc_gid) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Already assigned to the SSSD supplementary group\n");
+ ret = EOK;
+ goto done;
+ }
+ }
+ }
+
+ ret = setgroups(1, &nss_ctx->mc_gid);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Cannot setgroups [%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(supp_gids);
+ return ret;
+}
+
+int sss_nss_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb)
+{
+ struct resp_ctx *rctx;
+ struct sss_cmd_table *nss_cmds;
+ struct be_conn *iter;
+ struct sss_nss_ctx *nctx;
+ int ret;
+ enum idmap_error_code err;
+ int fd_limit;
+
+ nss_cmds = get_sss_nss_cmds();
+
+ ret = sss_process_init(mem_ctx, ev, cdb,
+ nss_cmds,
+ SSS_NSS_SOCKET_NAME, -1, NULL, -1,
+ CONFDB_NSS_CONF_ENTRY,
+ SSS_BUS_NSS, NSS_SBUS_SERVICE_NAME,
+ sss_nss_connection_setup,
+ &rctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n");
+ return ret;
+ }
+
+ nctx = talloc_zero(rctx, struct sss_nss_ctx);
+ if (!nctx) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing nss_ctx\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ nctx->rctx = rctx;
+ nctx->rctx->pvt_ctx = nctx;
+
+ ret = sss_nss_get_config(nctx, cdb);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting nss config\n");
+ goto fail;
+ }
+
+ for (iter = nctx->rctx->be_conns; iter; iter = iter->next) {
+ ret = sss_nss_register_backend_iface(iter->conn, nctx);
+ if (ret != EOK) {
+ goto fail;
+ }
+ }
+
+ err = sss_idmap_init(sss_idmap_talloc, nctx, sss_idmap_talloc_free,
+ &nctx->idmap_ctx);
+ if (err != IDMAP_SUCCESS) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "sss_idmap_init failed.\n");
+ ret = EFAULT;
+ goto fail;
+ }
+
+ nctx->pwent = talloc_zero(nctx, struct sss_nss_enum_ctx);
+ if (nctx->pwent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize pwent context!\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ nctx->grent = talloc_zero(nctx, struct sss_nss_enum_ctx);
+ if (nctx->grent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize grent context!\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ nctx->svcent = talloc_zero(nctx, struct sss_nss_enum_ctx);
+ if (nctx->svcent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize svcent context!\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ nctx->netgrent = sss_ptr_hash_create(nctx, NULL, NULL);
+ if (nctx->netgrent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize netgroups table!\n");
+ ret = EFAULT;
+ goto fail;
+ }
+
+ nctx->hostent = talloc_zero(nctx, struct sss_nss_enum_ctx);
+ if (nctx->hostent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize hostent context!\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ nctx->netent = talloc_zero(nctx, struct sss_nss_enum_ctx);
+ if (nctx->netent == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize netent context!\n");
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * Adding the NSS process to the SSSD supplementary group avoids
+ * dac_override AVC messages from SELinux in case sssd_nss runs
+ * as root and tries to write to memcache owned by sssd:sssd
+ */
+ ret = sssd_supplementary_group(nctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Cannot add process to the sssd supplementary group [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto fail;
+ }
+
+ ret = setup_memcaches(nctx);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ /* Set up file descriptor limits */
+ ret = confdb_get_int(nctx->rctx->cdb,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_SERVICE_FD_LIMIT,
+ DEFAULT_NSS_FD_LIMIT,
+ &fd_limit);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to set up file descriptor limit\n");
+ goto fail;
+ }
+ responder_set_fd_limit(fd_limit);
+
+ ret = schedule_get_domains_task(rctx, rctx->ev, rctx, nctx->rctx->ncache,
+ NULL, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n");
+ goto fail;
+ }
+
+ /* The responder is initialized. Now tell it to the monitor. */
+ ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_NSS,
+ NSS_SBUS_SERVICE_NAME,
+ NSS_SBUS_SERVICE_VERSION, MT_SVC_SERVICE,
+ &rctx->last_request_time, &rctx->mon_conn);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n");
+ goto fail;
+ }
+
+ ret = sss_nss_register_service_iface(nctx, rctx);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "NSS Initialization complete\n");
+
+ return EOK;
+
+fail:
+ talloc_free(rctx);
+ return ret;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ char *opt_logger = NULL;
+ struct main_context *main_ctx;
+ int ret;
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ SSSD_LOGGER_OPTS
+ SSSD_SERVER_OPTS(uid, gid)
+ SSSD_RESPONDER_OPTS
+ POPT_TABLEEND
+ };
+
+ /* Set debug level to invalid value so we can decide if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ umask(DFL_RSP_UMASK);
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+
+ poptFreeContext(pc);
+
+ /* set up things like debug, signals, daemonization, etc. */
+ debug_log_file = "sssd_nss";
+ DEBUG_INIT(debug_level, opt_logger);
+
+ ret = server_setup("nss", true, 0, uid, gid, CONFDB_NSS_CONF_ENTRY,
+ &main_ctx, false);
+ if (ret != EOK) return 2;
+
+ ret = die_if_parent_died();
+ if (ret != EOK) {
+ /* This is not fatal, don't return */
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not set up to exit when parent process does\n");
+ }
+
+ ret = sss_nss_process_init(main_ctx,
+ main_ctx->event_ctx,
+ main_ctx->confdb_ctx);
+ if (ret != EOK) return 3;
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ return 0;
+}
+
diff --git a/src/responder/nss/nsssrv_mmap_cache.c b/src/responder/nss/nsssrv_mmap_cache.c
new file mode 100644
index 0000000..cacdc7c
--- /dev/null
+++ b/src/responder/nss/nsssrv_mmap_cache.c
@@ -0,0 +1,1626 @@
+/*
+ SSSD
+
+ NSS Responder - Mmap Cache
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2011
+
+ 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 "util/util.h"
+#include "util/crypto/sss_crypto.h"
+#include "confdb/confdb.h"
+#include <sys/mman.h>
+#include <fcntl.h>
+#include "util/mmap_cache.h"
+#include "sss_client/idmap/sss_nss_idmap.h"
+#include "responder/nss/nss_private.h"
+#include "responder/nss/nsssrv_mmap_cache.h"
+
+#define MC_NEXT_BARRIER(val) ((((val) + 1) & 0x00ffffff) | 0xf0000000)
+
+#define MC_RAISE_BARRIER(m) do { \
+ m->b2 = MC_NEXT_BARRIER(m->b1); \
+ __sync_synchronize(); \
+} while (0)
+
+#define MC_LOWER_BARRIER(m) do { \
+ __sync_synchronize(); \
+ m->b1 = m->b2; \
+} while (0)
+
+#define MC_RAISE_INVALID_BARRIER(m) do { \
+ m->b2 = MC_INVALID_VAL; \
+ __sync_synchronize(); \
+} while (0)
+
+struct sss_mc_ctx {
+ char *name; /* mmap cache name */
+ enum sss_mc_type type; /* mmap cache type */
+ char *file; /* mmap cache file name */
+ int fd; /* file descriptor */
+
+ uid_t uid; /* User ID of owner */
+ gid_t gid; /* Group ID of owner */
+
+ uint32_t seed; /* pseudo-random seed to avoid collision attacks */
+ time_t valid_time_slot; /* maximum time the entry is valid in seconds */
+
+ void *mmap_base; /* base address of mmap */
+ size_t mmap_size; /* total size of mmap */
+
+ uint32_t *hash_table; /* hash table address (in mmap) */
+ uint32_t ht_size; /* size of hash table */
+
+ uint8_t *free_table; /* free list bitmaps */
+ uint32_t ft_size; /* size of free table */
+ uint32_t next_slot; /* the next slot after last allocation done via erasure */
+
+ uint8_t *data_table; /* data table address (in mmap) */
+ uint32_t dt_size; /* size of data table */
+};
+
+#define MC_FIND_BIT(base, num) \
+ uint32_t n = (num); \
+ uint8_t *b = (base) + n / 8; \
+ uint8_t c = 0x80 >> (n % 8);
+
+#define MC_SET_BIT(base, num) do { \
+ MC_FIND_BIT(base, num) \
+ *b |= c; \
+} while (0)
+
+#define MC_CLEAR_BIT(base, num) do { \
+ MC_FIND_BIT(base, num) \
+ *b &= ~c; \
+} while (0)
+
+#define MC_PROBE_BIT(base, num, used) do { \
+ MC_FIND_BIT(base, num) \
+ if (*b & c) used = true; \
+ else used = false; \
+} while (0)
+
+static inline
+uint32_t sss_mc_next_slot_with_hash(struct sss_mc_rec *rec,
+ uint32_t hash)
+{
+ if (rec->hash1 == hash) {
+ return rec->next1;
+ } else if (rec->hash2 == hash) {
+ return rec->next2;
+ } else {
+ /* it should never happen. */
+ return MC_INVALID_VAL;
+ }
+}
+
+static inline
+void sss_mc_chain_slot_to_record_with_hash(struct sss_mc_rec *rec,
+ uint32_t hash,
+ uint32_t slot)
+{
+ /* changing a single uint32_t is atomic, so there is no
+ * need to use barriers in this case */
+ if (rec->hash1 == hash) {
+ rec->next1 = slot;
+ } else if (rec->hash2 == hash) {
+ rec->next2 = slot;
+ }
+}
+
+/* This function will store corrupted memcache to disk for later
+ * analysis. */
+static void sss_mc_save_corrupted(struct sss_mc_ctx *mc_ctx)
+{
+ int err;
+ int fd = -1;
+ ssize_t written = -1;
+ char *file = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ if (mc_ctx == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Cannot store uninitialized cache. Nothing to do.\n");
+ return;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
+ return;
+ }
+
+ file = talloc_asprintf(tmp_ctx, "%s_%s",
+ mc_ctx->file, "corrupted");
+ if (file == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
+ goto done;
+ }
+
+ /* We will always store only the last problematic cache state */
+ fd = creat(file, 0600);
+ if (fd == -1) {
+ err = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to open file '%s' [%d]: %s\n",
+ file, err, strerror(err));
+ goto done;
+ }
+
+ written = sss_atomic_write_s(fd, mc_ctx->mmap_base, mc_ctx->mmap_size);
+ if (written != mc_ctx->mmap_size) {
+ if (written == -1) {
+ err = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "write() failed [%d]: %s\n", err, strerror(err));
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "write() returned %zd (expected (%zd))\n",
+ written, mc_ctx->mmap_size);
+ }
+ goto done;
+ }
+
+ sss_log(SSS_LOG_NOTICE,
+ "Stored copy of corrupted mmap cache in file '%s\n'", file);
+done:
+ if (fd != -1) {
+ close(fd);
+ if (written == -1) {
+ err = unlink(file);
+ if (err != 0) {
+ err = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to remove file '%s': %s.\n", file,
+ strerror(err));
+ }
+ }
+ }
+ talloc_free(tmp_ctx);
+}
+
+static uint32_t sss_mc_hash(struct sss_mc_ctx *mcc,
+ const char *key, size_t len)
+{
+ return murmurhash3(key, len, mcc->seed) % MC_HT_ELEMS(mcc->ht_size);
+}
+
+static void sss_mc_add_rec_to_chain(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec,
+ uint32_t hash)
+{
+ struct sss_mc_rec *cur;
+ uint32_t slot;
+
+ if (hash > MC_HT_ELEMS(mcc->ht_size)) {
+ /* Invalid hash. This should never happen, but better
+ * return than trying to access out of bounds memory */
+ return;
+ }
+
+ slot = mcc->hash_table[hash];
+ if (slot == MC_INVALID_VAL) {
+ /* no previous record/collision, just add to hash table */
+ mcc->hash_table[hash] = MC_PTR_TO_SLOT(mcc->data_table, rec);
+ return;
+ }
+
+ do {
+ cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ if (cur == rec) {
+ /* rec already stored in hash chain */
+ return;
+ }
+ slot = sss_mc_next_slot_with_hash(cur, hash);
+ } while (slot != MC_INVALID_VAL);
+ /* end of chain, append our record here */
+
+ slot = MC_PTR_TO_SLOT(mcc->data_table, rec);
+ sss_mc_chain_slot_to_record_with_hash(cur, hash, slot);
+}
+
+static void sss_mc_rm_rec_from_chain(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec,
+ uint32_t hash)
+{
+ struct sss_mc_rec *prev = NULL;
+ struct sss_mc_rec *cur = NULL;
+ uint32_t slot;
+
+ if (hash > MC_HT_ELEMS(mcc->ht_size)) {
+ /* It can happen if rec->hash1 and rec->hash2 was the same.
+ * or it is invalid hash. It is better to return
+ * than trying to access out of bounds memory
+ */
+ return;
+ }
+
+ slot = mcc->hash_table[hash];
+ if (slot == MC_INVALID_VAL) {
+ /* record has already been removed. It may happen if rec->hash1 and
+ * rec->has2 are the same. (It is not very likely).
+ */
+ return;
+ }
+ cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ if (cur == rec) {
+ mcc->hash_table[hash] = sss_mc_next_slot_with_hash(rec, hash);
+ } else {
+ slot = sss_mc_next_slot_with_hash(cur, hash);
+ while (slot != MC_INVALID_VAL) {
+ prev = cur;
+ cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ if (cur == rec) {
+ slot = sss_mc_next_slot_with_hash(cur, hash);
+
+ sss_mc_chain_slot_to_record_with_hash(prev, hash, slot);
+ slot = MC_INVALID_VAL;
+ } else {
+ slot = sss_mc_next_slot_with_hash(cur, hash);
+ }
+ }
+ }
+}
+
+static void sss_mc_free_slots(struct sss_mc_ctx *mcc, struct sss_mc_rec *rec)
+{
+ uint32_t slot;
+ uint32_t num;
+ uint32_t i;
+
+ slot = MC_PTR_TO_SLOT(mcc->data_table, rec);
+ num = MC_SIZE_TO_SLOTS(rec->len);
+ for (i = 0; i < num; i++) {
+ MC_CLEAR_BIT(mcc->free_table, slot + i);
+ }
+}
+
+static void sss_mc_invalidate_rec(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec)
+{
+ if (rec->b1 == MC_INVALID_VAL) {
+ /* record already invalid */
+ return;
+ }
+
+ /* Remove from hash chains */
+ /* hash chain 1 */
+ sss_mc_rm_rec_from_chain(mcc, rec, rec->hash1);
+ /* hash chain 2 */
+ sss_mc_rm_rec_from_chain(mcc, rec, rec->hash2);
+
+ /* Clear from free_table */
+ sss_mc_free_slots(mcc, rec);
+
+ /* Invalidate record fields */
+ MC_RAISE_INVALID_BARRIER(rec);
+ memset(rec->data, MC_INVALID_VAL8, ((MC_SLOT_SIZE * MC_SIZE_TO_SLOTS(rec->len))
+ - sizeof(struct sss_mc_rec)));
+ rec->len = MC_INVALID_VAL32;
+ rec->expire = MC_INVALID_VAL64;
+ rec->next1 = MC_INVALID_VAL32;
+ rec->next2 = MC_INVALID_VAL32;
+ rec->hash1 = MC_INVALID_VAL32;
+ rec->hash2 = MC_INVALID_VAL32;
+ MC_LOWER_BARRIER(rec);
+}
+
+static bool sss_mc_is_valid_rec(struct sss_mc_ctx *mcc, struct sss_mc_rec *rec)
+{
+ struct sss_mc_rec *self;
+ uint32_t slot;
+
+ if (((uint8_t *)rec < mcc->data_table) ||
+ ((uint8_t *)rec > (mcc->data_table + mcc->dt_size - MC_SLOT_SIZE))) {
+ return false;
+ }
+
+ if ((rec->b1 == MC_INVALID_VAL) ||
+ (rec->b1 != rec->b2)) {
+ return false;
+ }
+
+ if (!MC_CHECK_RECORD_LENGTH(mcc, rec)) {
+ return false;
+ }
+
+ if (rec->expire == MC_INVALID_VAL64) {
+ return false;
+ }
+
+ /* next record can be invalid if there are no next records */
+
+ if (rec->hash1 == MC_INVALID_VAL32) {
+ return false;
+ } else {
+ self = NULL;
+ slot = mcc->hash_table[rec->hash1];
+ while (slot != MC_INVALID_VAL32 && self != rec) {
+ self = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ slot = sss_mc_next_slot_with_hash(self, rec->hash1);
+ }
+ if (self != rec) {
+ return false;
+ }
+ }
+ if (rec->hash2 != MC_INVALID_VAL32) {
+ self = NULL;
+ slot = mcc->hash_table[rec->hash2];
+ while (slot != MC_INVALID_VAL32 && self != rec) {
+ self = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ slot = sss_mc_next_slot_with_hash(self, rec->hash2);
+ }
+ if (self != rec) {
+ return false;
+ }
+ }
+
+ /* all tests passed */
+ return true;
+}
+
+static const char *mc_type_to_str(enum sss_mc_type type)
+{
+ switch (type) {
+ case SSS_MC_PASSWD:
+ return "PASSWD";
+ case SSS_MC_GROUP:
+ return "GROUP";
+ case SSS_MC_INITGROUPS:
+ return "INITGROUPS";
+ case SSS_MC_SID:
+ return "SID";
+ default:
+ return "-UNKNOWN-";
+ }
+}
+
+/* FIXME: This is a very simplistic, inefficient, memory allocator,
+ * it will just free the oldest entries regardless of expiration if it
+ * cycled the whole free bits map and found no empty slot */
+static errno_t sss_mc_find_free_slots(struct sss_mc_ctx *mcc,
+ int num_slots, uint32_t *free_slot)
+{
+ struct sss_mc_rec *rec;
+ uint32_t tot_slots;
+ uint32_t cur;
+ uint32_t i;
+ uint32_t t;
+ bool used;
+
+ tot_slots = mcc->ft_size * 8;
+
+ /* Try to find a free slot w/o removing anything first */
+ /* FIXME: Is it really worth it? Maybe it is easier to
+ * just recycle the next set of slots? */
+ if ((mcc->next_slot + num_slots) > tot_slots) {
+ cur = 0;
+ } else {
+ cur = mcc->next_slot;
+ }
+
+ /* search for enough (num_slots) consecutive zero bits, indicating
+ * consecutive empty slots */
+ for (i = 0; i < mcc->ft_size; i++) {
+ t = cur / 8;
+ /* if all full in this byte skip directly to the next */
+ if (mcc->free_table[t] == 0xff) {
+ cur = ((cur + 8) & ~7);
+ if (cur >= tot_slots) {
+ cur = 0;
+ }
+ continue;
+ }
+
+ /* at least one bit in this byte is marked as empty */
+ for (t = ((cur + 8) & ~7) ; cur < t; cur++) {
+ MC_PROBE_BIT(mcc->free_table, cur, used);
+ if (!used) break;
+ }
+ /* check if we have enough slots before hitting the table end */
+ if ((cur + num_slots) > tot_slots) {
+ cur = 0;
+ continue;
+ }
+
+ /* check if we have at least num_slots empty starting from the first
+ * we found in the previous steps */
+ for (t = cur + num_slots; cur < t; cur++) {
+ MC_PROBE_BIT(mcc->free_table, cur, used);
+ if (used) break;
+ }
+ if (cur == t) {
+ /* ok found num_slots consecutive free bits */
+ *free_slot = cur - num_slots;
+ /* `mcc->next_slot` is not updated here intentionally.
+ * For details see discussion in https://github.com/SSSD/sssd/pull/999
+ */
+ return EOK;
+ }
+ }
+
+ /* no free slots found, free occupied slots after next_slot */
+ if ((mcc->next_slot + num_slots) > tot_slots) {
+ cur = 0;
+ } else {
+ cur = mcc->next_slot;
+ }
+ if (cur == 0) {
+ /* inform only once per full loop to avoid excessive spam */
+ DEBUG(SSSDBG_IMPORTANT_INFO, "mmap cache of type '%s' is full\n",
+ mc_type_to_str(mcc->type));
+ sss_log(SSS_LOG_NOTICE, "mmap cache of type '%s' is full, if you see "
+ "this message often then please consider increase of cache size",
+ mc_type_to_str(mcc->type));
+ }
+ for (i = 0; i < num_slots; i++) {
+ MC_PROBE_BIT(mcc->free_table, cur + i, used);
+ if (used) {
+ /* the first used slot should be a record header, however we
+ * carefully check it is a valid header and hardfail if not */
+ rec = MC_SLOT_TO_PTR(mcc->data_table, cur + i, struct sss_mc_rec);
+ if (!sss_mc_is_valid_rec(mcc, rec)) {
+ /* this is a fatal error, the caller should probably just
+ * invalidate the whole cache */
+ return EFAULT;
+ }
+ /* next loop skip the whole record */
+ i += MC_SIZE_TO_SLOTS(rec->len) - 1;
+
+ /* finally invalidate record completely */
+ sss_mc_invalidate_rec(mcc, rec);
+ }
+ }
+
+ mcc->next_slot = cur + num_slots;
+ *free_slot = cur;
+ return EOK;
+}
+
+static errno_t sss_mc_get_strs_offset(struct sss_mc_ctx *mcc,
+ size_t *_offset)
+{
+ switch (mcc->type) {
+ case SSS_MC_PASSWD:
+ *_offset = offsetof(struct sss_mc_pwd_data, strs);
+ return EOK;
+ case SSS_MC_GROUP:
+ *_offset = offsetof(struct sss_mc_grp_data, strs);
+ return EOK;
+ case SSS_MC_INITGROUPS:
+ *_offset = offsetof(struct sss_mc_initgr_data, gids);
+ return EOK;
+ case SSS_MC_SID:
+ *_offset = offsetof(struct sss_mc_sid_data, sid);
+ return EOK;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unknown memory cache type.\n");
+ return EINVAL;
+ }
+}
+
+static errno_t sss_mc_get_strs_len(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec,
+ size_t *_len)
+{
+ switch (mcc->type) {
+ case SSS_MC_PASSWD:
+ *_len = ((struct sss_mc_pwd_data *)&rec->data)->strs_len;
+ return EOK;
+ case SSS_MC_GROUP:
+ *_len = ((struct sss_mc_grp_data *)&rec->data)->strs_len;
+ return EOK;
+ case SSS_MC_INITGROUPS:
+ *_len = ((struct sss_mc_initgr_data *)&rec->data)->data_len;
+ return EOK;
+ case SSS_MC_SID:
+ *_len = ((struct sss_mc_sid_data *)&rec->data)->sid_len;
+ return EOK;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unknown memory cache type.\n");
+ return EINVAL;
+ }
+}
+
+static struct sss_mc_rec *sss_mc_find_record(struct sss_mc_ctx *mcc,
+ const struct sized_string *key)
+{
+ struct sss_mc_rec *rec = NULL;
+ uint32_t hash;
+ uint32_t slot;
+ rel_ptr_t name_ptr;
+ char *t_key;
+ size_t strs_offset;
+ size_t strs_len;
+ uint8_t *max_addr;
+ errno_t ret;
+
+ hash = sss_mc_hash(mcc, key->str, key->len);
+
+ slot = mcc->hash_table[hash];
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ return NULL;
+ }
+
+ /* Get max address of data table. */
+ max_addr = mcc->data_table + mcc->dt_size;
+
+ ret = sss_mc_get_strs_offset(mcc, &strs_offset);
+ if (ret != EOK) {
+ return NULL;
+ }
+
+ while (slot != MC_INVALID_VAL) {
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Corrupted memcache. Slot number too big.\n");
+ sss_mc_save_corrupted(mcc);
+ sss_mmap_cache_reset(mcc);
+ return NULL;
+ }
+
+ rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ ret = sss_mc_get_strs_len(mcc, rec, &strs_len);
+ if (ret != EOK) {
+ return NULL;
+ }
+
+ if (key->len > strs_len) {
+ /* The string cannot be in current record */
+ slot = sss_mc_next_slot_with_hash(rec, hash);
+ continue;
+ }
+
+ safealign_memcpy(&name_ptr, rec->data, sizeof(rel_ptr_t), NULL);
+ t_key = (char *)rec->data + name_ptr;
+ /* name_ptr must point to some data in the strs/gids area of the data
+ * payload. Since it is a pointer relative to rec->data it must be
+ * larger/equal to strs_offset and must be smaller then strs_offset + strs_len.
+ * Additionally the area must not end outside of the data table and
+ * t_key must be a zero-terminated string. */
+ if (name_ptr < strs_offset
+ || name_ptr >= strs_offset + strs_len
+ || (uint8_t *)rec->data > max_addr
+ || strs_offset > max_addr - (uint8_t *)rec->data
+ || strs_len > max_addr - (uint8_t *)rec->data - strs_offset) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Corrupted memcache entry at slot %u. "
+ "name_ptr value is %u.\n", slot, name_ptr);
+ sss_mc_save_corrupted(mcc);
+ sss_mmap_cache_reset(mcc);
+ return NULL;
+ }
+
+ if (strcmp(key->str, t_key) == 0) {
+ return rec;
+ }
+
+ slot = sss_mc_next_slot_with_hash(rec, hash);
+ }
+
+ return NULL;
+}
+
+static errno_t sss_mc_get_record(struct sss_mc_ctx **_mcc,
+ size_t rec_len,
+ const struct sized_string *key,
+ struct sss_mc_rec **_rec)
+{
+ struct sss_mc_ctx *mcc = *_mcc;
+ struct sss_mc_rec *old_rec = NULL;
+ struct sss_mc_rec *rec;
+ int old_slots;
+ int num_slots;
+ uint32_t base_slot;
+ errno_t ret;
+ int i;
+
+ num_slots = MC_SIZE_TO_SLOTS(rec_len);
+
+ old_rec = sss_mc_find_record(mcc, key);
+ if (old_rec) {
+ old_slots = MC_SIZE_TO_SLOTS(old_rec->len);
+
+ if (old_slots == num_slots) {
+ *_rec = old_rec;
+ return EOK;
+ }
+
+ /* slot size changed, invalidate record and fall through to get a
+ * fully new record */
+ sss_mc_invalidate_rec(mcc, old_rec);
+ }
+
+ /* we are going to use more space, find enough free slots */
+ ret = sss_mc_find_free_slots(mcc, num_slots, &base_slot);
+ if (ret != EOK) {
+ if (ret == EFAULT) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Fatal internal mmap cache error, invalidating cache!\n");
+ (void)sss_mmap_cache_reinit(talloc_parent(mcc),
+ -1, -1, -1, -1,
+ _mcc);
+ }
+ return ret;
+ }
+
+ rec = MC_SLOT_TO_PTR(mcc->data_table, base_slot, struct sss_mc_rec);
+
+ /* mark as not valid yet */
+ MC_RAISE_INVALID_BARRIER(rec);
+ rec->len = rec_len;
+ rec->next1 = MC_INVALID_VAL;
+ rec->next2 = MC_INVALID_VAL;
+ rec->padding = MC_INVALID_VAL;
+ MC_LOWER_BARRIER(rec);
+
+ /* and now mark slots as used */
+ for (i = 0; i < num_slots; i++) {
+ MC_SET_BIT(mcc->free_table, base_slot + i);
+ }
+
+ *_rec = rec;
+ return EOK;
+}
+
+static inline void sss_mmap_set_rec_header(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec,
+ size_t len, time_t ttl,
+ const char *key1, size_t key1_len,
+ const char *key2, size_t key2_len)
+{
+ rec->len = len;
+ rec->expire = time(NULL) + ttl;
+ rec->hash1 = sss_mc_hash(mcc, key1, key1_len);
+ rec->hash2 = sss_mc_hash(mcc, key2, key2_len);
+}
+
+static inline void sss_mmap_chain_in_rec(struct sss_mc_ctx *mcc,
+ struct sss_mc_rec *rec)
+{
+ /* name first */
+ sss_mc_add_rec_to_chain(mcc, rec, rec->hash1);
+ /* then uid/gid */
+ sss_mc_add_rec_to_chain(mcc, rec, rec->hash2);
+}
+
+/***************************************************************************
+ * generic invalidation
+ ***************************************************************************/
+
+static errno_t sss_mmap_cache_validate_or_reinit(struct sss_mc_ctx **_mcc);
+
+static errno_t sss_mmap_cache_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *key)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec;
+ int ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ rec = sss_mc_find_record(mcc, key);
+ if (rec == NULL) {
+ /* nothing to invalidate */
+ return ENOENT;
+ }
+
+ sss_mc_invalidate_rec(mcc, rec);
+
+ return EOK;
+}
+
+static errno_t sss_mmap_cache_validate_or_reinit(struct sss_mc_ctx **_mcc)
+{
+ struct sss_mc_ctx *mcc = *_mcc;
+ struct stat fdstat;
+ bool reinit = false;
+ errno_t ret;
+
+ /* No mcc initialized? Memory cache may be disabled. */
+ if (mcc == NULL || mcc->fd < 0) {
+ ret = EINVAL;
+ reinit = false;
+ goto done;
+ }
+
+ if (fstat(mcc->fd, &fdstat) == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to stat memory cache [file=%s, fd=%d] [%d]: %s\n",
+ mcc->file, mcc->fd, ret, sss_strerror(ret));
+ reinit = true;
+ goto done;
+ }
+
+ if (fdstat.st_nlink == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Memory cache file was removed\n");
+ ret = ENOENT;
+ reinit = true;
+ goto done;
+ }
+
+ if (fdstat.st_size != mcc->mmap_size) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Memory cache is corrupted, invalid size [file=%s, fd=%d, "
+ "expected_size=%zu, real_size=%zu]\n",
+ mcc->file, mcc->fd, mcc->mmap_size, fdstat.st_size);
+ ret = EINVAL;
+ reinit = true;
+ goto done;
+ }
+
+ ret = EOK;
+ reinit = false;
+
+done:
+ if (reinit) {
+ return sss_mmap_cache_reinit(talloc_parent(mcc), -1, -1, -1, -1, _mcc);
+ }
+
+ return ret;
+}
+
+/***************************************************************************
+ * passwd map
+ ***************************************************************************/
+
+errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *pw,
+ uid_t uid, gid_t gid,
+ const struct sized_string *gecos,
+ const struct sized_string *homedir,
+ const struct sized_string *shell)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec;
+ struct sss_mc_pwd_data *data;
+ struct sized_string uidkey;
+ char uidstr[11];
+ size_t data_len;
+ size_t rec_len;
+ size_t pos;
+ int ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ ret = snprintf(uidstr, 11, "%ld", (long)uid);
+ if (ret > 10) {
+ return EINVAL;
+ }
+ to_sized_string(&uidkey, uidstr);
+
+ data_len = name->len + pw->len + gecos->len + homedir->len + shell->len;
+ rec_len = sizeof(struct sss_mc_rec) +
+ sizeof(struct sss_mc_pwd_data) +
+ data_len;
+ if (rec_len > mcc->dt_size) {
+ return ENOMEM;
+ }
+
+ ret = sss_mc_get_record(_mcc, rec_len, name, &rec);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ data = (struct sss_mc_pwd_data *)rec->data;
+ pos = 0;
+
+ MC_RAISE_BARRIER(rec);
+
+ /* header */
+ sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot,
+ name->str, name->len, uidkey.str, uidkey.len);
+
+ /* passwd struct */
+ data->name = MC_PTR_DIFF(data->strs, data);
+ data->uid = uid;
+ data->gid = gid;
+ data->strs_len = data_len;
+ memcpy(&data->strs[pos], name->str, name->len);
+ pos += name->len;
+ memcpy(&data->strs[pos], pw->str, pw->len);
+ pos += pw->len;
+ memcpy(&data->strs[pos], gecos->str, gecos->len);
+ pos += gecos->len;
+ memcpy(&data->strs[pos], homedir->str, homedir->len);
+ pos += homedir->len;
+ memcpy(&data->strs[pos], shell->str, shell->len);
+
+ MC_LOWER_BARRIER(rec);
+
+ /* finally chain the rec in the hash table */
+ sss_mmap_chain_in_rec(mcc, rec);
+
+ return EOK;
+}
+
+errno_t sss_mmap_cache_pw_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name)
+{
+ return sss_mmap_cache_invalidate(_mcc, name);
+}
+
+errno_t sss_mmap_cache_pw_invalidate_uid(struct sss_mc_ctx **_mcc, uid_t uid)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec = NULL;
+ struct sss_mc_pwd_data *data;
+ uint32_t hash;
+ uint32_t slot;
+ char *uidstr;
+ errno_t ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ uidstr = talloc_asprintf(NULL, "%ld", (long)uid);
+ if (!uidstr) {
+ return ENOMEM;
+ }
+
+ hash = sss_mc_hash(mcc, uidstr, strlen(uidstr) + 1);
+
+ slot = mcc->hash_table[hash];
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ while (slot != MC_INVALID_VAL) {
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Corrupted memcache.\n");
+ sss_mc_save_corrupted(mcc);
+ sss_mmap_cache_reset(mcc);
+ ret = ENOENT;
+ goto done;
+ }
+
+ rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ data = (struct sss_mc_pwd_data *)(&rec->data);
+
+ if (uid == data->uid) {
+ break;
+ }
+
+ slot = sss_mc_next_slot_with_hash(rec, hash);
+ }
+
+ if (slot == MC_INVALID_VAL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ sss_mc_invalidate_rec(mcc, rec);
+
+ ret = EOK;
+
+done:
+ talloc_zfree(uidstr);
+ return ret;
+}
+
+/***************************************************************************
+ * group map
+ ***************************************************************************/
+
+int sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *pw,
+ gid_t gid, size_t memnum,
+ const char *membuf, size_t memsize)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec;
+ struct sss_mc_grp_data *data;
+ struct sized_string gidkey;
+ char gidstr[11];
+ size_t data_len;
+ size_t rec_len;
+ size_t pos;
+ int ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ ret = snprintf(gidstr, 11, "%ld", (long)gid);
+ if (ret > 10) {
+ return EINVAL;
+ }
+ to_sized_string(&gidkey, gidstr);
+
+ data_len = name->len + pw->len + memsize;
+ rec_len = sizeof(struct sss_mc_rec) +
+ sizeof(struct sss_mc_grp_data) +
+ data_len;
+ if (rec_len > mcc->dt_size) {
+ return ENOMEM;
+ }
+
+ ret = sss_mc_get_record(_mcc, rec_len, name, &rec);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ data = (struct sss_mc_grp_data *)rec->data;
+ pos = 0;
+
+ MC_RAISE_BARRIER(rec);
+
+ /* header */
+ sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot,
+ name->str, name->len, gidkey.str, gidkey.len);
+
+ /* group struct */
+ data->name = MC_PTR_DIFF(data->strs, data);
+ data->gid = gid;
+ data->members = memnum;
+ data->strs_len = data_len;
+ memcpy(&data->strs[pos], name->str, name->len);
+ pos += name->len;
+ memcpy(&data->strs[pos], pw->str, pw->len);
+ pos += pw->len;
+ memcpy(&data->strs[pos], membuf, memsize);
+
+ MC_LOWER_BARRIER(rec);
+
+ /* finally chain the rec in the hash table */
+ sss_mmap_chain_in_rec(mcc, rec);
+
+ return EOK;
+}
+
+errno_t sss_mmap_cache_gr_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name)
+{
+ return sss_mmap_cache_invalidate(_mcc, name);
+}
+
+errno_t sss_mmap_cache_gr_invalidate_gid(struct sss_mc_ctx **_mcc, gid_t gid)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec = NULL;
+ struct sss_mc_grp_data *data;
+ uint32_t hash;
+ uint32_t slot;
+ char *gidstr;
+ errno_t ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ gidstr = talloc_asprintf(NULL, "%ld", (long)gid);
+ if (!gidstr) {
+ return ENOMEM;
+ }
+
+ hash = sss_mc_hash(mcc, gidstr, strlen(gidstr) + 1);
+
+ slot = mcc->hash_table[hash];
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ while (slot != MC_INVALID_VAL) {
+ if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Corrupted memcache.\n");
+ sss_mc_save_corrupted(mcc);
+ sss_mmap_cache_reset(mcc);
+ ret = ENOENT;
+ goto done;
+ }
+
+ rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec);
+ data = (struct sss_mc_grp_data *)(&rec->data);
+
+ if (gid == data->gid) {
+ break;
+ }
+
+ slot = sss_mc_next_slot_with_hash(rec, hash);
+ }
+
+ if (slot == MC_INVALID_VAL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ sss_mc_invalidate_rec(mcc, rec);
+
+ ret = EOK;
+
+done:
+ talloc_zfree(gidstr);
+ return ret;
+}
+
+errno_t sss_mmap_cache_initgr_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *unique_name,
+ uint32_t num_groups,
+ const uint8_t *gids_buf)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec;
+ struct sss_mc_initgr_data *data;
+ size_t data_len;
+ size_t rec_len;
+ size_t pos;
+ int ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ /* array of gids + name + unique_name */
+ data_len = num_groups * sizeof(uint32_t) + name->len + unique_name->len;
+ rec_len = sizeof(struct sss_mc_rec) + sizeof(struct sss_mc_initgr_data)
+ + data_len;
+ if (rec_len > mcc->dt_size) {
+ return ENOMEM;
+ }
+
+ /* use unique name for searching potential old records */
+ ret = sss_mc_get_record(_mcc, rec_len, unique_name, &rec);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ data = (struct sss_mc_initgr_data *)rec->data;
+ pos = 0;
+
+ MC_RAISE_BARRIER(rec);
+
+ sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot,
+ name->str, name->len,
+ unique_name->str, unique_name->len);
+
+ /* initgroups struct */
+ data->strs_len = name->len + unique_name->len;
+ data->data_len = data_len;
+ data->num_groups = num_groups;
+ memcpy((char *)data->gids + pos, gids_buf, num_groups * sizeof(uint32_t));
+ pos += num_groups * sizeof(uint32_t);
+
+ memcpy((char *)data->gids + pos, unique_name->str, unique_name->len);
+ data->strs = data->unique_name = MC_PTR_DIFF((char *)data->gids + pos, data);
+ pos += unique_name->len;
+
+ memcpy((char *)data->gids + pos, name->str, name->len);
+ data->name = MC_PTR_DIFF((char *)data->gids + pos, data);
+
+ MC_LOWER_BARRIER(rec);
+
+ /* finally chain the rec in the hash table */
+ sss_mmap_chain_in_rec(mcc, rec);
+
+ return EOK;
+}
+
+errno_t sss_mmap_cache_initgr_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name)
+{
+ return sss_mmap_cache_invalidate(_mcc, name);
+}
+
+errno_t sss_mmap_cache_sid_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *sid,
+ uint32_t id,
+ uint32_t type,
+ bool explicit_lookup)
+{
+ struct sss_mc_ctx *mcc;
+ struct sss_mc_rec *rec;
+ struct sss_mc_sid_data *data;
+ char idkey[16];
+ size_t rec_len;
+ int ret;
+
+ ret = sss_mmap_cache_validate_or_reinit(_mcc);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ mcc = *_mcc;
+
+ ret = snprintf(idkey, sizeof(idkey), "%d-%ld",
+ (type == SSS_ID_TYPE_GID) ? SSS_ID_TYPE_GID : SSS_ID_TYPE_UID,
+ (long)id);
+ if (ret > (sizeof(idkey) - 1)) {
+ return EINVAL;
+ }
+
+ rec_len = sizeof(struct sss_mc_rec) +
+ sizeof(struct sss_mc_sid_data) +
+ sid->len;
+
+ ret = sss_mc_get_record(_mcc, rec_len, sid, &rec);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ data = (struct sss_mc_sid_data *)rec->data;
+ MC_RAISE_BARRIER(rec);
+
+ sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot,
+ sid->str, sid->len, idkey, strlen(idkey) + 1);
+
+ data->name = MC_PTR_DIFF(data->sid, data);
+ data->type = type;
+ data->id = id;
+ data->populated_by = (explicit_lookup ? 1 : 0);
+ data->sid_len = sid->len;
+ memcpy(data->sid, sid->str, sid->len);
+
+ MC_LOWER_BARRIER(rec);
+ sss_mmap_chain_in_rec(mcc, rec);
+
+ return EOK;
+}
+
+/***************************************************************************
+ * initialization
+ ***************************************************************************/
+
+/* Copy of sss_mc_set_recycled is present in the src/tools/tools_mc_util.c.
+ * If you modify this function, you should modify the duplicated function
+ * too. */
+static errno_t sss_mc_set_recycled(int fd)
+{
+ uint32_t w = SSS_MC_HEADER_RECYCLED;
+ off_t offset;
+ off_t pos;
+ ssize_t written;
+
+ offset = offsetof(struct sss_mc_header, status);
+
+ pos = lseek(fd, offset, SEEK_SET);
+ if (pos == -1) {
+ /* What do we do now? */
+ return errno;
+ }
+
+ errno = 0;
+ written = sss_atomic_write_s(fd, (uint8_t *)&w, sizeof(w));
+ if (written == -1) {
+ return errno;
+ }
+
+ if (written != sizeof(w)) {
+ /* Write error */
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static void sss_mc_destroy_file(const char *filename)
+{
+ const useconds_t t = 50000;
+ const int retries = 3;
+ int ofd;
+ int ret;
+
+ ofd = open(filename, O_RDWR);
+ if (ofd != -1) {
+ ret = sss_br_lock_file(ofd, 0, 1, retries, t);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to lock file %s.\n", filename);
+ }
+ ret = sss_mc_set_recycled(ofd);
+ if (ret) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to mark mmap file %s as"
+ " recycled: %d (%s)\n",
+ filename, ret, strerror(ret));
+ }
+ close(ofd);
+ } else if (errno != ENOENT) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to open old memory cache file %s: %d (%s)\n",
+ filename, ret, strerror(ret));
+ }
+
+ errno = 0;
+ ret = unlink(filename);
+ if (ret == -1 && errno != ENOENT) {
+ ret = errno;
+ DEBUG(SSSDBG_TRACE_FUNC, "Failed to delete mmap file %s: %d (%s)\n",
+ filename, ret, strerror(ret));
+ }
+}
+
+static errno_t sss_mc_create_file(struct sss_mc_ctx *mc_ctx)
+{
+ const useconds_t t = 50000;
+ const int retries = 3;
+ mode_t old_mask;
+ int ret, uret;
+
+ /* temporarily relax umask as we need the file to be readable
+ * by everyone for now */
+ old_mask = umask(0022);
+
+ errno = 0;
+ mc_ctx->fd = open(mc_ctx->file, O_CREAT | O_EXCL | O_RDWR, 0644);
+ umask(old_mask);
+ if (mc_ctx->fd == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to open mmap file %s: %d(%s)\n",
+ mc_ctx->file, ret, strerror(ret));
+ return ret;
+ }
+
+ /* Make sure that the memory cache files are chowned to sssd.sssd even
+ * if the nss responder runs as root. This is because the specfile
+ * has the ownership recorded as sssd.sssd
+ */
+ ret = fchown(mc_ctx->fd, mc_ctx->uid, mc_ctx->gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to chown mmap file %s: %d(%s)\n",
+ mc_ctx->file, ret, strerror(ret));
+ return ret;
+ }
+
+ ret = fchmod(mc_ctx->fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to chmod mmap file %s: %d(%s)\n",
+ mc_ctx->file, ret, strerror(ret));
+ return ret;
+ }
+
+ ret = sss_br_lock_file(mc_ctx->fd, 0, 1, retries, t);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to lock file %s.\n", mc_ctx->file);
+ close(mc_ctx->fd);
+ mc_ctx->fd = -1;
+
+ /* Report on unlink failures but don't overwrite the errno
+ * from sss_br_lock_file
+ */
+ errno = 0;
+ uret = unlink(mc_ctx->file);
+ if (uret == -1) {
+ uret = errno;
+ DEBUG(SSSDBG_TRACE_FUNC, "Failed to rm mmap file %s: %d(%s)\n",
+ mc_ctx->file, uret, strerror(uret));
+ }
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static void sss_mc_header_update(struct sss_mc_ctx *mc_ctx, int status)
+{
+ struct sss_mc_header *h;
+
+ /* update header using barriers */
+ h = (struct sss_mc_header *)mc_ctx->mmap_base;
+ MC_RAISE_BARRIER(h);
+ if (status == SSS_MC_HEADER_ALIVE) {
+ /* no reason to update anything else if the file is recycled or
+ * right before reset */
+ h->hash_table = MC_PTR_DIFF(mc_ctx->hash_table, mc_ctx->mmap_base);
+ h->free_table = MC_PTR_DIFF(mc_ctx->free_table, mc_ctx->mmap_base);
+ h->data_table = MC_PTR_DIFF(mc_ctx->data_table, mc_ctx->mmap_base);
+ h->ht_size = mc_ctx->ht_size;
+ h->ft_size = mc_ctx->ft_size;
+ h->dt_size = mc_ctx->dt_size;
+ h->major_vno = SSS_MC_MAJOR_VNO;
+ h->minor_vno = SSS_MC_MINOR_VNO;
+ h->seed = mc_ctx->seed;
+ h->reserved = 0;
+ }
+ h->status = status;
+ MC_LOWER_BARRIER(h);
+}
+
+static int mc_ctx_destructor(struct sss_mc_ctx *mc_ctx)
+{
+ int ret;
+
+ /* Print debug message to logs if munmap() or close()
+ * fail but always return 0 */
+
+ if (mc_ctx->mmap_base != NULL) {
+ ret = munmap(mc_ctx->mmap_base, mc_ctx->mmap_size);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to unmap old memory cache file."
+ "[%d]: %s\n", ret, strerror(ret));
+ }
+ }
+
+ if (mc_ctx->fd != -1) {
+ ret = close(mc_ctx->fd);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to close old memory cache file."
+ "[%d]: %s\n", ret, strerror(ret));
+ }
+ }
+
+ return 0;
+}
+
+#define POSIX_FALLOCATE_ATTEMPTS 3
+
+errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name,
+ uid_t uid, gid_t gid,
+ enum sss_mc_type type, size_t n_elem,
+ time_t timeout, struct sss_mc_ctx **mcc)
+{
+ /* sss_mc_rec alone occupies whole slot,
+ * so each entry takes 2 slots at the very least
+ */
+ static const int PAYLOAD_FACTOR = 2;
+
+ struct sss_mc_ctx *mc_ctx = NULL;
+ int ret, dret;
+ char *filename;
+
+ filename = talloc_asprintf(mem_ctx, "%s/%s", SSS_NSS_MCACHE_DIR, name);
+ if (!filename) {
+ return ENOMEM;
+ }
+ /*
+ * First of all mark the current file as recycled
+ * and unlink so active clients will abandon its use ASAP
+ */
+ sss_mc_destroy_file(filename);
+
+ if ((timeout == 0) || (n_elem == 0)) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Fast '%s' mmap cache is explicitly DISABLED\n",
+ mc_type_to_str(type));
+ *mcc = NULL;
+ return EOK;
+ }
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Fast '%s' mmap cache: memcache_timeout = %"SPRItime", slots = %zu\n",
+ mc_type_to_str(type), timeout, n_elem);
+
+ mc_ctx = talloc_zero(mem_ctx, struct sss_mc_ctx);
+ if (!mc_ctx) {
+ talloc_free(filename);
+ return ENOMEM;
+ }
+ mc_ctx->fd = -1;
+ talloc_set_destructor(mc_ctx, mc_ctx_destructor);
+
+ mc_ctx->name = talloc_strdup(mc_ctx, name);
+ if (!mc_ctx->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ mc_ctx->uid = uid;
+ mc_ctx->gid = gid;
+
+ mc_ctx->type = type;
+
+ mc_ctx->valid_time_slot = timeout;
+
+ mc_ctx->file = talloc_steal(mc_ctx, filename);
+
+ /* elements must always be multiple of 8 to make things easier to handle,
+ * so we increase by the necessary amount if they are not a multiple */
+ /* We can use MC_ALIGN64 for this */
+ n_elem = MC_ALIGN64(n_elem);
+
+ /* hash table is double the size because it will store both forward and
+ * reverse keys (name/uid, name/gid, ..) */
+ mc_ctx->ht_size = MC_HT_SIZE(2 * n_elem / PAYLOAD_FACTOR);
+ mc_ctx->dt_size = n_elem * MC_SLOT_SIZE;
+ mc_ctx->ft_size = n_elem / 8; /* 1 bit per slot */
+ mc_ctx->mmap_size = MC_HEADER_SIZE +
+ MC_ALIGN64(mc_ctx->dt_size) +
+ MC_ALIGN64(mc_ctx->ft_size) +
+ MC_ALIGN64(mc_ctx->ht_size);
+
+
+ ret = sss_mc_create_file(mc_ctx);
+ if (ret) {
+ goto done;
+ }
+
+ /* Attempt allocation several times, in case of EINTR */
+ for (int i = 0; i < POSIX_FALLOCATE_ATTEMPTS; i++) {
+ ret = posix_fallocate(mc_ctx->fd, 0, mc_ctx->mmap_size);
+ if (ret != EINTR)
+ break;
+ }
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate file %s: %d(%s)\n",
+ mc_ctx->file, ret, strerror(ret));
+ goto done;
+ }
+
+ mc_ctx->mmap_base = mmap(NULL, mc_ctx->mmap_size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, mc_ctx->fd, 0);
+ if (mc_ctx->mmap_base == MAP_FAILED) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to mmap file %s(%zu): %d(%s)\n",
+ mc_ctx->file, mc_ctx->mmap_size,
+ ret, strerror(ret));
+ goto done;
+ }
+
+ mc_ctx->data_table = MC_PTR_ADD(mc_ctx->mmap_base, MC_HEADER_SIZE);
+ mc_ctx->free_table = MC_PTR_ADD(mc_ctx->data_table,
+ MC_ALIGN64(mc_ctx->dt_size));
+ mc_ctx->hash_table = MC_PTR_ADD(mc_ctx->free_table,
+ MC_ALIGN64(mc_ctx->ft_size));
+
+ memset(mc_ctx->data_table, 0xff, mc_ctx->dt_size);
+ memset(mc_ctx->free_table, 0x00, mc_ctx->ft_size);
+ memset(mc_ctx->hash_table, 0xff, mc_ctx->ht_size);
+
+ /* generate a pseudo-random seed.
+ * Needed to fend off dictionary based collision attacks */
+ ret = sss_generate_csprng_buffer((uint8_t *)&mc_ctx->seed, sizeof(mc_ctx->seed));
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_mc_header_update(mc_ctx, SSS_MC_HEADER_ALIVE);
+
+ ret = EOK;
+
+done:
+ if (ret) {
+ /* Closing the file descriptor and unmapping the file
+ * from memory is done in the mc_ctx_destructor. */
+ if (mc_ctx && mc_ctx->file && mc_ctx->fd != -1) {
+ dret = unlink(mc_ctx->file);
+ if (dret == -1) {
+ dret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to rm mmap file %s: %d(%s)\n", mc_ctx->file,
+ dret, strerror(dret));
+ }
+ }
+
+ talloc_free(mc_ctx);
+ } else {
+ *mcc = mc_ctx;
+ }
+ return ret;
+}
+
+errno_t sss_mmap_cache_reinit(TALLOC_CTX *mem_ctx,
+ uid_t uid, gid_t gid,
+ size_t n_elem,
+ time_t timeout, struct sss_mc_ctx **mc_ctx)
+{
+ errno_t ret;
+ TALLOC_CTX* tmp_ctx = NULL;
+ char *name;
+ enum sss_mc_type type;
+
+ if (mc_ctx == NULL || (*mc_ctx) == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unable to re-init uninitialized memory cache.\n");
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
+ return ENOMEM;
+ }
+
+ name = talloc_strdup(tmp_ctx, (*mc_ctx)->name);
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ type = (*mc_ctx)->type;
+
+ if (n_elem == (size_t)-1) {
+ n_elem = (*mc_ctx)->ft_size * 8;
+ }
+
+ if (timeout == (time_t)-1) {
+ timeout = (*mc_ctx)->valid_time_slot;
+ }
+
+ if (uid == (uid_t)-1) {
+ uid = (*mc_ctx)->uid;
+ }
+
+ if (gid == (gid_t)-1) {
+ gid = (*mc_ctx)->gid;
+ }
+
+ talloc_free(*mc_ctx);
+
+ /* make sure we do not leave a potentially freed pointer around */
+ *mc_ctx = NULL;
+
+ ret = sss_mmap_cache_init(mem_ctx,
+ name,
+ uid, gid,
+ type,
+ n_elem,
+ timeout,
+ mc_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to re-initialize mmap cache.\n");
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* Erase all contents of the mmap cache. This will bring the cache
+ * to the same state as if it was just initialized. */
+void sss_mmap_cache_reset(struct sss_mc_ctx *mc_ctx)
+{
+ if (mc_ctx == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Fastcache not initialized. Nothing to do.\n");
+ return;
+ }
+
+ sss_mc_header_update(mc_ctx, SSS_MC_HEADER_UNINIT);
+
+ /* Reset the mmapped area */
+ memset(mc_ctx->data_table, 0xff, mc_ctx->dt_size);
+ memset(mc_ctx->free_table, 0x00, mc_ctx->ft_size);
+ memset(mc_ctx->hash_table, 0xff, mc_ctx->ht_size);
+
+ sss_mc_header_update(mc_ctx, SSS_MC_HEADER_ALIVE);
+}
diff --git a/src/responder/nss/nsssrv_mmap_cache.h b/src/responder/nss/nsssrv_mmap_cache.h
new file mode 100644
index 0000000..28ee5ad
--- /dev/null
+++ b/src/responder/nss/nsssrv_mmap_cache.h
@@ -0,0 +1,86 @@
+/*
+ SSSD
+
+ NSS Responder - Mmap Cache
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2011
+
+ 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/>.
+*/
+
+#ifndef _NSSSRV_MMAP_CACHE_H_
+#define _NSSSRV_MMAP_CACHE_H_
+
+struct sss_mc_ctx;
+
+enum sss_mc_type {
+ SSS_MC_NONE = 0,
+ SSS_MC_PASSWD,
+ SSS_MC_GROUP,
+ SSS_MC_INITGROUPS,
+ SSS_MC_SID,
+};
+
+errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name,
+ uid_t uid, gid_t gid,
+ enum sss_mc_type type, size_t n_elem,
+ time_t valid_time, struct sss_mc_ctx **mcc);
+
+errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *pw,
+ uid_t uid, gid_t gid,
+ const struct sized_string *gecos,
+ const struct sized_string *homedir,
+ const struct sized_string *shell);
+
+errno_t sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *pw,
+ gid_t gid, size_t memnum,
+ const char *membuf, size_t memsize);
+
+errno_t sss_mmap_cache_initgr_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name,
+ const struct sized_string *unique_name,
+ uint32_t num_groups,
+ const uint8_t *gids_buf);
+
+errno_t sss_mmap_cache_sid_store(struct sss_mc_ctx **_mcc,
+ const struct sized_string *sid,
+ uint32_t id,
+ uint32_t type, /* enum sss_id_type*/
+ bool explicit_lookup); /* false ~ by_id(), true ~ by_uid/gid() */
+
+errno_t sss_mmap_cache_pw_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name);
+
+errno_t sss_mmap_cache_pw_invalidate_uid(struct sss_mc_ctx **_mcc, uid_t uid);
+
+errno_t sss_mmap_cache_gr_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name);
+
+errno_t sss_mmap_cache_gr_invalidate_gid(struct sss_mc_ctx **_mcc, gid_t gid);
+
+errno_t sss_mmap_cache_initgr_invalidate(struct sss_mc_ctx **_mcc,
+ const struct sized_string *name);
+
+errno_t sss_mmap_cache_reinit(TALLOC_CTX *mem_ctx,
+ uid_t uid, gid_t gid,
+ size_t n_elem,
+ time_t timeout, struct sss_mc_ctx **mc_ctx);
+
+void sss_mmap_cache_reset(struct sss_mc_ctx *mc_ctx);
+
+#endif /* _NSSSRV_MMAP_CACHE_H_ */