From 6eccd0de064485475dfe7346a716567725f57a12 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
Date: Fri, 15 Jul 2016 13:16:39 +0300
Subject: [PATCH 11/12] NSS: Add session recording shell substitution

Add support for substituting user shell with a session recording shell
for users and groups with session recording enabled, for getpwnam,
getpwuid, and getpwent calls.
---
 Makefile.am                                  |   6 +-
 src/responder/nss/nsssrv.c                   |  44 +++++
 src/responder/nss/nsssrv.h                   |  28 +++
 src/responder/nss/nsssrv_cmd.c               | 194 +++++++++++++++++++-
 src/responder/nss/nsssrv_private.h           |  18 ++
 src/responder/nss/nsssrv_session_recording.c | 265 +++++++++++++++++++++++++++
 src/responder/nss/nsssrv_session_recording.h |  85 +++++++++
 7 files changed, 629 insertions(+), 11 deletions(-)
 create mode 100644 src/responder/nss/nsssrv_session_recording.c
 create mode 100644 src/responder/nss/nsssrv_session_recording.h

diff --git a/Makefile.am b/Makefile.am
index 5d1d671..d0c41b3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -610,6 +610,7 @@ dist_noinst_HEADERS = \
     src/responder/nss/nsssrv_netgroup.h \
     src/responder/nss/nsssrv_services.h \
     src/responder/nss/nsssrv_mmap_cache.h \
+    src/responder/nss/nsssrv_session_recording.h \
     src/responder/pac/pacsrv.h \
     src/responder/common/negcache_files.h \
     src/responder/common/negcache.h \
@@ -1244,6 +1245,7 @@ sssd_nss_SOURCES = \
     src/responder/nss/nsssrv_netgroup.c \
     src/responder/nss/nsssrv_services.c \
     src/responder/nss/nsssrv_mmap_cache.c \
+    src/responder/nss/nsssrv_session_recording.c \
     $(SSSD_RESPONDER_OBJ)
 sssd_nss_LDADD = \
     $(TDB_LIBS) \
@@ -2109,7 +2111,9 @@ nss_srv_tests_SOURCES = \
      src/responder/nss/nsssrv_cmd.c \
      src/responder/nss/nsssrv_netgroup.c \
      src/responder/nss/nsssrv_services.c \
-     src/responder/nss/nsssrv_mmap_cache.c
+     src/responder/nss/nsssrv_mmap_cache.c \
+     src/responder/nss/nsssrv_session_recording.c \
+     $(NULL)
 nss_srv_tests_CFLAGS = \
     $(AM_CFLAGS)
 nss_srv_tests_LDFLAGS = \
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
index 8be3455..c30629e 100644
--- a/src/responder/nss/nsssrv.c
+++ b/src/responder/nss/nsssrv.c
@@ -322,6 +322,50 @@ static int nss_get_config(struct nss_ctx *nctx,
         }
     }
 
+    /* Read session_recording/scope option */
+    ret = confdb_get_string(cdb, nctx, CONFDB_SESSION_RECORDING_CONF_ENTRY,
+                            CONFDB_SESSION_RECORDING_SCOPE, "none", &tmp_str);
+    if (ret != EOK) goto done;
+    if (strcasecmp(tmp_str, "none") == 0) {
+        nctx->session_recording_scope = NSS_CTX_SESSION_RECORDING_SCOPE_NONE;
+    } else if (strcasecmp(tmp_str, "some") == 0) {
+        nctx->session_recording_scope = NSS_CTX_SESSION_RECORDING_SCOPE_SOME;
+    } else if (strcasecmp(tmp_str, "all") == 0) {
+        nctx->session_recording_scope = NSS_CTX_SESSION_RECORDING_SCOPE_ALL;
+    } else {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Unknown value for session recording scope: %s\n",
+              tmp_str);
+        ret = EINVAL;
+        goto done;
+    }
+
+    /* Read session_recording/users option */
+    ret = confdb_get_string_as_list(cdb, nctx,
+                                    CONFDB_SESSION_RECORDING_CONF_ENTRY,
+                                    CONFDB_SESSION_RECORDING_USERS,
+                                    &nctx->session_recording_users);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* Read session_recording/groups option */
+    ret = confdb_get_string_as_list(cdb, nctx,
+                                    CONFDB_SESSION_RECORDING_CONF_ENTRY,
+                                    CONFDB_SESSION_RECORDING_GROUPS,
+                                    &nctx->session_recording_groups);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* If configured to enable session recording for specific groups */
+    if (nctx->session_recording_scope ==
+            NSS_CTX_SESSION_RECORDING_SCOPE_SOME &&
+        nctx->session_recording_groups != NULL &&
+        nctx->session_recording_groups[0] != NULL) {
+        /* Always request user's groups when requesting user info */
+        nctx->user_req_type = SSS_DP_INITGROUPS;
+    } else {
+        /* Request normal user info only */
+        nctx->user_req_type = SSS_DP_USER;
+    }
+
     ret = 0;
 done:
     return ret;
diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h
index 2977479..da27408 100644
--- a/src/responder/nss/nsssrv.h
+++ b/src/responder/nss/nsssrv.h
@@ -41,6 +41,13 @@
 struct getent_ctx;
 struct sss_mc_ctx;
 
+/** Scope of users whose session should be recorded */
+enum nss_ctx_session_recording_scope {
+    NSS_CTX_SESSION_RECORDING_SCOPE_NONE,    /**< None, no users */
+    NSS_CTX_SESSION_RECORDING_SCOPE_SOME,    /**< Some users specified elsewhere */
+    NSS_CTX_SESSION_RECORDING_SCOPE_ALL      /**< All users */
+};
+
 struct nss_ctx {
     struct resp_ctx *rctx;
 
@@ -75,6 +82,27 @@ struct nss_ctx {
     struct sss_names_ctx *global_names;
 
     const char **extra_attributes;
+
+    enum nss_ctx_session_recording_scope
+                    session_recording_scope;    /**< Session recording scope:
+                                                     whether to record nobody,
+                                                     everyone, or some
+                                                     users/groups */
+    char **session_recording_users;             /**< NULL-terminated list of
+                                                     users whose session
+                                                     should be recorded, only
+                                                     applicable if scope is
+                                                     "some" */
+    char **session_recording_groups;            /**< NULL-terminated list of
+                                                     groups, members of which
+                                                     should have their
+                                                     sessions recorded, only
+                                                     applicable if scope is
+                                                     "some" */
+    enum sss_dp_acct_type user_req_type;        /**< Request type to use when
+                                                     refreshing cached user
+                                                     information from the
+                                                     provider */
 };
 
 struct nss_packet;
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 72faa13..b95ca5b 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -29,6 +29,7 @@
 #include "responder/nss/nsssrv_netgroup.h"
 #include "responder/nss/nsssrv_services.h"
 #include "responder/nss/nsssrv_mmap_cache.h"
+#include "responder/nss/nsssrv_session_recording.h"
 #include "responder/common/negcache.h"
 #include "responder/common/responder_cache_req.h"
 #include "providers/data_provider.h"
@@ -551,12 +552,22 @@ static int fill_pwent(struct sss_packet *packet,
             to_sized_string(&homedir, tmpstr);
         }
 
-        tmpstr = get_shell_override(tmp_ctx, msg, nctx, dom);
+        ret = nsssrv_session_recording_get_shell(tmp_ctx, orig_name, name->str,
+                                                 cmdctx, dom, &tmpstr);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Failed retrieving session recording shell for user "
+                    "[%s] in [%s]: %d! Skipping user.\n",
+                  name->str, domain, ret);
+            continue;
+        }
         if (!tmpstr) {
-            to_sized_string(&shell, "");
-        } else {
-            to_sized_string(&shell, tmpstr);
+            tmpstr = get_shell_override(tmp_ctx, msg, nctx, dom);
+        }
+        if (!tmpstr) {
+            tmpstr = "";
         }
+        to_sized_string(&shell, tmpstr);
 
         rsize = 2 * sizeof(uint32_t) + name->len + gecos.len +
                                        homedir.len + shell.len + pwfield.len;
@@ -1222,7 +1233,8 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
                 extra_flag = NULL;
             }
 
