nsssrv_cmd.c revision 87f8bee53ee1b4ca87b602ff8536bc5fd5b5b595
55cf6e01272ec475edea32aa9b7923de2d36cb42Christian Maeder/*
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder SSSD
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder NSS Responder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
98890889ffb2e8f6f722b00e265a211f13b5a861Corneliu-Claudiu Prodescu Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder This program is free software; you can redistribute it and/or modify
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder it under the terms of the GNU General Public License as published by
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder the Free Software Foundation; either version 3 of the License, or
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder (at your option) any later version.
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder This program is distributed in the hope that it will be useful,
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder but WITHOUT ANY WARRANTY; without even the implied warranty of
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder GNU General Public License for more details.
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder You should have received a copy of the GNU General Public License
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder along with this program. If not, see <http://www.gnu.org/licenses/>.
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder*/
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "util/util.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "util/sss_nss.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/nss/nsssrv.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/nss/nsssrv_private.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/nss/nsssrv_netgroup.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/nss/nsssrv_services.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/nss/nsssrv_mmap_cache.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "responder/common/negcache.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "providers/data_provider.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "confdb/confdb.h"
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder#include "db/sysdb.h"
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder#include "sss_client/idmap/sss_nss_idmap.h"
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder#include <time.h>
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maederstatic int nss_cmd_send_error(struct nss_cmd_ctx *cmdctx, int err)
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder{
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return sss_cmd_send_error(cmdctx->cctx, err);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder}
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maederstatic int nss_cmd_send_empty(struct nss_cmd_ctx *cmdctx)
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder{
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder struct cli_ctx *cctx = cmdctx->cctx;
2a5b885d9350ec6dd8bc4992ee91d2f68aa592f4Christian Maeder return sss_cmd_send_empty(cctx, cmdctx);
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder}
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maederint nss_cmd_done(struct nss_cmd_ctx *cmdctx, int ret)
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder{
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder switch (ret) {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder case EOK:
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder /* all fine, just return here */
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder break;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder case ENOENT:
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder ret = nss_cmd_send_empty(cmdctx);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder if (ret) {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return EFAULT;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder }
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder break;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder case EAGAIN:
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder /* async processing, just return here */
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder break;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder case EFAULT:
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder /* very bad error */
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return EFAULT;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder default:
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder ret = nss_cmd_send_error(cmdctx, ret);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder if (ret) {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return EFAULT;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder }
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder sss_cmd_done(cmdctx->cctx, cmdctx);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder break;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder }
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return EOK;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder}
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder/***************************
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder * Enumeration procedures *
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder ***************************/
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maedererrno_t nss_setent_add_ref(TALLOC_CTX *memctx,
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder struct getent_ctx *getent_ctx,
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder struct tevent_req *req)
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder{
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return setent_add_ref(memctx, getent_ctx, &getent_ctx->reqs, req);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder}
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maedervoid nss_setent_notify_error(struct getent_ctx *getent_ctx, errno_t ret)
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder{
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return setent_notify(&getent_ctx->reqs, ret);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder}
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maedervoid nss_setent_notify_done(struct getent_ctx *getent_ctx)
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder{
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder return setent_notify_done(&getent_ctx->reqs);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder}
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maederstruct setent_ctx {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder struct cli_ctx *client;
dece9056c18ada64bcc8f2fba285270374139ee8Christian Maeder struct nss_ctx *nctx;
e0f1794e365dd347e97b37d7d22b2fce27296fa1Christian Maeder struct nss_dom_ctx *dctx;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder struct getent_ctx *getent_ctx;
dece9056c18ada64bcc8f2fba285270374139ee8Christian Maeder};
dece9056c18ada64bcc8f2fba285270374139ee8Christian Maeder
dece9056c18ada64bcc8f2fba285270374139ee8Christian Maeder/****************************************************************************
dece9056c18ada64bcc8f2fba285270374139ee8Christian Maeder * PASSWD db related functions
2a5b885d9350ec6dd8bc4992ee91d2f68aa592f4Christian Maeder ***************************************************************************/
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maedervoid nss_update_pw_memcache(struct nss_ctx *nctx)
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder{
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder struct sss_domain_info *dom;
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder struct ldb_result *res;
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder uint64_t exp;
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder struct sized_string key;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder const char *id;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder time_t now;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder int ret;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder int i;
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder now = time(NULL);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder for (dom = nctx->rctx->domains; dom; dom = get_next_domain(dom, false)) {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder ret = sysdb_enumpwent_with_views(nctx, dom, &res);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder if (ret != EOK) {
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder DEBUG(SSSDBG_CRIT_FAILURE,
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder "Failed to enumerate users for domain [%s]\n", dom->name);
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder continue;
51dc4ec3c58b834d0ef0eb3d5a8d9379983377bfChristian Maeder }
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder for (i = 0; i < res->count; i++) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder exp = ldb_msg_find_attr_as_uint64(res->msgs[i],
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder SYSDB_CACHE_EXPIRE, 0);
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder if (exp >= now) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder continue;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder }
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder /* names require more manipulation (build up fqname conditionally),
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder * but uidNumber is unique and always resolvable too, so we use
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder * that to update the cache, as it points to the same entry */
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder id = sss_view_ldb_msg_find_attr_as_string(dom, res->msgs[i],
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder SYSDB_UIDNUM, NULL);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder if (!id) {
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder DEBUG(SSSDBG_CRIT_FAILURE,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder "Failed to find uidNumber in %s.\n",
2a5b885d9350ec6dd8bc4992ee91d2f68aa592f4Christian Maeder ldb_dn_get_linearized(res->msgs[i]->dn));
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder continue;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
e2330d7f462d9e15378c8408f825f23aac37100aChristian Maeder to_sized_string(&key, id);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder ret = sss_mmap_cache_pw_invalidate(nctx->pwd_mc_ctx, &key);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder if (ret != EOK && ret != ENOENT) {
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder DEBUG(SSSDBG_CRIT_FAILURE,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder "Internal failure in memory cache code: %d [%s]\n",
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder ret, strerror(ret));
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder talloc_zfree(res);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder}
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maederstatic gid_t get_gid_override(struct ldb_message *msg,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder struct sss_domain_info *dom)
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder{
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder return dom->override_gid ?
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder dom->override_gid :
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder}
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maederstatic const char *get_homedir_override(TALLOC_CTX *mem_ctx,
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder struct ldb_message *msg,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder struct nss_ctx *nctx,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder struct sss_domain_info *dom,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder struct sss_nss_homedir_ctx *homedir_ctx)
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder{
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder const char *homedir;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder const char *orig_name = homedir_ctx->username;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder errno_t ret;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder homedir = sss_view_ldb_msg_find_attr_as_string(dom, msg, SYSDB_HOMEDIR,
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder NULL);
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder homedir_ctx->original = homedir;
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder /* Subdomain users store FQDN in their name attribute */
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder ret = sss_parse_name_const(mem_ctx, dom->names, orig_name,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder NULL, &homedir_ctx->username);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder if (ret != EOK) {
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse [%s] into "
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder "name-value components.\n", orig_name);
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder return NULL;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder /* Check to see which homedir_prefix to use. */
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (dom->homedir_substr != NULL) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder homedir_ctx->config_homedir_substr = dom->homedir_substr;
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder } else if (nctx->homedir_substr != NULL) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder homedir_ctx->config_homedir_substr = nctx->homedir_substr;
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder /* Check whether we are unconditionally overriding the server
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder * for home directory locations.
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder */
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder if (dom->override_homedir) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return expand_homedir_template(mem_ctx, dom->override_homedir,
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder homedir_ctx);
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder } else if (nctx->override_homedir) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return expand_homedir_template(mem_ctx, nctx->override_homedir,
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder homedir_ctx);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (!homedir || *homedir == '\0') {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder /* In the case of a NULL or empty homedir, check to see if
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder * we have a fallback homedir to use.
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder */
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder if (dom->fallback_homedir) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return expand_homedir_template(mem_ctx, dom->fallback_homedir,
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder homedir_ctx);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder } else if (nctx->fallback_homedir) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder return expand_homedir_template(mem_ctx, nctx->fallback_homedir,
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder homedir_ctx);
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder /* Provider can also return template, try to expand it.*/
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return expand_homedir_template(mem_ctx, homedir, homedir_ctx);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder}
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maederstatic const char *get_shell_override(TALLOC_CTX *mem_ctx,
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder struct ldb_message *msg,
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder struct nss_ctx *nctx,
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder struct sss_domain_info *dom)
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder{
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder const char *user_shell;
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder int i;
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder /* Check whether we are unconditionally overriding the server
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder * for the login shell.
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder */
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder if (dom->override_shell) {
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder return dom->override_shell;
08eabcc70456fa8e6d34521ba20946630d5e16b2Christian Maeder } else if (nctx->override_shell) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder return nctx->override_shell;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder }
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder user_shell = sss_view_ldb_msg_find_attr_as_string(dom, msg, SYSDB_SHELL,
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder NULL);
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder if (!user_shell) {
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder /* Check whether there is a default shell specified */
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder if (dom->default_shell) {
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder return talloc_strdup(mem_ctx, dom->default_shell);
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder } else if (nctx->default_shell) {
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder return talloc_strdup(mem_ctx, nctx->default_shell);
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder }
3a2de7e5a3c5da0a96c9563617ab332685a41cedChristian Maeder return NULL;
ea9c85afaa9e7cc986b1bf81ad3abaa05b8af463Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (!nctx->allowed_shells && !nctx->vetoed_shells) return talloc_strdup(mem_ctx, user_shell);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (nctx->vetoed_shells) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder for (i=0; nctx->vetoed_shells[i]; i++) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (strcmp(nctx->vetoed_shells[i], user_shell) == 0) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder DEBUG(SSSDBG_FUNC_DATA, "The shell '%s' is vetoed. "
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder "Using fallback\n", user_shell);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder return talloc_strdup(mem_ctx, nctx->shell_fallback);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (nctx->etc_shells) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder for (i=0; nctx->etc_shells[i]; i++) {
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder if (strcmp(user_shell, nctx->etc_shells[i]) == 0) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder DEBUG(SSSDBG_TRACE_ALL, "Shell %s found in /etc/shells\n",
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder nctx->etc_shells[i]);
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder break;
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder if (nctx->etc_shells[i]) {
534d2a17ea35f30d0d462fa539d633b6ba389da6Christian Maeder DEBUG(SSSDBG_TRACE_ALL, "Using original shell '%s'\n", user_shell);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder return talloc_strdup(mem_ctx, user_shell);
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
ac4003061d04445bce1fad507f5caf50f8903685Christian Maeder }
if (nctx->allowed_shells) {
if (strcmp(nctx->allowed_shells[0], "*") == 0) {
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is allowed but does not exist. "
"Using fallback\n", user_shell);
return talloc_strdup(mem_ctx, nctx->shell_fallback);
} else {
for (i=0; nctx->allowed_shells[i]; i++) {
if (strcmp(nctx->allowed_shells[i], user_shell) == 0) {
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is allowed but does not exist. "
"Using fallback\n", user_shell);
return talloc_strdup(mem_ctx, nctx->shell_fallback);
}
}
}
}
DEBUG(SSSDBG_FUNC_DATA,
"The shell '%s' is not allowed and does not exist.\n",
user_shell);
return talloc_strdup(mem_ctx, NOLOGIN_SHELL);
}
static int fill_pwent(struct sss_packet *packet,
struct sss_domain_info *dom,
struct nss_ctx *nctx,
bool filter_users, bool pw_mmap_cache,
struct ldb_message **msgs,
int *count)
{
struct ldb_message *msg;
uint8_t *body;
const char *upn;
const char *tmpstr;
const char *orig_name;
struct sized_string name;
struct sized_string gecos;
struct sized_string homedir;
struct sized_string shell;
struct sized_string pwfield;
struct sized_string fullname;
uint32_t uid;
uint32_t gid;
size_t rsize, rp, blen;
int fq_len = 0;
int i, ret, num;
bool add_domain = (!IS_SUBDOMAIN(dom) && dom->fqnames);
const char *domain = dom->name;
bool packet_initialized = false;
int ncret;
TALLOC_CTX *tmp_ctx = NULL;
struct sss_nss_homedir_ctx homedir_ctx;
to_sized_string(&pwfield, nctx->pwfield);
rp = 2*sizeof(uint32_t);
num = 0;
for (i = 0; i < *count; i++) {
talloc_zfree(tmp_ctx);
tmp_ctx = talloc_new(NULL);
msg = msgs[i];
upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL);
if (DOM_HAS_VIEWS(dom)) {
orig_name = ldb_msg_find_attr_as_string(msg,
OVERRIDE_PREFIX SYSDB_NAME,
NULL);
if (orig_name != NULL && IS_SUBDOMAIN(dom)) {
/* Override names are not fully qualified */
add_domain = true;
}
gid = ldb_msg_find_attr_as_uint64(msg,
OVERRIDE_PREFIX SYSDB_GIDNUM, 0);
} else {
orig_name = NULL;
gid = 0;
}
if (orig_name == NULL) {
orig_name = ldb_msg_find_attr_as_string(msg,
SYSDB_DEFAULT_OVERRIDE_NAME,
NULL);
if (orig_name == NULL) {
orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
}
}
uid = sss_view_ldb_msg_find_attr_as_uint64(dom, msg, SYSDB_UIDNUM, 0);
if (gid == 0) {
gid = get_gid_override(msg, dom);
}
if (!orig_name || !uid || !gid) {
DEBUG(SSSDBG_OP_FAILURE, "Incomplete user object for %s[%llu]! Skipping\n",
orig_name?orig_name:"<NULL>", (unsigned long long int)uid);
continue;
}
if (filter_users) {
ncret = sss_ncache_check_user(nctx->ncache,
nctx->neg_timeout,
dom, orig_name);
if (ncret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"User [%s@%s] filtered out! (negative cache)\n",
orig_name, domain);
continue;
}
}
if (!packet_initialized) {
/* first 2 fields (len and reserved), filled up later */
ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
if (ret != EOK) return ret;
packet_initialized = true;
}
tmpstr = sss_get_cased_name(tmp_ctx, orig_name, dom->case_preserve);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_get_cased_name failed, skipping\n");
continue;
}
tmpstr = sss_replace_space(tmp_ctx, tmpstr,
nctx->rctx->override_space);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_replace_space failed, skipping\n");
continue;
}
to_sized_string(&name, tmpstr);
tmpstr = sss_view_ldb_msg_find_attr_as_string(dom, msg, SYSDB_GECOS,
NULL);
if (!tmpstr) {
to_sized_string(&gecos, "");
} else {
to_sized_string(&gecos, tmpstr);
}
ZERO_STRUCT(homedir_ctx);
homedir_ctx.username = name.str;
homedir_ctx.uid = uid;
homedir_ctx.domain = dom->name;
homedir_ctx.upn = upn;
tmpstr = get_homedir_override(tmp_ctx, msg, nctx, dom, &homedir_ctx);
if (!tmpstr) {
to_sized_string(&homedir, "/");
} else {
to_sized_string(&homedir, tmpstr);
}
tmpstr = get_shell_override(tmp_ctx, msg, nctx, dom);
if (!tmpstr) {
to_sized_string(&shell, "");
} else {
to_sized_string(&shell, tmpstr);
}
rsize = 2 * sizeof(uint32_t) + name.len + gecos.len +
homedir.len + shell.len + pwfield.len;
if (add_domain) {
fq_len = sss_fqname(NULL, 0, dom->names, dom, name.str);
if (fq_len >= 0) {
fq_len += 1;
rsize -= name.len;
rsize += fq_len;
} else {
fq_len = 0;
}
}
ret = sss_packet_grow(packet, rsize);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
SAFEALIGN_SET_UINT32(&body[rp], uid, &rp);
SAFEALIGN_SET_UINT32(&body[rp], gid, &rp);
if (add_domain) {
ret = sss_fqname((char *) &body[rp], fq_len, dom->names, dom, name.str);
if (ret < 0 || ret != fq_len - 1) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to generate a fully qualified name for user "
"[%s] in [%s]! Skipping user.\n", name.str, domain);
continue;
}
} else {
memcpy(&body[rp], name.str, name.len);
}
to_sized_string(&fullname, (const char *)&body[rp]);
rp += fullname.len;
memcpy(&body[rp], pwfield.str, pwfield.len);
rp += pwfield.len;
memcpy(&body[rp], gecos.str, gecos.len);
rp += gecos.len;
memcpy(&body[rp], homedir.str, homedir.len);
rp += homedir.len;
memcpy(&body[rp], shell.str, shell.len);
rp += shell.len;
num++;
if (pw_mmap_cache && nctx->pwd_mc_ctx) {
ret = sss_mmap_cache_pw_store(&nctx->pwd_mc_ctx,
&fullname, &pwfield,
uid, gid,
&gecos, &homedir, &shell);
if (ret != EOK && ret != ENOMEM) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to store user %s(%s) in mmap cache!\n",
name.str, domain);
}
}
}
talloc_zfree(tmp_ctx);
done:
*count = i;
/* if there are no results just return ENOENT,
* let the caller decide if this is the last packet or not */
if (!packet_initialized) return ENOENT;
sss_packet_get_body(packet, &body, &blen);
SAFEALIGN_COPY_UINT32(body, &num, NULL); /* num results */
SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */
return EOK;
}
static int nss_cmd_getpw_send_reply(struct nss_dom_ctx *dctx, bool filter)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int i;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
i = dctx->res->count;
ret = fill_pwent(cctx->creq->out,
dctx->domain,
nctx, filter, true,
dctx->res->msgs, &i);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
/* Currently only refreshing expired netgroups is supported. */
static bool
is_refreshed_on_bg(enum sss_dp_acct_type req_type,
enum sss_dp_acct_type refresh_expired_interval)
{
if (refresh_expired_interval == 0) {
return false;
}
switch (req_type) {
case SSS_DP_NETGR:
return true;
default:
return false;
}
return false;
}
static void nsssrv_dp_send_acct_req_done(struct tevent_req *req);
/* FIXME: do not check res->count, but get in a msgs and check in parent */
errno_t check_cache(struct nss_dom_ctx *dctx,
struct nss_ctx *nctx,
struct ldb_result *res,
enum sss_dp_acct_type req_type,
const char *opt_name,
uint32_t opt_id,
const char *extra,
sss_dp_callback_t callback,
void *pvt)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct tevent_req *req = NULL;
struct dp_callback_ctx *cb_ctx = NULL;
uint64_t cacheExpire = 0;
/* when searching for a user or netgroup, more than one reply is a
* db error
*/
if ((req_type == SSS_DP_USER || req_type == SSS_DP_NETGR) &&
(res->count > 1)) {
DEBUG(SSSDBG_CRIT_FAILURE,
"getpwXXX call returned more than one result! DB Corrupted?\n");
return ENOENT;
}
/* if we have any reply let's check cache validity, but ignore netgroups
* if refresh_expired_interval is set (which implies that another method
* is used to refresh netgroups)
*/
if (res->count > 0) {
if (is_refreshed_on_bg(req_type,
dctx->domain->refresh_expired_interval)) {
ret = EOK;
} else {
if (req_type == SSS_DP_INITGROUPS) {
cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
SYSDB_INITGR_EXPIRE,
1);
}
if (cacheExpire == 0) {
cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
SYSDB_CACHE_EXPIRE,
0);
}
/* if we have any reply let's check cache validity */
ret = sss_cmd_check_cache(res->msgs[0],
nctx->cache_refresh_percent,
cacheExpire);
}
if (ret == EOK) {
DEBUG(SSSDBG_TRACE_FUNC, "Cached entry is valid, returning..\n");
return EOK;
} else if (ret != EAGAIN && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE, "Error checking cache: %d\n", ret);
goto error;
}
} else {
/* No replies */
ret = ENOENT;
}
/* EAGAIN (off band) or ENOENT (cache miss) -> check cache */
if (ret == EAGAIN) {
/* No callback required
* This was an out-of-band update. We'll return EOK
* so the calling function can return the cached entry
* immediately.
*/
DEBUG(SSSDBG_TRACE_FUNC,
"Performing midpoint cache update on [%s]\n", opt_name);
req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
req_type, opt_name, opt_id, extra);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Out of memory sending out-of-band data provider "
"request\n");
/* This is non-fatal, so we'll continue here */
} else {
DEBUG(SSSDBG_TRACE_FUNC, "Updating cache out-of-band\n");
}
/* We don't need to listen for a reply, so we will free the
* request here.
*/
talloc_zfree(req);
} else {
/* This is a cache miss. Or the cache is expired.
* We need to get the updated user information before returning it.
*/
/* dont loop forever :-) */
dctx->check_provider = false;
/* keep around current data in case backend is offline */
if (res->count) {
dctx->res = talloc_steal(dctx, res);
}
req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
req_type, opt_name, opt_id, extra);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Out of memory sending data provider request\n");
ret = ENOMEM;
goto error;
}
cb_ctx = talloc_zero(dctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(req);
ret = ENOMEM;
goto error;
}
cb_ctx->callback = callback;
cb_ctx->ptr = pvt;
cb_ctx->cctx = dctx->cmdctx->cctx;
cb_ctx->mem_ctx = dctx;
tevent_req_set_callback(req, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
return EOK;
error:
ret = nss_cmd_send_error(cmdctx, ret);
if (ret != EOK) {
NSS_CMD_FATAL_ERROR_CODE(cctx, ret);
}
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nsssrv_dp_send_acct_req_done(struct tevent_req *req)
{
struct dp_callback_ctx *cb_ctx =
tevent_req_callback_data(req, struct dp_callback_ctx);
errno_t ret;
dbus_uint16_t err_maj;
dbus_uint32_t err_min;
char *err_msg;
ret = sss_dp_get_account_recv(cb_ctx->mem_ctx, req,
&err_maj, &err_min,
&err_msg);
talloc_zfree(req);
if (ret != EOK) {
NSS_CMD_FATAL_ERROR(cb_ctx->cctx);
}
cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr);
}
static int delete_entry_from_memcache(struct sss_domain_info *dom, char *name,
struct sss_mc_ctx *mc_ctx)
{
TALLOC_CTX *tmp_ctx = NULL;
struct sized_string delete_name;
char *fqdn = NULL;
int ret;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
return ENOMEM;
}
if (dom->fqnames) {
fqdn = sss_tc_fqname(tmp_ctx, dom->names, dom, name);
if (fqdn == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n");
ret = ENOMEM;
goto done;
}
to_sized_string(&delete_name, fqdn);
} else {
to_sized_string(&delete_name, name);
}
ret = sss_mmap_cache_pw_invalidate(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));
goto done;
}
ret = EOK;
done:
talloc_free(tmp_ctx);
return ret;
}
static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
/* search for a user.
* Returns:
* ENOENT, if user is definitely not found
* EAGAIN, if user is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct nss_ctx *nctx;
int ret;
static const char *user_attrs[] = SYSDB_PW_ATTRS;
struct ldb_message *msg;
const char *extra_flag = NULL;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames
&& !cmdctx->name_is_upn) {
dom = get_next_domain(dom, false);
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(cmdctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
name = sss_reverse_replace_space(dctx, name,
nctx->rctx->override_space);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_reverse_replace_space failed\n");
return ENOMEM;
}
/* verify this user has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"User [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name);
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(SSSDBG_CONF_SETTINGS,
"Requesting info for [%s@%s]\n", name, dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EIO;
}
if (cmdctx->name_is_upn) {
ret = sysdb_search_user_by_upn(cmdctx, dom, name, user_attrs, &msg);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn failed.\n");
return ret;
}
dctx->res = talloc_zero(cmdctx, struct ldb_result);
if (dctx->res == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
return ENOMEM;
}
if (ret == ENOENT) {
dctx->res->count = 0;
dctx->res->msgs = NULL;
ret = EOK;
} else {
dctx->res->count = 1;
dctx->res->msgs = talloc_array(dctx->res,
struct ldb_message *, 1);
if (dctx->res->msgs == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
return ENOMEM;
}
dctx->res->msgs[0] = talloc_steal(dctx->res->msgs, msg);
}
} else {
ret = sysdb_getpwnam_with_views(cmdctx, dom, name, &dctx->res);
}
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"getpwnam call returned more than one result !?!\n");
sss_log(SSS_LOG_ERR,
"More users have the same name [%s@%s] in SSSD cache. "
"SSSD will not work correctly.\n",
name, dom->name);
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_user(nctx->ncache, false, dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set negcache for %s@%s\n",
name, dom->name);
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
if (dom) continue;
}
DEBUG(SSSDBG_OP_FAILURE, "No results for getpwnam call\n");
/* User not found in ldb -> delete user from memory cache. */
ret = delete_entry_from_memcache(dctx->domain, name,
nctx->pwd_mc_ctx);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Deleting user from memcache failed.\n");
}
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (cmdctx->name_is_upn) {
extra_flag = EXTRA_NAME_IS_UPN;
} else if (DOM_HAS_VIEWS(dom) && dctx->res->count == 0) {
extra_flag = EXTRA_INPUT_MAYBE_WITH_VIEW;
} else {
extra_flag = NULL;
}
ret = check_cache(dctx, nctx, dctx->res, SSS_DP_USER, name, 0,
extra_flag, nss_cmd_getby_dp_callback, dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(SSSDBG_TRACE_FUNC,
"Returning info for user [%s@%s]\n", name, dom->name);
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
"No matching domain found for [%s], fail!\n", cmdctx->name);
return ENOENT;
}
static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx);
static int nss_cmd_getgr_send_reply(struct nss_dom_ctx *dctx, bool filter);
static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx);
static int nss_cmd_initgr_send_reply(struct nss_dom_ctx *dctx);
static int nss_cmd_getpwuid_search(struct nss_dom_ctx *dctx);
static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx);
static errno_t nss_cmd_getbysid_search(struct nss_dom_ctx *dctx);
static errno_t nss_cmd_getbysid_send_reply(struct nss_dom_ctx *dctx);
static errno_t nss_cmd_getsidby_search(struct nss_dom_ctx *dctx);
static int nss_cmd_assume_upn(struct nss_dom_ctx *dctx)
{
int ret;
if (dctx->domain == NULL) {
dctx->domain = dctx->cmdctx->cctx->rctx->domains;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
dctx->cmdctx->check_next = true;
dctx->cmdctx->name = talloc_strdup(dctx->cmdctx, dctx->rawname);
dctx->cmdctx->name_is_upn = true;
if (dctx->cmdctx->name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
return ENOMEM;
}
}
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWNAM:
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getpw_send_reply(dctx, false);
}
break;
case SSS_NSS_INITGR:
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
ret = nss_cmd_initgr_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
return ret;
}
static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr)
{
struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
int ret;
bool check_subdomains;
if (err_maj) {
DEBUG(SSSDBG_OP_FAILURE,
"Unable to get information from Data Provider\n"
"Error: %u, %u, %s\n"
"Will try to return what we have in cache\n",
(unsigned int)err_maj, (unsigned int)err_min, err_msg);
if ((dctx->res && dctx->res->count == 1) ||
(dctx->cmdctx->cmd == SSS_NSS_INITGR &&
dctx->res && dctx->res->count != 0)) {
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWNAM:
ret = nss_cmd_getpw_send_reply(dctx, false);
break;
case SSS_NSS_GETGRNAM:
ret = nss_cmd_getgr_send_reply(dctx, false);
break;
case SSS_NSS_INITGR:
ret = nss_cmd_initgr_send_reply(dctx);
break;
case SSS_NSS_GETPWUID:
ret = nss_cmd_getpw_send_reply(dctx, true);
break;
case SSS_NSS_GETGRGID:
ret = nss_cmd_getgr_send_reply(dctx, true);
break;
case SSS_NSS_GETNAMEBYSID:
case SSS_NSS_GETIDBYSID:
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETORIGBYNAME:
case SSS_NSS_GETSIDBYID:
ret = nss_cmd_getbysid_send_reply(dctx);
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
goto done;
}
/* Since subdomain users and groups are fully qualified they are
* typically not subject of multi-domain searches. But since POSIX
* ID do not contain a domain name we have to decend to subdomains
* here. */
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWUID:
case SSS_NSS_GETGRGID:
case SSS_NSS_GETSIDBYID:
check_subdomains = true;
break;
default:
check_subdomains = false;
}
/* no previous results, just loop to next domain if possible */
if (cmdctx->check_next &&
get_next_domain(dctx->domain, check_subdomains)) {
dctx->domain = get_next_domain(dctx->domain, check_subdomains);
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
} else {
/* nothing available */
ret = ENOENT;
goto done;
}
}
/* ok the backend returned, search to see if we have updated results */
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWNAM:
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
}
break;
case SSS_NSS_GETGRNAM:
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
break;
case SSS_NSS_INITGR:
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
}
break;
case SSS_NSS_GETPWUID:
ret = nss_cmd_getpwuid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, true);
}
break;
case SSS_NSS_GETGRGID:
ret = nss_cmd_getgrgid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, true);
}
break;
case SSS_NSS_GETNAMEBYSID:
case SSS_NSS_GETIDBYSID:
ret = nss_cmd_getbysid_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETORIGBYNAME:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
case SSS_NSS_GETSIDBYID:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
done:
if (ret == ENOENT
&& (dctx->cmdctx->cmd == SSS_NSS_GETPWNAM
|| dctx->cmdctx->cmd == SSS_NSS_INITGR)
&& dctx->rawname != NULL && strchr(dctx->rawname, '@') != NULL) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
}
ret = nss_cmd_done(cmdctx, ret);
if (ret) {
NSS_CMD_FATAL_ERROR(cctx);
}
}
static int nss_check_name_of_well_known_sid(struct nss_cmd_ctx *cmdctx,
const char *full_name)
{
char *wk_name = NULL;
char *wk_dom_name = NULL;
const char *wk_sid;
int ret;
struct sized_string sid;
uint8_t *body;
size_t blen;
struct cli_ctx *cctx;
struct nss_ctx *nss_ctx;
size_t pctr = 0;
nss_ctx = talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
ret = sss_parse_name(cmdctx, nss_ctx->global_names, full_name,
&wk_dom_name, &wk_name);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "sss_parse_name failed.\n");
return ret;
}
ret = name_to_well_known_sid(wk_dom_name, wk_name, &wk_sid);
talloc_free(wk_dom_name);
talloc_free(wk_name);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_ALL, "Name [%s] is not the name of a " \
"Well-Known SID.\n", full_name);
return ret;
}
to_sized_string(&sid, wk_sid);
cctx = cmdctx->cctx;
ret = sss_packet_new(cctx->creq, sid.len + 3 * sizeof(uint32_t),
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ENOMEM;
}
sss_packet_get_body(cctx->creq->out, &body, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_SETMEM_UINT32(body + pctr, SSS_ID_TYPE_GID, &pctr);
memcpy(&body[pctr], sid.str, sid.len);
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx);
static void nss_cmd_getbynam_done(struct tevent_req *req);
static int nss_cmd_getpwnam(struct cli_ctx *cctx)
{
return nss_cmd_getbynam(SSS_NSS_GETPWNAM, cctx);
}
static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
{
struct tevent_req *req;
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
const char *rawname;
char *domname;
uint8_t *body;
size_t blen;
int ret;
switch(cmd) {
case SSS_NSS_GETPWNAM:
case SSS_NSS_GETGRNAM:
case SSS_NSS_INITGR:
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETORIGBYNAME:
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command type [%d].\n", cmd);
return EINVAL;
}
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
cmdctx->cmd = cmd;
dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
if (!dctx) {
ret = ENOMEM;
goto done;
}
dctx->cmdctx = cmdctx;
/* get user name to query */
sss_packet_get_body(cctx->creq->in, &body, &blen);
/* if not terminated fail */
if (body[blen -1] != '\0') {
ret = EINVAL;
goto done;
}
/* If the body isn't valid UTF-8, fail */
if (!sss_utf8_check(body, blen -1)) {
ret = EINVAL;
goto done;
}
rawname = (const char *)body;
DEBUG(SSSDBG_TRACE_FUNC, "Running command [%d] with input [%s].\n",
dctx->cmdctx->cmd, rawname);
if (dctx->cmdctx->cmd == SSS_NSS_GETSIDBYNAME) {
ret = nss_check_name_of_well_known_sid(cmdctx, rawname);
if (ret != ENOENT) {
if (ret == EOK) {
DEBUG(SSSDBG_TRACE_ALL, "Name [%s] is the name of a " \
"Well-Known SID.\n", rawname);
} else {
DEBUG(SSSDBG_OP_FAILURE,
"nss_check_name_of_well_known_sid failed.\n");
}
goto done;
}
}
/* We need to attach to subdomain request, if the first one is not
* finished yet. We may not be able to lookup object in AD otherwise. */
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->rawname = rawname;
tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
ret = EAGAIN;
}
goto done;
}
domname = NULL;
ret = sss_parse_name_for_domains(cmdctx, cctx->rctx->domains,
cctx->rctx->default_domain, rawname,
&domname, &cmdctx->name);
if (ret == EAGAIN) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, domname);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->rawname = rawname;
tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
ret = EAGAIN;
}
goto done;
} else if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawname);
ret = ENOENT;
goto done;
}
DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for [%s] from [%s]\n",
cmdctx->name, domname?domname:"<ALL>");
if (domname) {
dctx->domain = responder_get_domain(cctx->rctx, domname);
if (!dctx->domain) {
ret = ENOENT;
goto done;
}
} else {
/* this is a multidomain search */
dctx->rawname = rawname;
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, false, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
ret = EAGAIN;
}
goto done;
}
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWNAM:
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
} else if (ret == ENOENT
&& dctx->rawname != NULL
&& strchr(dctx->rawname, '@') != NULL) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
}
break;
case SSS_NSS_GETGRNAM:
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
break;
case SSS_NSS_INITGR:
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
} else if (ret == ENOENT
&& dctx->rawname != NULL
&& strchr(dctx->rawname, '@') != NULL) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
}
break;
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETORIGBYNAME:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
done:
return nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getbynam_done(struct tevent_req *req)
{
struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
char *domname = NULL;
const char *rawname = dctx->rawname;
errno_t ret;
ret = sss_dp_get_domains_recv(req);
talloc_free(req);
if (ret != EOK) {
goto done;
}
ret = sss_parse_name_for_domains(cmdctx, cctx->rctx->domains,
cctx->rctx->default_domain, rawname,
&domname, &cmdctx->name);
if (ret == EAGAIN && (dctx->cmdctx->cmd == SSS_NSS_GETPWNAM
|| dctx->cmdctx->cmd == SSS_NSS_INITGR)) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
goto done;
} else if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawname);
ret = ENOENT;
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC, "Requesting info for [%s] from [%s]\n",
cmdctx->name, domname?domname:"<ALL>");
if (domname) {
dctx->domain = responder_get_domain(cctx->rctx, domname);
if (dctx->domain == NULL) {
ret = ENOENT;
goto done;
}
} else {
/* this is a multidomain search */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
switch (dctx->cmdctx->cmd) {
case SSS_NSS_GETPWNAM:
ret = nss_cmd_getpwnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, false);
} else if (ret == ENOENT
&& dctx->rawname != NULL
&& strchr(dctx->rawname, '@') != NULL) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
}
break;
case SSS_NSS_GETGRNAM:
ret = nss_cmd_getgrnam_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, false);
}
break;
case SSS_NSS_INITGR:
ret = nss_cmd_initgroups_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_initgr_send_reply(dctx);
} else if (ret == ENOENT
&& dctx->rawname != NULL
&& strchr(dctx->rawname, '@') != NULL) {
/* assume Kerberos principal */
ret = nss_cmd_assume_upn(dctx);
}
break;
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETORIGBYNAME:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
done:
nss_cmd_done(cmdctx, ret);
}
/* search for a uid.
* Returns:
* ENOENT, if uid is definitely not found
* EAGAIN, if uid is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getpwuid_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int err;
const char *extra_flag = NULL;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* check that the uid is valid for this domain */
if ((dom->id_min && (cmdctx->id < dom->id_min)) ||
(dom->id_max && (cmdctx->id > dom->id_max))) {
DEBUG(SSSDBG_CONF_SETTINGS,
"Uid [%"PRIu32"] does not exist in domain [%s]! "
"(id out of range)\n",
cmdctx->id, dom->name);
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
ret = ENOENT;
goto done;
}
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(SSSDBG_CONF_SETTINGS,
"Requesting info for [%"PRIu32"@%s]\n", cmdctx->id, dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
ret = EIO;
goto done;
}
ret = sysdb_getpwuid_with_views(cmdctx, dom, cmdctx->id, &dctx->res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (dctx->res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"getpwuid call returned more than one result !?!\n");
sss_log(SSS_LOG_ERR,
"More users have the same UID [%"PRIu32"] in directory "
"server. SSSD will not work correctly.\n", cmdctx->id);
ret = ENOENT;
goto done;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
/* set negative cache only if not result of cache check */
DEBUG(SSSDBG_MINOR_FAILURE, "No results for getpwuid call\n");
ret = ENOENT;
goto done;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (DOM_HAS_VIEWS(dom) && dctx->res->count == 0) {
extra_flag = EXTRA_INPUT_MAYBE_WITH_VIEW;
} else {
extra_flag = NULL;
}
ret = check_cache(dctx, nctx, dctx->res, SSS_DP_USER, NULL,
cmdctx->id, extra_flag, nss_cmd_getby_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
goto done;
}
}
/* One result found */
DEBUG(SSSDBG_TRACE_FUNC,
"Returning info for uid [%"PRIu32"@%s]\n", cmdctx->id, dom->name);
ret = EOK;
goto done;
}
/* All domains were tried and none had the entry. */
ret = ENOENT;
done:
if (ret == ENOENT) {
/* The entry was not found, need to set result in negative cache */
err = sss_ncache_set_uid(nctx->ncache, false, cmdctx->id);
if (err != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negative cache for UID %"PRIu32"\n", cmdctx->id);
}
}
DEBUG(SSSDBG_MINOR_FAILURE, "No matching domain found for [%"PRIu32"]\n", cmdctx->id);
return ret;
}
static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx);
static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx);
static void nss_cmd_getbyid_done(struct tevent_req *req);
static int nss_cmd_getpwuid(struct cli_ctx *cctx)
{
return nss_cmd_getbyid(SSS_NSS_GETPWUID, cctx);
}
static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
struct nss_ctx *nctx;
uint8_t *body;
size_t blen;
int ret;
struct tevent_req *req;
switch (cmd) {
case SSS_NSS_GETPWUID:
case SSS_NSS_GETGRGID:
case SSS_NSS_GETSIDBYID:
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command type [%d].\n", cmd);
return EINVAL;
}
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
cmdctx->cmd = cmd;
dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
if (!dctx) {
ret = ENOMEM;
goto done;
}
dctx->cmdctx = cmdctx;
/* get id to query */
sss_packet_get_body(cctx->creq->in, &body, &blen);
if (blen != sizeof(uint32_t)) {
ret = EINVAL;
goto done;
}
SAFEALIGN_COPY_UINT32(&cmdctx->id, body, NULL);
DEBUG(SSSDBG_TRACE_FUNC, "Running command [%d] with id [%"PRIu32"].\n",
dctx->cmdctx->cmd, cmdctx->id);
switch(dctx->cmdctx->cmd) {
case SSS_NSS_GETPWUID:
ret = sss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, cmdctx->id);
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Uid [%"PRIu32"] does not exist! (negative cache)\n",
cmdctx->id);
ret = ENOENT;
goto done;
}
break;
case SSS_NSS_GETGRGID:
ret = sss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, cmdctx->id);
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Gid [%"PRIu32"] does not exist! (negative cache)\n",
cmdctx->id);
ret = ENOENT;
goto done;
}
break;
case SSS_NSS_GETSIDBYID:
ret = sss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, cmdctx->id);
if (ret != EEXIST) {
ret = sss_ncache_check_gid(nctx->ncache, nctx->neg_timeout,
cmdctx->id);
}
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Id [%"PRIu32"] does not exist! (negative cache)\n",
cmdctx->id);
ret = ENOENT;
goto done;
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
goto done;
}
/* id searches are always multidomain */
dctx->domain = cctx->rctx->domains;
cmdctx->check_next = true;
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
if (cctx->rctx->get_domains_last_call.tv_sec == 0) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, false, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
tevent_req_set_callback(req, nss_cmd_getbyid_done, dctx);
ret = EAGAIN;
}
goto done;
}
/* ok, find it ! */
switch(dctx->cmdctx->cmd) {
case SSS_NSS_GETPWUID:
ret = nss_cmd_getpwuid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, true);
}
break;
case SSS_NSS_GETGRGID:
ret = nss_cmd_getgrgid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, true);
}
break;
case SSS_NSS_GETSIDBYID:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
done:
return nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_getbyid_done(struct tevent_req *req)
{
struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
errno_t ret;
ret = sss_dp_get_domains_recv(req);
talloc_free(req);
if (ret != EOK) {
goto done;
}
/* ok, find it ! */
switch(dctx->cmdctx->cmd) {
case SSS_NSS_GETPWUID:
ret = nss_cmd_getpwuid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getpw_send_reply(dctx, true);
}
break;
case SSS_NSS_GETGRGID:
ret = nss_cmd_getgrgid_search(dctx);
if (ret == EOK) {
/* we have results to return */
ret = nss_cmd_getgr_send_reply(dctx, true);
}
break;
case SSS_NSS_GETNAMEBYSID:
case SSS_NSS_GETIDBYSID:
ret = responder_get_domain_by_id(cmdctx->cctx->rctx, cmdctx->secid,
&dctx->domain);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for SID [%s].\n",
cmdctx->secid);
ret = ENOENT;
goto done;
}
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
ret = nss_cmd_getbysid_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
case SSS_NSS_GETSIDBYID:
ret = nss_cmd_getsidby_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
dctx->cmdctx->cmd);
ret = EINVAL;
}
done:
nss_cmd_done(cmdctx, ret);
}
/* to keep it simple at this stage we are retrieving the
* full enumeration again for each request for each process
* and we also block on setpwent() for the full time needed
* to retrieve the data. And endpwent() frees all the data.
* Next steps are:
* - use an nsssrv wide cache with data already structured
* so that it can be immediately returned (see nscd way)
* - use mutexes so that setpwent() can return immediately
* even if the data is still being fetched
* - make getpwent() wait on the mutex
*
* Alternatively:
* - use a smarter search mechanism that keeps track of the
* last user searched and return the next X users doing
* an alphabetic sort and starting from the user following
* the last returned user.
*/
static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx);
struct tevent_req * nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client);
static void nss_cmd_setpwent_done(struct tevent_req *req);
static int nss_cmd_setpwent(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
errno_t ret = EOK;
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
req = nss_cmd_setpwent_send(cmdctx, cctx);
if (!req) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal error calling nss_cmd_setpwent_send\n");
ret = EIO;
goto done;
}
tevent_req_set_callback(req, nss_cmd_setpwent_done, cmdctx);
done:
return nss_cmd_done(cmdctx, ret);
}
static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx);
struct tevent_req *nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client)
{
errno_t ret;
struct nss_ctx *nctx;
struct tevent_req *req;
struct setent_ctx *state;
struct sss_domain_info *dom;
struct setent_step_ctx *step_ctx;
DEBUG(SSSDBG_CONF_SETTINGS, "Received setpwent request\n");
nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
/* Reset the read pointers */
client->pwent_dom_idx = 0;
client->pwent_cur = 0;
req = tevent_req_create(mem_ctx, &state, struct setent_ctx);
if (!req) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not create tevent request for setpwent\n");
return NULL;
}
state->nctx = nctx;
state->client = client;
state->dctx = talloc_zero(state, struct nss_dom_ctx);
if (!state->dctx) {
ret = ENOMEM;
goto error;
}
/* check if enumeration is enabled in any domain */
for (dom = client->rctx->domains; dom; dom = get_next_domain(dom, true)) {
if (dom->enumerate == true) break;
}
state->dctx->domain = dom;
if (state->dctx->domain == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Enumeration disabled on all domains!\n");
ret = ENOENT;
goto error;
}
state->dctx->check_provider =
NEED_CHECK_PROVIDER(state->dctx->domain->provider);
/* Is the result context already available */
if (state->nctx->pctx) {
if (state->nctx->pctx->ready) {
/* All of the necessary data is in place
* We can return now, getpwent requests will work at this point
*/
tevent_req_done(req);
tevent_req_post(req, state->nctx->rctx->ev);
}
else {
/* Object is still being constructed
* Register for notification when it's
* ready.
*/
ret = nss_setent_add_ref(state, state->nctx->pctx, req);
if (ret != EOK) {
talloc_free(req);
return NULL;
}
}
return req;
}
/* Create a new result context
* We are creating it on the nss_ctx so that it doesn't
* go away if the original request does. We will delete
* it when the refcount goes to zero;
*/
state->nctx->pctx = talloc_zero(nctx, struct getent_ctx);
if (!state->nctx->pctx) {
ret = ENOMEM;
goto error;
}
state->getent_ctx = nctx->pctx;
/* Add a callback reference for ourselves */
ret = nss_setent_add_ref(state, state->nctx->pctx, req);
if (ret) goto error;
/* ok, start the searches */
step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx);
if (!step_ctx) {
ret = ENOMEM;
goto error;
}
/* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
* this request is canceled while other requests are in-progress.
*/
step_ctx->dctx = talloc_steal(step_ctx, state->dctx);
step_ctx->nctx = state->nctx;
step_ctx->getent_ctx = state->getent_ctx;
step_ctx->rctx = client->rctx;
step_ctx->cctx = client;
step_ctx->returned_to_mainloop = false;
ret = nss_cmd_setpwent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) goto error;
if (ret == EOK) {
tevent_req_post(req, client->rctx->ev);
}
return req;
error:
tevent_req_error(req, ret);
tevent_req_post(req, client->rctx->ev);
return req;
}
static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static void setpwent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
/* nss_cmd_setpwent_step returns
* EOK if everything is done and the request needs to be posted explicitly
* EAGAIN if the caller can safely return to the main loop
*/
static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx)
{
errno_t ret;
struct sss_domain_info *dom = step_ctx->dctx->domain;
struct resp_ctx *rctx = step_ctx->rctx;
struct nss_dom_ctx *dctx = step_ctx->dctx;
struct getent_ctx *pctx = step_ctx->getent_ctx;
struct nss_ctx *nctx = step_ctx->nctx;
struct ldb_result *res;
struct timeval tv;
struct tevent_timer *te;
struct tevent_req *dpreq;
struct dp_callback_ctx *cb_ctx;
while (dom) {
while (dom && dom->enumerate == false) {
dom = get_next_domain(dom, true);
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting info for domain [%s]\n", dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EIO;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
step_ctx->returned_to_mainloop = true;
/* Only do this once per provider */
dctx->check_provider = false;
dpreq = sss_dp_get_account_send(step_ctx, rctx, dctx->domain, true,
SSS_DP_USER, NULL, 0, NULL);
if (!dpreq) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Enum Cache refresh for domain [%s] failed."
" Trying to return what we have in cache!\n",
dom->name);
} else {
cb_ctx = talloc_zero(step_ctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(dpreq);
return ENOMEM;
}
cb_ctx->callback = nss_cmd_setpwent_dp_callback;
cb_ctx->ptr = step_ctx;
cb_ctx->cctx = step_ctx->cctx;
cb_ctx->mem_ctx = step_ctx;
tevent_req_set_callback(dpreq, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
}
ret = sysdb_enumpwent_with_views(dctx, dom, &res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Enum from cache failed, skipping domain [%s]\n",
dom->name);
dom = get_next_domain(dom, true);
continue;
}
if (res->count == 0) {
DEBUG(SSSDBG_CONF_SETTINGS,
"Domain [%s] has no users, skipping.\n", dom->name);
dom = get_next_domain(dom, true);
continue;
}
nctx->pctx->doms = talloc_realloc(pctx, pctx->doms,
struct dom_ctx, pctx->num +1);
if (!pctx->doms) {
talloc_free(pctx);
nctx->pctx = NULL;
return ENOMEM;
}
nctx->pctx->doms[pctx->num].domain = dctx->domain;
nctx->pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res);
nctx->pctx->num++;
/* do not reply until all domain searches are done */
dom = get_next_domain(dom, true);
}
/* We've finished all our lookups
* The result object is now safe to read.
*/
nctx->pctx->ready = true;
/* Set up a lifetime timer for this result object
* We don't want this result object to outlive the
* enum cache refresh timeout
*/
tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0);
te = tevent_add_timer(rctx->ev, nctx->pctx, tv,
setpwent_result_timeout, nctx);
if (!te) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not set up life timer for setpwent result object. "
"Entries may become stale.\n");
}
/* Notify the waiting clients */
nss_setent_notify_done(nctx->pctx);
if (step_ctx->returned_to_mainloop) {
return EAGAIN;
} else {
return EOK;
}
}
static void setpwent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx);
DEBUG(SSSDBG_CRIT_FAILURE,
"setpwent result object has expired. Cleaning up.\n");
/* Free the passwd enumeration context.
* If additional getpwent requests come in, they will invoke
* an implicit setpwent and refresh the result object.
*/
talloc_zfree(nctx->pctx);
}
static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr)
{
struct setent_step_ctx *step_ctx =
talloc_get_type(ptr, struct setent_step_ctx);
int ret;
if (err_maj) {
DEBUG(SSSDBG_OP_FAILURE,
"Unable to get information from Data Provider\n"
"Error: %u, %u, %s\n"
"Will try to return what we have in cache\n",
(unsigned int)err_maj, (unsigned int)err_min, err_msg);
}
ret = nss_cmd_setpwent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) {
/* Notify any waiting processes of failure */
nss_setent_notify_error(step_ctx->nctx->pctx, ret);
}
}
static errno_t nss_cmd_setpwent_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static void nss_cmd_setpwent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setpwent_recv(req);
talloc_zfree(req);
if (ret == EOK || ret == ENOENT) {
/* Either we succeeded or no domains were eligible */
ret = sss_packet_new(cmdctx->cctx->creq, 0,
sss_packet_get_cmd(cmdctx->cctx->creq->in),
&cmdctx->cctx->creq->out);
if (ret == EOK) {
sss_cmd_done(cmdctx->cctx, cmdctx);
return;
}
}
/* Something bad happened */
nss_cmd_done(cmdctx, ret);
}
static void nss_cmd_implicit_setpwent_done(struct tevent_req *req);
static int nss_cmd_getpwent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for all accounts\n");
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
/* Save the current index and cursor locations
* If we end up calling setpwent implicitly, because the response object
* expired and has to be recreated, we want to resume from the same
* location.
*/
cmdctx->saved_dom_idx = cctx->pwent_dom_idx;
cmdctx->saved_cur = cctx->pwent_cur;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if(!nctx->pctx || !nctx->pctx->ready) {
/* Make sure we invoke setpwent if it hasn't been run or is still
* processing from another client
*/
req = nss_cmd_setpwent_send(cctx, cctx);
if (!req) {
return EIO;
}
tevent_req_set_callback(req, nss_cmd_implicit_setpwent_done, cmdctx);
return EOK;
}
return nss_cmd_getpwent_immediate(cmdctx);
}
static int nss_cmd_retpwent(struct cli_ctx *cctx, int num);
static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx)
{
struct cli_ctx *cctx = cmdctx->cctx;
uint8_t *body;
size_t blen;
uint32_t num;
int ret;
/* get max num of entries to return in one call */
sss_packet_get_body(cctx->creq->in, &body, &blen);
if (blen != sizeof(uint32_t)) {
return EINVAL;
}
SAFEALIGN_COPY_UINT32(&num, body, NULL);
/* create response packet */
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ret;
}
ret = nss_cmd_retpwent(cctx, num);
sss_packet_set_error(cctx->creq->out, ret);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static int nss_cmd_retpwent(struct cli_ctx *cctx, int num)
{
struct nss_ctx *nctx;
struct getent_ctx *pctx;
struct ldb_message **msgs = NULL;
struct dom_ctx *pdom = NULL;
int n = 0;
int ret = ENOENT;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if (!nctx->pctx) goto none;
pctx = nctx->pctx;
while (ret == ENOENT) {
if (cctx->pwent_dom_idx >= pctx->num) break;
pdom = &pctx->doms[cctx->pwent_dom_idx];
n = pdom->res->count - cctx->pwent_cur;
if (n <= 0 && (cctx->pwent_dom_idx+1 < pctx->num)) {
cctx->pwent_dom_idx++;
pdom = &pctx->doms[cctx->pwent_dom_idx];
n = pdom->res->count;
cctx->pwent_cur = 0;
}
if (!n) break;
if (n < 0) {
DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Negative difference"
"[%d - %d = %d]\n", pdom->res->count, cctx->pwent_cur, n);
DEBUG(SSSDBG_CRIT_FAILURE, "Domain: %d (total %d)\n",
cctx->pwent_dom_idx, pctx->num);
break;
}
if (n > num) n = num;
msgs = &(pdom->res->msgs[cctx->pwent_cur]);
ret = fill_pwent(cctx->creq->out, pdom->domain, nctx,
true, false, msgs, &n);
cctx->pwent_cur += n;
}
none:
if (ret == ENOENT) {
ret = sss_cmd_empty_packet(cctx->creq->out);
}
return ret;
}
static void nss_cmd_implicit_setpwent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setpwent_recv(req);
talloc_zfree(req);
/* ENOENT is acceptable, as it just means that there were no entries
* to be returned. This will be handled gracefully in nss_cmd_retpwent
* later.
*/
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Implicit setpwent failed with unexpected error [%d][%s]\n",
ret, strerror(ret));
NSS_CMD_FATAL_ERROR(cmdctx);
}
/* Restore the saved index and cursor locations */
cmdctx->cctx->pwent_dom_idx = cmdctx->saved_dom_idx;
cmdctx->cctx->pwent_cur = cmdctx->saved_cur;
ret = nss_cmd_getpwent_immediate(cmdctx);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Immediate retrieval failed with unexpected error "
"[%d][%s]\n", ret, strerror(ret));
NSS_CMD_FATAL_ERROR(cmdctx);
}
}
static int nss_cmd_endpwent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
int ret;
DEBUG(SSSDBG_CONF_SETTINGS, "Terminating request info for all accounts\n");
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
/* create response packet */
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ret;
}
if (nctx->pctx == NULL) goto done;
/* Reset the indices so that subsequent requests start at zero */
cctx->pwent_dom_idx = 0;
cctx->pwent_cur = 0;
done:
sss_cmd_done(cctx, NULL);
return EOK;
}
/****************************************************************************
* GROUP db related functions
***************************************************************************/
void nss_update_gr_memcache(struct nss_ctx *nctx)
{
struct sss_domain_info *dom;
struct ldb_result *res;
uint64_t exp;
struct sized_string key;
const char *id;
time_t now;
int ret;
int i;
now = time(NULL);
for (dom = nctx->rctx->domains; dom; dom = get_next_domain(dom, false)) {
ret = sysdb_enumgrent_with_views(nctx, dom, &res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to enumerate users for domain [%s]\n", dom->name);
continue;
}
for (i = 0; i < res->count; i++) {
exp = ldb_msg_find_attr_as_uint64(res->msgs[i],
SYSDB_CACHE_EXPIRE, 0);
if (exp >= now) {
continue;
}
/* names require more manipulation (build up fqname conditionally),
* but uidNumber is unique and always resolvable too, so we use
* that to update the cache, as it points to the same entry */
id = sss_view_ldb_msg_find_attr_as_string(dom, res->msgs[i],
SYSDB_GIDNUM, NULL);
if (!id) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to find gidNumber in %s.\n",
ldb_dn_get_linearized(res->msgs[i]->dn));
continue;
}
to_sized_string(&key, id);
ret = sss_mmap_cache_gr_invalidate(nctx->grp_mc_ctx, &key);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Internal failure in memory cache code: %d [%s]\n",
ret, strerror(ret));
}
}
talloc_zfree(res);
}
}
#define GID_ROFFSET 0
#define MNUM_ROFFSET sizeof(uint32_t)
#define STRS_ROFFSET 2*sizeof(uint32_t)
static int parse_member(TALLOC_CTX *mem_ctx, struct sss_domain_info *group_dom,
const char *member, struct sss_domain_info **_member_dom,
struct sized_string *_name, bool *_add_domain)
{
errno_t ret;
char *username;
char *domname;
const char *use_member;
struct sss_domain_info *member_dom;
bool add_domain;
ret = sss_parse_name(mem_ctx, group_dom->names, member, &domname, &username);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Could not parse [%s] into "
"name-value components.\n", member);
return ret;
}
add_domain = (!IS_SUBDOMAIN(group_dom) && group_dom->fqnames);
use_member = member;
member_dom = group_dom;
if (IS_SUBDOMAIN(group_dom) == false && domname != NULL) {
/* The group is stored in the parent domain, but the member comes from.
* a subdomain. No need to add the domain component, it's already
* present in the memberuid/ghost attribute
*/
add_domain = false;
}
if (IS_SUBDOMAIN(group_dom) == true && domname == NULL) {
/* The group is stored in a subdomain, but the member comes
* from the parent domain. Need to add the domain component
* of the parent domain
*/
add_domain = true;
use_member = username;
member_dom = group_dom->parent;
}
to_sized_string(_name, use_member);
*_add_domain = add_domain;
*_member_dom = member_dom;
return EOK;
}
static int fill_members(struct sss_packet *packet,
struct sss_domain_info *dom,
struct nss_ctx *nctx,
struct ldb_message_element *el,
size_t *_rzero,
size_t *_rsize,
int *_memnum)
{
int i, ret = EOK;
int memnum = *_memnum;
size_t rzero= *_rzero;
size_t rsize = *_rsize;
const char *tmpstr;
struct sized_string name;
TALLOC_CTX *tmp_ctx = NULL;
int nlen = 0;
uint8_t *body;
size_t blen;
const char *domain = dom->name;
bool add_domain;
struct sss_domain_info *member_dom;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
return ENOMEM;
}
sss_packet_get_body(packet, &body, &blen);
for (i = 0; i < el->num_values; i++) {
tmpstr = sss_get_cased_name(tmp_ctx, (char *)el->values[i].data,
dom->case_preserve);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_get_cased_name failed, skipping\n");
continue;
}
tmpstr = sss_replace_space(tmp_ctx, tmpstr,
nctx->rctx->override_space);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_replace_space failed\n");
ret = ENOMEM;
goto done;
}
if (nctx->filter_users_in_groups) {
ret = sss_ncache_check_user(nctx->ncache,
nctx->neg_timeout,
dom, tmpstr);
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Group [%s] member [%s@%s] filtered out!"
" (negative cache)\n",
(char *)&body[rzero+STRS_ROFFSET], tmpstr, domain);
continue;
}
}
ret = parse_member(tmp_ctx, dom, tmpstr, &member_dom, &name, &add_domain);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Could not process member %s, skipping\n", tmpstr);
continue;
}
if (add_domain) {
nlen = sss_fqname(NULL, 0, member_dom->names, member_dom,
name.str);
if (nlen >= 0) {
nlen += 1;
} else {
/* Other failures caught below */
nlen = 0;
}
} else {
nlen = name.len;
}
ret = sss_packet_grow(packet, nlen);
if (ret != EOK) {
goto done;
}
sss_packet_get_body(packet, &body, &blen);
if (add_domain) {
ret = sss_fqname((char *)&body[rzero + rsize], nlen,
member_dom->names, member_dom, name.str);
if (ret < 0 || ret != nlen - 1) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to generate a fully qualified name"
" for member [%s@%s] of group [%s]!"
" Skipping\n", name.str, domain,
(char *)&body[rzero+STRS_ROFFSET]);
/* reclaim space */
ret = sss_packet_shrink(packet, nlen);
if (ret != EOK) {
goto done;
}
continue;
}
} else {
memcpy(&body[rzero + rsize], name.str, name.len);
}
rsize += nlen;
memnum++;
}
ret = 0;
done:
*_memnum = memnum;
*_rzero = rzero;
*_rsize = rsize;
talloc_zfree(tmp_ctx);
return ret;
}
static int fill_grent(struct sss_packet *packet,
struct sss_domain_info *dom,
struct nss_ctx *nctx,
bool filter_groups, bool gr_mmap_cache,
struct ldb_message **msgs,
int *count)
{
struct ldb_message *msg;
struct ldb_message_element *el;
uint8_t *body;
size_t blen;
uint32_t gid;
const char *tmpstr;
const char *orig_name = NULL;
struct sized_string name;
struct sized_string pwfield;
struct sized_string fullname;
int fq_len = 0;
int i = 0;
int ret, num, memnum;
size_t rzero, rsize;
bool add_domain = (!IS_SUBDOMAIN(dom) && dom->fqnames);
const char *domain = dom->name;
TALLOC_CTX *tmp_ctx = NULL;
to_sized_string(&pwfield, nctx->pwfield);
num = 0;
/* first 2 fields (len and reserved), filled up later */
ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
if (ret != EOK) {
goto done;
}
sss_packet_get_body(packet, &body, &blen);
rzero = 2*sizeof(uint32_t);
rsize = 0;
for (i = 0; i < *count; i++) {
talloc_zfree(tmp_ctx);
tmp_ctx = talloc_new(NULL);
msg = msgs[i];
/* new group */
if (!ldb_msg_check_string_attribute(msg, "objectClass",
SYSDB_GROUP_CLASS)) {
DEBUG(SSSDBG_CRIT_FAILURE, "Wrong object (%s) found on stack!\n",
ldb_dn_get_linearized(msg->dn));
continue;
}
/* new result starts at end of previous result */
rzero += rsize;
rsize = 0;
/* find group name/gid */
/* start with an empty name for each iteration */
orig_name = NULL;
if (DOM_HAS_VIEWS(dom)) {
orig_name = ldb_msg_find_attr_as_string(msg,
OVERRIDE_PREFIX SYSDB_NAME,
NULL);
if (orig_name != NULL && IS_SUBDOMAIN(dom)) {
/* Override names are not fully qualified */
add_domain = true;
}
}
if (orig_name == NULL) {
orig_name = ldb_msg_find_attr_as_string(msg,
SYSDB_DEFAULT_OVERRIDE_NAME,
NULL);
if (orig_name == NULL) {
orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
}
}
gid = sss_view_ldb_msg_find_attr_as_uint64(dom, msg, SYSDB_GIDNUM, 0);
if (!orig_name || !gid) {
DEBUG(SSSDBG_OP_FAILURE,
"Incomplete group object for %s[%llu]! Skipping\n",
orig_name?orig_name:"<NULL>", (unsigned long long int)gid);
continue;
}
if (filter_groups) {
ret = sss_ncache_check_group(nctx->ncache,
nctx->neg_timeout, dom, orig_name);
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Group [%s@%s] filtered out! (negative cache)\n",
orig_name, domain);
continue;
}
}
tmpstr = sss_get_cased_name(tmp_ctx, orig_name, dom->case_preserve);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_get_cased_name failed, skipping\n");
continue;
}
tmpstr = sss_replace_space(tmp_ctx, tmpstr,
nctx->rctx->override_space);
if (tmpstr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_replace_space failed, skipping\n");
continue;
}
to_sized_string(&name, tmpstr);
/* fill in gid and name and set pointer for number of members */
rsize = STRS_ROFFSET + name.len + pwfield.len; /* name\0x\0 */
if (add_domain) {
fq_len = sss_fqname(NULL, 0, dom->names, dom, name.str);
if (fq_len >= 0) {
fq_len += 1;
rsize -= name.len;
rsize += fq_len;
} else {
/* Other failures caught below */
fq_len = 0;
}
}
ret = sss_packet_grow(packet, rsize);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
/* 0-3: 32bit number gid */
SAFEALIGN_SET_UINT32(&body[rzero+GID_ROFFSET], gid, NULL);
/* 4-7: 32bit unsigned number of members */
SAFEALIGN_SET_UINT32(&body[rzero+MNUM_ROFFSET], 0, NULL);
/* 8-X: sequence of strings (name, passwd, mem..) */
if (add_domain) {
ret = sss_fqname((char *)&body[rzero+STRS_ROFFSET], fq_len,
dom->names, dom, name.str);
if (ret < 0 || ret != fq_len - 1) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to generate a fully qualified name for"
" group [%s] in [%s]! Skipping\n", name.str, domain);
/* reclaim space */
ret = sss_packet_shrink(packet, rsize);
if (ret != EOK) {
num = 0;
goto done;
}
rsize = 0;
continue;
}
} else {
memcpy(&body[rzero+STRS_ROFFSET], name.str, name.len);
}
to_sized_string(&fullname, (const char *)&body[rzero+STRS_ROFFSET]);
/* group passwd field */
memcpy(&body[rzero+STRS_ROFFSET + fullname.len],
pwfield.str, pwfield.len);
memnum = 0;
if (!dom->ignore_group_members) {
el = sss_view_ldb_msg_find_element(dom, msg, SYSDB_MEMBERUID);
if (el) {
ret = fill_members(packet, dom, nctx, el, &rzero, &rsize,
&memnum);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
}
el = ldb_msg_find_element(msg, SYSDB_GHOST);
if (el) {
if (DOM_HAS_VIEWS(dom) && el->num_values != 0) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Domain has a view [%s] but group [%s] still has " \
"ghost members.\n", dom->view_name, orig_name);
num = 0;
goto done;
}
ret = fill_members(packet, dom, nctx, el, &rzero, &rsize,
&memnum);
if (ret != EOK) {
num = 0;
goto done;
}
sss_packet_get_body(packet, &body, &blen);
}
}
if (memnum) {
/* set num of members */
SAFEALIGN_SET_UINT32(&body[rzero+MNUM_ROFFSET], memnum, NULL);
}
num++;
if (gr_mmap_cache && nctx->grp_mc_ctx) {
/* body was reallocated, so fullname might be pointing to
* where body used to be, not where it is */
to_sized_string(&fullname, (const char *)&body[rzero+STRS_ROFFSET]);
ret = sss_mmap_cache_gr_store(&nctx->grp_mc_ctx,
&fullname, &pwfield, gid, memnum,
(char *)&body[rzero] + STRS_ROFFSET +
fullname.len + pwfield.len,
rsize - STRS_ROFFSET -
fullname.len - pwfield.len);
if (ret != EOK && ret != ENOMEM) {
DEBUG(SSSDBG_OP_FAILURE,
"Failed to store group %s(%s) in mmap cache!\n",
name.str, domain);
}
}
continue;
}
talloc_zfree(tmp_ctx);
done:
*count = i;
if (num == 0) {
/* if num is 0 most probably something went wrong,
* reset packet and return ENOENT */
ret = sss_packet_set_size(packet, 0);
if (ret != EOK) return ret;
return ENOENT;
}
SAFEALIGN_COPY_UINT32(body, &num, NULL); /* num results */
SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */
return EOK;
}
static int nss_cmd_getgr_send_reply(struct nss_dom_ctx *dctx, bool filter)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int i;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
i = dctx->res->count;
ret = fill_grent(cctx->creq->out,
dctx->domain,
nctx, filter, true,
dctx->res->msgs, &i);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
/* search for a group.
* Returns:
* ENOENT, if group is definitely not found
* EAGAIN, if group is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct nss_ctx *nctx;
int ret;
const char *extra_flag = NULL;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames) {
dom = get_next_domain(dom, false);
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(dctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
name = sss_reverse_replace_space(dctx, name,
nctx->rctx->override_space);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_reverse_replace_space failed\n");
return ENOMEM;
}
/* verify this group has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_group(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"Group [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name);
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(SSSDBG_CONF_SETTINGS,
"Requesting info for [%s@%s]\n", name, dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EIO;
}
ret = sysdb_getgrnam_with_views(cmdctx, dom, name, &dctx->res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"getgrnam call returned more than one result !?!\n");
sss_log(SSS_LOG_ERR,
"More groups have the same name [%s@%s] in SSSD cache. "
"SSSD will not work correctly.\n",
name, dom->name);
return ENOENT;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_group(nctx->ncache, false, dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set negcache for %s@%s\n",
name, dom->name);
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
if (dom) continue;
}
DEBUG(SSSDBG_OP_FAILURE, "No results for getgrnam call\n");
/* Group not found in ldb -> delete group from memory cache. */
ret = delete_entry_from_memcache(dctx->domain, name,
nctx->grp_mc_ctx);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Deleting group from memcache failed.\n");
}
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (DOM_HAS_VIEWS(dom) && dctx->res->count == 0) {
extra_flag = EXTRA_INPUT_MAYBE_WITH_VIEW;
} else {
extra_flag = NULL;
}
ret = check_cache(dctx, nctx, dctx->res, SSS_DP_GROUP, name, 0,
extra_flag, nss_cmd_getby_dp_callback, dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(SSSDBG_TRACE_FUNC,
"Returning info for group [%s@%s]\n", name, dom->name);
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
"No matching domain found for [%s], fail!\n", cmdctx->name);
return ENOENT;
}
static int nss_cmd_getgrnam(struct cli_ctx *cctx)
{
return nss_cmd_getbynam(SSS_NSS_GETGRNAM, cctx);
}
/* search for a gid.
* Returns:
* ENOENT, if gid is definitely not found
* EAGAIN, if gid is beeing fetched from backend via async operations
* EOK, if found
* anything else on a fatal error
*/
static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct nss_ctx *nctx;
int ret;
int err;
const char *extra_flag = NULL;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* check that the gid is valid for this domain */
if ((dom->id_min && (cmdctx->id < dom->id_min)) ||
(dom->id_max && (cmdctx->id > dom->id_max))) {
DEBUG(SSSDBG_CONF_SETTINGS,
"Gid [%"PRIu32"] does not exist in domain [%s]! "
"(id out of range)\n",
cmdctx->id, dom->name);
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
ret = ENOENT;
goto done;
}
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(SSSDBG_CONF_SETTINGS,
"Requesting info for [%"PRIu32"@%s]\n", cmdctx->id, dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
ret = EIO;
goto done;
}
ret = sysdb_getgrgid_with_views(cmdctx, dom, cmdctx->id, &dctx->res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (dctx->res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"getgrgid call returned more than one result !?!\n");
sss_log(SSS_LOG_ERR,
"More groups have the same GID [%"PRIu32"] in directory "
"server. SSSD will not work correctly.\n", cmdctx->id);
ret = ENOENT;
goto done;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
/* set negative cache only if not result of cache check */
DEBUG(SSSDBG_MINOR_FAILURE, "No results for getgrgid call\n");
ret = ENOENT;
goto done;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (DOM_HAS_VIEWS(dom) && dctx->res->count == 0) {
extra_flag = EXTRA_INPUT_MAYBE_WITH_VIEW;
} else {
extra_flag = NULL;
}
ret = check_cache(dctx, nctx, dctx->res, SSS_DP_GROUP, NULL,
cmdctx->id, extra_flag, nss_cmd_getby_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
goto done;
}
}
/* One result found */
DEBUG(SSSDBG_TRACE_FUNC,
"Returning info for gid [%"PRIu32"@%s]\n", cmdctx->id, dom->name);
/* Success. Break from the loop and return EOK */
ret = EOK;
goto done;
}
/* All domains were tried and none had the entry. */
ret = ENOENT;
done:
if (ret == ENOENT) {
/* The entry was not found, need to set result in negative cache */
err = sss_ncache_set_gid(nctx->ncache, false, cmdctx->id);
if (err != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negative cache for GID %"PRIu32"\n", cmdctx->id);
}
}
DEBUG(SSSDBG_MINOR_FAILURE, "No matching domain found for [%"PRIu32"]\n", cmdctx->id);
return ret;
}
static int nss_cmd_getgrgid(struct cli_ctx *cctx)
{
return nss_cmd_getbyid(SSS_NSS_GETGRGID, cctx);
}
/* to keep it simple at this stage we are retrieving the
* full enumeration again for each request for each process
* and we also block on setgrent() for the full time needed
* to retrieve the data. And endgrent() frees all the data.
* Next steps are:
* - use and nsssrv wide cache with data already structured
* so that it can be immediately returned (see nscd way)
* - use mutexes so that setgrent() can return immediately
* even if the data is still being fetched
* - make getgrent() wait on the mutex
*/
struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client);
static void nss_cmd_setgrent_done(struct tevent_req *req);
static int nss_cmd_setgrent(struct cli_ctx *cctx)
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
errno_t ret = EOK;
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
req = nss_cmd_setgrent_send(cmdctx, cctx);
if (!req) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal error calling nss_cmd_setgrent_send\n");
ret = EIO;
goto done;
}
tevent_req_set_callback(req, nss_cmd_setgrent_done, cmdctx);
done:
return nss_cmd_done(cmdctx, ret);
}
static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx);
struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx,
struct cli_ctx *client)
{
errno_t ret;
struct nss_ctx *nctx;
struct tevent_req *req;
struct setent_ctx *state;
struct sss_domain_info *dom;
struct setent_step_ctx *step_ctx;
DEBUG(SSSDBG_CONF_SETTINGS, "Received setgrent request\n");
nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx);
/* Reset the read pointers */
client->grent_dom_idx = 0;
client->grent_cur = 0;
req = tevent_req_create(mem_ctx, &state, struct setent_ctx);
if (!req) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not create tevent request for setgrent\n");
return NULL;
}
state->nctx = nctx;
state->client = client;
state->dctx = talloc_zero(state, struct nss_dom_ctx);
if (!state->dctx) {
ret = ENOMEM;
goto error;
}
/* check if enumeration is enabled in any domain */
for (dom = client->rctx->domains; dom; dom = get_next_domain(dom, true)) {
if (dom->enumerate == true) break;
}
state->dctx->domain = dom;
if (state->dctx->domain == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Enumeration disabled on all domains!\n");
ret = ENOENT;
goto error;
}
state->dctx->check_provider =
NEED_CHECK_PROVIDER(state->dctx->domain->provider);
/* Is the result context already available */
if (state->nctx->gctx) {
if (state->nctx->gctx->ready) {
/* All of the necessary data is in place
* We can return now, getgrent requests will work at this point
*/
tevent_req_done(req);
tevent_req_post(req, state->nctx->rctx->ev);
}
else {
/* Object is still being constructed
* Register for notification when it's
* ready.
*/
ret = nss_setent_add_ref(state, state->nctx->gctx, req);
if (ret != EOK) {
talloc_free(req);
return NULL;
}
}
return req;
}
/* Create a new result context
* We are creating it on the nss_ctx so that it doesn't
* go away if the original request does. We will delete
* it when the refcount goes to zero;
*/
state->nctx->gctx = talloc_zero(nctx, struct getent_ctx);
if (!state->nctx->gctx) {
ret = ENOMEM;
goto error;
}
state->getent_ctx = nctx->gctx;
/* Add a callback reference for ourselves */
ret = nss_setent_add_ref(state, state->nctx->gctx, req);
if (ret) goto error;
/* ok, start the searches */
step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx);
if (!step_ctx) {
ret = ENOMEM;
goto error;
}
/* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
* this request is canceled while other requests are in-progress.
*/
step_ctx->dctx = talloc_steal(step_ctx, state->dctx);
step_ctx->nctx = state->nctx;
step_ctx->getent_ctx = state->getent_ctx;
step_ctx->rctx = client->rctx;
step_ctx->cctx = client;
step_ctx->returned_to_mainloop = false;
ret = nss_cmd_setgrent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) goto error;
if (ret == EOK) {
tevent_req_post(req, client->rctx->ev);
}
return req;
error:
tevent_req_error(req, ret);
tevent_req_post(req, client->rctx->ev);
return req;
}
static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr);
static void setgrent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
/* nss_cmd_setgrent_step returns
* EOK if everything is done and the request needs to be posted explicitly
* EAGAIN if the caller can safely return to the main loop
*/
static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx)
{
errno_t ret;
struct sss_domain_info *dom = step_ctx->dctx->domain;
struct resp_ctx *rctx = step_ctx->rctx;
struct nss_dom_ctx *dctx = step_ctx->dctx;
struct getent_ctx *gctx = step_ctx->getent_ctx;
struct nss_ctx *nctx = step_ctx->nctx;
struct ldb_result *res;
struct timeval tv;
struct tevent_timer *te;
struct tevent_req *dpreq;
struct dp_callback_ctx *cb_ctx;
while (dom) {
while (dom && dom->enumerate == false) {
dom = get_next_domain(dom, true);
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting info for domain [%s]\n", dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EIO;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
step_ctx->returned_to_mainloop = true;
/* Only do this once per provider */
dctx->check_provider = false;
dpreq = sss_dp_get_account_send(step_ctx, rctx, dctx->domain, true,
SSS_DP_GROUP, NULL, 0, NULL);
if (!dpreq) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Enum Cache refresh for domain [%s] failed."
" Trying to return what we have in cache!\n",
dom->name);
} else {
cb_ctx = talloc_zero(step_ctx, struct dp_callback_ctx);
if(!cb_ctx) {
talloc_zfree(dpreq);
return ENOMEM;
}
cb_ctx->callback = nss_cmd_setgrent_dp_callback;
cb_ctx->ptr = step_ctx;
cb_ctx->cctx = step_ctx->cctx;
cb_ctx->mem_ctx = step_ctx;
tevent_req_set_callback(dpreq, nsssrv_dp_send_acct_req_done, cb_ctx);
return EAGAIN;
}
}
ret = sysdb_enumgrent_with_views(dctx, dom, &res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Enum from cache failed, skipping domain [%s]\n",
dom->name);
dom = get_next_domain(dom, true);
continue;
}
if (res->count == 0) {
DEBUG(SSSDBG_CONF_SETTINGS,
"Domain [%s] has no groups, skipping.\n", dom->name);
dom = get_next_domain(dom, true);
continue;
}
nctx->gctx->doms = talloc_realloc(gctx, gctx->doms,
struct dom_ctx, gctx->num +1);
if (!gctx->doms) {
talloc_free(gctx);
nctx->gctx = NULL;
return ENOMEM;
}
nctx->gctx->doms[gctx->num].domain = dctx->domain;
nctx->gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res);
nctx->gctx->num++;
/* do not reply until all domain searches are done */
dom = get_next_domain(dom, true);
}
/* We've finished all our lookups
* The result object is now safe to read.
*/
nctx->gctx->ready = true;
/* Set up a lifetime timer for this result object
* We don't want this result object to outlive the
* enum cache refresh timeout
*/
tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0);
te = tevent_add_timer(rctx->ev, nctx->gctx, tv,
setgrent_result_timeout, nctx);
if (!te) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not set up life timer for setgrent result object. "
"Entries may become stale.\n");
}
/* Notify the waiting clients */
nss_setent_notify_done(nctx->gctx);
if (step_ctx->returned_to_mainloop) {
return EAGAIN;
} else {
return EOK;
}
}
static void setgrent_result_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx);
DEBUG(SSSDBG_CRIT_FAILURE,
"setgrent result object has expired. Cleaning up.\n");
/* Free the group enumeration context.
* If additional getgrent requests come in, they will invoke
* an implicit setgrent and refresh the result object.
*/
talloc_zfree(nctx->gctx);
}
static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min,
const char *err_msg, void *ptr)
{
struct setent_step_ctx *step_ctx =
talloc_get_type(ptr, struct setent_step_ctx);
int ret;
if (err_maj) {
DEBUG(SSSDBG_OP_FAILURE,
"Unable to get information from Data Provider\n"
"Error: %u, %u, %s\n"
"Will try to return what we have in cache\n",
(unsigned int)err_maj, (unsigned int)err_min, err_msg);
}
ret = nss_cmd_setgrent_step(step_ctx);
if (ret != EOK && ret != EAGAIN) {
/* Notify any waiting processes of failure */
nss_setent_notify_error(step_ctx->nctx->gctx, ret);
}
}
static errno_t nss_cmd_setgrent_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static void nss_cmd_setgrent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setgrent_recv(req);
talloc_zfree(req);
if (ret == EOK || ret == ENOENT) {
/* Either we succeeded or no domains were eligible */
ret = sss_packet_new(cmdctx->cctx->creq, 0,
sss_packet_get_cmd(cmdctx->cctx->creq->in),
&cmdctx->cctx->creq->out);
if (ret == EOK) {
sss_cmd_done(cmdctx->cctx, cmdctx);
return;
}
}
/* Something bad happened */
nss_cmd_done(cmdctx, ret);
}
static int nss_cmd_retgrent(struct cli_ctx *cctx, int num)
{
struct nss_ctx *nctx;
struct getent_ctx *gctx;
struct ldb_message **msgs = NULL;
struct dom_ctx *gdom = NULL;
int n = 0;
int ret = ENOENT;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if (!nctx->gctx) goto none;
gctx = nctx->gctx;
while (ret == ENOENT) {
if (cctx->grent_dom_idx >= gctx->num) break;
gdom = &gctx->doms[cctx->grent_dom_idx];
n = gdom->res->count - cctx->grent_cur;
if (n <= 0 && (cctx->grent_dom_idx+1 < gctx->num)) {
cctx->grent_dom_idx++;
gdom = &gctx->doms[cctx->grent_dom_idx];
n = gdom->res->count;
cctx->grent_cur = 0;
}
if (!n) break;
if (n > num) n = num;
msgs = &(gdom->res->msgs[cctx->grent_cur]);
ret = fill_grent(cctx->creq->out,
gdom->domain,
nctx, true, false, msgs, &n);
cctx->grent_cur += n;
}
none:
if (ret == ENOENT) {
ret = sss_cmd_empty_packet(cctx->creq->out);
}
return ret;
}
static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx)
{
struct cli_ctx *cctx = cmdctx->cctx;
uint8_t *body;
size_t blen;
uint32_t num;
int ret;
/* get max num of entries to return in one call */
sss_packet_get_body(cctx->creq->in, &body, &blen);
if (blen != sizeof(uint32_t)) {
return EINVAL;
}
SAFEALIGN_COPY_UINT32(&num, body, NULL);
/* create response packet */
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ret;
}
ret = nss_cmd_retgrent(cctx, num);
sss_packet_set_error(cctx->creq->out, ret);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static void nss_cmd_implicit_setgrent_done(struct tevent_req *req);
static int nss_cmd_getgrent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for all groups\n");
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
/* Save the current index and cursor locations
* If we end up calling setgrent implicitly, because the response object
* expired and has to be recreated, we want to resume from the same
* location.
*/
cmdctx->saved_dom_idx = cctx->grent_dom_idx;
cmdctx->saved_cur = cctx->grent_cur;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
if(!nctx->gctx || !nctx->gctx->ready) {
/* Make sure we invoke setgrent if it hasn't been run or is still
* processing from another client
*/
req = nss_cmd_setgrent_send(cctx, cctx);
if (!req) {
return EIO;
}
tevent_req_set_callback(req, nss_cmd_implicit_setgrent_done, cmdctx);
return EOK;
}
return nss_cmd_getgrent_immediate(cmdctx);
}
static void nss_cmd_implicit_setgrent_done(struct tevent_req *req)
{
errno_t ret;
struct nss_cmd_ctx *cmdctx =
tevent_req_callback_data(req, struct nss_cmd_ctx);
ret = nss_cmd_setgrent_recv(req);
talloc_zfree(req);
/* ENOENT is acceptable, as it just means that there were no entries
* to be returned. This will be handled gracefully in nss_cmd_retpwent
* later.
*/
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Implicit setgrent failed with unexpected error [%d][%s]\n",
ret, strerror(ret));
NSS_CMD_FATAL_ERROR(cmdctx);
}
/* Restore the saved index and cursor locations */
cmdctx->cctx->grent_dom_idx = cmdctx->saved_dom_idx;
cmdctx->cctx->grent_cur = cmdctx->saved_cur;
ret = nss_cmd_getgrent_immediate(cmdctx);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Immediate retrieval failed with unexpected error "
"[%d][%s]\n", ret, strerror(ret));
NSS_CMD_FATAL_ERROR(cmdctx);
}
}
static int nss_cmd_endgrent(struct cli_ctx *cctx)
{
struct nss_ctx *nctx;
int ret;
DEBUG(SSSDBG_CONF_SETTINGS, "Terminating request info for all groups\n");
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
/* create response packet */
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ret;
}
if (nctx->gctx == NULL) goto done;
/* Reset the indices so that subsequent requests start at zero */
cctx->grent_dom_idx = 0;
cctx->grent_cur = 0;
done:
sss_cmd_done(cctx, NULL);
return EOK;
}
void nss_update_initgr_memcache(struct nss_ctx *nctx,
const char *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, false)) {
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);
ret = sysdb_initgroups(tmp_ctx, dom, name, &res);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache! [%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 */
to_sized_string(&delete_name, name);
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));
}
}
}
done:
talloc_free(tmp_ctx);
}
/* FIXME: what about mpg, should we return the user's GID ? */
/* FIXME: should we filter out GIDs ? */
static int fill_initgr(struct sss_packet *packet, struct sss_domain_info *dom,
struct ldb_result *res)
{
uint8_t *body;
size_t blen;
gid_t gid;
int ret, i, num;
size_t bindex;
int skipped = 0;
const char *posix;
gid_t orig_primary_gid;
if (res->count == 0) {
return ENOENT;
}
/* one less, the first one is the user entry */
num = res->count -1;
ret = sss_packet_grow(packet, (2 + res->count) * sizeof(uint32_t));
if (ret != EOK) {
return ret;
}
sss_packet_get_body(packet, &body, &blen);
orig_primary_gid = sss_view_ldb_msg_find_attr_as_uint64(dom, res->msgs[0],
SYSDB_PRIMARY_GROUP_GIDNUM,
0);
/* If the GID of the original primary group is available but equal to the
* current primary GID it must not be added. */
if (orig_primary_gid != 0) {
gid = sss_view_ldb_msg_find_attr_as_uint64(dom, res->msgs[0],
SYSDB_GIDNUM, 0);
if (orig_primary_gid == gid) {
orig_primary_gid = 0;
}
}
/* 0-3: 32bit unsigned number of results
* 4-7: 32bit unsigned (reserved/padding) */
bindex = 2 * sizeof(uint32_t);
/* skip first entry, it's the user entry */
for (i = 0; i < num; i++) {
gid = sss_view_ldb_msg_find_attr_as_uint64(dom, res->msgs[i + 1],
SYSDB_GIDNUM, 0);
posix = ldb_msg_find_attr_as_string(res->msgs[i + 1],
SYSDB_POSIX, NULL);
if (!gid) {
if (posix && strcmp(posix, "FALSE") == 0) {
skipped++;
continue;
} else {
DEBUG(SSSDBG_CRIT_FAILURE,
"Incomplete group object for initgroups! Aborting\n");
return EFAULT;
}
}
SAFEALIGN_COPY_UINT32(body + bindex, &gid, &bindex);
/* do not add the GID of the original primary group is the user is
* already and explicit member of the group. */
if (orig_primary_gid == gid) {
orig_primary_gid = 0;
}
}
if (orig_primary_gid != 0) {
SAFEALIGN_COPY_UINT32(body + bindex, &orig_primary_gid, &bindex);
num++;
}
SAFEALIGN_SETMEM_UINT32(body, num - skipped, NULL); /* num results */
SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */
blen = bindex;
ret = sss_packet_set_size(packet, blen);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Could not set packet size to value:%zu\n", blen);
return ret;
}
return EOK;
}
static int nss_cmd_initgr_send_reply(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
int ret;
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
ret = fill_initgr(cctx->creq->out, dctx->domain, dctx->res);
if (ret) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
char *name = NULL;
struct nss_ctx *nctx;
int ret;
static const char *user_attrs[] = SYSDB_PW_ATTRS;
struct ldb_message *msg;
const char *sysdb_name;
size_t c;
const char *extra_flag = NULL;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames
&& !cmdctx->name_is_upn) {
dom = get_next_domain(dom, false);
}
if (!dom) break;
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
talloc_free(name);
name = sss_get_cased_name(dctx, cmdctx->name, dom->case_sensitive);
if (!name) return ENOMEM;
name = sss_reverse_replace_space(dctx, name,
nctx->rctx->override_space);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_reverse_replace_space failed\n");
return ENOMEM;
}
/* verify this user has not yet been negatively cached,
* or has been permanently filtered */
ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
dom, name);
/* if neg cached, return we didn't find it */
if (ret == EEXIST) {
DEBUG(SSSDBG_TRACE_FUNC,
"User [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name);
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
return ENOENT;
}
DEBUG(SSSDBG_CONF_SETTINGS,
"Requesting info for [%s@%s]\n", name, dom->name);
if (dom->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EIO;
}
if (cmdctx->name_is_upn) {
ret = sysdb_search_user_by_upn(cmdctx, dom, name, user_attrs, &msg);
if (ret == ENOENT) {
dctx->res = talloc_zero(cmdctx, struct ldb_result);
if (dctx->res == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
return ENOMEM;
}
dctx->res->count = 0;
dctx->res->msgs = NULL;
ret = EOK;
} else if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn failed.\n");
return ret;
} else {
sysdb_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
if (sysdb_name == NULL) {
DEBUG(SSSDBG_OP_FAILURE,
"Sysdb entry does not have a name.\n");
return EINVAL;
}
ret = sysdb_initgroups(cmdctx, dom, sysdb_name, &dctx->res);
if (ret == EOK && DOM_HAS_VIEWS(dom)) {
for (c = 0; c < dctx->res->count; c++) {
ret = sysdb_add_overrides_to_object(dom, dctx->res->msgs[c],
NULL, NULL);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"sysdb_add_overrides_to_object failed.\n");
return ret;
}
}
}
}
} else {
ret = sysdb_initgroups_with_views(cmdctx, dom, name, &dctx->res);
}
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache! [%d][%s]\n",
ret, strerror(ret));
return EIO;
}
if (dctx->res->count == 0 && !dctx->check_provider) {
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_user(nctx->ncache, false, dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set negcache for %s@%s\n",
name, dom->name);
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
if (dom) continue;
}
DEBUG(SSSDBG_OP_FAILURE, "No results for initgroups call\n");
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (cmdctx->name_is_upn) {
extra_flag = EXTRA_NAME_IS_UPN;
} else if (DOM_HAS_VIEWS(dom) && dctx->res->count == 0) {
extra_flag = EXTRA_INPUT_MAYBE_WITH_VIEW;
} else {
extra_flag = NULL;
}
ret = check_cache(dctx, nctx, dctx->res, SSS_DP_INITGROUPS, name, 0,
extra_flag, nss_cmd_getby_dp_callback, dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
DEBUG(SSSDBG_TRACE_FUNC,
"Initgroups for [%s@%s] completed\n", name, dom->name);
return EOK;
}
DEBUG(SSSDBG_MINOR_FAILURE,
"No matching domain found for [%s], fail!\n", cmdctx->name);
return ENOENT;
}
/* for now, if we are online, try to always query the backend */
static int nss_cmd_initgroups(struct cli_ctx *cctx)
{
return nss_cmd_getbynam(SSS_NSS_INITGR, cctx);
}
static errno_t nss_cmd_getsidby_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
int err;
const char *default_attrs[] = {SYSDB_NAME, SYSDB_OBJECTCLASS, SYSDB_SID_STR,
ORIGINALAD_PREFIX SYSDB_NAME,
ORIGINALAD_PREFIX SYSDB_UIDNUM,
ORIGINALAD_PREFIX SYSDB_GIDNUM,
ORIGINALAD_PREFIX SYSDB_GECOS,
ORIGINALAD_PREFIX SYSDB_HOMEDIR,
ORIGINALAD_PREFIX SYSDB_SHELL,
SYSDB_UPN,
SYSDB_DEFAULT_OVERRIDE_NAME,
SYSDB_AD_ACCOUNT_EXPIRES,
SYSDB_AD_USER_ACCOUNT_CONTROL,
SYSDB_SSH_PUBKEY,
SYSDB_ORIG_DN,
SYSDB_ORIG_MEMBEROF,
SYSDB_DEFAULT_ATTRS, NULL};
const char **attrs;
bool user_found = false;
bool group_found = false;
struct ldb_message *msg = NULL;
char *sysdb_name = NULL;
char *name = NULL;
char *req_name;
uint32_t req_id;
enum sss_dp_acct_type req_type;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
while (dom) {
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
/* check that the uid is valid for this domain */
if ((dom->id_min && (cmdctx->id < dom->id_min)) ||
(dom->id_max && (cmdctx->id > dom->id_max))) {
DEBUG(SSSDBG_TRACE_FUNC,
"Uid [%"PRIu32"] does not exist in domain [%s]! "
"(id out of range)\n",
cmdctx->id, dom->name);
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
ret = ENOENT;
goto done;
}
} else {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (dom && cmdctx->check_next && dom->fqnames) {
dom = get_next_domain(dom, false);
}
if (!dom) break;
}
if (dom != dctx->domain) {
/* make sure we reset the check_provider flag when we check
* a new domain */
dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
}
/* make sure to update the dctx if we changed domain */
dctx->domain = dom;
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
DEBUG(SSSDBG_TRACE_FUNC, "Requesting info for [%"PRIu32"@%s]\n",
cmdctx->id, dom->name);
} else {
talloc_free(name);
talloc_zfree(sysdb_name);
name = sss_get_cased_name(cmdctx, cmdctx->name, dom->case_sensitive);
if (name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "sss_get_cased_name failed.\n");
ret = ENOMEM;
goto done;
}
name = sss_reverse_replace_space(dctx, name,
nctx->rctx->override_space);
if (name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sss_reverse_replace_space failed\n");
ret = ENOMEM;
goto done;
}
/* For subdomains a fully qualified name is needed for
* sysdb_search_user_by_name and sysdb_search_group_by_name. */
if (IS_SUBDOMAIN(dom)) {
sysdb_name = sss_tc_fqname(cmdctx, dom->names, dom, name);
if (sysdb_name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
ret = ENOMEM;
goto done;
}
}
/* verify this name has not yet been negatively cached, as user
* and groupm, or has been permanently filtered */
ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
dom, name);
if (ret == EEXIST) {
ret = sss_ncache_check_group(nctx->ncache, nctx->neg_timeout,
dom, name);
if (ret == EEXIST) {
/* if neg cached, return we didn't find it */
DEBUG(SSSDBG_TRACE_FUNC,
"SID [%s] does not exist in [%s]! (negative cache)\n",
name, dom->name);
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, false);
continue;
}
/* There are no further domains or this was a
* fully-qualified user request.
*/
ret = ENOENT;
goto done;
}
}
DEBUG(SSSDBG_TRACE_FUNC, "Requesting info for [%s@%s]\n",
name, dom->name);
}
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
ret = EIO;
goto done;
}
attrs = default_attrs;
if (cmdctx->cmd == SSS_NSS_GETORIGBYNAME
&& nctx->extra_attributes != NULL) {
ret = add_strings_lists(cmdctx, default_attrs,
nctx->extra_attributes, false,
discard_const(&attrs));
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "add_strings_lists failed.\n");
goto done;
}
}
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
ret = sysdb_search_user_by_uid(cmdctx, dom, cmdctx->id, attrs,
&msg);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (ret == EOK) {
user_found = true;
} else {
talloc_free(msg);
ret = sysdb_search_group_by_gid(cmdctx, dom, cmdctx->id, attrs,
&msg);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (ret == EOK) {
group_found = true;
}
}
} else {
ret = sysdb_search_user_by_name(cmdctx, dom,
sysdb_name ? sysdb_name : name,
attrs, &msg);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (ret == EOK) {
user_found = true;
} else {
talloc_free(msg);
ret = sysdb_search_group_by_name(cmdctx, dom,
sysdb_name ? sysdb_name : name,
attrs, &msg);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
ret = EIO;
goto done;
}
if (ret == EOK) {
group_found = true;
}
}
}
dctx->res = talloc_zero(cmdctx, struct ldb_result);
if (dctx->res == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
ret = ENOMEM;
goto done;
}
if (user_found || group_found) {
dctx->res->count = 1;
dctx->res->msgs = talloc_array(dctx->res, struct ldb_message *, 1);
if (dctx->res->msgs == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
ret = ENOMEM;
goto done;
}
dctx->res->msgs[0] = talloc_steal(dctx->res, msg);
}
if (dctx->res->count == 0 && !dctx->check_provider) {
if (cmdctx->cmd == SSS_NSS_GETSIDBYNAME
|| cmdctx->cmd == SSS_NSS_GETORIGBYNAME) {
ret = sss_ncache_set_user(nctx->ncache, false, dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negcache for %s@%s\n", name, dom->name);
}
ret = sss_ncache_set_group(nctx->ncache, false, dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negcache for %s@%s\n", name, dom->name);
}
}
/* if a multidomain search, try with next */
if (cmdctx->check_next) {
dom = get_next_domain(dom, true);
continue;
}
DEBUG(SSSDBG_OP_FAILURE, "No matching user or group found.\n");
ret = ENOENT;
goto done;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
req_name = NULL;
req_id = cmdctx->id;
} else {
req_name = name;
req_id = 0;
}
if (user_found) {
req_type = SSS_DP_USER;
} else if (group_found) {
req_type = SSS_DP_GROUP;
} else {
req_type = SSS_DP_USER_AND_GROUP;
}
ret = check_cache(dctx, nctx, dctx->res,
req_type, req_name, req_id, NULL,
nss_cmd_getby_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
goto done;
}
}
/* One result found */
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
DEBUG(SSSDBG_TRACE_FUNC, "Returning info for id [%"PRIu32"@%s]\n",
cmdctx->id, dom->name);
} else {
DEBUG(SSSDBG_TRACE_FUNC, "Returning info for user/group [%s@%s]\n",
name, dom->name);
}
/* Success. Break from the loop and return EOK */
ret = EOK;
goto done;
}
/* All domains were tried and none had the entry. */
ret = ENOENT;
done:
if (ret == ENOENT) {
/* The entry was not found, need to set result in negative cache */
if (cmdctx->cmd == SSS_NSS_GETSIDBYID) {
DEBUG(SSSDBG_MINOR_FAILURE,
"No matching domain found for [%"PRIu32"], fail!\n", cmdctx->id);
err = sss_ncache_set_uid(nctx->ncache, false, cmdctx->id);
if (err != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negative cache for UID %"PRIu32"\n", cmdctx->id);
}
err = sss_ncache_set_gid(nctx->ncache, false, cmdctx->id);
if (err != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negative cache for GID %"PRIu32"\n", cmdctx->id);
}
} else {
DEBUG(SSSDBG_MINOR_FAILURE,
"No matching domain found for [%s], fail!\n", cmdctx->name);
}
}
return ret;
}
static errno_t nss_cmd_getbysid_search(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct sss_domain_info *dom = dctx->domain;
struct cli_ctx *cctx = cmdctx->cctx;
struct sysdb_ctx *sysdb;
struct nss_ctx *nctx;
int ret;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
DEBUG(SSSDBG_TRACE_FUNC, "Requesting info for [%s@%s]\n", cmdctx->secid,
dom->name);
sysdb = dom->sysdb;
if (sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE, "Fatal: Sysdb CTX not found for this " \
"domain!\n");
return EIO;
}
ret = sysdb_search_object_by_sid(cmdctx, dom, cmdctx->secid, NULL,
&dctx->res);
if (ret == ENOENT) {
if (!dctx->check_provider) {
DEBUG(SSSDBG_OP_FAILURE, "No results for getbysid call.\n");
/* set negative cache only if not result of cache check */
ret = sss_ncache_set_sid(nctx->ncache, false, cmdctx->secid);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Cannot set negative cache for %s\n", cmdctx->secid);
}
return ENOENT;
}
dctx->res = talloc_zero(cmdctx, struct ldb_result);
if (dctx->res == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
return ENOMEM;
}
/* Fall through and call the backend */
} else if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to make request to our cache!\n");
return EIO;
}
if (dctx->res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE, "getbysid call returned more than one " \
"result !?!\n");
return ENOENT;
}
/* if this is a caching provider (or if we haven't checked the cache
* yet) then verify that the cache is uptodate */
if (dctx->check_provider) {
ret = check_cache(dctx, nctx, dctx->res,
SSS_DP_SECID, cmdctx->secid, 0, NULL,
nss_cmd_getby_dp_callback,
dctx);
if (ret != EOK) {
/* Anything but EOK means we should reenter the mainloop
* because we may be refreshing the cache
*/
return ret;
}
}
/* One result found */
DEBUG(SSSDBG_TRACE_FUNC, "Returning info for sid [%s@%s]\n", cmdctx->secid,
dom->name);
return EOK;
}
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_OBJECTCLASS);
if (el == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Objectclass 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 fill_sid(struct sss_packet *packet,
enum sss_id_type id_type,
struct ldb_message *msg)
{
int ret;
const char *sid_str;
struct sized_string sid;
uint8_t *body;
size_t blen;
size_t pctr = 0;
sid_str = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL);
if (sid_str == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID.\n");
return EINVAL;
}
to_sized_string(&sid, sid_str);
ret = sss_packet_grow(packet, 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, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* Num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_COPY_UINT32(body + pctr, &id_type, &pctr);
memcpy(&body[pctr], sid.str, sid.len);
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;
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;
}
}
for (d = 0; d < el->num_values; d++) {
to_sized_string(&keys[*found], attr_list[c]);
*sum += keys[*found].len;
val = &(el->values[d]);
if (val == NULL || 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;
}
static errno_t fill_orig(struct sss_packet *packet,
struct resp_ctx *rctx,
enum sss_id_type id_type,
struct ldb_message *msg)
{
int ret;
TALLOC_CTX *tmp_ctx;
uint8_t *body;
size_t blen;
size_t pctr = 0;
size_t c;
size_t sum;
size_t found;
size_t array_size;
size_t extra_attrs_count = 0;
const char **extra_attrs_list = NULL;
const char *orig_attr_list[] = {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_ORIG_DN,
SYSDB_ORIG_MEMBEROF,
NULL};
struct sized_string *keys;
struct sized_string *vals;
struct nss_ctx *nctx;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
return ENOMEM;
}
nctx = talloc_get_type(rctx->pvt_ctx, struct nss_ctx);
if (nctx->extra_attributes != NULL) {
extra_attrs_list = nctx->extra_attributes;
for(extra_attrs_count = 0;
extra_attrs_list[extra_attrs_count] != NULL;
extra_attrs_count++);
}
array_size = sizeof(orig_attr_list) + extra_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;
ret = process_attr_list(tmp_ctx, msg, orig_attr_list, &keys, &vals,
&array_size, &sum, &found);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "process_attr_list failed.\n");
goto done;
}
if (extra_attrs_count != 0) {
ret = process_attr_list(tmp_ctx, msg, extra_attrs_list, &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, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* Num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_COPY_UINT32(body + pctr, &id_type, &pctr);
for (c = 0; c < found; c++) {
memcpy(&body[pctr], keys[c].str, keys[c].len);
pctr+= keys[c].len;
memcpy(&body[pctr], vals[c].str, vals[c].len);
pctr+= vals[c].len;
}
ret = EOK;
done:
talloc_free(tmp_ctx);
return ret;
}
static errno_t fill_name(struct sss_packet *packet,
struct sss_domain_info *dom,
enum sss_id_type id_type,
bool apply_no_view,
struct ldb_message *msg)
{
int ret;
TALLOC_CTX *tmp_ctx = NULL;
const char *orig_name = NULL;
const char *cased_name;
const char *fq_name;
struct sized_string name;
bool add_domain = (!IS_SUBDOMAIN(dom) && dom->fqnames);
uint8_t *body;
size_t blen;
size_t pctr = 0;
if (apply_no_view) {
orig_name = ldb_msg_find_attr_as_string(msg,
ORIGINALAD_PREFIX SYSDB_NAME,
NULL);
} else {
if (DOM_HAS_VIEWS(dom)) {
orig_name = ldb_msg_find_attr_as_string(msg,
OVERRIDE_PREFIX SYSDB_NAME,
NULL);
if (orig_name != NULL && IS_SUBDOMAIN(dom)) {
/* Override names are un-qualified */
add_domain = true;
}
}
}
if (orig_name == NULL) {
orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
}
if (orig_name == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n");
return EINVAL;
}
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
return ENOMEM;
}
cased_name= sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive);
if (cased_name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "sss_get_cased_name failed.\n");
ret = ENOMEM;
goto done;
}
if (add_domain) {
fq_name = sss_tc_fqname(tmp_ctx, dom->names, dom, cased_name);
if (fq_name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
ret = ENOMEM;
goto done;
}
to_sized_string(&name, fq_name);
} else {
to_sized_string(&name, cased_name);
}
ret = sss_packet_grow(packet, name.len + 3 * sizeof(uint32_t));
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n");
goto done;
}
sss_packet_get_body(packet, &body, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* Num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_COPY_UINT32(body + pctr, &id_type, &pctr);
memcpy(&body[pctr], name.str, name.len);
ret = EOK;
done:
talloc_free(tmp_ctx);
return ret;
}
static errno_t fill_id(struct sss_packet *packet,
enum sss_id_type id_type,
struct ldb_message *msg)
{
int ret;
uint8_t *body;
size_t blen;
size_t pctr = 0;
uint64_t tmp_id;
uint32_t id;
if (id_type == SSS_ID_TYPE_GID) {
tmp_id = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
} else {
tmp_id = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
}
if (tmp_id == 0 || tmp_id >= UINT32_MAX) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid POSIX ID.\n");
return EINVAL;
}
id = (uint32_t) tmp_id;
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, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* Num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_COPY_UINT32(body + pctr, &id_type, &pctr);
SAFEALIGN_COPY_UINT32(body + pctr, &id, &pctr);
return EOK;
}
static errno_t nss_cmd_getbysid_send_reply(struct nss_dom_ctx *dctx)
{
struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
struct cli_ctx *cctx = cmdctx->cctx;
int ret;
enum sss_id_type id_type;
if (dctx->res->count > 1) {
return EINVAL;
} else if (dctx->res->count == 0) {
return ENOENT;
}
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return EFAULT;
}
ret = find_sss_id_type(dctx->res->msgs[0], dctx->domain->mpg, &id_type);
if (ret != 0) {
DEBUG(SSSDBG_OP_FAILURE, "find_sss_id_type failed.\n");
return ret;
}
switch(cmdctx->cmd) {
case SSS_NSS_GETNAMEBYSID:
ret = fill_name(cctx->creq->out,
dctx->domain,
id_type,
true,
dctx->res->msgs[0]);
break;
case SSS_NSS_GETIDBYSID:
ret = fill_id(cctx->creq->out, id_type, dctx->res->msgs[0]);
break;
case SSS_NSS_GETSIDBYNAME:
case SSS_NSS_GETSIDBYID:
ret = fill_sid(cctx->creq->out, id_type, dctx->res->msgs[0]);
break;
case SSS_NSS_GETORIGBYNAME:
ret = fill_orig(cctx->creq->out, cctx->rctx, id_type,
dctx->res->msgs[0]);
break;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported request type.\n");
return EINVAL;
}
if (ret != EOK) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static int nss_check_well_known_sid(struct nss_cmd_ctx *cmdctx)
{
const char *wk_name;
const char *wk_dom_name;
int ret;
char *fq_name = NULL;
struct sized_string name;
uint8_t *body;
size_t blen;
struct cli_ctx *cctx;
struct nss_ctx *nss_ctx;
size_t pctr = 0;
ret = well_known_sid_to_name(cmdctx->secid, &wk_dom_name, &wk_name);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_ALL, "SID [%s] is not a Well-Known SID.\n",
cmdctx->secid);
return ret;
}
if (cmdctx->cmd != SSS_NSS_GETNAMEBYSID) {
DEBUG(SSSDBG_TRACE_ALL,
"Well-Known SIDs can only be translated to names.\n");
return EINVAL;
}
if (wk_dom_name != NULL) {
nss_ctx = talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
fq_name = sss_tc_fqname2(cmdctx, nss_ctx->global_names,
wk_dom_name, wk_dom_name, wk_name);
if (fq_name == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "sss_tc_fqname2 failed.\n");
return ENOMEM;
}
to_sized_string(&name, fq_name);
} else {
to_sized_string(&name, wk_name);
}
cctx = cmdctx->cctx;
ret = sss_packet_new(cctx->creq, name.len + 3 * sizeof(uint32_t),
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
talloc_free(fq_name);
return ENOMEM;
}
sss_packet_get_body(cctx->creq->out, &body, &blen);
SAFEALIGN_SETMEM_UINT32(body, 1, &pctr); /* num results */
SAFEALIGN_SETMEM_UINT32(body + pctr, 0, &pctr); /* reserved */
SAFEALIGN_SETMEM_UINT32(body + pctr, SSS_ID_TYPE_GID, &pctr);
memcpy(&body[pctr], name.str, name.len);
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmdctx);
return EOK;
}
static int nss_cmd_getbysid(enum sss_cli_command cmd, struct cli_ctx *cctx)
{
struct tevent_req *req;
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
const char *sid_str;
uint8_t *body;
size_t blen;
int ret;
struct nss_ctx *nctx;
enum idmap_error_code err;
uint8_t *bin_sid = NULL;
size_t bin_sid_length;
if (cmd != SSS_NSS_GETNAMEBYSID && cmd != SSS_NSS_GETIDBYSID) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command type [%d].\n", cmd);
return EINVAL;
}
cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
if (!cmdctx) {
return ENOMEM;
}
cmdctx->cctx = cctx;
cmdctx->cmd = cmd;
dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
if (!dctx) {
ret = ENOMEM;
goto done;
}
dctx->cmdctx = cmdctx;
/* get SID to query */
sss_packet_get_body(cctx->creq->in, &body, &blen);
/* if not terminated fail */
if (body[blen -1] != '\0') {
ret = EINVAL;
goto done;
}
sid_str = (const char *) body;
nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
/* If the body isn't a SID, fail */
err = sss_idmap_sid_to_bin_sid(nctx->idmap_ctx, sid_str,
&bin_sid, &bin_sid_length);
sss_idmap_free_bin_sid(nctx->idmap_ctx, bin_sid);
if (err != IDMAP_SUCCESS) {
DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_sid_to_bin_sid failed for [%s].\n",
body);
ret = EINVAL;
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC, "Running command [%d] with SID [%s].\n",
dctx->cmdctx->cmd, sid_str);
cmdctx->secid = talloc_strdup(cmdctx, sid_str);
if (cmdctx->secid == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
ret = ENOMEM;
goto done;
}
ret = nss_check_well_known_sid(cmdctx);
if (ret != ENOENT) {
if (ret == EOK) {
DEBUG(SSSDBG_TRACE_ALL, "SID [%s] is a Well-Known SID.\n",
cmdctx->secid);
} else {
DEBUG(SSSDBG_OP_FAILURE, "nss_check_well_known_sid failed.\n");
}
goto done;
}
ret = responder_get_domain_by_id(cctx->rctx, cmdctx->secid, &dctx->domain);
if (ret == EAGAIN || ret == ENOENT) {
req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, NULL);
if (req == NULL) {
ret = ENOMEM;
} else {
dctx->rawname = sid_str;
tevent_req_set_callback(req, nss_cmd_getbyid_done, dctx);
ret = EAGAIN;
}
goto done;
} else if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "responder_get_domain_by_id failed.\n");
goto done;
}
DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for [%s] from [%s]\n",
cmdctx->secid, dctx->domain->name);
dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
/* ok, find it ! */
ret = nss_cmd_getbysid_search(dctx);
if (ret == EOK) {
ret = nss_cmd_getbysid_send_reply(dctx);
}
done:
return nss_cmd_done(cmdctx, ret);
}
static int nss_cmd_getsidbyname(struct cli_ctx *cctx)
{
return nss_cmd_getbynam(SSS_NSS_GETSIDBYNAME, cctx);
}
static int nss_cmd_getsidbyid(struct cli_ctx *cctx)
{
return nss_cmd_getbyid(SSS_NSS_GETSIDBYID, cctx);
}
static int nss_cmd_getnamebysid(struct cli_ctx *cctx)
{
return nss_cmd_getbysid(SSS_NSS_GETNAMEBYSID, cctx);
}
static int nss_cmd_getidbysid(struct cli_ctx *cctx)
{
return nss_cmd_getbysid(SSS_NSS_GETIDBYSID, cctx);
}
static int nss_cmd_getorigbyname(struct cli_ctx *cctx)
{
return nss_cmd_getbynam(SSS_NSS_GETORIGBYNAME, cctx);
}
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;
}
static struct sss_cmd_table nss_cmds[] = {
{SSS_GET_VERSION, sss_cmd_get_version},
{SSS_NSS_GETPWNAM, nss_cmd_getpwnam},
{SSS_NSS_GETPWUID, nss_cmd_getpwuid},
{SSS_NSS_SETPWENT, nss_cmd_setpwent},
{SSS_NSS_GETPWENT, nss_cmd_getpwent},
{SSS_NSS_ENDPWENT, nss_cmd_endpwent},
{SSS_NSS_GETGRNAM, nss_cmd_getgrnam},
{SSS_NSS_GETGRGID, nss_cmd_getgrgid},
{SSS_NSS_SETGRENT, nss_cmd_setgrent},
{SSS_NSS_GETGRENT, nss_cmd_getgrent},
{SSS_NSS_ENDGRENT, nss_cmd_endgrent},
{SSS_NSS_INITGR, nss_cmd_initgroups},
{SSS_NSS_SETNETGRENT, nss_cmd_setnetgrent},
{SSS_NSS_GETNETGRENT, nss_cmd_getnetgrent},
{SSS_NSS_ENDNETGRENT, nss_cmd_endnetgrent},
{SSS_NSS_GETSERVBYNAME, nss_cmd_getservbyname},
{SSS_NSS_GETSERVBYPORT, nss_cmd_getservbyport},
{SSS_NSS_SETSERVENT, nss_cmd_setservent},
{SSS_NSS_GETSERVENT, nss_cmd_getservent},
{SSS_NSS_ENDSERVENT, nss_cmd_endservent},
{SSS_NSS_GETSIDBYNAME, nss_cmd_getsidbyname},
{SSS_NSS_GETSIDBYID, nss_cmd_getsidbyid},
{SSS_NSS_GETNAMEBYSID, nss_cmd_getnamebysid},
{SSS_NSS_GETIDBYSID, nss_cmd_getidbysid},
{SSS_NSS_GETORIGBYNAME, nss_cmd_getorigbyname},
{SSS_CLI_NULL, NULL}
};
struct sss_cmd_table *get_nss_cmds(void) {
return nss_cmds;
}