/* 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 "confdb/confdb.h" #include "db/sysdb.h" #include struct nss_cmd_ctx { struct cli_ctx *cctx; const char *name; uint32_t id; bool immediate; bool done; int nr; }; 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!")); \ talloc_free(cctx); \ return; \ } while(0) int nss_parse_name(TALLOC_CTX *memctx, struct nss_ctx *nctx, const char *origname, const char **domain, const char **name) { pcre *re = nctx->parse_name_re; const char *errstr; const char *pattern = "(?[^@]+)@?(?[^@]*$)"; const char *result; int options; int errpos; int errval; int ovec[30]; int origlen; int ret, strnum; options = PCRE_DUPNAMES | PCRE_EXTENDED; if (!re) { re = pcre_compile2(pattern, options, &errval, &errstr, &errpos, NULL); if (!re) { DEBUG(1, ("Invalid Regular Expression pattern at position %d. (%d [%s])\n", errpos, errstr)); return EFAULT; } } nctx->parse_name_re = re; origlen = strlen(origname); options = PCRE_NOTEMPTY; ret = pcre_exec(re, NULL, origname, origlen, 0, options, ovec, 30); if (ret < 0) { DEBUG(2, ("PCRE Matching error, %d\n", ret)); return EINVAL; } if (ret == 0) { DEBUG(1, ("Too many matches, the pattern is invalid.\n")); } strnum = ret; result = NULL; ret = pcre_get_named_substring(re, origname, ovec, strnum, "name", &result); if (ret < 0 || !result) { DEBUG(2, ("Name not found!\n")); return EINVAL; } *name = talloc_steal(memctx, result); if (!*name) return ENOMEM; result = NULL; ret = pcre_get_named_substring(re, origname, ovec, strnum, "domain", &result); if (ret < 0 || !result) { DEBUG(4, ("Domain not provided!\n")); *domain = NULL; } else { /* ignore "" string */ if (*result) { *domain = talloc_steal(memctx, result); if (!*domain) return ENOMEM; } else { *domain = NULL; } } return EOK; } static int nss_dom_ctx_init(struct nss_dom_ctx *dctx, struct btreemap *domain_map, const char *domain) { struct sss_domain_info *info; /* Check for registered domain */ info = btreemap_get_value(domain_map, (void *)domain); if (!info) { return EINVAL; } dctx->domain = info; dctx->check_provider = (info->provider != NULL); return EOK; } /**************************************************************************** * PASSWD db related functions ***************************************************************************/ static int fill_pwent(struct sss_packet *packet, struct sss_domain_info *info, 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; size_t dom_len = 0; int i, ret, num; bool add_domain = info->fqnames; const char *domain = info->name; int ncret; if (add_domain) dom_len = strlen(domain) +1; /* first 2 fields (len and reserved), filled up later */ ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); 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 = nss_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; } } /* check that the uid is valid for this domain */ if ((info->id_min && (uid < info->id_min)) || (info->id_max && (uid > info->id_max))) { DEBUG(4, ("User [%s@%s] filtered out! (id out of range)\n", name, domain)); continue; } 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; rsize = 2*sizeof(uint32_t) +s1 + 2 + s2 + s3 +s4; if (add_domain) rsize += dom_len; ret = sss_packet_grow(packet, rsize); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); ((uint32_t *)(&body[rp]))[0] = uid; ((uint32_t *)(&body[rp]))[1] = gid; rp += 2*sizeof(uint32_t); memcpy(&body[rp], name, s1); rp += s1; if (add_domain) { body[rp-1] = NSS_DOMAIN_DELIM; memcpy(&body[rp], domain, dom_len); rp += dom_len; } memcpy(&body[rp], "x", 2); rp += 2; memcpy(&body[rp], gecos, s2); rp += s2; memcpy(&body[rp], homedir, s3); rp += s3; memcpy(&body[rp], shell, s4); rp += s4; num++; } done: sss_packet_get_body(packet, &body, &blen); ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* reserved */ return EOK; } static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static void nss_cmd_getpwnam_callback(void *ptr, int status, struct ldb_result *res) { 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; struct nss_ctx *nctx; int timeout; uint64_t lastUpdate; uint8_t *body; size_t blen; bool call_provider = false; bool neghit = false; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* one less to go */ cmdctx->nr--; /* check if another callback already replied */ if (cmdctx->done) { /* now check if this is the last callback */ if (cmdctx->nr == 0) { /* ok we are really done with this request */ goto done; } } if (status != LDB_SUCCESS) { ret = nss_cmd_send_error(cmdctx, status); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } if (dctx->check_provider) { switch (res->count) { case 0: call_provider = true; break; case 1: timeout = nctx->cache_timeout; lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); if (lastUpdate + timeout < time(NULL)) { call_provider = true; } break; default: DEBUG(1, ("getpwnam call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } } if (call_provider) { /* yet one more call to go */ cmdctx->nr++; /* 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 = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getpwnam_dp_callback, dctx, timeout, dctx->domain->name, NSS_DP_USER, cmdctx->name, 0); 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(cctx); } goto done; } return; } switch (res->count) { case 0: if (cmdctx->nr != 0) { /* nothing to do */ return; } DEBUG(2, ("No results for getpwnam call\n")); /* set negative cache only if not result of cache check */ if (!neghit) { ret = nss_ncache_set_user(nctx->ncache, false, dctx->domain->name, cmdctx->name); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ break; case 1: /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } ret = fill_pwent(cctx->creq->out, dctx->domain, nctx, false, res->msgs, res->count); sss_packet_set_error(cctx->creq->out, ret); break; default: DEBUG(1, ("getpwnam call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } done: if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } 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) { /* return 0 results */ dctx->res = talloc_zero(dctx, struct ldb_result); if (!dctx->res) { ret = ENOMEM; goto done; } } nss_cmd_getpwnam_callback(dctx, LDB_SUCCESS, dctx->res); return; } ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getpwnam_callback, dctx); done: if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache! (%d [%s])\n", ret, strerror(ret))); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_getpwnam(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct sss_domain_info *info; struct nss_ctx *nctx; const char **domains; const char *domname; const char *rawname; uint8_t *body; size_t blen; int ret, num, i; int ncret; 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; /* get user name to query */ sss_packet_get_body(cctx->creq->in, &body, &blen); /* if not terminated fail */ if (body[blen -1] != '\0') { talloc_free(cmdctx); ret = EINVAL; goto done; } rawname = (const char *)body; domname = NULL; ret = nss_parse_name(cmdctx, nctx, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname?domname:"")); if (domname) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domname, cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("User [%s] does not exist! (negative cache)\n", rawname)); ret = ENOENT; goto done; } dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; ret = nss_dom_ctx_init(dctx, cctx->rctx->domain_map, domname); if (ret != EOK) { DEBUG(2, ("Invalid domain name received [%s]\n", domname)); goto done; } cmdctx->nr = 1; ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getpwnam_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); } } else { dctx = NULL; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) goto done; cmdctx->nr = 0; for (i = 0; i < num; i++) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domains[i], cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("User [%s] does not exist! (neg cache)\n", rawname)); continue; } info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); /* skip domains that require FQnames */ if (info->fqnames) continue; cmdctx->nr++; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; dctx->domain = info; dctx->check_provider = (info->provider != NULL); DEBUG(4, ("Requesting info for [%s@%s]\n", cmdctx->name, dctx->domain->name)); ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getpwnam_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); /* shutdown ? */ goto done; } } } if (cmdctx->nr == 0) { ret = ENOENT; } done: if (ret != EOK) { if (ret == ENOENT) { /* we do not have any entry to return */ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ } } if (ret != EOK) { ret = nss_cmd_send_error(cmdctx, ret); } if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } return EOK; } static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static void nss_cmd_getpwuid_callback(void *ptr, int status, struct ldb_result *res) { 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; struct nss_ctx *nctx; int timeout; uint64_t lastUpdate; uint8_t *body; size_t blen; bool call_provider = false; bool neghit = false; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* one less to go */ cmdctx->nr--; /* check if another callback already replied */ if (cmdctx->done) { /* now check if this is the last callback */ if (cmdctx->nr == 0) { /* ok we are really done with this request */ goto done; } } if (status != LDB_SUCCESS) { ret = nss_cmd_send_error(cmdctx, status); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } if (dctx->check_provider) { switch (res->count) { case 0: call_provider = true; break; case 1: timeout = nctx->cache_timeout; lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); if (lastUpdate + timeout < time(NULL)) { call_provider = true; } break; default: DEBUG(1, ("getpwuid call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } } if (call_provider) { /* yet one more call to go */ cmdctx->nr++; /* 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 = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getpwuid_dp_callback, dctx, timeout, dctx->domain->name, NSS_DP_USER, NULL, cmdctx->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(cctx); } goto done; } return; } switch (res->count) { case 0: if (cmdctx->nr != 0) { /* nothing to do */ return; } DEBUG(2, ("No results for getpwuid call\n")); /* set negative cache only if not result of cache check */ if (!neghit) { ret = nss_ncache_set_uid(nctx->ncache, false, cmdctx->id); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ break; case 1: /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } ret = fill_pwent(cctx->creq->out, dctx->domain, nctx, true, res->msgs, res->count); sss_packet_set_error(cctx->creq->out, ret); break; default: DEBUG(1, ("getpwnam call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } done: if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } 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) { /* return 0 results */ dctx->res = talloc_zero(dctx, struct ldb_result); if (!dctx->res) { ret = ENOMEM; goto done; } } nss_cmd_getpwuid_callback(dctx, LDB_SUCCESS, dctx->res); return; } ret = sysdb_getpwuid(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->id, nss_cmd_getpwuid_callback, dctx); done: if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_getpwuid(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct sss_domain_info *info; struct nss_ctx *nctx; const char **domains; uint8_t *body; size_t blen; int i, num, ret; int ncret; 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; /* 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); /* FIXME: Just ask all backends for now, until we check for ranges */ dctx = NULL; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) { goto done; } cmdctx->nr = 0; for (i = 0; i < num; i++) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, cmdctx->id); if (ncret != ENOENT) { DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", (unsigned long)cmdctx->id)); continue; } info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); /* check that the uid is valid for this domain */ if ((info->id_min && (cmdctx->id < info->id_min)) || (info->id_max && (cmdctx->id > info->id_max))) { DEBUG(4, ("Uid [%lu] does not exist in domain [%s]! " "(id out of range)\n", (unsigned long)cmdctx->id, domains[i])); continue; } cmdctx->nr++; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; dctx->domain = info; dctx->check_provider = (info->provider != NULL); DEBUG(4, ("Requesting info for [%lu@%s]\n", cmdctx->id, dctx->domain->name)); ret = sysdb_getpwuid(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->id, nss_cmd_getpwuid_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); /* shutdown ? */ goto done; } } if (cmdctx->nr == 0) { ret = ENOENT; } done: if (ret != EOK) { if (ret == ENOENT) { /* we do not have any entry to return */ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ } } if (ret != EOK) { ret = nss_cmd_send_error(cmdctx, ret); } if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } return EOK; } /* 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 */ static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx); static void nss_cmd_setpwent_callback(void *ptr, int status, struct ldb_result *res) { 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; struct getent_ctx *pctx; struct nss_ctx *nctx; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); pctx = nctx->pctx; cmdctx->nr--; if (cmdctx->done) { /* do not reply until all domain searches are done */ if (cmdctx->nr != 0) return; else goto done; } if (status != LDB_SUCCESS) { /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_set_error(cctx->creq->out, status); cmdctx->done = true; return; } pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1); if (!pctx->doms) NSS_CMD_FATAL_ERROR(cctx); 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 */ if (cmdctx->nr) return; /* set cache mark */ nctx->last_user_enum = time(NULL); if (cmdctx->immediate) { /* this was a getpwent call w/o setpwent, * return immediately one result */ ret = nss_cmd_getpwent_immediate(cmdctx); if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx); return; } /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } done: sss_cmd_done(cctx, cmdctx); } static void nss_cmd_setpw_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 = sysdb_enumpwent(cmdctx, cctx->rctx->sysdb, dctx->domain, NULL, nss_cmd_setpwent_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_setpwent_ext(struct cli_ctx *cctx, bool immediate) { struct sss_domain_info *info; struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct getent_ctx *pctx; struct nss_ctx *nctx; const char **domains; time_t now = time(NULL); bool cached = false; int timeout; int i, ret, num; DEBUG(4, ("Requesting info for all users\n")); 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; talloc_free(nctx->pctx); pctx = talloc_zero(nctx, struct getent_ctx); if (!pctx) { talloc_free(cmdctx); return ENOMEM; } nctx->pctx = pctx; cmdctx->immediate = immediate; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) { return ret; } /* 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) { cached = true; } } /* check if enumeration is enabled in any domain */ for (i = 0; i < num; i++) { info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); if ((info->enumerate & NSS_ENUM_USERS) == 0) { continue; } /* TODO: enabled, check if we have a recent cached enumeration */ /* ok no cache, go and ask the backend to enumerate */ dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) return ENOMEM; dctx->cmdctx = cmdctx; dctx->domain = info; if (cached) { dctx->check_provider = false; } else { dctx->check_provider = (info->provider != NULL); } if (dctx->check_provider) { timeout = SSS_CLI_SOCKET_TIMEOUT/(i+2); ret = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_setpw_dp_callback, dctx, timeout, domains[i], NSS_DP_USER, NULL, 0); } else { ret = sysdb_enumpwent(dctx, cctx->rctx->sysdb, dctx->domain, NULL, nss_cmd_setpwent_callback, dctx); } if (ret != EOK) { /* FIXME: shutdown ? */ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", domains[i])); continue; } /* number of replies to wait for before setpwent is done */ cmdctx->nr++; } if (cmdctx->nr == 0) { /* 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, ret); sss_cmd_done(cctx, cmdctx); return EOK; } return 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; int n = 0; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); pctx = nctx->pctx; if (pctx->cur >= pctx->num) goto done; 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) goto done; if (n > num) n = num; msgs = &(pdom->res->msgs[pdom->cur]); pdom->cur += n; done: return fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, n); } /* used only if a process calls getpwent() without first calling setpwent() */ 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) { nctx->pctx = talloc_zero(nctx, struct getent_ctx); if (!nctx->pctx) return ENOMEM; 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 ***************************************************************************/ static int fill_grent(struct sss_packet *packet, struct sss_domain_info *info, struct nss_ctx *nctx, bool filter_groups, struct ldb_message **msgs, int count) { struct ldb_message_element *el; struct ldb_message *msg; uint8_t *body; const char *name; uint32_t gid, uid; size_t rsize, rp, blen, mnump; int i, j, n, ret, num, memnum; bool get_members; bool skip_members; size_t dom_len = 0; size_t name_len; bool add_domain = info->fqnames; const char *domain = info->name; int ncret; if (add_domain) dom_len = strlen(domain) +1; /* first 2 fields (len and reserved), filled up later */ ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); rp = 2*sizeof(uint32_t); num = 0; mnump = 0; get_members = false; for (i = 0; i < count; i++) { msg = msgs[i]; /* new group */ if (ldb_msg_check_string_attribute(msg, "objectClass", SYSDB_GROUP_CLASS)) { if (get_members) { /* this marks the end of a previous group */ sss_packet_get_body(packet, &body, &blen); ((uint32_t *)(&body[mnump]))[0] = memnum; /* num members */ get_members = false; } /* 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]! Aborting\n", name?name:"", (unsigned long long int)gid)); num = 0; goto done; } skip_members = false; if (filter_groups) { ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, domain, name); if (ncret == EEXIST) { DEBUG(4, ("Group [%s@%s] filtered out! (negative cache)\n", name, domain)); skip_members = true; continue; } } /* check that the gid is valid for this domain */ if ((info->id_min && (gid < info->id_min)) || (info->id_max && (gid > info->id_max))) { DEBUG(4, ("User [%s@%s] filtered out! (id out of range)\n", name, domain)); skip_members = true; continue; } /* fill in gid and name and set pointer for number of members */ name_len = strlen(name)+1; rsize = 2 * sizeof(uint32_t) + name_len +2; if (add_domain) rsize += dom_len; ret = sss_packet_grow(packet, rsize); sss_packet_get_body(packet, &body, &blen); /* 0-3: 64bit number gid */ rp = blen - rsize; ((uint32_t *)(&body[rp]))[0] = gid; rp += sizeof(uint32_t); /* 4-7: 32bit unsigned number of members */ ((uint32_t *)(&body[rp]))[0] = 0; /* init members num to 0 */ mnump = rp; /* keep around members num pointer to set later */ rp += sizeof(uint32_t); /* 8-X: sequence of strings (name, passwd, mem..) */ memcpy(&body[rp], name, name_len); rp += name_len; if (add_domain) { body[rp-1] = NSS_DOMAIN_DELIM; memcpy(&body[rp], domain, dom_len); rp += dom_len; } body[rp] = 'x'; /* group passwd field */ body[rp+1] = '\0'; memnum = 0; num++; /* legacy style group, members are in SYSDB_LEGACY_MEMBER */ el = ldb_msg_find_element(msg, SYSDB_LEGACY_MEMBER); if (el) { /* legacy */ memnum = el->num_values; n = 0; for (j = 0; j < memnum; j++) { ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domain, (char *)el->values[j].data); if (ncret == EEXIST) { DEBUG(4, ("User [%s@%s] filtered out! (negative cache)\n", name, domain)); continue; } rsize = el->values[j].length + 1; if (add_domain) { name_len = rsize; rsize += dom_len; } ret = sss_packet_grow(packet, rsize); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); rp = blen - rsize; memcpy(&body[rp], el->values[j].data, el->values[j].length); if (add_domain) { rp += name_len; body[rp-1] = NSS_DOMAIN_DELIM; memcpy(&body[rp], domain, dom_len); } body[blen-1] = '\0'; n++; } sss_packet_get_body(packet, &body, &blen); ((uint32_t *)(&body[mnump]))[0] = n; /* num members */ } else { get_members = true; } continue; } if (skip_members) continue; if (!get_members) { DEBUG(1, ("Wrong object found on stack! Aborting\n")); num = 0; goto done; } /* member */ if (ldb_msg_check_string_attribute(msg, "objectClass", SYSDB_USER_CLASS)) { name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); if (!name || !uid) { DEBUG(1, ("Incomplete user object! Aborting\n")); num = 0; goto done; } ncret = nss_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; } /* check that the uid is valid for this domain */ if ((info->id_min && (uid < info->id_min)) || (info->id_max && (uid > info->id_max))) { DEBUG(4, ("User [%s@%s] filtered out! (id out of range)\n", name, domain)); continue; } rsize = strlen(name) + 1; if (add_domain) { name_len = rsize; rsize += dom_len; } ret = sss_packet_grow(packet, rsize); if (ret != EOK) { num = 0; goto done; } sss_packet_get_body(packet, &body, &blen); rp = blen - rsize; memcpy(&body[rp], name, rsize); if (add_domain) { body[rp-1] = NSS_DOMAIN_DELIM; memcpy(&body[rp], domain, dom_len); rp += dom_len; } memnum++; continue; } DEBUG(1, ("Wrong object found on stack! Aborting\n")); num = 0; goto done; } if (mnump) { /* fill in the last group member count */ sss_packet_get_body(packet, &body, &blen); ((uint32_t *)(&body[mnump]))[0] = memnum; /* num members */ } done: sss_packet_get_body(packet, &body, &blen); ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* reserved */ return EOK; } static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static void nss_cmd_getgrnam_callback(void *ptr, int status, struct ldb_result *res) { 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; struct nss_ctx *nctx; int timeout; uint64_t lastUpdate; uint8_t *body; size_t blen; bool call_provider = false; bool neghit = false; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* one less to go */ cmdctx->nr--; /* check if another callback already replied */ if (cmdctx->done) { /* now check if this is the last callback */ if (cmdctx->nr == 0) { /* ok we are really done with this request */ goto done; } } if (status != LDB_SUCCESS) { ret = nss_cmd_send_error(cmdctx, status); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } if (dctx->check_provider) { switch (res->count) { case 0: call_provider = true; break; default: timeout = nctx->cache_timeout; lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); if (lastUpdate + timeout < time(NULL)) { call_provider = true; } } } if (call_provider) { /* yet one more call to go */ cmdctx->nr++; /* 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 = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getgrnam_dp_callback, dctx, timeout, dctx->domain->name, NSS_DP_GROUP, cmdctx->name, 0); 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(cctx); } goto done; } return; } switch (res->count) { case 0: if (cmdctx->nr != 0) { /* nothing to do */ return; } DEBUG(2, ("No results for getgrnam call\n")); /* set negative cache only if not result of cache check */ if (!neghit) { ret = nss_ncache_set_group(nctx->ncache, false, dctx->domain->name, cmdctx->name); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ break; default: DEBUG(6, ("Returning info for group [%s]\n", cmdctx->name)); /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } ret = fill_grent(cctx->creq->out, dctx->domain, nctx, false, res->msgs, res->count); sss_packet_set_error(cctx->creq->out, ret); } done: if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } 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) { /* return 0 results */ dctx->res = talloc_zero(dctx, struct ldb_result); if (!dctx->res) { ret = ENOMEM; goto done; } } nss_cmd_getgrnam_callback(dctx, LDB_SUCCESS, dctx->res); return; } ret = sysdb_getgrnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getgrnam_callback, dctx); done: if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_getgrnam(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct sss_domain_info *info; struct nss_ctx *nctx; const char **domains; const char *domname; const char *rawname; uint8_t *body; size_t blen; int ret, num, i; int ncret; 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; /* 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 = nss_parse_name(cmdctx, nctx, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname?domname:"")); if (domname) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, domname, cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("Group [%s] does not exist! (negative cache)\n", rawname)); ret = ENOENT; goto done; } dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; ret = nss_dom_ctx_init(dctx, cctx->rctx->domain_map, domname); if (ret != EOK) { DEBUG(2, ("Invalid domain name received [%s]\n", domname)); goto done; } cmdctx->nr = 1; ret = sysdb_getgrnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getgrnam_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); } } else { dctx = NULL; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) goto done; cmdctx->nr = 0; for (i = 0; i < num; i++) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, domains[i], cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("Group [%s] does not exist! (negative cache)\n", rawname)); continue; } info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); /* skip domains that require FQnames */ if (info->fqnames) continue; cmdctx->nr++; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; dctx->domain = info; dctx->check_provider = (info->provider != NULL); DEBUG(4, ("Requesting info for [%s@%s]\n", cmdctx->name, dctx->domain->name)); ret = sysdb_getgrnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getgrnam_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); /* shutdown ? */ goto done; } } } if (cmdctx->nr == 0) { ret = ENOENT; } done: if (ret != EOK) { if (ret == ENOENT) { /* we do not have any entry to return */ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ } } if (ret != EOK) { ret = nss_cmd_send_error(cmdctx, ret); } if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } return EOK; } static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static void nss_cmd_getgrgid_callback(void *ptr, int status, struct ldb_result *res) { 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; struct nss_ctx *nctx; int timeout; uint64_t lastUpdate; uint8_t *body; size_t blen; bool call_provider = false; bool neghit = false; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* one less to go */ cmdctx->nr--; /* check if another callback already replied */ if (cmdctx->done) { /* now check if this is the last callback */ if (cmdctx->nr == 0) { /* ok we are really done with this request */ goto done; } } if (status != LDB_SUCCESS) { ret = nss_cmd_send_error(cmdctx, status); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } if (dctx->check_provider) { switch (res->count) { case 0: call_provider = true; break; default: timeout = nctx->cache_timeout; lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); if (lastUpdate + timeout < time(NULL)) { call_provider = true; } } } if (call_provider) { /* yet one more call to go */ cmdctx->nr++; /* 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 = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getgrgid_dp_callback, dctx, timeout, dctx->domain->name, NSS_DP_GROUP, NULL, cmdctx->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(cctx); } goto done; } return; } switch (res->count) { case 0: if (cmdctx->nr != 0) { /* nothing to do */ return; } DEBUG(2, ("No results for getgrgid call\n")); /* set negative cache only if not result of cache check */ if (!neghit) { ret = nss_ncache_set_gid(nctx->ncache, false, cmdctx->id); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ break; default: DEBUG(6, ("Returning info for group [%u]\n", (unsigned)cmdctx->id)); /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } ret = fill_grent(cctx->creq->out, dctx->domain, nctx, true, res->msgs, res->count); sss_packet_set_error(cctx->creq->out, ret); } done: if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } 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) { /* return 0 results */ dctx->res = talloc_zero(dctx, struct ldb_result); if (!dctx->res) { ret = ENOMEM; goto done; } } nss_cmd_getgrgid_callback(dctx, LDB_SUCCESS, dctx->res); return; } ret = sysdb_getgrgid(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->id, nss_cmd_getgrgid_callback, dctx); done: if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_getgrgid(struct cli_ctx *cctx) { struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct sss_domain_info *info; struct nss_ctx *nctx; const char **domains; uint8_t *body; size_t blen; int i, num, ret; int ncret; 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; /* 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); /* FIXME: Just ask all backends for now, until we check for ranges */ dctx = NULL; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) { goto done; } cmdctx->nr = 0; for (i = 0; i < num; i++) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, cmdctx->id); if (ncret != ENOENT) { DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", (unsigned long)cmdctx->id)); continue; } info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); /* check that the uid is valid for this domain */ if ((info->id_min && (cmdctx->id < info->id_min)) || (info->id_max && (cmdctx->id > info->id_max))) { DEBUG(4, ("Gid [%lu] does not exist! (id out of range)\n", (unsigned long)cmdctx->id)); continue; } cmdctx->nr++; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; dctx->domain = info; dctx->check_provider = (info->provider != NULL); DEBUG(4, ("Requesting info for [%lu@%s]\n", cmdctx->id, dctx->domain->name)); ret = sysdb_getgrgid(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->id, nss_cmd_getgrgid_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); /* shutdown ? */ goto done; } } if (cmdctx->nr == 0) { ret = ENOENT; } done: if (ret != EOK) { if (ret == ENOENT) { /* we do not have any entry to return */ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ } } if (ret != EOK) { ret = nss_cmd_send_error(cmdctx, ret); } if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } return EOK; } /* 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_setgrent_callback(void *ptr, int status, struct ldb_result *res) { 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; struct getent_ctx *gctx; struct nss_ctx *nctx; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); gctx = nctx->gctx; cmdctx->nr--; if (cmdctx->done) { /* do not reply until all domain searches are done */ if (cmdctx->nr != 0) return; else goto done; } if (status != LDB_SUCCESS) { /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_set_error(cctx->creq->out, status); cmdctx->done = true; return; } gctx->doms = talloc_realloc(gctx, gctx->doms, struct dom_ctx, gctx->num +1); if (!gctx->doms) NSS_CMD_FATAL_ERROR(cctx); 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 */ if (cmdctx->nr) return; /* set cache mark */ nctx->last_group_enum = time(NULL); if (cmdctx->immediate) { /* this was a getgrent call w/o setgrent, * return immediately one result */ ret = nss_cmd_getgrent_immediate(cmdctx); if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx); return; } /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } done: sss_cmd_done(cctx, cmdctx); } static void nss_cmd_setgr_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 = sysdb_enumgrent(dctx, cctx->rctx->sysdb, dctx->domain, nss_cmd_setgrent_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static int nss_cmd_setgrent_ext(struct cli_ctx *cctx, bool immediate) { struct sss_domain_info *info; struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; struct getent_ctx *gctx; struct nss_ctx *nctx; const char **domains; time_t now = time(NULL); bool cached = false; int timeout; int i, ret, num; DEBUG(4, ("Requesting info for all groups\n")); 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; talloc_free(nctx->gctx); gctx = talloc_zero(nctx, struct getent_ctx); if (!gctx) { talloc_free(cmdctx); return ENOMEM; } nctx->gctx = gctx; cmdctx->immediate = immediate; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if(ret != EOK) { return ret; } /* 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) { cached = true; } } /* check if enumeration is enabled in any domain */ for (i = 0; i < num; i++) { info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); if ((info->enumerate & NSS_ENUM_GROUPS) == 0) { continue; } /* TODO: enabled, check if we have a recent cached enumeration */ /* ok no cache, go and ask the backend to enumerate */ dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) return ENOMEM; dctx->cmdctx = cmdctx; dctx->domain = info; if (cached) { dctx->check_provider = false; } else { dctx->check_provider = (info->provider != NULL); } if (dctx->check_provider) { timeout = SSS_CLI_SOCKET_TIMEOUT/(i+2); ret = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_setgr_dp_callback, dctx, timeout, domains[i], NSS_DP_GROUP, NULL, 0); } else { ret = sysdb_enumgrent(dctx, cctx->rctx->sysdb, dctx->domain, nss_cmd_setgrent_callback, dctx); } if (ret != EOK) { /* FIXME: shutdown ? */ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", domains[i])); continue; } cmdctx->nr++; } if (cmdctx->nr == 0) { /* 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, ret); sss_cmd_done(cctx, cmdctx); return EOK; } return 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; int n = 0; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); gctx = nctx->gctx; if (gctx->cur >= gctx->num) goto done; 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) goto done; if (n > num) n = num; msgs = &(gdom->res->msgs[gdom->cur]); gdom->cur += n; done: return fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, n); } /* used only if a process calls getpwent() without first calling setpwent() */ 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) { nctx->gctx = talloc_zero(nctx, struct getent_ctx); if (!nctx->gctx) return ENOMEM; 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; } static void nss_cmd_initgr_callback(void *ptr, int status, struct ldb_result *res) { struct nss_cmd_ctx *cmdctx = talloc_get_type(ptr, struct nss_cmd_ctx); struct cli_ctx *cctx = cmdctx->cctx; uint8_t *body; size_t blen; uint32_t gid; uint32_t num; int ret, i; /* create response packet */ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } if (status != LDB_SUCCESS) { sss_packet_set_error(cctx->creq->out, status); goto done; } num = res->count; ret = sss_packet_grow(cctx->creq->out, (2 + num) * sizeof(uint32_t)); if (ret != EOK) { sss_packet_set_error(cctx->creq->out, ret); goto done; } sss_packet_get_body(cctx->creq->out, &body, &blen); for (i = 0; i < num; i++) { gid = ldb_msg_find_attr_as_uint64(res->msgs[i], SYSDB_GIDNUM, 0); if (!gid) { DEBUG(1, ("Incomplete group object for initgroups! Aborting\n")); sss_packet_set_error(cctx->creq->out, EIO); num = 0; goto done; } ((uint32_t *)body)[2+i] = gid; } ((uint32_t *)body)[0] = num; /* num results */ ((uint32_t *)body)[1] = 0; /* reserved */ done: sss_cmd_done(cctx, cmdctx); } static void nss_cmd_getinitgr_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 = sysdb_initgroups(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_initgr_callback, cmdctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static void nss_cmd_getinit_callback(void *ptr, int status, struct ldb_result *res); static void nss_cmd_getinitnam_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) { /* return 0 results */ dctx->res = talloc_zero(dctx, struct ldb_result); if (!dctx->res) { ret = ENOMEM; goto done; } } nss_cmd_getinit_callback(dctx, LDB_SUCCESS, dctx->res); return; } ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getinit_callback, dctx); done: if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_cmd_done(cctx, cmdctx); } } static void nss_cmd_getinit_callback(void *ptr, int status, struct ldb_result *res) { 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; struct nss_ctx *nctx; int timeout; uint64_t lastUpdate; uint8_t *body; size_t blen; bool call_provider = false; bool neghit = false; int ret; nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); /* one less to go */ cmdctx->nr--; /* check if another callback already replied */ if (cmdctx->done) { /* now check if this is the last callback */ if (cmdctx->nr == 0) { /* ok we are really done with this request */ goto done; } } if (status != LDB_SUCCESS) { ret = nss_cmd_send_error(cmdctx, status); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } if (dctx->check_provider) { switch (res->count) { case 0: call_provider = true; break; case 1: timeout = nctx->cache_timeout; lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_LAST_UPDATE, 0); if (lastUpdate + timeout < time(NULL)) { call_provider = true; } break; default: DEBUG(1, ("getpwnam call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } goto done; } } if (call_provider) { /* yet one more call to go */ cmdctx->nr++; /* 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 = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getinitnam_dp_callback, dctx, timeout, dctx->domain->name, NSS_DP_USER, cmdctx->name, 0); 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(cctx); } goto done; } return; } switch (res->count) { case 0: if (cmdctx->nr != 0) { /* nothing to do */ return; } DEBUG(2, ("No results for initgroups call\n")); /* set negative cache only if not result of cache check */ if (!neghit) { ret = nss_ncache_set_user(nctx->ncache, false, dctx->domain->name, cmdctx->name); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ break; case 1: timeout = SSS_CLI_SOCKET_TIMEOUT/2; ret = nss_dp_send_acct_req(cctx->rctx, cmdctx, nss_cmd_getinitgr_callback, dctx, timeout, dctx->domain->name, NSS_DP_INITGROUPS, cmdctx->name, 0); 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(cctx); } goto done; } return; default: DEBUG(1, ("getpwnam call returned more than one result !?!\n")); ret = nss_cmd_send_error(cmdctx, ret); if (ret != EOK) { NSS_CMD_FATAL_ERROR(cctx); } } done: if (cmdctx->nr != 0) { cmdctx->done = true; /* signal that we are done */ return; } sss_cmd_done(cctx, cmdctx); } /* 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; struct sss_domain_info *info; struct nss_ctx *nctx; const char **domains; const char *domname; const char *rawname; uint8_t *body; size_t blen; int ret, num, i; int ncret; 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; /* 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 = nss_parse_name(cmdctx, nctx, rawname, &domname, &cmdctx->name); if (ret != EOK) { DEBUG(2, ("Invalid name received [%s]\n", rawname)); goto done; } DEBUG(4, ("Requesting info for [%s] from [%s]\n", cmdctx->name, domname ? : "")); if (domname) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domname, cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("User [%s] does not exist! (negative cache)\n", rawname)); ret = ENOENT; goto done; } dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; ret = nss_dom_ctx_init(dctx, cctx->rctx->domain_map, domname); if (ret != EOK) { DEBUG(2, ("Invalid domain name received [%s]\n", domname)); goto done; } cmdctx->nr = 1; ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getinit_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); } } else { dctx = NULL; domains = NULL; num = 0; /* get domains list */ ret = btreemap_get_keys(cmdctx, cctx->rctx->domain_map, (const void ***)&domains, &num); if (ret != EOK) goto done; cmdctx->nr = 0; for (i = 0; i < num; i++) { /* verify this user has not yet been negatively cached, * or has been permanently filtered */ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, domains[i], cmdctx->name); if (ncret != ENOENT) { DEBUG(3, ("User does not exist! (neg cache)\n")); continue; } info = btreemap_get_value(cctx->rctx->domain_map, domains[i]); /* skip domains that require FQnames */ if (info->fqnames) continue; cmdctx->nr++; dctx = talloc_zero(cmdctx, struct nss_dom_ctx); if (!dctx) { ret = ENOMEM; goto done; } dctx->cmdctx = cmdctx; dctx->domain = info; dctx->check_provider = (info->provider != NULL); DEBUG(4, ("Requesting info for [%s@%s]\n", cmdctx->name, dctx->domain->name)); ret = sysdb_getpwnam(cmdctx, cctx->rctx->sysdb, dctx->domain, cmdctx->name, nss_cmd_getinit_callback, dctx); if (ret != EOK) { DEBUG(1, ("Failed to make request to our cache!\n")); /* shutdown ? */ goto done; } } } if (cmdctx->nr == 0) { ret = ENOENT; } done: if (ret != EOK) { if (ret == ENOENT) { /* we do not have any entry to return */ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t), sss_packet_get_cmd(cctx->creq->in), &cctx->creq->out); if (ret == EOK) { sss_packet_get_body(cctx->creq->out, &body, &blen); ((uint32_t *)body)[0] = 0; /* 0 results */ ((uint32_t *)body)[1] = 0; /* reserved */ } } if (ret != EOK) { ret = nss_cmd_send_error(cmdctx, ret); } if (ret == EOK) { sss_cmd_done(cctx, cmdctx); } return ret; } return EOK; } 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; }