diff options
Diffstat (limited to 'src/responder/nss')
-rw-r--r-- | src/responder/nss/nsssrv.c | 367 | ||||
-rw-r--r-- | src/responder/nss/nsssrv.h | 70 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_cmd.c | 3182 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_nc.c | 321 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_nc.h | 51 |
5 files changed, 3991 insertions, 0 deletions
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c new file mode 100644 index 00000000..7de346f0 --- /dev/null +++ b/src/responder/nss/nsssrv.c @@ -0,0 +1,367 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> + +#include "popt.h" +#include "util/util.h" +#include "responder/nss/nsssrv.h" +#include "responder/nss/nsssrv_nc.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "monitor/monitor_interfaces.h" +#include "sbus/sbus_client.h" + +#define SSS_NSS_PIPE_NAME "nss" + +#define DEFAULT_PWFIELD "*" + +static int service_reload(DBusMessage *message, struct sbus_connection *conn); + +struct sbus_method monitor_nss_methods[] = { + { MON_CLI_METHOD_PING, monitor_common_pong }, + { MON_CLI_METHOD_RELOAD, service_reload }, + { MON_CLI_METHOD_RES_INIT, monitor_common_res_init }, + { NULL, NULL } +}; + +struct sbus_interface monitor_nss_interface = { + MONITOR_INTERFACE, + MONITOR_PATH, + SBUS_DEFAULT_VTABLE, + monitor_nss_methods, + NULL +}; + +static int service_reload(DBusMessage *message, struct sbus_connection *conn) +{ + /* Monitor calls this function when we need to reload + * our configuration information. Perform whatever steps + * are needed to update the configuration objects. + */ + + /* Send an empty reply to acknowledge receipt */ + return monitor_common_pong(message, conn); +} + +static int nss_get_config(struct nss_ctx *nctx, + struct resp_ctx *rctx, + struct confdb_ctx *cdb) +{ + TALLOC_CTX *tmpctx; + struct sss_domain_info *dom; + char *domain, *name; + char **filter_list; + int ret, i; + + tmpctx = talloc_new(nctx); + if (!tmpctx) return ENOMEM; + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENUM_CACHE_TIMEOUT, 120, + &nctx->enum_cache_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_NEG_TIMEOUT, 15, + &nctx->neg_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_bool(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS_IN_GROUPS, true, + &nctx->filter_users_in_groups); + if (ret != EOK) goto done; + + + ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE, 0, + &nctx->cache_refresh_percent); + if (ret != EOK) goto done; + if (nctx->cache_refresh_percent < 0 || + nctx->cache_refresh_percent > 99) { + DEBUG(0,("Configuration error: entry_cache_nowait_percentage is" + "invalid. Disabling feature.\n")); + nctx->cache_refresh_percent = 0; + } + + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS, &filter_list); + if (ret == ENOENT) { + filter_list = talloc_array(tmpctx, char *, 2); + filter_list[0] = talloc_strdup(tmpctx, "root"); + filter_list[1] = NULL; + if (!filter_list || !filter_list[0]) { + ret = ENOMEM; + goto done; + } + ret = EOK; + } + else if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name(tmpctx, nctx->rctx->names, + filter_list[i], &domain, &name); + if (ret != EOK) { + DEBUG(1, ("Invalid name in filterUsers list: [%s] (%d)\n", + filter_list[i], ret)); + continue; + } + if (domain) { + ret = nss_ncache_set_user(nctx->ncache, true, domain, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent user filter for [%s]" + " (%d [%s])\n", filter_list[i], + ret, strerror(ret))); + continue; + } + } else { + for (dom = rctx->domains; dom; dom = dom->next) { + ret = nss_ncache_set_user(nctx->ncache, true, dom->name, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent user filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret))); + continue; + } + } + } + } + + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_GROUPS, &filter_list); + if (ret == ENOENT) { + filter_list = talloc_array(tmpctx, char *, 2); + filter_list[0] = talloc_strdup(tmpctx, "root"); + filter_list[1] = NULL; + if (!filter_list || !filter_list[0]) { + ret = ENOMEM; + goto done; + } + ret = EOK; + } + else if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name(tmpctx, nctx->rctx->names, + filter_list[i], &domain, &name); + if (ret != EOK) { + DEBUG(1, ("Invalid name in filterGroups list: [%s] (%d)\n", + filter_list[i], ret)); + continue; + } + if (domain) { + ret = nss_ncache_set_group(nctx->ncache, true, domain, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent group filter for" + " [%s] (%d [%s])\n", filter_list[i], + ret, strerror(ret))); + continue; + } + } else { + for (dom = rctx->domains; dom; dom = dom->next) { + ret = nss_ncache_set_group(nctx->ncache, true, dom->name, name); + if (ret != EOK) { + DEBUG(1, ("Failed to store permanent group filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret))); + continue; + } + } + } + } + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_PWFIELD, DEFAULT_PWFIELD, + &nctx->pwfield); + if (ret != EOK) goto done; + + ret = 0; +done: + talloc_free(tmpctx); + return ret; +} + +static struct sbus_method nss_dp_methods[] = { + { NULL, NULL } +}; + +struct sbus_interface nss_dp_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + nss_dp_methods, + NULL +}; + + +static void nss_dp_reconnect_init(struct sbus_connection *conn, + int status, void *pvt) +{ + struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn); + int ret; + + /* Did we reconnect successfully? */ + if (status == SBUS_RECONNECT_SUCCESS) { + DEBUG(1, ("Reconnected to the Data Provider.\n")); + + /* Identify ourselves to the data provider */ + ret = dp_common_send_id(be_conn->conn, + DATA_PROVIDER_VERSION, + "NSS", be_conn->domain->name); + /* all fine */ + if (ret == EOK) return; + } + + /* Failed to reconnect */ + DEBUG(0, ("Could not reconnect to %s provider.\n", + be_conn->domain->name)); + + /* FIXME: kill the frontend and let the monitor restart it ? */ + /* nss_shutdown(rctx); */ +} + +int nss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct sss_cmd_table *nss_cmds; + struct be_conn *iter; + struct nss_ctx *nctx; + int ret, max_retries; + + nctx = talloc_zero(mem_ctx, struct nss_ctx); + if (!nctx) { + DEBUG(0, ("fatal error initializing nss_ctx\n")); + return ENOMEM; + } + + ret = nss_ncache_init(nctx, &nctx->ncache); + if (ret != EOK) { + DEBUG(0, ("fatal error initializing negative cache\n")); + return ret; + } + + nss_cmds = get_nss_cmds(); + + ret = sss_process_init(nctx, ev, cdb, + nss_cmds, + SSS_NSS_SOCKET_NAME, NULL, + CONFDB_NSS_CONF_ENTRY, + NSS_SBUS_SERVICE_NAME, + NSS_SBUS_SERVICE_VERSION, + &monitor_nss_interface, + "NSS", &nss_dp_interface, + &nctx->rctx); + if (ret != EOK) { + return ret; + } + nctx->rctx->pvt_ctx = nctx; + + ret = nss_get_config(nctx, nctx->rctx, cdb); + if (ret != EOK) { + DEBUG(0, ("fatal error getting nss config\n")); + return ret; + } + + /* Enable automatic reconnection to the Data Provider */ + ret = confdb_get_int(nctx->rctx->cdb, nctx->rctx, + CONFDB_NSS_CONF_ENTRY, + CONFDB_SERVICE_RECON_RETRIES, + 3, &max_retries); + if (ret != EOK) { + DEBUG(0, ("Failed to set up automatic reconnection\n")); + return ret; + } + + for (iter = nctx->rctx->be_conns; iter; iter = iter->next) { + sbus_reconnect_init(iter->conn, max_retries, + nss_dp_reconnect_init, iter); + } + + DEBUG(1, ("NSS Initialization complete\n")); + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + struct main_context *main_ctx; + int ret; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + { NULL } + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc... */ + debug_log_file = "sssd_nss"; + + ret = server_setup("sssd[nss]", 0, CONFDB_NSS_CONF_ENTRY, &main_ctx); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(2, ("Could not set up to exit when parent process does\n")); + } + + ret = nss_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h new file mode 100644 index 00000000..a6c66183 --- /dev/null +++ b/src/responder/nss/nsssrv.h @@ -0,0 +1,70 @@ +/* + SSSD + + NSS Responder, header file + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __NSSSRV_H__ +#define __NSSSRV_H__ + +#include <stdint.h> +#include <sys/un.h> +#include "config.h" +#include "talloc.h" +#include "tevent.h" +#include "ldb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/nss/nsssrv_nc.h" + +#define NSS_SBUS_SERVICE_VERSION 0x0001 +#define NSS_SBUS_SERVICE_NAME "nss" + +#define NSS_PACKET_MAX_RECV_SIZE 1024 + +struct getent_ctx; + +struct nss_ctx { + struct resp_ctx *rctx; + + int neg_timeout; + struct nss_nc_ctx *ncache; + + int cache_refresh_percent; + + int enum_cache_timeout; + time_t last_user_enum; + time_t last_group_enum; + + struct getent_ctx *pctx; + struct getent_ctx *gctx; + + bool filter_users_in_groups; + + char *pwfield; +}; + +struct nss_packet; + +int nss_cmd_execute(struct cli_ctx *cctx); + +struct sss_cmd_table *get_nss_cmds(void); + +#endif /* __NSSSRV_H__ */ diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c new file mode 100644 index 00000000..46d4a236 --- /dev/null +++ b/src/responder/nss/nsssrv_cmd.c @@ -0,0 +1,3182 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "responder/nss/nsssrv.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include <time.h> + +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!")); \ + talloc_free(cctx); \ + return; \ +} while(0) + +#define NSS_CMD_FATAL_ERROR_CODE(cctx, ret) do { \ + DEBUG(1,("Fatal error, killing connection!")); \ + 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; +} + +/**************************************************************************** + * 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:"<NULL>", (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; + } + } + + 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); + + ((uint32_t *)(&body[rp]))[0] = uid; + ((uint32_t *)(&body[rp]))[1] = gid; + rp += 2*sizeof(uint32_t); + + 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: + /* 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 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 EIO; + } + + 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); + +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 sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_USER, cmdctx->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; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getpwnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) 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: + DEBUG(6, ("Returning info for user [%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_pwent(cctx->creq->out, + dctx->domain, + nctx, false, + res->msgs, res->count); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + 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, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwnam(cmdctx, 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + 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 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:"<ALL>")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dctx->domain->name, cmdctx->name); + if (ncret == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getpwnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getpwnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ret; + int ncret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, 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; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + dom = dctx->domain->next; + ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(0, ("No matching domain found for [%lu], fail!\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwuid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getpwuid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) 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: + DEBUG(6, ("Returning info for user [%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_pwent(cctx->creq->out, + dctx->domain, + nctx, true, + res->msgs, res->count); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + 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, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } + + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getpwuid(cmdctx, 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); + } + 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + int ret; + int ncret; + + ret = ENOENT; + 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); + + /* this is a multidomain search */ + cmdctx->check_next = true; + + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + /* 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 == EEXIST) { + DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + continue; + } + + /* 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)); + continue; + } + + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + DEBUG(4, ("Requesting info for [%lu@%s]\n", + cmdctx->id, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getpwuid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getpwuid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + + break; + } + +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_setpw_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct getent_ctx *pctx; + struct nss_ctx *nctx; + int timeout; + int ret; + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + 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) { + ret = nss_cmd_send_error(cmdctx, ENOMEM); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + nctx->pctx = pctx; + } + + pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1); + if (!pctx->doms) { + talloc_free(pctx); + nctx->pctx = NULL; + 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 */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain != NULL) { + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setpw_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_USER, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumpwent(dctx, 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", + dom->name)); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } + 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); + } + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumpwent(cmdctx, 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct nss_ctx *nctx; + time_t now = time(NULL); + int timeout; + uint8_t *body; + size_t blen; + 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")); + ret = ENOENT; + goto done; + } + + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setpw_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_USER, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_enumpwent(dctx, 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", + dom->name)); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + if (cmdctx->immediate) { + /* 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 */ + } + } + else { + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +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; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + pctx = nctx->pctx; + +retry: + if (pctx->cur >= pctx->num) goto none; + + 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 none; + + if (n > num) n = num; + + msgs = &(pdom->res->msgs[pdom->cur]); + pdom->cur += n; + + ret = fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, n); + if (ret == ENOENT) goto retry; + return ret; + +none: + return fill_empty(cctx->creq->out); +} + +/* 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 + ***************************************************************************/ + +#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 max, 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; + } + + /* if we reached the max allowed entries, simply return */ + if (num >= max) { + goto done; + } + + /* 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:"<NULL>", (unsigned long long int)gid)); + continue; + } + + if (filter_groups) { + ret = nss_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 */ + ((uint32_t *)(&body[rzero+GID_ROFFSET]))[0] = gid; + + /* 4-7: 32bit unsigned number of members */ + ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = 0; + + /* 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 = nss_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 */ + ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = memnum; + } + } + + 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 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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int i, ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_GROUP, cmdctx->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; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_group(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) 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); + } + i = res->count; + ret = fill_grent(cctx->creq->out, + dctx->domain, + nctx, false, + res->msgs, 1, &i); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_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_getgrnam(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + 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 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:"<ALL>")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, + dctx->domain->name, cmdctx->name); + if (ncret == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getgrnam(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getgrnam_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int i, ret; + int ncret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, 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; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + dom = dctx->domain->next; + + ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout, + cmdctx->id); + if (ncret == EEXIST) { + DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(0, ("No matching domain found for [%lu], fail!\n", + (unsigned long)cmdctx->id)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrgid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getgrgid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) 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); + } + i = res->count; + ret = fill_grent(cctx->creq->out, + dctx->domain, + nctx, true, + res->msgs, 1, &i); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_getgrgid(cmdctx, 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + int ret; + int ncret; + + ret = ENOENT; + 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); + + /* this is a multidomain search */ + cmdctx->check_next = true; + + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + /* 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 == EEXIST) { + DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n", + (unsigned long)cmdctx->id)); + continue; + } + + /* 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, ("Gid [%lu] does not exist in domain [%s]! " + "(id out of range)\n", + (unsigned long)cmdctx->id, dom->name)); + continue; + } + + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + DEBUG(4, ("Requesting info for [%lu@%s]\n", + cmdctx->id, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_getgrgid(cmdctx, sysdb, + dctx->domain, cmdctx->id, + nss_cmd_getgrgid_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + + break; + } + +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_setgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct getent_ctx *gctx; + struct nss_ctx *nctx; + int timeout; + int ret; + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, ENOENT); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + 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) { + ret = nss_cmd_send_error(cmdctx, ENOMEM); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + nctx->gctx = gctx; + } + + 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 */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + dctx->domain = dom; + + if (dctx->domain != NULL) { + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setgr_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_GROUP, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumgrent(dctx, sysdb, + dctx->domain, + nss_cmd_setgrent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + + ret = nss_cmd_send_error(cmdctx, ret); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + } + 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); + } + 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; + struct sysdb_ctx *sysdb; + 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_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_enumgrent(dctx, 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_cmd_ctx *cmdctx; + struct nss_dom_ctx *dctx; + struct nss_ctx *nctx; + time_t now = time(NULL); + int timeout; + uint8_t *body; + size_t blen; + 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")); + ret = ENOENT; + goto done; + } + + if (cmdctx->enum_cached) { + dctx->check_provider = false; + } else { + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + } + + if (dctx->check_provider) { + timeout = SSS_CLI_SOCKET_TIMEOUT; + ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, + nss_cmd_setgr_dp_callback, dctx, + timeout, dom->name, true, + SSS_DP_GROUP, NULL, 0); + } else { + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_enumgrent(dctx, sysdb, + dctx->domain, + nss_cmd_setgrent_callback, dctx); + } + if (ret != EOK) { + /* FIXME: shutdown ? */ + DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n", + dom->name)); + } + +done: + if (ret != EOK) { + if (ret == ENOENT) { + if (cmdctx->immediate) { + /* 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 */ + } + } + else { + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + } + } + if (ret != EOK) { + ret = nss_cmd_send_error(cmdctx, ret); + } + if (ret == EOK) { + sss_cmd_done(cctx, cmdctx); + } + return ret; + } + + return EOK; +} + +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; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + gctx = nctx->gctx; + + do { + if (gctx->cur >= gctx->num) goto none; + + 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 none; + + msgs = &(gdom->res->msgs[gdom->cur]); + + ret = fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, num, &n); + + gdom->cur += n; + + } while(ret == ENOENT); + + return ret; + +none: + return fill_empty(cctx->creq->out); +} + +/* 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 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 void nss_cmd_getinitgr_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static void nss_cmd_getinitgr_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 sss_domain_info *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + uint8_t *body; + size_t blen; + bool neghit = false; + int ncret; + int ret; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + if (status != LDB_SUCCESS) { + ret = nss_cmd_send_error(cmdctx, status); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + sss_cmd_done(cctx, cmdctx); + return; + } + + if (dctx->check_provider) { + ret = check_cache(dctx, nctx, res, + SSS_DP_INITGROUPS, cmdctx->name, 0, + nss_cmd_getinitgr_dp_callback); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return; + } + } + + switch (res->count) { + case 0: + if (cmdctx->check_next) { + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = dctx->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + cmdctx->name)); + ret = ENOENT; + } + + if (ret == EOK) { + dctx->domain = dom; + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + if (dctx->res) talloc_free(res); + dctx->res = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) 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; + + default: + + DEBUG(6, ("Returning initgr for user [%s]\n", cmdctx->name)); + + 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_initgr(cctx->creq->out, res); + if (ret == ENOENT) { + ret = fill_empty(cctx->creq->out); + } + sss_packet_set_error(cctx->creq->out, ret); + } + + sss_cmd_done(cctx, cmdctx); +} + +static void nss_cmd_getinitgr_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; + struct sysdb_ctx *sysdb; + 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_getinitgr_callback(dctx, LDB_SUCCESS, dctx->res); + return; + } + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + NSS_CMD_FATAL_ERROR(cctx); + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_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); + } +} + +/* 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 *dom; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + const char *rawname; + char *domname; + uint8_t *body; + size_t blen; + int ret; + int ncret; + bool neghit = false; + + 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 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 ? : "<ALL>")); + + if (domname) { + dctx->domain = nss_get_dom(cctx->rctx->domains, domname); + if (!dctx->domain) { + ret = ENOENT; + goto done; + } + + /* 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 == EEXIST) { + neghit = true; + } + } + else { + /* skip domains that require FQnames or have negative caches */ + for (dom = cctx->rctx->domains; dom; dom = dom->next) { + + if (dom->fqnames) continue; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; + } + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + dctx->domain = dom; + } + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname)); + ret = ENOENT; + goto done; + } + if (dctx->domain == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname)); + ret = ENOENT; + goto done; + } + + dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + + if (!domname) { + /* this is a multidomain search */ + cmdctx->check_next = true; + } + + DEBUG(4, ("Requesting info for [%s@%s]\n", + cmdctx->name, dctx->domain->name)); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + dctx->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + ret = EFAULT; + goto done; + } + ret = sysdb_initgroups(cmdctx, sysdb, + dctx->domain, cmdctx->name, + nss_cmd_getinitgr_callback, dctx); + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + +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; +} + +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; +} + diff --git a/src/responder/nss/nsssrv_nc.c b/src/responder/nss/nsssrv_nc.c new file mode 100644 index 00000000..1fa7d612 --- /dev/null +++ b/src/responder/nss/nsssrv_nc.c @@ -0,0 +1,321 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include <fcntl.h> +#include <time.h> +#include "tdb.h" + +#define NC_ENTRY_PREFIX "NCE/" +#define NC_USER_PREFIX NC_ENTRY_PREFIX"USER" +#define NC_GROUP_PREFIX NC_ENTRY_PREFIX"GROUP" +#define NC_UID_PREFIX NC_ENTRY_PREFIX"UID" +#define NC_GID_PREFIX NC_ENTRY_PREFIX"GID" + +struct nss_nc_ctx { + struct tdb_context *tdb; +}; + +static int string_to_tdb_data(char *str, TDB_DATA *ret) +{ + if (!str || !ret) return EINVAL; + + ret->dptr = (uint8_t *)str; + ret->dsize = strlen(str)+1; + + return EOK; +} + +int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx) +{ + struct nss_nc_ctx *ctx; + + ctx = talloc_zero(memctx, struct nss_nc_ctx); + if (!ctx) return ENOMEM; + + errno = 0; + /* open a memory only tdb with default hash size */ + ctx->tdb = tdb_open("memcache", 0, TDB_INTERNAL, O_RDWR|O_CREAT, 0); + if (!ctx->tdb) return errno; + + *_ctx = ctx; + return EOK; +}; + +static int nss_ncache_check_str(struct nss_nc_ctx *ctx, char *str, int ttl) +{ + TDB_DATA key; + TDB_DATA data; + unsigned long long int timestamp; + bool expired = false; + char *ep; + int ret; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) goto done; + + data = tdb_fetch(ctx->tdb, key); + + if (!data.dptr) { + ret = ENOENT; + goto done; + } + + if (ttl == -1) { + /* a negative ttl means: never expires */ + ret = EEXIST; + goto done; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 0); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it and return no entry */ + expired = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + ret = EEXIST; + goto done; + } + + if (timestamp + ttl > time(NULL)) { + /* still valid */ + ret = EEXIST; + goto done; + } + + expired = true; + +done: + if (expired) { + /* expired, remove and return no entry */ + tdb_delete(ctx->tdb, key); + ret = ENOENT; + } + + return ret; +} + +static int nss_ncache_set_str(struct nss_nc_ctx *ctx, + char *str, bool permanent) +{ + TDB_DATA key; + TDB_DATA data; + char *timest; + int ret; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) return ret; + + if (permanent) { + timest = talloc_strdup(ctx, "0"); + } else { + timest = talloc_asprintf(ctx, "%llu", + (unsigned long long int)time(NULL)); + } + if (!timest) return ENOMEM; + + ret = string_to_tdb_data(timest, &data); + if (ret != EOK) goto done; + + ret = tdb_store(ctx->tdb, key, data, TDB_REPLACE); + if (ret != 0) { + DEBUG(1, ("Negative cache failed to set entry: [%s]", + tdb_errorstr(ctx->tdb))); + ret = EFAULT; + } + +done: + talloc_free(timest); + return ret; +} + +int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid); + if (!str) return ENOMEM; + + ret = nss_ncache_check_str(ctx, str, ttl); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid); + if (!str) return ENOMEM; + + ret = nss_ncache_set_str(ctx, str, permanent); + + talloc_free(str); + return ret; +} + +static int delete_permanent(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, void *state) +{ + unsigned long long int timestamp; + bool remove_key = false; + char *ep; + + if (strncmp((char *)key.dptr, + NC_ENTRY_PREFIX, sizeof(NC_ENTRY_PREFIX)) != 0) { + /* not interested in this key */ + return 0; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 0); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it */ + remove_key = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + remove_key = true; + } + +done: + if (remove_key) { + return tdb_delete(tdb, key); + } + + return 0; +} + +int nss_ncache_reset_permament(struct nss_nc_ctx *ctx) +{ + int ret; + + ret = tdb_traverse(ctx->tdb, delete_permanent, NULL); + if (ret < 0) + return EIO; + + return EOK; +} diff --git a/src/responder/nss/nsssrv_nc.h b/src/responder/nss/nsssrv_nc.h new file mode 100644 index 00000000..c0fa197c --- /dev/null +++ b/src/responder/nss/nsssrv_nc.h @@ -0,0 +1,51 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _NSS_NEG_CACHE_H_ +#define _NSS_NEG_CACHE_H_ + +struct nss_nc_ctx; + +/* init the in memory negative cache */ +int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx); + +/* check if the user is expired according to the passed in time to live */ +int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name); +int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl, + const char *domain, const char *name); +int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid); +int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid); + +/* add a new neg-cache entry setting the timestamp to "now" unless + * "permanent" is set to true, in which case the timestamps is set to 0 + * and the negative cache never expires (used to permanently filter out + * users and groups) */ +int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name); +int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name); +int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid); +int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid); + +int nss_ncache_reset_permament(struct nss_nc_ctx *ctx); + +#endif /* _NSS_NEG_CACHE_H_ */ |