From 92a8255206ff8bc5500f85d4c87a18dfef7d6aa7 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 6/6] NSS: Add support for enabling session recording shell

---
 Makefile.am                                  |   6 +-
 configure.ac                                 |   1 +
 src/conf_macros.m4                           |  16 ++
 src/confdb/confdb.h                          |   6 +
 src/config/SSSDConfigTest.py                 |   6 +-
 src/config/etc/sssd.api.conf                 |   6 +
 src/responder/nss/nsssrv.c                   |  44 +++++
 src/responder/nss/nsssrv.h                   |  28 +++
 src/responder/nss/nsssrv_cmd.c               |  78 +++++++-
 src/responder/nss/nsssrv_private.h           |  10 +
 src/responder/nss/nsssrv_session_recording.c | 262 +++++++++++++++++++++++++++
 src/responder/nss/nsssrv_session_recording.h |  81 +++++++++
 src/tests/intg/Makefile.am                   |   1 +
 src/tests/intg/config.py.m4                  |  19 +-
 src/tests/intg/ldap_test.py                  | 172 ++++++++++++++++++
 15 files changed, 715 insertions(+), 21 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 706b60d..67c44eb 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 \
@@ -1243,6 +1244,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) \
@@ -2108,7 +2110,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/configure.ac b/configure.ac
index 8760a85..7d276fb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -134,6 +134,7 @@ WITH_SEMANAGE
 WITH_AD_GPO_DEFAULT
 WITH_GPO_CACHE_PATH
 WITH_NOLOGIN_SHELL
+WITH_SESSION_RECORDING_SHELL
 WITH_APP_LIBS
 WITH_SUDO
 WITH_SUDO_LIB_PATH
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index bc295c5..16542fb 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -592,6 +592,22 @@ AC_DEFUN([WITH_NOLOGIN_SHELL],
     AC_DEFINE_UNQUOTED(NOLOGIN_SHELL, "$nologin_shell", [The shell used to deny access to users])
   ])
 
