summaryrefslogtreecommitdiff
path: root/src/responder/nss
diff options
context:
space:
mode:
Diffstat (limited to 'src/responder/nss')
-rw-r--r--src/responder/nss/nsssrv.c367
-rw-r--r--src/responder/nss/nsssrv.h70
-rw-r--r--src/responder/nss/nsssrv_cmd.c3182
-rw-r--r--src/responder/nss/nsssrv_nc.c321
-rw-r--r--src/responder/nss/nsssrv_nc.h51
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_ */