-            ret = check_cache(dctx, nctx, dctx->res, SSS_DP_USER, name, 0,
+            ret = check_cache(dctx, nctx, dctx->res, nctx->user_req_type,
+                              name, 0,
                               extra_flag, nss_cmd_getby_dp_callback, dctx);
             if (ret != EOK) {
                 /* Anything but EOK means we should reenter the mainloop
@@ -1540,6 +1552,9 @@ static int nss_check_name_of_well_known_sid(struct nss_cmd_ctx *cmdctx,
 
 static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx);
 static void nss_cmd_getbynam_get_domains_done(struct tevent_req *req);
+static int nss_cmd_getbynam_session_recording_get_groups(struct nss_dom_ctx *dctx);
+static void nss_cmd_getbynam_session_recording_get_groups_done(struct tevent_req *req);
+
 static int nss_cmd_getpwnam(struct cli_ctx *cctx)
 {
     return nss_cmd_getbynam(SSS_NSS_GETPWNAM, cctx);
@@ -1737,7 +1752,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         }
     }
 
-    ret = nss_cmd_getbynam_search_and_reply(dctx);
+    ret = nss_cmd_getbynam_session_recording_get_groups(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
@@ -1790,6 +1805,56 @@ static void nss_cmd_getbynam_get_domains_done(struct tevent_req *req)
         /* Not fatal */
     }
 
+    ret = nss_cmd_getbynam_session_recording_get_groups(dctx);
+
+done:
+    nss_cmd_done(cmdctx, ret);
+}
+
+static int nss_cmd_getbynam_session_recording_get_groups(struct nss_dom_ctx *dctx)
+{
+    struct nss_ctx *nctx;
+    struct tevent_req *req;
+
+    nctx = talloc_get_type(dctx->cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
+
+    /* If we need to match a user against session recording groups */
+    if (dctx->cmdctx->cmd == SSS_NSS_GETPWNAM &&
+        nctx->session_recording_scope ==
+            NSS_CTX_SESSION_RECORDING_SCOPE_SOME &&
+        nctx->session_recording_groups != NULL &&
+        nctx->session_recording_groups[0] != NULL) {
+        /*
+         * Do an async request for groups with session recording enabled.
+         * Store them in the command context.
+         */
+        req = nss_session_recording_get_groups_send(
+                                    dctx->cmdctx,
+                                    dctx->cmdctx->cctx->rctx,
+                                    &dctx->cmdctx->session_recording_groups);
+        if (req == NULL) {
+            return ENOMEM;
+        } else {
+            tevent_req_set_callback(req, nss_cmd_getbynam_session_recording_get_groups_done, dctx);
+            return EAGAIN;
+        }
+    }
+
+    return nss_cmd_getbynam_search_and_reply(dctx);
+}
+
+static void nss_cmd_getbynam_session_recording_get_groups_done(struct tevent_req *req)
+{
+    struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
+    struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+    errno_t ret;
+
+    ret = nss_session_recording_get_groups_recv(req);
+    talloc_free(req);
+    if (ret != EOK) {
+        goto done;
+    }
+
     ret = nss_cmd_getbynam_search_and_reply(dctx);
 
 done:
@@ -1896,7 +1961,7 @@ static int nss_cmd_getpwuid_search(struct nss_dom_ctx *dctx)
                 extra_flag = NULL;
             }
 
-            ret = check_cache(dctx, nctx, dctx->res, SSS_DP_USER, NULL,
+            ret = check_cache(dctx, nctx, dctx->res, nctx->user_req_type, NULL,
                               cmdctx->id, extra_flag, nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -1935,6 +2000,9 @@ done:
 static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx);
 static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx);
 static void nss_cmd_getbyid_get_domains_done(struct tevent_req *req);
+static int nss_cmd_getbyid_session_recording_get_groups(struct nss_dom_ctx *dctx);
+static void nss_cmd_getbyid_session_recording_get_groups_done(struct tevent_req *req);
+
 static int nss_cmd_getpwuid(struct cli_ctx *cctx)
 {
     return nss_cmd_getbyid(SSS_NSS_GETPWUID, cctx);
@@ -2107,7 +2175,7 @@ static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx)
         goto done;
     }
 
-    ret = nss_cmd_getbyid_search_and_reply(dctx);
+    ret = nss_cmd_getbyid_session_recording_get_groups(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
@@ -2131,6 +2199,58 @@ static void nss_cmd_getbyid_get_domains_done(struct tevent_req *req)
         /* Not fatal */
     }
 