+AC_DEFUN([WITH_SESSION_RECORDING_SHELL],
+  [ AC_ARG_WITH([session-recording-shell],
+                [AC_HELP_STRING([--with-session-recording-shell=PATH],
+                                [The shell used to record user sessions [/usr/bin/tlog-rec]]
+                               )
+                ]
+               )
+    session_recording_shell="/usr/bin/tlog-rec"
+    if test x"$with_session_recording_shell" != x; then
+        nologin_shell=$with_session_recording_shell
+    fi
+    AC_SUBST(session_recording_shell)
+    AC_DEFINE_UNQUOTED(SESSION_RECORDING_SHELL, "$session_recording_shell",
+                       [The shell used to record user sessions])
+  ])
+
 AC_ARG_ENABLE([all-experimental-features],
               [AS_HELP_STRING([--enable-all-experimental-features],
                               [build all experimental features])],
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 72adbd8..cbdfe6e 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -157,6 +157,12 @@
 #define CONFDB_IFP_USER_ATTR_LIST "user_attributes"
 #define CONFDB_IFP_WILDCARD_LIMIT "wildcard_limit"
 
+/* Session Recording */
+#define CONFDB_SESSION_RECORDING_CONF_ENTRY "config/session_recording"
+#define CONFDB_SESSION_RECORDING_SCOPE "scope"
+#define CONFDB_SESSION_RECORDING_USERS "users"
+#define CONFDB_SESSION_RECORDING_GROUPS "groups"
+
 /* Domains */
 #define CONFDB_DOMAIN_PATH_TMPL "config/domain/%s"
 #define CONFDB_DOMAIN_BASEDN "cn=domain,cn=config"
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 332d870..07225ad 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -1351,7 +1351,8 @@ class SSSDConfigTestSSSDConfig(unittest.TestCase):
             'autofs',
             'ssh',
             'pac',
-            'ifp']
+            'ifp',
+            'session_recording']
         for section in control_list:
             self.assertTrue(sssdconfig.has_section(section),
                             "Section [%s] missing" %
@@ -1444,7 +1445,8 @@ class SSSDConfigTestSSSDConfig(unittest.TestCase):
             'autofs',
             'ssh',
             'pac',
-            'ifp']
+            'ifp',
+            'session_recording']
         service_list = sssdconfig.list_services()
         for service in control_list:
             self.assertTrue(service in service_list,
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 737f0e1..a989388 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -95,6 +95,12 @@ pac_lifetime = int, None, false
 allowed_uids = str, None, false
 user_attributes = str, None, false
 
+[session_recording]
+# Session recording service
+scope = str, None, false
+users = list, str, false
+groups = list, str, false
+
 [provider]
 #Available provider types
 id_provider = str, None, true
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 2e1bc93..9e73d85 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;
@@ -1212,7 +1223,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
@@ -1530,6 +1542,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);
@@ -1596,7 +1611,6 @@ static int nss_cmd_getbynam_search_and_reply(struct nss_dom_ctx *dctx)
 
 static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
 {
-
     struct tevent_req *req;
     struct cli_protocol *pctx;
     struct nss_cmd_ctx *cmdctx;
@@ -1731,7 +1745,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);
@@ -1784,6 +1798,52 @@ 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 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) {
+        /*
+         * 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);
+        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:
@@ -1890,7 +1950,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) {
@@ -2372,7 +2432,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."
diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index fb78d38..692aa79 100644
--- a/src/responder/nss/nsssrv_private.h
+++ b/src/responder/nss/nsssrv_private.h
@@ -25,7 +25,11 @@
 #ifndef NSSSRV_PRIVATE_H_
 #define NSSSRV_PRIVATE_H_
 
+#include "sss_client/sss_cli.h"
+#include "responder/common/responder.h"
 #include <dhash.h>
+#include <talloc.h>
+#include <tevent.h>
 
 struct nss_state_ent {
     int dom_idx;
@@ -59,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 {
diff --git a/src/responder/nss/nsssrv_session_recording.c b/src/responder/nss/nsssrv_session_recording.c
new file mode 100644
index 0000000..a443607
--- /dev/null
+++ b/src/responder/nss/nsssrv_session_recording.c
@@ -0,0 +1,262 @@
+/*
+    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    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);
+        goto done;
+    }
+
+    /* Lookup next name */
+    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 nss_cmd_ctx *cmdctx)
+{
+    errno_t ret;
+    struct nss_ctx *nctx;
+    struct resp_ctx *rctx = cmdctx->cctx->rctx;
+    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 */
+    cmdctx->session_recording_groups =
+        talloc_zero_array(mem_ctx, struct ldb_result *, num + 1);
+    if (cmdctx->session_recording_groups == NULL) {
+        ret = ENOMEM;
+        goto error;
+    }
+
+    /* Fill state */
+    state->mem_ctx = mem_ctx;
+    state->rctx = rctx;
+    state->pname = nctx->session_recording_groups;
+    state->presult = cmdctx->session_recording_groups;
+
+    /* 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..7baa1ea
--- /dev/null
+++ b/src/responder/nss/nsssrv_session_recording.h
@@ -0,0 +1,81 @@
+/*
+    SSSD
+
+    NSS Responder session recording functions
+
+    Authors:
+        Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+
+    Copyright (C) 2010 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 command context.
+ *
+ * @param mem_ctx   The memory context to allocate results and request state
+ *                  with.
+ * @param cmdctx    NSS command context to operate within.
+ *
+ * @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 nss_cmd_ctx *cmdctx);
+
+/**
+ * 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_ */
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index d79f642..65b4fdf 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -22,6 +22,7 @@ config.py: config.py.m4
 	   -D "pidpath=\`$(pidpath)'" \
 	   -D "logpath=\`$(logpath)'" \
 	   -D "mcpath=\`$(mcpath)'" \
+	   -D "session_recording_shell=\`$(session_recording_shell)'" \
 	   $< > $@
 
 root:
diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4
index 563127c..971d81a 100644
--- a/src/tests/intg/config.py.m4
+++ b/src/tests/intg/config.py.m4
@@ -2,12 +2,13 @@
 Build configuration variables.
 """
 
-PREFIX              = "prefix"
-SYSCONFDIR          = "sysconfdir"
-SSSDCONFDIR         = SYSCONFDIR + "/sssd"
-CONF_PATH           = SSSDCONFDIR + "/sssd.conf"
-DB_PATH             = "dbpath"
-PID_PATH            = "pidpath"
-PIDFILE_PATH        = PID_PATH + "/sssd.pid"
-LOG_PATH            = "logpath"
-MCACHE_PATH         = "mcpath"
+PREFIX                  = "prefix"
+SYSCONFDIR              = "sysconfdir"
+SSSDCONFDIR             = SYSCONFDIR + "/sssd"
+CONF_PATH               = SSSDCONFDIR + "/sssd.conf"
+DB_PATH                 = "dbpath"
+PID_PATH                = "pidpath"
+PIDFILE_PATH            = PID_PATH + "/sssd.pid"
+LOG_PATH                = "logpath"
+MCACHE_PATH             = "mcpath"
+SESSION_RECORDING_SHELL = "session_recording_shell"
diff --git a/src/tests/intg/ldap_test.py b/src/tests/intg/ldap_test.py
index 84f7f2d..06183d8 100644
--- a/src/tests/intg/ldap_test.py
+++ b/src/tests/intg/ldap_test.py
@@ -789,3 +789,175 @@ def test_vetoed_shells(vetoed_shells):
                  shell="/bin/default")
         )
     )
+
+
+@pytest.fixture
+def session_recording_ldap(request, ldap_conn):
+    ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+    ent_list.add_user("user1", 1001, 2001, loginShell="/bin/sh")
+    ent_list.add_user("user2", 1002, 2002, loginShell="/bin/sh")
+    ent_list.add_user("user3", 1003, 2003, loginShell="/bin/sh")
+    ent_list.add_group("group1", 2001)
+    ent_list.add_group("group2", 2002)
+    ent_list.add_group("group3", 2003)
+    ent_list.add_group("empty_group", 2010)
+    ent_list.add_group("one_user_group", 2011, ["user1"])
+    ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+    ent_list.add_group("three_user_group", 2013, ["user1", "user2", "user3"])
+    create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+@pytest.fixture
+def session_recording_none(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = none
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_none(session_recording_none):
+    """Test session recording "none" scope"""
+    ent.assert_passwd(
+        ent.contains_only(
+            dict(name="user1", uid=1001, shell="/bin/sh"),
+            dict(name="user2", uid=1002, shell="/bin/sh"),
+            dict(name="user3", uid=1003, shell="/bin/sh"),
+        )
+    )
+
+
+@pytest.fixture
+def session_recording_all(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = all
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_all(session_recording_all):
+    """Test session recording "all" scope"""
+    ent.assert_passwd(
+        ent.contains_only(
+            dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+            dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+            dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+        )
+    )
+
+
+@pytest.fixture
+def session_recording_some_empty(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = some
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_some_empty(session_recording_some_empty):
+    """Test session recording "some" scope with no users or groups"""
+    ent.assert_passwd(
+        ent.contains_only(
+            dict(name="user1", uid=1001, shell="/bin/sh"),
+            dict(name="user2", uid=1002, shell="/bin/sh"),
+            dict(name="user3", uid=1003, shell="/bin/sh"),
+        )
+    )
+
+
+@pytest.fixture
+def session_recording_some_users(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = some
+            users = user1, user2
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_some_users(session_recording_some_users):
+    """Test session recording "some" scope with user list"""
+    ent.assert_passwd(
+        ent.contains_only(
+            dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+            dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+            dict(name="user3", uid=1003, shell="/bin/sh"),
+        )
+    )
+
+
+@pytest.fixture
+def session_recording_some_users(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = some
+            users = user1, user2
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+@pytest.fixture
+def session_recording_some_groups(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = some
+            groups = one_user_group, two_user_group
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_some_groups(session_recording_some_groups):
+    """Test session recording "some" scope with group list"""
+    ent.assert_each_passwd_by_name(
+        dict(
+            user1=dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+            user2=dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+            user3=dict(name="user3", uid=1003, shell="/bin/sh"),
+        )
+    )
+
+
+@pytest.fixture
+def session_recording_some_users_and_groups(request, ldap_conn, session_recording_ldap):
+    conf = \
+        format_basic_conf(ldap_conn, SCHEMA_RFC2307, enum=True) + \
+        unindent("""\
+            [session_recording]
+            scope = some
+            users = user3
+            groups = one_user_group
+        """).format(**locals())
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+
+
+def test_session_recording_some_users_and_groups(
+                                        session_recording_some_users_and_groups):
+    """Test session recording "some" scope with group list"""
+    ent.assert_each_passwd_by_name(
+        dict(
+            user1=dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+            user2=dict(name="user2", uid=1002, shell="/bin/sh"),
+            user3=dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+        )
+    )
-- 
2.8.1

