/* SSSD NSS Responder Copyright (C) Simo Sorce 2008 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "util/util.h" #include "responder/nss/nsssrv.h" #include "responder/common/negcache.h" #include "confdb/confdb.h" #include "db/sysdb.h" #include struct nss_cmd_ctx { struct cli_ctx *cctx; char *name; uint32_t id; bool immediate; bool check_next; bool enum_cached; }; struct dom_ctx { struct sss_domain_info *domain; struct ldb_result *res; int cur; }; struct getent_ctx { struct dom_ctx *doms; int num; int cur; }; struct nss_dom_ctx { struct nss_cmd_ctx *cmdctx; struct sss_domain_info *domain; bool check_provider; /* cache results */ struct ldb_result *res; }; static int nss_cmd_send_error(struct nss_cmd_ctx *cmdctx, int err) { struct cli_ctx *cctx = cmdctx->cctx; int ret; /* 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; } sss_packet_set_error(cctx->creq->out, err); return EOK; } #define NSS_CMD_FATAL_ERROR(cctx) do { \ DEBUG(1,("Fatal error, killing connection!\n")); \ talloc_free(cctx); \ return; \ } while(0) #define NSS_CMD_FATAL_ERROR_CODE(cctx, ret) do { \ DEBUG(1,("Fatal error, killing connection!\n")); \ talloc_free(cctx); \ return ret; \ } while(0) static struct sss_domain_info *nss_get_dom(struct sss_domain_info *doms, const char *domain) { struct sss_domain_info *dom; for (dom = doms; dom; dom = dom->next) { if (strcasecmp(dom->name, domain) == 0) break; } if (!dom) DEBUG(2, ("Unknown domain [%s]!\n", domain)); return dom; } static int fill_empty(struct sss_packet *packet) { uint8_t *body; size_t blen; int ret; ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); if (ret != EOK) return ret; sss_packet_get_body(packet, &body, &blen); ((uint32_t *)body)[0] = 0; /* num results */ ((uint32_t *)body)[1] = 0; /* reserved */ return EOK; } static int nss_cmd_send_empty(struct nss_cmd_ctx *cmdctx) { struct cli_ctx *cctx = cmdctx->cctx; int ret; /* 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 = fill_empty(cctx->creq->out); if (ret != EOK) { return ret; } sss_packet_set_error(cctx->creq->out, EOK); sss_cmd_done(cctx, cmdctx); return EOK; } static int nss_cmd_done(struct nss_cmd_ctx *cmdctx, int ret) { switch (ret) { case EOK: /* all fine, just return here */ break; case ENOENT: ret = nss_cmd_send_empty(cmdctx); if (ret) { return EFAULT; } break; case EAGAIN: /* async processing, just return here */ break; case EFAULT: /* very bad error */ return EFAULT; default: ret = nss_cmd_send_error(cmdctx, ret); if (ret) { return EFAULT; } sss_cmd_done(cmdctx->cctx, cmdctx); break; } return EOK; } /**************************************************************************** * PASSWD db related functions ***************************************************************************/ static int fill_pwent(struct sss_packet *packet, struct sss_domain_info *dom, struct nss_ctx *nctx, bool filter_users, struct ldb_message **msgs, int *count) { struct ldb_message *msg; uint8_t *body; const char *name; const char *gecos; const char *homedir; const char *shell; uint32_t uid; uint32_t gid; size_t rsize, rp, blen; size_t s1, s2, s3, s4, s5; size_t dom_len = 0; int delim = 1; int i, ret, num, t; bool add_domain = dom->fqnames; const char *domain = dom->name; const char *namefmt = nctx->rctx->names->fq_fmt; bool packet_initialized = false; int ncret; if (add_domain) dom_len = strlen(domain); rp = 2*sizeof(uint32_t); num = 0; for (i = 0; i < *count; i++) { msg = msgs[i]; name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); if (!name || !uid || !gid) { DEBUG(1, ("Incomplete user object for %s[%llu]! Skipping\n", name?name:"", (unsigned long long int)uid)); continue; } if (filter_users) { ncret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domain, name); if (ncret == EEXIST) { DEBUG(4, ("User [%s@%s] filtered out! (negative cache)\n", 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; } gecos = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL); homedir = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL); if (!gecos) gecos = ""; if (!homedir) homedir = "/"; if (!shell) shell = ""; s1 = strlen(name) + 1; s2 = strlen(gecos) + 1; s3 = strlen(homedir) + 1; s4 = strlen(shell) + 1; s5 = strlen(nctx->pwfield) + 1; if (add_domain) s1 += delim + dom_len; rsize = 2*sizeof(uint32_t) +s1 + s2 + s3 + s4 + s5; 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 = snprintf((char *)&body[rp], s1, namefmt, name, domain); if (ret >= s1) { /* need more space, got creative with the print format ? */ t = ret - s1 + 1; ret = sss_packet_grow(packet, t); if (ret != EOK) { num = 0; goto done; } delim += t; s1 += t; sss_packet_get_body(packet, &body, &blen); /* retry */ ret = snprintf((char *)&body[rp], s1, namefmt, name, domain); } if (ret != s1-1) { DEBUG(1, ("Failed to generate a fully qualified name for user " "[%s] in [%s]! Skipping user.\n", name, domain)); continue; } } else { memcpy(&body[rp], name, s1); } rp += s1; memcpy(&body[rp], nctx->pwfield, s5); rp += s5; memcpy(&body[rp], gecos, s2); rp += s2; memcpy(&body[rp], homedir, s3); rp += s3; memcpy(&body[rp], shell, s4); rp += s4; num++; } 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); ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* 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, dctx->res->msgs, &i); if (ret) { return ret; } sss_packet_set_error(cctx->creq->out, EOK); sss_cmd_done(cctx, cmdctx); return EOK; } /* FIXME: do not check res->count, but get in a msgs and check in parent */ /* FIXME: do not sss_cmd_done, but return error and let parent do it */ static errno_t check_cache(struct nss_dom_ctx *dctx, struct nss_ctx *nctx, struct ldb_result *res, int req_type, const char *opt_name, uint32_t opt_id, sss_dp_callback_t callback) { errno_t ret; int timeout; time_t now; uint64_t lastUpdate; uint64_t cacheExpire = 0; uint64_t midpoint_refresh; struct nss_cmd_ctx *cmdctx = dctx->cmdctx; struct cli_ctx *cctx = cmdctx->cctx; bool off_band_update = false; /* when searching for a user, more than one reply is a db error */ if ((req_type == SSS_DP_USER) && (res->count > 1)) { DEBUG(1, ("getpwXXX call returned more than one result!" " DB Corrupted?\n")); ret = nss_cmd_send_error(cmdctx, ENOENT); if (ret != EOK) { NSS_CMD_FATAL_ERROR_CODE(cctx, ENOENT); } sss_cmd_done(cctx, cmdctx); return ENOENT; } /* if we have any reply let's check cache validity */ if (res->count > 0) { now = time(NULL); lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); 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); } midpoint_refresh = 0; if(nctx->cache_refresh_percent) { midpoint_refresh = lastUpdate + (cacheExpire - lastUpdate)*nctx->cache_refresh_percent/100; if (midpoint_refresh - lastUpdate < 10) { /* If the percentage results in an expiration * less than ten seconds after the lastUpdate time, * that's too often we will simply set it to 10s */ midpoint_refresh = lastUpdate+10; } } if (cacheExpire > now) { /* cache still valid */ if (midpoint_refresh && midpoint_refresh < now) { /* We're past the the cache refresh timeout * We'll return the value from the cache, but we'll also * queue the cache entry for update out-of-band. */ DEBUG(6, ("Performing midpoint cache update on [%s]\n", opt_name)); off_band_update = true; } else { /* Cache is still valid. Just return it. */ return EOK; } } } if (off_band_update) { timeout = SSS_CLI_SOCKET_TIMEOUT/2; /* No callback required * This was an out-of-band update. We'll return EOK * so the calling function can return the cached entry * immediately. */ ret = sss_dp_send_acct_req(cctx->rctx, NULL, NULL, NULL, timeout, dctx->domain->name, true, req_type, opt_name, opt_id); if (ret != EOK) { DEBUG(3, ("Failed to dispatch request: %d(%s)\n", ret, strerror(ret))); } else { DEBUG(3, ("Updating cache out-of-band\n")); } } 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; timeout = SSS_CLI_SOCKET_TIMEOUT/2; /* keep around current data in case backend is offline */ if (res->count) { dctx->res = talloc_steal(dctx, res); } ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, callback, dctx, timeout, dctx->domain->name, true, req_type, opt_name, opt_id); if (ret != EOK) { DEBUG(3, ("Failed to dispatch request: %d(%s)\n", ret, strerror(ret))); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR_CODE(cctx, EIO); } sss_cmd_done(cctx, cmdctx); } return EAGAIN; } return EOK; } static void nss_cmd_getpwnam_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; const char *name = cmdctx->name; struct sysdb_ctx *sysdb; struct nss_ctx *nctx; int ret; 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 = dom->next; } 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; /* 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, name); /* if neg cached, return we didn't find it */ if (ret == EEXIST) { DEBUG(2, ("User [%s] does not exist! (negative cache)\n", name)); /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } } DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; } ret = sysdb_getpwnam(cmdctx, sysdb, dom, name, &dctx->res); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); return EIO; } if (dctx->res->count > 1) { DEBUG(0, ("getpwnam call returned more than one result !?!\n")); return ENOENT; } if (dctx->res->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } DEBUG(2, ("No results for getpwnam call\n")); /* set negative cache only if not result of cache check */ ret = sss_ncache_set_user(nctx->ncache, false, dom->name, name); if (ret != EOK) { return ret; } 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_USER, name, 0, nss_cmd_getpwnam_dp_callback); 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(6, ("Returning info for user [%s@%s]\n", name, dom->name)); return EOK; } DEBUG(2, ("No matching domain found for [%s], fail!\n", name)); return ENOENT; } static void nss_cmd_getpwnam_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; if (err_maj) { DEBUG(2, ("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) { ret = nss_cmd_getpw_send_reply(dctx, false); goto done; } /* no previous results, just loop to next domain if possible */ if (dctx->domain->next && cmdctx->check_next) { dctx->domain = dctx->domain->next; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); } else { /* nothing vailable */ ret = ENOENT; goto done; } } /* ok the backend returned, search to see if we have updated results */ ret = nss_cmd_getpwnam_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, false); } done: ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_getpwnam(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; const char *rawname; char *domname; uint8_t *body; size_t blen; int ret; cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; 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; } rawname = (const char *)body; domname = NULL; ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); ret = ENOENT; goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname?domname:"")); if (domname) { dctx->domain = nss_get_dom(cctx->rctx->domains, domname); if (!dctx->domain) { 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 ! */ ret = nss_cmd_getpwnam_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, false); } done: return nss_cmd_done(cmdctx, ret); } static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); /* 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 sysdb_ctx *sysdb; struct nss_ctx *nctx; int ret; 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(4, ("Uid [%lu] does not exist in domain [%s]! " "(id out of range)\n", (unsigned long)cmdctx->id, dom->name)); if (cmdctx->check_next) { dom = dom->next; continue; } return ENOENT; } 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(4, ("Requesting info for [%d@%s]\n", cmdctx->id, dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; } ret = sysdb_getpwuid(cmdctx, sysdb, dom, cmdctx->id, &dctx->res); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); return EIO; } if (dctx->res->count > 1) { DEBUG(0, ("getpwuid call returned more than one result !?!\n")); return ENOENT; } if (dctx->res->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } DEBUG(2, ("No results for getpwuid call\n")); /* set negative cache only if not result of cache check */ ret = sss_ncache_set_uid(nctx->ncache, false, cmdctx->id); if (ret != EOK) { return ret; } 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_USER, NULL, cmdctx->id, nss_cmd_getpwuid_dp_callback); 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(6, ("Returning info for uid [%d@%s]\n", cmdctx->id, dom->name)); return EOK; } DEBUG(2, ("No matching domain found for [%d], fail!\n", cmdctx->id)); return ENOENT; } static void nss_cmd_getpwuid_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; if (err_maj) { DEBUG(2, ("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) { ret = nss_cmd_getpw_send_reply(dctx, true); goto done; } /* no previous results, just loop to next domain if possible */ if (dctx->domain->next && cmdctx->check_next) { dctx->domain = dctx->domain->next; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); } else { /* nothing vailable */ ret = ENOENT; goto done; } } /* ok the backend returned, search to see if we have updated results */ ret = nss_cmd_getpwuid_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, true); } done: ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_getpwuid(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; 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; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; /* get uid to query */ sss_packet_get_body(cctx->creq->in, &body, &blen); if (blen != sizeof(uint32_t)) { ret = EINVAL; goto done; } cmdctx->id = *((uint32_t *)body); ret = sss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, cmdctx->id); if (ret == EEXIST) { DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", (unsigned long)cmdctx->id)); ret = ENOENT; goto done; } /* uid searches are always multidomain */ dctx->domain = cctx->rctx->domains; cmdctx->check_next = true; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); /* ok, find it ! */ ret = nss_cmd_getpwuid_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getpw_send_reply(dctx, true); } done: return 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); static void nss_cmd_getpwent_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static int nss_cmd_getpwent_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 ldb_result *res; struct getent_ctx *pctx; struct nss_ctx *nctx; int timeout; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); pctx = nctx->pctx; if (pctx == NULL) { pctx = talloc_zero(nctx, struct getent_ctx); if (!pctx) { return ENOMEM; } nctx->pctx = pctx; } while (dom) { while (dom && dom->enumerate == 0) { dom = dom->next; } if (!dom) break; if (dom != dctx->domain) { /* make sure we reset the check_provider flag when we check * a new domain */ if (cmdctx->enum_cached) { dctx->check_provider = false; } else { dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider); } } /* make sure to update the dctx if we changed domain */ dctx->domain = dom; DEBUG(4, ("Requesting info for domain [%s]\n", dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("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) { dctx->check_provider = false; timeout = SSS_CLI_SOCKET_TIMEOUT; ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getpwent_dp_callback, dctx, timeout, dom->name, true, SSS_DP_USER, NULL, 0); if (ret == EOK) { return ret; } else { DEBUG(2, ("Enum Cache refresh for domain [%s] failed." " Trying to return what we have in cache!\n", dom->name)); } } ret = sysdb_enumpwent(dctx, sysdb, dctx->domain, &res); if (ret != EOK) { DEBUG(1, ("Enum from cache failed, skipping domain [%s]\n", dom->name)); dom = dom->next; continue; } if (res->count == 0) { DEBUG(4, ("Domain [%s] has no users, skipping.\n", dom->name)); dom = dom->next; continue; } pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1); if (!pctx->doms) { talloc_free(pctx); nctx->pctx = NULL; return ENOMEM; } pctx->doms[pctx->num].domain = dctx->domain; pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res); pctx->doms[pctx->num].cur = 0; pctx->num++; /* do not reply until all domain searches are done */ dom = dom->next; } /* set cache mark */ nctx->last_user_enum = time(NULL); if (cmdctx->immediate) { /* this was a getpwent call w/o setpwent, * return immediately one result */ return nss_cmd_getpwent_immediate(cmdctx); } /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } static void nss_cmd_getpwent_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; if (err_maj) { DEBUG(2, ("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_getpwent_search(dctx); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_setpwent_ext(struct cli_ctx *cctx, bool immediate) { struct sss_domain_info *dom; struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct nss_ctx *nctx; time_t now = time(NULL); int ret; DEBUG(4, ("Requesting info for all users\n")); nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); talloc_free(nctx->pctx); nctx->pctx = NULL; cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; cmdctx->immediate = immediate; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; /* do not query backends if we have a recent enumeration */ if (nctx->enum_cache_timeout) { if (nctx->last_user_enum + nctx->enum_cache_timeout > now) { cmdctx->enum_cached = true; } } /* check if enumeration is enabled in any domain */ for (dom = cctx->rctx->domains; dom; dom = dom->next) { if (dom->enumerate != 0) break; } dctx->domain = dom; if (dctx->domain == NULL) { DEBUG(2, ("Enumeration disabled on all domains!\n")); if (cmdctx->immediate) { ret = ENOENT; } else { ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } } goto done; } dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); /* ok, start the searches */ ret = nss_cmd_getpwent_search(dctx); done: return nss_cmd_done(cmdctx, ret); } static int nss_cmd_setpwent(struct cli_ctx *cctx) { return nss_cmd_setpwent_ext(cctx, false); } 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 (pctx->cur >= pctx->num) break; pdom = &pctx->doms[pctx->cur]; n = pdom->res->count - pdom->cur; if (n == 0 && (pctx->cur+1 < pctx->num)) { pctx->cur++; pdom = &pctx->doms[pctx->cur]; n = pdom->res->count - pdom->cur; } if (!n) break; if (n > num) n = num; msgs = &(pdom->res->msgs[pdom->cur]); ret = fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, &n); pdom->cur += n; } none: if (ret == ENOENT) { ret = fill_empty(cctx->creq->out); } return ret; } 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; } num = *((uint32_t *)body); /* 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_getpwent(struct cli_ctx *cctx) { struct nss_ctx *nctx; struct nss_cmd_ctx *cmdctx; DEBUG(4, ("Requesting info for all accounts\n")); nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* see if we need to trigger an implicit setpwent() */ if (nctx->pctx == NULL) { return nss_cmd_setpwent_ext(cctx, true); } cmdctx = talloc(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; return nss_cmd_getpwent_immediate(cmdctx); } static int nss_cmd_endpwent(struct cli_ctx *cctx) { struct nss_ctx *nctx; int ret; DEBUG(4, ("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 (nctx->pctx == NULL) goto done; /* free results and reset */ talloc_free(nctx->pctx); nctx->pctx = NULL; done: sss_cmd_done(cctx, NULL); return EOK; } /**************************************************************************** * GROUP db related functions ***************************************************************************/ #define GID_ROFFSET 0 #define MNUM_ROFFSET sizeof(uint32_t) #define STRS_ROFFSET 2*sizeof(uint32_t) static int fill_grent(struct sss_packet *packet, struct sss_domain_info *dom, struct nss_ctx *nctx, bool filter_groups, 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 *name; size_t nsize; size_t delim; size_t dom_len; size_t pwlen; int i = 0; int j = 0; int ret, num, memnum; size_t rzero, rsize; bool add_domain = dom->fqnames; const char *domain = dom->name; const char *namefmt = nctx->rctx->names->fq_fmt; if (add_domain) { delim = 1; dom_len = strlen(domain); } else { delim = 0; dom_len = 0; } num = 0; pwlen = strlen(nctx->pwfield) + 1; /* 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++) { msg = msgs[i]; /* new group */ if (!ldb_msg_check_string_attribute(msg, "objectClass", SYSDB_GROUP_CLASS)) { DEBUG(1, ("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 */ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); if (!name || !gid) { DEBUG(1, ("Incomplete group object for %s[%llu]! Skipping\n", name?name:"", (unsigned long long int)gid)); continue; } if (filter_groups) { ret = sss_ncache_check_group(nctx->ncache, nctx->neg_timeout, domain, name); if (ret == EEXIST) { DEBUG(4, ("Group [%s@%s] filtered out! (negative cache)\n", name, domain)); continue; } } nsize = strlen(name) + 1; /* includes terminating \0 */ if (add_domain) nsize += delim + dom_len; /* fill in gid and name and set pointer for number of members */ rsize = STRS_ROFFSET + nsize + pwlen; /* name\0x\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 = snprintf((char *)&body[rzero+STRS_ROFFSET], nsize, namefmt, name, domain); if (ret >= nsize) { /* need more space, got creative with the print format ? */ int t = ret - nsize + 1; ret = sss_packet_grow(packet, t); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); rsize += t; delim += t; nsize += t; /* retry */ ret = snprintf((char *)&body[rzero+STRS_ROFFSET], nsize, namefmt, name, domain); } if (ret != nsize-1) { DEBUG(1, ("Failed to generate a fully qualified name for" " group [%s] in [%s]! Skipping\n", name, 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, nsize); } /* group passwd field */ memcpy(&body[rzero + rsize -pwlen], nctx->pwfield, pwlen); el = ldb_msg_find_element(msg, SYSDB_MEMBERUID); if (el) { memnum = 0; for (j = 0; j < el->num_values; j++) { name = (const char *)el->values[j].data; if (nctx->filter_users_in_groups) { ret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domain, name); if (ret == EEXIST) { DEBUG(6, ("Group [%s] member [%s@%s] filtered out!" " (negative cache)\n", (char *)&body[rzero+STRS_ROFFSET], name, domain)); continue; } } nsize = strlen(name) + 1; /* includes terminating \0 */ if (add_domain) nsize += delim + dom_len; ret = sss_packet_grow(packet, nsize); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); if (add_domain) { ret = snprintf((char *)&body[rzero + rsize], nsize, namefmt, name, domain); if (ret >= nsize) { /* need more space, * got creative with the print format ? */ int t = ret - nsize + 1; ret = sss_packet_grow(packet, t); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); delim += t; nsize += t; /* retry */ ret = snprintf((char *)&body[rzero + rsize], nsize, namefmt, name, domain); } if (ret != nsize-1) { DEBUG(1, ("Failed to generate a fully qualified name" " for member [%s@%s] of group [%s]!" " Skipping\n", name, domain, (char *)&body[rzero+STRS_ROFFSET])); /* reclaim space */ ret = sss_packet_shrink(packet, nsize); if (ret != EOK) { num = 0; goto done; } continue; } } else { memcpy(&body[rzero + rsize], name, nsize); } rsize += nsize; memnum++; } if (memnum) { /* set num of members */ SAFEALIGN_SET_UINT32(&body[rzero+MNUM_ROFFSET], memnum, NULL); } } num++; continue; } 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; } ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* 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, dctx->res->msgs, &i); if (ret) { return ret; } sss_packet_set_error(cctx->creq->out, EOK); sss_cmd_done(cctx, cmdctx); return EOK; } static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); /* 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; const char *name = cmdctx->name; struct sysdb_ctx *sysdb; struct nss_ctx *nctx; int ret; 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 = dom->next; } 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; /* 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, name); /* if neg cached, return we didn't find it */ if (ret == EEXIST) { DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", name)); /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } } DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; } ret = sysdb_getgrnam(cmdctx, sysdb, dom, name, &dctx->res); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); return EIO; } if (dctx->res->count > 1) { DEBUG(0, ("getgrnam call returned more than one result !?!\n")); return ENOENT; } if (dctx->res->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } DEBUG(2, ("No results for getgrnam call\n")); /* set negative cache only if not result of cache check */ ret = sss_ncache_set_group(nctx->ncache, false, dom->name, name); if (ret != EOK) { return ret; } 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_GROUP, name, 0, nss_cmd_getgrnam_dp_callback); 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(6, ("Returning info for group [%s@%s]\n", name, dom->name)); return EOK; } DEBUG(2, ("No matching domain found for [%s], fail!\n", name)); return ENOENT; } static void nss_cmd_getgrnam_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; if (err_maj) { DEBUG(2, ("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) { ret = nss_cmd_getgr_send_reply(dctx, false); goto done; } /* no previous results, just loop to next domain if possible */ if (dctx->domain->next && cmdctx->check_next) { dctx->domain = dctx->domain->next; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); } else { /* nothing vailable */ ret = ENOENT; goto done; } } /* ok the backend returned, search to see if we have updated results */ ret = nss_cmd_getgrnam_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getgr_send_reply(dctx, false); } done: ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_getgrnam(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; const char *rawname; char *domname; uint8_t *body; size_t blen; int ret; cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; 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; } rawname = (const char *)body; domname = NULL; ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); ret = ENOENT; goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname?domname:"")); if (domname) { dctx->domain = nss_get_dom(cctx->rctx->domains, domname); if (!dctx->domain) { 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 ! */ ret = nss_cmd_getgrnam_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getgr_send_reply(dctx, false); } done: return nss_cmd_done(cmdctx, ret); } static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); /* 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 sysdb_ctx *sysdb; struct nss_ctx *nctx; int ret; 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(4, ("Gid [%lu] does not exist in domain [%s]! " "(id out of range)\n", (unsigned long)cmdctx->id, dom->name)); if (cmdctx->check_next) { dom = dom->next; continue; } return ENOENT; } 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(4, ("Requesting info for [%d@%s]\n", cmdctx->id, dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; } ret = sysdb_getgrgid(cmdctx, sysdb, dom, cmdctx->id, &dctx->res); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); return EIO; } if (dctx->res->count > 1) { DEBUG(0, ("getgrgid call returned more than one result !?!\n")); return ENOENT; } if (dctx->res->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } DEBUG(2, ("No results for getgrgid call\n")); /* set negative cache only if not result of cache check */ ret = sss_ncache_set_gid(nctx->ncache, false, cmdctx->id); if (ret != EOK) { return ret; } 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_GROUP, NULL, cmdctx->id, nss_cmd_getgrgid_dp_callback); 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(6, ("Returning info for gid [%d@%s]\n", cmdctx->id, dom->name)); return EOK; } DEBUG(2, ("No matching domain found for [%d], fail!\n", cmdctx->id)); return ENOENT; } static void nss_cmd_getgrgid_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; if (err_maj) { DEBUG(2, ("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) { ret = nss_cmd_getgr_send_reply(dctx, true); goto done; } /* no previous results, just loop to next domain if possible */ if (dctx->domain->next && cmdctx->check_next) { dctx->domain = dctx->domain->next; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); } else { /* nothing vailable */ ret = ENOENT; goto done; } } /* ok the backend returned, search to see if we have updated results */ ret = nss_cmd_getgrgid_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getgr_send_reply(dctx, true); } done: ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_getgrgid(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; 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; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; /* get uid to query */ sss_packet_get_body(cctx->creq->in, &body, &blen); if (blen != sizeof(uint32_t)) { ret = EINVAL; goto done; } cmdctx->id = *((uint32_t *)body); ret = sss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, cmdctx->id); if (ret == EEXIST) { DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", (unsigned long)cmdctx->id)); ret = ENOENT; goto done; } /* gid searches are always multidomain */ dctx->domain = cctx->rctx->domains; cmdctx->check_next = true; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); /* ok, find it ! */ ret = nss_cmd_getgrgid_search(dctx); if (ret == EOK) { /* we have results to return */ ret = nss_cmd_getgr_send_reply(dctx, true); } done: return 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 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 */ static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx); static void nss_cmd_getgrent_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static int nss_cmd_getgrent_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 ldb_result *res; struct getent_ctx *gctx; struct nss_ctx *nctx; int timeout; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); gctx = nctx->gctx; if (gctx == NULL) { gctx = talloc_zero(nctx, struct getent_ctx); if (!gctx) { return ENOMEM; } nctx->gctx = gctx; } while (dom) { while (dom && dom->enumerate == 0) { dom = dom->next; } if (!dom) break; if (dom != dctx->domain) { /* make sure we reset the check_provider flag when we check * a new domain */ if (cmdctx->enum_cached) { dctx->check_provider = false; } else { dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider); } } /* make sure to update the dctx if we changed domain */ dctx->domain = dom; DEBUG(4, ("Requesting info for domain [%s]\n", dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("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) { dctx->check_provider = false; timeout = SSS_CLI_SOCKET_TIMEOUT; ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getgrent_dp_callback, dctx, timeout, dom->name, true, SSS_DP_GROUP, NULL, 0); if (ret == EOK) { return ret; } else { DEBUG(2, ("Enum Cache refresh for domain [%s] failed." " Trying to return what we have in cache!\n", dom->name)); } } ret = sysdb_enumgrent(dctx, sysdb, dctx->domain, &res); if (ret != EOK) { DEBUG(1, ("Enum from cache failed, skipping domain [%s]\n", dom->name)); dom = dom->next; continue; } if (res->count == 0) { DEBUG(4, ("Domain [%s] has no groups, skipping.\n", dom->name)); dom = dom->next; continue; } gctx->doms = talloc_realloc(gctx, gctx->doms, struct dom_ctx, gctx->num +1); if (!gctx->doms) { talloc_free(gctx); nctx->gctx = NULL; return ENOMEM; } gctx->doms[gctx->num].domain = dctx->domain; gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res); gctx->doms[gctx->num].cur = 0; gctx->num++; /* do not reply until all domain searches are done */ dom = dom->next; } /* set cache mark */ nctx->last_group_enum = time(NULL); if (cmdctx->immediate) { /* this was a getgrent call w/o setgrent, * return immediately one result */ return nss_cmd_getgrent_immediate(cmdctx); } /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } static void nss_cmd_getgrent_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; if (err_maj) { DEBUG(2, ("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_getgrent_search(dctx); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } static int nss_cmd_setgrent_ext(struct cli_ctx *cctx, bool immediate) { struct sss_domain_info *dom; struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct nss_ctx *nctx; time_t now = time(NULL); int ret; DEBUG(4, ("Requesting info for all groups\n")); nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); talloc_free(nctx->gctx); nctx->gctx = NULL; cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; cmdctx->immediate = immediate; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; /* do not query backends if we have a recent enumeration */ if (nctx->enum_cache_timeout) { if (nctx->last_group_enum + nctx->enum_cache_timeout > now) { cmdctx->enum_cached = true; } } /* check if enumeration is enabled in any domain */ for (dom = cctx->rctx->domains; dom; dom = dom->next) { if (dom->enumerate != 0) break; } dctx->domain = dom; if (dctx->domain == NULL) { DEBUG(2, ("Enumeration disabled on all domains!\n")); if (cmdctx->immediate) { ret = ENOENT; } else { ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } } goto done; } dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); /* ok, start the searches */ ret = nss_cmd_getgrent_search(dctx); done: return nss_cmd_done(cmdctx, ret); } static int nss_cmd_setgrent(struct cli_ctx *cctx) { return nss_cmd_setgrent_ext(cctx, false); } 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 (gctx->cur >= gctx->num) break; gdom = &gctx->doms[gctx->cur]; n = gdom->res->count - gdom->cur; if (n == 0 && (gctx->cur+1 < gctx->num)) { gctx->cur++; gdom = &gctx->doms[gctx->cur]; n = gdom->res->count - gdom->cur; } if (!n) break; if (n > num) n = num; msgs = &(gdom->res->msgs[gdom->cur]); ret = fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, &n); gdom->cur += n; } none: if (ret == ENOENT) { ret = fill_empty(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; } num = *((uint32_t *)body); /* 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 int nss_cmd_getgrent(struct cli_ctx *cctx) { struct nss_ctx *nctx; struct nss_cmd_ctx *cmdctx; DEBUG(4, ("Requesting info for all groups\n")); nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* see if we need to trigger an implicit setpwent() */ if (nctx->gctx == NULL) { return nss_cmd_setgrent_ext(cctx, true); } cmdctx = talloc(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; return nss_cmd_getgrent_immediate(cmdctx); } static int nss_cmd_endgrent(struct cli_ctx *cctx) { struct nss_ctx *nctx; int ret; DEBUG(4, ("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 (nctx->gctx == NULL) goto done; /* free results and reset */ talloc_free(nctx->gctx); nctx->gctx = NULL; done: sss_cmd_done(cctx, NULL); return EOK; } /* 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 ldb_result *res) { uint8_t *body; size_t blen; gid_t gid; int ret, i, num; 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); /* skip first entry, it's the user entry */ for (i = 0; i < num; i++) { gid = ldb_msg_find_attr_as_uint64(res->msgs[i + 1], SYSDB_GIDNUM, 0); if (!gid) { DEBUG(1, ("Incomplete group object for initgroups! Aborting\n")); return EFAULT; } ((uint32_t *)body)[2 + i] = gid; } ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* reserved */ 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; 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_initgr(cctx->creq->out, dctx->res); if (ret) { return ret; } sss_packet_set_error(cctx->creq->out, EOK); sss_cmd_done(cctx, cmdctx); return EOK; } static void nss_cmd_initgroups_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); 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; const char *name = cmdctx->name; struct sysdb_ctx *sysdb; struct nss_ctx *nctx; int ret; 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 = dom->next; } 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; /* 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, name); /* if neg cached, return we didn't find it */ if (ret == EEXIST) { DEBUG(2, ("User [%s] does not exist! (negative cache)\n", name)); /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } } DEBUG(4, ("Requesting info for [%s@%s]\n", name, dom->name)); ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; } ret = sysdb_initgroups(cmdctx, sysdb, dom, name, &dctx->res); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); return EIO; } if (dctx->res->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmdctx->check_next) { dom = dom->next; continue; } DEBUG(2, ("No results for initgroups call\n")); /* set negative cache only if not result of cache check */ ret = sss_ncache_set_user(nctx->ncache, false, dom->name, name); if (ret != EOK) { return ret; } 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_INITGROUPS, name, 0, nss_cmd_initgroups_dp_callback); if (ret != EOK) { /* Anything but EOK means we should reenter the mainloop * because we may be refreshing the cache */ return ret; } } DEBUG(6, ("Initgroups for [%s@%s] completed\n", name, dom->name)); return nss_cmd_initgr_send_reply(dctx); } DEBUG(2, ("No matching domain found for [%s], fail!\n", name)); return ENOENT; } static void nss_cmd_initgroups_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; if (err_maj) { DEBUG(2, ("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 != 0) { ret = nss_cmd_initgr_send_reply(dctx); goto done; } /* no previous results, just loop to next domain if possible */ if (dctx->domain->next && cmdctx->check_next) { dctx->domain = dctx->domain->next; dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); } else { /* nothing vailable */ ret = ENOENT; goto done; } } /* ok the backend returned, search to see if we have updated results */ ret = nss_cmd_initgroups_search(dctx); done: ret = nss_cmd_done(cmdctx, ret); if (ret) { NSS_CMD_FATAL_ERROR(cctx); } } /* for now, if we are online, try to always query the backend */ static int nss_cmd_initgroups(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; const char *rawname; char *domname; uint8_t *body; size_t blen; int ret; cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; 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; } rawname = (const char *)body; domname = NULL; ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); ret = ENOENT; goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname?domname:"")); if (domname) { dctx->domain = nss_get_dom(cctx->rctx->domains, domname); if (!dctx->domain) { 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 ! */ ret = nss_cmd_initgroups_search(dctx); done: return nss_cmd_done(cmdctx, ret); } 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_CLI_NULL, NULL} }; struct sss_cmd_table *get_nss_cmds(void) { return nss_cmds; } int nss_cmd_execute(struct cli_ctx *cctx) { enum sss_cli_command cmd; int i; cmd = sss_packet_get_cmd(cctx->creq->in); for (i = 0; nss_cmds[i].cmd != SSS_CLI_NULL; i++) { if (cmd == nss_cmds[i].cmd) { return nss_cmds[i].fn(cctx); } } return EINVAL; }