diff options
Diffstat (limited to 'src/responder/nss')
-rw-r--r-- | src/responder/nss/nss_cmd.c | 1439 | ||||
-rw-r--r-- | src/responder/nss/nss_enum.c | 361 | ||||
-rw-r--r-- | src/responder/nss/nss_get_object.c | 546 | ||||
-rw-r--r-- | src/responder/nss/nss_iface.c | 242 | ||||
-rw-r--r-- | src/responder/nss/nss_iface.h | 31 | ||||
-rw-r--r-- | src/responder/nss/nss_private.h | 155 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol.c | 487 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol.h | 217 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_grent.c | 495 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_hostent.c | 299 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_netent.c | 243 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_netgr.c | 181 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_pwent.c | 338 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_sid.c | 704 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_subid.c | 60 | ||||
-rw-r--r-- | src/responder/nss/nss_protocol_svcent.c | 270 | ||||
-rw-r--r-- | src/responder/nss/nss_utils.c | 38 | ||||
-rw-r--r-- | src/responder/nss/nsssrv.c | 740 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_mmap_cache.c | 1626 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_mmap_cache.h | 86 |
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 */, + <ype); + 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_ */ |