+    ret = nss_cmd_getbyid_session_recording_get_groups(dctx);
+
+done:
+    nss_cmd_done(cmdctx, ret);
+}
+
+static int nss_cmd_getbyid_session_recording_get_groups(struct nss_dom_ctx *dctx)
+{
+    struct nss_ctx *nctx;
+    struct tevent_req *req;
+
+    nctx = talloc_get_type(dctx->cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
+
+    /* If we need to match a user against session recording groups */
+    if (dctx->cmdctx->cmd == SSS_NSS_GETPWUID &&
+        nctx->session_recording_scope ==
+            NSS_CTX_SESSION_RECORDING_SCOPE_SOME &&
+        nctx->session_recording_groups != NULL &&
+        nctx->session_recording_groups[0] != NULL) {
+        /*
+         * Do an async request for groups with session recording enabled.
+         * Store them in the command context.
+         */
+        req = nss_session_recording_get_groups_send(
+                                    dctx->cmdctx,
+                                    dctx->cmdctx->cctx->rctx,
+                                    &dctx->cmdctx->session_recording_groups);
+        if (req == NULL) {
+            return ENOMEM;
+        } else {
+            tevent_req_set_callback(req,
+                                    nss_cmd_getbyid_session_recording_get_groups_done,
+                                    dctx);
+            return EAGAIN;
+        }
+    }
+
+    return nss_cmd_getbyid_search_and_reply(dctx);
+}
+
+static void nss_cmd_getbyid_session_recording_get_groups_done(struct tevent_req *req)
+{
+    struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx);
+    struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+    errno_t ret;
+
+    ret = nss_session_recording_get_groups_recv(req);
+    talloc_free(req);
+    if (ret != EOK) {
+        goto done;
+    }
+
     ret = nss_cmd_getbyid_search_and_reply(dctx);
 
 done:
@@ -2290,6 +2410,11 @@ struct tevent_req *nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx,
     step_ctx->rctx = client->rctx;
     step_ctx->cctx = client;
     step_ctx->returned_to_mainloop = false;
+    step_ctx->need_session_recording_groups =
+                    state->nctx->session_recording_scope ==
+                        NSS_CTX_SESSION_RECORDING_SCOPE_SOME &&
+                    state->nctx->session_recording_groups != NULL &&
+                    state->nctx->session_recording_groups[0] != NULL;
 
     ret = nss_cmd_setpwent_step(step_ctx);
     if (ret != EOK && ret != EAGAIN) goto error;
@@ -2312,6 +2437,8 @@ static void setpwent_result_timeout(struct tevent_context *ev,
                                     struct tevent_timer *te,
                                     struct timeval current_time,
                                     void *pvt);
+static void nss_cmd_setpwent_session_recording_get_groups_done(
+                                                    struct tevent_req *req);
 
 /* nss_cmd_setpwent_step returns
  *   EOK if everything is done and the request needs to be posted explicitly
@@ -2331,6 +2458,22 @@ static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx)
     struct tevent_req *dpreq;
     struct dp_callback_ctx *cb_ctx;
 
+    if (step_ctx->need_session_recording_groups) {
+        struct tevent_req *req;
+        req = nss_session_recording_get_groups_send(
+                                        pctx, rctx,
+                                        &pctx->session_recording_groups);
+        if (req == NULL) {
+            return ENOMEM;
+        } else {
+            tevent_req_set_callback(
+                        req,
+                        nss_cmd_setpwent_session_recording_get_groups_done,
+                        step_ctx);
+            return EAGAIN;
+        }
+    }
+
     while (dom) {
         while (dom && dom->enumerate == false) {
             dom = get_next_domain(dom, SSS_GND_DESCEND);
@@ -2364,7 +2507,7 @@ static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx)
             dctx->check_provider = false;
 
             dpreq = sss_dp_get_account_send(step_ctx, rctx, dctx->domain, true,
-                                          SSS_DP_USER, NULL, 0, NULL);
+                                            nctx->user_req_type, NULL, 0, NULL);
             if (!dpreq) {
                 DEBUG(SSSDBG_MINOR_FAILURE,
                       "Enum Cache refresh for domain [%s] failed."
@@ -2466,6 +2609,31 @@ static void setpwent_result_timeout(struct tevent_context *ev,
     talloc_zfree(nctx->pctx);
 }
 
+static void nss_cmd_setpwent_session_recording_get_groups_done(struct tevent_req *req)
+{
+    struct setent_step_ctx *step_ctx =
+                        tevent_req_callback_data(req, struct setent_step_ctx);
+    errno_t ret;
+
+    /* We're done, don't try again */
+    step_ctx->need_session_recording_groups = false;
+
+    ret = nss_session_recording_get_groups_recv(req);
+    talloc_free(req);
+    if (ret != EOK) {
+        /* Notify any waiting processes of failure */
+        nss_setent_notify_error(step_ctx->nctx->pctx, ret);
+        return;
+    }
+
+    ret = nss_cmd_setpwent_step(step_ctx);
+    if (ret != EOK && ret != EAGAIN) {
+        /* Notify any waiting processes of failure */
+        nss_setent_notify_error(step_ctx->nctx->pctx, ret);
+        return;
+    }
+}
+
 static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min,
                                          const char *err_msg, void *ptr)
 {
@@ -2557,6 +2725,12 @@ static int nss_cmd_getpwent(struct cli_ctx *cctx)
         return EOK;
     }
 
+    /*
+     * Use the groups setpwent retrieved as groups for which session recording
+     * is enabled.
+     */
+    cmdctx->session_recording_groups = nctx->pctx->session_recording_groups;
+
     return nss_cmd_getpwent_immediate(cmdctx);
 }
 
@@ -5380,7 +5554,7 @@ static int nss_cmd_getbysid(enum sss_cli_command cmd, struct cli_ctx *cctx)
     DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for [%s] from [%s]\n",
               cmdctx->secid, dctx->domain->name);
 
-    ret = nss_cmd_getbyid_search_and_reply(dctx);
+    ret = nss_cmd_getbyid_session_recording_get_groups(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index deaca4a..779b16b 100644
--- a/src/responder/nss/nsssrv_private.h
+++ b/src/responder/nss/nsssrv_private.h
@@ -63,6 +63,12 @@ struct nss_cmd_ctx {
 
     int saved_dom_idx;
     int saved_cur;
+
+    struct ldb_result **session_recording_groups; /**< NULL-terminated list of
+                                                       results of looking up
+                                                       groups for which
+                                                       session recording is
+                                                       enabled */
 };
 
 struct dom_ctx {
@@ -82,6 +88,13 @@ struct getent_ctx {
     char *name;
     char *domain;
     bool found;
+
+    /* Passwd-specific */
+    struct ldb_result **session_recording_groups; /**< NULL-terminated list of
+                                                       results of looking up
+                                                       groups for which
+                                                       session recording is
+                                                       enabled */
 };
 
 struct nss_dom_ctx {
@@ -119,6 +132,11 @@ struct setent_step_ctx {
 
     /* Netgroup-specific */
     char *name;
+
+    /* Passwd-specific */
+    bool need_session_recording_groups; /**< True if groups for which session
+                                             recording is enabled need to be
+                                             retrieved on the next step */
 };
 
 #define NSS_CMD_FATAL_ERROR(cctx) do { \
diff --git a/src/responder/nss/nsssrv_session_recording.c b/src/responder/nss/nsssrv_session_recording.c
new file mode 100644
index 0000000..88141e6
--- /dev/null
+++ b/src/responder/nss/nsssrv_session_recording.c
@@ -0,0 +1,265 @@
+/*
+    SSSD
+
+    NSS Responder session recording functions
+
+    Authors:
+        Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+
+    Copyright (C) 2016 Red Hat
+
+    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 "responder/nss/nsssrv_session_recording.h"
+#include "sss_client/sss_cli.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/common/responder_cache_req.h"
+
+/**
+ * Internal state of the asynchronous request to retrieve information on
+ * groups for which session recording is enabled.
+ */
+struct nss_session_recording_get_groups_state {
+    TALLOC_CTX *mem_ctx;            /**< Memory context to allocate results
+                                         with */
+    struct resp_ctx *rctx;          /**< Responder context to work within */
+    char **pname;                   /**< Pointer to name of the next group to
+                                         retrieve info about */
+    struct ldb_result **presult;    /**< Next location to store retrieved
+                                         result pointer in */
+};
+
+/**
+ * Handle a result of a group info request and submit another one, if there's
+ * any.
+ *
+ * @param subreq    The completed group info request.
+ */
+static void nss_session_recording_get_groups_step(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct nss_session_recording_get_groups_state *state =
+            tevent_req_data(req,
+                            struct nss_session_recording_get_groups_state);
+    struct resp_ctx *rctx = state->rctx;
+
+    /* Get the lookup result, move onto next array element if we got one */
+    ret = cache_req_group_by_name_recv(state->mem_ctx, subreq,
+                                       state->presult, NULL, NULL);
+    talloc_zfree(subreq);
+    if (ret == EOK) {
+        state->presult++;
+    } else if (ret != ENOENT) {
+        goto done;
+    }
+
+    /* Move onto the next group name */
+    state->pname++;
+
+    /* If there are no more names */
+    if (*state->pname == NULL) {
+        tevent_req_done(req);
+        ret = EOK;
+        goto done;
+    }
+
+    /* Lookup next name */
+    /* TODO: Figure out proper caching parameters */
+    subreq = cache_req_group_by_name_send(state->mem_ctx, rctx->ev, rctx,
+                                          rctx->ncache, 0, NULL,
+                                          *state->pname);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(subreq,
+                            nss_session_recording_get_groups_step, req);
+    return;
+
+done:
+    tevent_req_error(req, ret);
+}
+
+struct tevent_req *nss_session_recording_get_groups_send(
+                                        TALLOC_CTX *mem_ctx,
+                                        struct resp_ctx *rctx,
+                                        struct ldb_result ***presult_list)
+{
+    errno_t ret;
+    struct nss_ctx *nctx;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct nss_session_recording_get_groups_state *state;
+    char **pname;
+    size_t num;
+
+    /* Extract the NSS context to get to the group name list */
+    nctx = talloc_get_type(rctx->pvt_ctx, struct nss_ctx);
+
+    /* Create the tracking "request" */
+    req = tevent_req_create(mem_ctx, &state,
+                            struct nss_session_recording_get_groups_state);
+    if (req == NULL) {
+         return NULL;
+    }
+
+    /* Count groups to request */
+    num = 0;
+    pname = nctx->session_recording_groups;
+    if (pname != NULL) {
+        for (; *pname != NULL; pname++, num++);
+    }
+
+    /* Allocate array for lookup results, include terminating NULL */
+    *presult_list = talloc_zero_array(mem_ctx, struct ldb_result *, num + 1);
+    if (*presult_list == NULL) {
+        ret = ENOMEM;
+        goto error;
+    }
+
+    /* TODO Handle zero groups */
+
+    /* Fill state */
+    state->mem_ctx = mem_ctx;
+    state->rctx = rctx;
+    state->pname = nctx->session_recording_groups;
+    state->presult = *presult_list;
+
+    /* Start first group lookup */
+    subreq = cache_req_group_by_name_send(mem_ctx, rctx->ev, rctx,
+                                          rctx->ncache, 0, NULL,
+                                          *state->pname);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto error;
+    }
+    tevent_req_set_callback(subreq,
+                            nss_session_recording_get_groups_step, req);
+
+    return req;
+
+error:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, rctx->ev);
+    return req;
+}
+
+errno_t nss_session_recording_get_groups_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
+
+errno_t nsssrv_session_recording_get_shell(TALLOC_CTX *mem_ctx,
+                                           const char *orig_name,
+                                           const char *name,
+                                           struct nss_cmd_ctx *cmdctx,
+                                           struct sss_domain_info *dom,
+                                           const char **pshell)
+{
+    errno_t ret;
+    struct nss_ctx *nctx;
+    const char *shell = NULL;
+    char **pname;
+    struct ldb_result **pgroup_result;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    nctx = talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
+
+    switch (nctx->session_recording_scope) {
+        case NSS_CTX_SESSION_RECORDING_SCOPE_NONE:
+            ret = EOK;
+            goto done;
+        case NSS_CTX_SESSION_RECORDING_SCOPE_ALL:
+            shell = SESSION_RECORDING_SHELL;
+            ret = EOK;
+            goto done;
+        case NSS_CTX_SESSION_RECORDING_SCOPE_SOME:
+            /*
+             * Check if user name matches recorded names
+             */
+            if (nctx->session_recording_users != NULL) {
+                for (pname = nctx->session_recording_users; *pname != NULL; pname++) {
+                    if (strcmp(name, *pname) == 0) {
+                        shell = SESSION_RECORDING_SHELL;
+                        goto done;
+                    }
+                }
+            }
+
+            /*
+             * Check if user is in a group with session recording enabled
+             */
+            pgroup_result = cmdctx->session_recording_groups;
+            if (pgroup_result != NULL && *pgroup_result != NULL) {
+                struct ldb_result *user_result;
+                struct ldb_result *group_result;
+                size_t i;
+                size_t j;
+
+                /* Retrieve groups the user is member of */
+                ret = sysdb_initgroups_with_views(tmp_ctx, dom, orig_name, &user_result);
+                if (ret != EOK) {
+                    goto done;
+                }
+                /*
+                 * For each result of a session recording-enabled group lookup
+                 */
+                do {
+                    group_result = *pgroup_result;
+                    /* For each message (match) in the result */
+                    for (i = 0; i < group_result->count; i++) {
+                        /* For each user's group */
+                        for (j = 1; j < user_result->count; j++) {
+                            /* If their DN's match */
+                            if (ldb_dn_compare(
+                                        group_result->msgs[i]->dn,
+                                        user_result->msgs[j]->dn) == 0) {
+                                shell = SESSION_RECORDING_SHELL;
+                                ret = EOK;
+                                goto done;
+                            }
+                        }
+                    }
+                } while (*++pgroup_result != NULL);
+            }
+            ret = EOK;
+            break;
+        default:
+            ret = EINVAL;
+            goto done;
+    }
+
+done:
+    if (shell != NULL) {
+        shell = talloc_strdup(mem_ctx, shell);
+        if (shell == NULL) {
+            ret = ENOMEM;
+        }
+    }
+    talloc_zfree(tmp_ctx);
+    *pshell = shell;
+    return ret;
+}
+
diff --git a/src/responder/nss/nsssrv_session_recording.h b/src/responder/nss/nsssrv_session_recording.h
new file mode 100644
index 0000000..632404c
--- /dev/null
+++ b/src/responder/nss/nsssrv_session_recording.h
@@ -0,0 +1,85 @@
+/*
+    SSSD
+
+    NSS Responder session recording functions
+
+    Authors:
+        Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+
+    Copyright (C) 2016 Red Hat
+
+    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_SESSION_RECORDING_H_
+#define NSSSRV_SESSION_RECORDING_H_
+
+#include "responder/nss/nsssrv_private.h"
+
+/**
+ * Start an asynchronous request to retrieve groups for which session
+ * recording is enabled, for an NSS responder context.
+ *
+ * @param mem_ctx       The memory context to allocate results and request
+ *                      state with.
+ * @param rctx          NSS responder context to operate within.
+ * @param presult_list  Location for the pointer to a NULL-terminated array of
+ *                      pointers to retrieved group results, allocated with
+ *                      mem_ctx.
+ *
+ * @return The created asynchronous request, or NULL, if failed to allocate
+ *         memory for it.
+ */
+extern struct tevent_req *nss_session_recording_get_groups_send(
+                                        TALLOC_CTX *mem_ctx,
+                                        struct resp_ctx *rctx,
+                                        struct ldb_result ***presult_list);
+
+/**
+ * Retrieve status code for a finished request to retrieve groups for which
+ * session recording is enabled.
+ *
+ * @return Status code.
+ */
+extern errno_t nss_session_recording_get_groups_recv(struct tevent_req *req);
+
+/**
+ * Retrieve a user's shell used for session recording from an LDB message,
+ * according to global configuration.
+ *
+ * @param mem_ctx   Context to allocate returned shell value with.
+ * @param orig_name User's name. Used to retrieve the groups the user belongs
+ *                  to.
+ * @param name      User's name as returned to NSS client. Used to match
+ *                  against the list of users with enabled session recording.
+ * @param cmdctx    NSS command context to operate within.
+ * @param dom       Domain the user belongs to. Used to retrieve the groups
+ *                  the user belongs to.
+ * @param pshell    Location for the retrieved shell value, or for NULL, if
+ *                  session recording is not enabled for the user.
+ *                  Set to NULL in case of error.
+ *
+ * @return Status code:
+ *          EOK     - shell (or absence of) retrieved successfully,
+ *          EINVAL  - invalid arguments supplied,
+ *          ENOMEM  - failed allocating memory for shell value.
+ */
+extern int nsssrv_session_recording_get_shell(TALLOC_CTX *mem_ctx,
+                                              const char *orig_name,
+                                              const char *name,
+                                              struct nss_cmd_ctx *cmdctx,
+                                              struct sss_domain_info *dom,
+                                              const char **pshell);
+
+#endif /* NSSSRV_SESSION_RECORDING_H_ */
-- 
2.8.1

