Hi everyone,

Here is the second version of RFC for SSSD/tlog integration. This one covers
support for all necessary NSS requests: getpwnam, getpwuid and getpwent.

I don't think it handles caching well, is probably not very fast, has little
tests, and still tries to document code, apart from likely being wrong in many
parts otherwise, but I would like to show it to you, so you can correct me
earlier rather than later.

I'll proceed to the PAM part and adding more tests in the meantime.

Thank you!

Nick
>From 7c15a05f6c2b5be2b78d1c277e82ee1613559c25 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Fri, 15 Jul 2016 13:13:52 +0300
Subject: [PATCH 01/12] Add and correct some code documentation

---
 src/responder/common/responder.h        | 23 ++++++++++++++---
 src/responder/common/responder_packet.c | 45 +++++++++++++++++++--------------
 src/responder/nss/nsssrv_cmd.c          | 32 ++++++++++++++++++++++-
 src/responder/nss/nsssrv_private.h      | 26 +++++++++++--------
 4 files changed, 92 insertions(+), 34 deletions(-)

diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
index 335b313..8d96a39 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -88,6 +88,7 @@ struct be_conn {
     struct sbus_connection *conn;
 };
 
+/** Responder context */
 struct resp_ctx {
     struct tevent_context *ev;
     struct tevent_fd *lfde;
@@ -123,16 +124,19 @@ struct resp_ctx {
 
     uint32_t cache_req_num;
 
-    void *pvt_ctx;
+    void *pvt_ctx;          /**< Specific responder's private context.
+                                 E.g. NSS client info, etc. for NSS
+                                 responder */
 
     bool shutting_down;
 };
 
 struct cli_creds;
 
+/** Client context */
 struct cli_ctx {
-    struct tevent_context *ev;
-    struct resp_ctx *rctx;
+    struct tevent_context *ev;          /**< The tevent context to add events to */
+    struct resp_ctx *rctx;              /**< Responder context */
     int cfd;
     struct tevent_fd *cfde;
     tevent_fd_handler_t cfd_handler;
@@ -141,7 +145,7 @@ struct cli_ctx {
 
     struct cli_creds *creds;
 
-    void *protocol_ctx;
+    void *protocol_ctx;                 /**< Protocol context */
     void *state_ctx;
 
     struct tevent_timer *idle;
@@ -327,6 +331,17 @@ void idle_handler(struct tevent_context *ev,
 
 #define GET_DOMAINS_DEFAULT_TIMEOUT 60
 
+/**
+ * Discover trusted domains and store them as sssd's subdomains.
+ *
+ * @param mem_ctx   The memory context to allocate request with.
+ * @param rctx      Responder context to work with.
+ * @param force     Ignore timeouts and do request now, if true,
+ *                  observe timeouts, if false.
+ * @param hint
+ *
+ * @return Created event, or NULL if event creation failed.
+ */
 struct tevent_req *sss_dp_get_domains_send(TALLOC_CTX *mem_ctx,
                                            struct resp_ctx *rctx,
                                            bool force,
diff --git a/src/responder/common/responder_packet.c b/src/responder/common/responder_packet.c
index 4f5e110..97f93b0 100644
--- a/src/responder/common/responder_packet.c
+++ b/src/responder/common/responder_packet.c
@@ -31,21 +31,22 @@
 #define SSSSRV_PACKET_MEM_SIZE 512
 
 struct sss_packet {
-    size_t memsize;
-
-    /* Structure of the buffer:
-    * Bytes    Content
-    * ---------------------------------
-    * 0-15     packet header
-    * 0-3      packet length (uint32_t)
-    * 4-7      command type (uint32_t)
-    * 8-11     status (uint32_t)
-    * 12-15    reserved
-    * 16+      packet body */
-    uint8_t *buffer;
-
-    /* io pointer */
-    size_t iop;
+    size_t memsize;     /**< Packet buffer size */
+
+    uint8_t *buffer;    /**< Packet buffer with the following structure:
+                         * <pre>
+                         * Bytes    Content
+                         * ---------------------------------
+                         * 0-15     packet header
+                         * 0-3      packet length (uint32_t)
+                         * 4-7      command type (uint32_t)
+                         * 8-11     status (uint32_t)
+                         * 12-15    reserved
+                         * 16+      packet body
+                         * </pre>
+                         */
+
+    size_t iop;         /**< Current I/O pointer */
 };
 
 /* Offsets to data in sss_packet's buffer */
@@ -59,11 +60,17 @@ static void sss_packet_set_cmd(struct sss_packet *packet,
                                enum sss_cli_command cmd);
 static uint32_t sss_packet_get_len(struct sss_packet *packet);
 
-/*
- * Allocate a new packet structure
+/**
+ * Allocate and initialize an empty packet.
+ *
+ * @param mem_ctx   Context to allocate the packet with.
+ * @param size      Packet body size.
+ *                  If zero, SSSSRV_PACKET_MEM_SIZE is used instead.
+ * @param rpacket   Location for the created packet pointer, cannot be NULL.
  *
- * - if size is defined use it otherwise the default packet will be
- *   SSSSRV_PACKET_MEM_SIZE bytes.
+ * @return Error code:
+ *          ENOMEM - memory allocation failed,
+ *          EOK - packet created successfully.
  */
 int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size,
                    enum sss_cli_command cmd,
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 573959e..edcfee4 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -234,6 +234,18 @@ static const char *get_homedir_override(TALLOC_CTX *mem_ctx,
                                    dom->case_preserve, homedir_ctx);
 }
 
+/**
+ * Retrieve a user's overriden shell value from an LDB message, according to
+ * global and user's domain-specific configuration.
+ *
+ * @param mem_ctx   Context to allocate returned value with.
+ * @param msg       The LDB message to retrieve user info from.
+ * @param nctx      NSS context to operate within.
+ * @param dom       Domain the user belongs to.
+ *
+ * @return User shell allocated with mem_ctx, or NULL on allocation error,
+ *         or unspecified shell.
+ */
 static const char *get_shell_override(TALLOC_CTX *mem_ctx,
                                       struct ldb_message *msg,
                                       struct nss_ctx *nctx,
@@ -400,6 +412,24 @@ done:
     return ret;
 }
 
+/**
+ * Transfer user information from LDB messages to a packet and NSS memory
+ * cache.
+ *
+ * @param packet        The packet to transfer user information to.
+ * @param dom           Domain the users belong to.
+ * @param nctx          NSS context the operation is carried within.
+ * @param filter_users  True if users present in negative cache should be
+ *                      skipped, false otherwise.
+ * @param pw_mmap_cache True if unskipped users should be stored in mmap
+ *                      cache referenced by nctx, if it's present.
+ * @param msgs          Array of LDB messages containing user information to
+ *                      be transferred.
+ * @param count         Number of LDB messages in msgs array to transfer user
+ *                      information from.
+ *
+ * @return Status code.
+ */
 static int fill_pwent(struct sss_packet *packet,
                       struct sss_domain_info *dom,
                       struct nss_ctx *nctx,
@@ -781,7 +811,7 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
         return ENOENT;
     }
 
-    /* In case of local view we have to always contant DP with the original
+    /* In case of local view we have to always contact DP with the original
      * name or id. */
     get_dp_name_and_id(dctx->cmdctx, dctx->domain, req_type, opt_name, opt_id,
                        &name, &id);
diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index 391eaaf..e686771 100644
--- a/src/responder/nss/nsssrv_private.h
+++ b/src/responder/nss/nsssrv_private.h
@@ -40,17 +40,21 @@ struct nss_state_ctx {
     int netgrent_cur;
 };
 
+/** NSS command execution context */
 struct nss_cmd_ctx {
-    struct cli_ctx *cctx;
-    enum sss_cli_command cmd;
-    char *name;
-    const char *normalized_name;
-    bool name_is_upn;
-    uint32_t id;
+    struct cli_ctx *cctx;           /**< Client context */
+    enum sss_cli_command cmd;       /**< Command being executed */
+    char *name;                     /**< Name of the entry being requested */
+    const char *normalized_name;    /**< Normalized name of the entry being requested */
+    bool name_is_upn;               /**< Names above are Kerberos UPNs */
+    uint32_t id;                    /**< ID of the entry being looked up */
     char *secid;
 
     bool immediate;
-    bool check_next;
+    bool check_next;                /**< False if only the domain pointed to
+                                         by the domain context should be
+                                         looked up, true if following linked
+                                         domains should also be looked up */
     bool enum_cached;
 
     int saved_dom_idx;
@@ -77,13 +81,15 @@ struct getent_ctx {
 };
 
 struct nss_dom_ctx {
-    struct nss_cmd_ctx *cmdctx;
-    struct sss_domain_info *domain;
+    struct nss_cmd_ctx *cmdctx;         /**< Parent command context */
+    struct sss_domain_info *domain;     /**< Domain to start lookup from */
 
     /* For a case when we are discovering subdomains */
     const char *rawname;
 
-    bool check_provider;
+    bool check_provider;                /**< True if the current domain's
+                                             cache needs refresh from the
+                                             provider, false otherwise */
 
     /* cache results */
     struct ldb_result *res;
-- 
2.8.1

>From 126fb4d7195dfc8a7ba1a786abc87eb43444f7ee Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Tue, 9 Aug 2016 15:42:38 +0300
Subject: [PATCH 02/12] SYSDB: Fix debug message

---
 src/db/sysdb_search.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/db/sysdb_search.c b/src/db/sysdb_search.c
index cfee578..1830e95 100644
--- a/src/db/sysdb_search.c
+++ b/src/db/sysdb_search.c
@@ -657,7 +657,7 @@ int sysdb_enumpwent_filter_with_views(TALLOC_CTX *mem_ctx,
 
     ret = sysdb_enumpwent_filter(tmp_ctx, domain, name_filter, addtl_filter, &res);
     if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, "sysdb_enumpwent failed.\n");
+        DEBUG(SSSDBG_OP_FAILURE, "sysdb_enumpwent_filter failed.\n");
         goto done;
     }
 
-- 
2.8.1

>From a7b87bc8dedefac21b963c9929ff3341449f9232 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Thu, 11 Aug 2016 13:43:49 +0300
Subject: [PATCH 03/12] NSS: Add missing headers to nsssrv_private.h

---
 src/responder/nss/nsssrv_private.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index e686771..deaca4a 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;
-- 
2.8.1

>From 9156709b1a705ef355704365b1c24c2b39a29305 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Thu, 11 Aug 2016 13:12:50 +0300
Subject: [PATCH 04/12] NSS: Clarify comments on resuming getgrent/getpwent

---
 src/responder/nss/nsssrv_cmd.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index edcfee4..c95ccb0 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -2588,8 +2588,8 @@ static int nss_cmd_getpwent(struct cli_ctx *cctx)
 
     /* Save the current index and cursor locations
      * If we end up calling setpwent implicitly, because the response object
-     * expired and has to be recreated, we want to resume from the same
-     * location.
+     * expired and has to be recreated, we want to resume returning entries
+     * from the same location.
      */
     cmdctx->saved_dom_idx = state_ctx->pwent.dom_idx;
     cmdctx->saved_cur = state_ctx->pwent.cur;
@@ -3900,8 +3900,8 @@ static int nss_cmd_getgrent(struct cli_ctx *cctx)
 
     /* Save the current index and cursor locations
      * If we end up calling setgrent implicitly, because the response object
-     * expired and has to be recreated, we want to resume from the same
-     * location.
+     * expired and has to be recreated, we want to resume returning entries
+     * from the same location.
      */
     state_ctx = talloc_get_type(cctx->state_ctx, struct nss_state_ctx);
     cmdctx->saved_dom_idx = state_ctx->grent.dom_idx;
-- 
2.8.1

>From b478157c9198ca7dcf14e5bda4f5312ddd5b4cab Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:08:40 +0300
Subject: [PATCH 05/12] NSS: Extract getbynam/id/sid search and reply parts

Extract the duplicate getbynam/id/sid search and reply parts into
functions to avoid maintenance cost and to prepare for insertion of
extra async steps.
---
 src/responder/nss/nsssrv_cmd.c | 299 +++++++++++++++++------------------------
 1 file changed, 120 insertions(+), 179 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index c95ccb0..9de853f 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1544,6 +1544,65 @@ static int nss_cmd_getpwnam(struct cli_ctx *cctx)
     return nss_cmd_getbynam(SSS_NSS_GETPWNAM, cctx);
 }
 
+static int nss_cmd_getbynam_search_and_reply(struct nss_dom_ctx *dctx)
+{
+    errno_t ret;
+    const char *domname = dctx->domain->name;
+
+    DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for [%s] from [%s]\n",
+              dctx->cmdctx->name, domname?domname:"<ALL>");
+
+    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+    /* ok, find it ! */
+    switch (dctx->cmdctx->cmd) {
+    case SSS_NSS_GETPWNAM:
+        ret = nss_cmd_getpwnam_search(dctx);
+        if (ret == EOK) {
+            /* we have results to return */
+            ret = nss_cmd_getpw_send_reply(dctx, false);
+        } else if (ret == ENOENT
+                && dctx->rawname != NULL
+                && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
+        }
+        break;
+    case SSS_NSS_GETGRNAM:
+        ret = nss_cmd_getgrnam_search(dctx);
+        if (ret == EOK) {
+            /* we have results to return */
+            ret = nss_cmd_getgr_send_reply(dctx, false);
+        }
+        break;
+    case SSS_NSS_INITGR:
+        ret = nss_cmd_initgroups_search(dctx);
+        if (ret == EOK) {
+            /* we have results to return */
+            ret = nss_cmd_initgr_send_reply(dctx);
+        } else if (ret == ENOENT
+                && dctx->rawname != NULL
+                && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
+        }
+        break;
+    case SSS_NSS_GETSIDBYNAME:
+    case SSS_NSS_GETORIGBYNAME:
+        ret = nss_cmd_getsidby_search(dctx);
+        if (ret == EOK) {
+            ret = nss_cmd_getbysid_send_reply(dctx);
+        }
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
+              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
+        ret = EINVAL;
+    }
+
+    return ret;
+}
+
 static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
 {
 
@@ -1654,9 +1713,6 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         goto done;
     }
 
-    DEBUG(SSSDBG_CONF_SETTINGS, "Requesting info for [%s] from [%s]\n",
-              cmdctx->name, domname?domname:"<ALL>");
-
     if (domname) {
         dctx->domain = responder_get_domain(cctx->rctx, domname);
         if (!dctx->domain) {
@@ -1679,53 +1735,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         }
     }
 
-    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
-
-    /* ok, find it ! */
-    switch (dctx->cmdctx->cmd) {
-    case SSS_NSS_GETPWNAM:
-        ret = nss_cmd_getpwnam_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getpw_send_reply(dctx, false);
-        } else if (ret == ENOENT
-                && dctx->rawname != NULL
-                && strchr(dctx->rawname, '@') != NULL) {
-            /* assume Kerberos principal */
-            ret = nss_cmd_assume_upn(dctx);
-        }
-        break;
-    case SSS_NSS_GETGRNAM:
-        ret = nss_cmd_getgrnam_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getgr_send_reply(dctx, false);
-        }
-        break;
-    case SSS_NSS_INITGR:
-        ret = nss_cmd_initgroups_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_initgr_send_reply(dctx);
-        } else if (ret == ENOENT
-                && dctx->rawname != NULL
-                && strchr(dctx->rawname, '@') != NULL) {
-            /* assume Kerberos principal */
-            ret = nss_cmd_assume_upn(dctx);
-        }
-        break;
-    case SSS_NSS_GETSIDBYNAME:
-    case SSS_NSS_GETORIGBYNAME:
-        ret = nss_cmd_getsidby_search(dctx);
-        if (ret == EOK) {
-            ret = nss_cmd_getbysid_send_reply(dctx);
-        }
-        break;
-    default:
-        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
-              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
-        ret = EINVAL;
-    }
+    ret = nss_cmd_getbynam_search_and_reply(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
@@ -1766,9 +1776,6 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         /* Not fatal */
     }
 
-    DEBUG(SSSDBG_TRACE_FUNC, "Requesting info for [%s] from [%s]\n",
-              cmdctx->name, domname?domname:"<ALL>");
-
     if (domname) {
         dctx->domain = responder_get_domain(cctx->rctx, domname);
         if (dctx->domain == NULL) {
@@ -1781,53 +1788,7 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         cmdctx->check_next = true;
     }
 
-    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
-
-    /* ok, find it ! */
-    switch (dctx->cmdctx->cmd) {
-    case SSS_NSS_GETPWNAM:
-        ret = nss_cmd_getpwnam_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getpw_send_reply(dctx, false);
-        } else if (ret == ENOENT
-                    && dctx->rawname != NULL
-                    && strchr(dctx->rawname, '@') != NULL) {
-            /* assume Kerberos principal */
-            ret = nss_cmd_assume_upn(dctx);
-        }
-        break;
-    case SSS_NSS_GETGRNAM:
-        ret = nss_cmd_getgrnam_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getgr_send_reply(dctx, false);
-        }
-        break;
-    case SSS_NSS_INITGR:
-        ret = nss_cmd_initgroups_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_initgr_send_reply(dctx);
-        } else if (ret == ENOENT
-                    && dctx->rawname != NULL
-                    && strchr(dctx->rawname, '@') != NULL) {
-            /* assume Kerberos principal */
-            ret = nss_cmd_assume_upn(dctx);
-        }
-        break;
-    case SSS_NSS_GETSIDBYNAME:
-    case SSS_NSS_GETORIGBYNAME:
-        ret = nss_cmd_getsidby_search(dctx);
-        if (ret == EOK) {
-            ret = nss_cmd_getbysid_send_reply(dctx);
-        }
-        break;
-    default:
-        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
-              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
-        ret = EINVAL;
-    }
+    ret = nss_cmd_getbynam_search_and_reply(dctx);
 
 done:
     nss_cmd_done(cmdctx, ret);
@@ -1977,6 +1938,62 @@ static int nss_cmd_getpwuid(struct cli_ctx *cctx)
     return nss_cmd_getbyid(SSS_NSS_GETPWUID, cctx);
 }
 
+static int nss_cmd_getbyid_search_and_reply(struct nss_dom_ctx *dctx)
+{
+    int ret;
+
+    /* ok, find it ! */
+    switch(dctx->cmdctx->cmd) {
+    case SSS_NSS_GETPWUID:
+        ret = nss_cmd_getpwuid_search(dctx);
+        if (ret == EOK) {
+            /* we have results to return */
+            ret = nss_cmd_getpw_send_reply(dctx, true);
+        }
+        break;
+    case SSS_NSS_GETGRGID:
+        ret = nss_cmd_getgrgid_search(dctx);
+        if (ret == EOK) {
+            /* we have results to return */
+            ret = nss_cmd_getgr_send_reply(dctx, true);
+        }
+        break;
+    case SSS_NSS_GETNAMEBYSID:
+    case SSS_NSS_GETIDBYSID:
+        if (dctx->domain == NULL) {
+            ret = responder_get_domain_by_id(dctx->cmdctx->cctx->rctx,
+                                             dctx->cmdctx->secid,
+                                             &dctx->domain);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for SID [%s].\n",
+                                          dctx->cmdctx->secid);
+                ret = ENOENT;
+                break;
+            }
+        }
+
+        dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+        ret = nss_cmd_getbysid_search(dctx);
+        if (ret == EOK) {
+            ret = nss_cmd_getbysid_send_reply(dctx);
+        }
+        break;
+    case SSS_NSS_GETSIDBYID:
+        ret = nss_cmd_getsidby_search(dctx);
+        if (ret == EOK) {
+            ret = nss_cmd_getbysid_send_reply(dctx);
+        }
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
+              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
+        ret = EINVAL;
+    }
+
+    return ret;
+}
+
 static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx)
 {
     struct cli_protocol *pctx;
@@ -2087,33 +2104,7 @@ static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx)
         goto done;
     }
 
-    /* ok, find it ! */
-    switch(dctx->cmdctx->cmd) {
-    case SSS_NSS_GETPWUID:
-        ret = nss_cmd_getpwuid_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getpw_send_reply(dctx, true);
-        }
-        break;
-    case SSS_NSS_GETGRGID:
-        ret = nss_cmd_getgrgid_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getgr_send_reply(dctx, true);
-        }
-        break;
-    case SSS_NSS_GETSIDBYID:
-        ret = nss_cmd_getsidby_search(dctx);
-        if (ret == EOK) {
-            ret = nss_cmd_getbysid_send_reply(dctx);
-        }
-        break;
-    default:
-        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
-              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
-        ret = EINVAL;
-    }
+    ret = nss_cmd_getbyid_search_and_reply(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
@@ -2137,51 +2128,7 @@ static void nss_cmd_getbyid_done(struct tevent_req *req)
         /* Not fatal */
     }
 
-    /* ok, find it ! */
-    switch(dctx->cmdctx->cmd) {
-    case SSS_NSS_GETPWUID:
-        ret = nss_cmd_getpwuid_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getpw_send_reply(dctx, true);
-        }
-        break;
-    case SSS_NSS_GETGRGID:
-        ret = nss_cmd_getgrgid_search(dctx);
-        if (ret == EOK) {
-            /* we have results to return */
-            ret = nss_cmd_getgr_send_reply(dctx, true);
-        }
-        break;
-    case SSS_NSS_GETNAMEBYSID:
-    case SSS_NSS_GETIDBYSID:
-        ret = responder_get_domain_by_id(cmdctx->cctx->rctx, cmdctx->secid,
-                                         &dctx->domain);
-        if (ret != EOK) {
-            DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for SID [%s].\n",
-                                      cmdctx->secid);
-            ret = ENOENT;
-            goto done;
-        }
-
-        dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
-
-        ret = nss_cmd_getbysid_search(dctx);
-        if (ret == EOK) {
-            ret = nss_cmd_getbysid_send_reply(dctx);
-        }
-        break;
-    case SSS_NSS_GETSIDBYID:
-        ret = nss_cmd_getsidby_search(dctx);
-        if (ret == EOK) {
-            ret = nss_cmd_getbysid_send_reply(dctx);
-        }
-        break;
-    default:
-        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d][%s].\n",
-              dctx->cmdctx->cmd, sss_cmd2str(dctx->cmdctx->cmd));
-        ret = EINVAL;
-    }
+    ret = nss_cmd_getbyid_search_and_reply(dctx);
 
 done:
     nss_cmd_done(cmdctx, ret);
@@ -5429,13 +5376,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);
 
-    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
-
-    /* ok, find it ! */
-    ret = nss_cmd_getbysid_search(dctx);
-    if (ret == EOK) {
-        ret = nss_cmd_getbysid_send_reply(dctx);
-    }
+    ret = nss_cmd_getbyid_search_and_reply(dctx);
 
 done:
     return nss_cmd_done(cmdctx, ret);
-- 
2.8.1

>From 207b4c111263a4e026ba8db2cdb97aa6333120ba Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:16:27 +0300
Subject: [PATCH 06/12] NSS: Reset negcache after domain stuff for getbynam

Move nss_reset_negcache after code dealing with figuring out domains in
nss_cmd_getbynam_done. This keeps the domain code together and makes it
correspond more closely to the code doing the same in nss_cmd_getbynam,
which it mimics and supports.
---
 src/responder/nss/nsssrv_cmd.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 9de853f..0d978f3 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1770,12 +1770,6 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         goto done;
     }
 
-    ret = nss_reset_negcache(cctx->rctx);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_MINOR_FAILURE, "Cannot reset negcache records\n");
-        /* Not fatal */
-    }
-
     if (domname) {
         dctx->domain = responder_get_domain(cctx->rctx, domname);
         if (dctx->domain == NULL) {
@@ -1788,6 +1782,12 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         cmdctx->check_next = true;
     }
 
+    ret = nss_reset_negcache(cctx->rctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Cannot reset negcache records\n");
+        /* Not fatal */
+    }
+
     ret = nss_cmd_getbynam_search_and_reply(dctx);
 
 done:
-- 
2.8.1

>From 3b86b89ba5c9e3908216273ea710270fd6b2dc31 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:24:52 +0300
Subject: [PATCH 07/12] NSS: Rename nss_cmd_getbynam/id_done

Rename nss_cmd_getby(nam|id)_done to
nss_cmd_getby(nam|id)_get_domains_done to prepare for addition of
another async request to nss_cmd_getby(nam|id) processing.
---
 src/responder/nss/nsssrv_cmd.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 0d978f3..42eb57e 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1538,7 +1538,7 @@ 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_done(struct tevent_req *req);
+static void nss_cmd_getbynam_get_domains_done(struct tevent_req *req);
 static int nss_cmd_getpwnam(struct cli_ctx *cctx)
 {
     return nss_cmd_getbynam(SSS_NSS_GETPWNAM, cctx);
@@ -1688,7 +1688,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         if (req == NULL) {
             ret = ENOMEM;
         } else {
-            tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
+            tevent_req_set_callback(req, nss_cmd_getbynam_get_domains_done, dctx);
             ret = EAGAIN;
         }
         goto done;
@@ -1703,7 +1703,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         if (req == NULL) {
             ret = ENOMEM;
         } else {
-            tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
+            tevent_req_set_callback(req, nss_cmd_getbynam_get_domains_done, dctx);
             ret = EAGAIN;
         }
         goto done;
@@ -1728,7 +1728,8 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
             if (req == NULL) {
                 ret = ENOMEM;
             } else {
-                tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx);
+                tevent_req_set_callback(req,
+                                        nss_cmd_getbynam_get_domains_done, dctx);
                 ret = EAGAIN;
             }
             goto done;
@@ -1741,7 +1742,7 @@ done:
     return nss_cmd_done(cmdctx, ret);
 }
 
-static void nss_cmd_getbynam_done(struct tevent_req *req)
+static void nss_cmd_getbynam_get_domains_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;
@@ -1932,7 +1933,7 @@ 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_done(struct tevent_req *req);
+static void nss_cmd_getbyid_get_domains_done(struct tevent_req *req);
 static int nss_cmd_getpwuid(struct cli_ctx *cctx)
 {
     return nss_cmd_getbyid(SSS_NSS_GETPWUID, cctx);
@@ -2098,7 +2099,8 @@ static int nss_cmd_getbyid(enum sss_cli_command cmd, struct cli_ctx *cctx)
         if (req == NULL) {
             ret = ENOMEM;
         } else {
-            tevent_req_set_callback(req, nss_cmd_getbyid_done, dctx);
+            tevent_req_set_callback(req,
+                                    nss_cmd_getbyid_get_domains_done, dctx);
             ret = EAGAIN;
         }
         goto done;
@@ -2110,7 +2112,7 @@ done:
     return nss_cmd_done(cmdctx, ret);
 }
 
-static void nss_cmd_getbyid_done(struct tevent_req *req)
+static void nss_cmd_getbyid_get_domains_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;
@@ -5364,7 +5366,8 @@ static int nss_cmd_getbysid(enum sss_cli_command cmd, struct cli_ctx *cctx)
             ret = ENOMEM;
         } else {
             dctx->rawname = sid_str;
-            tevent_req_set_callback(req, nss_cmd_getbyid_done, dctx);
+            tevent_req_set_callback(req,
+                                    nss_cmd_getbyid_get_domains_done, dctx);
             ret = EAGAIN;
         }
         goto done;
-- 
2.8.1

>From 6221cd424c8fe825e70624217de35db024855d06 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:53:57 +0300
Subject: [PATCH 08/12] NSS: Make fill_pwent take cmdctx, not nctx

Make fill_pwent in nsssrv_cmd.c accept more specific command context,
instead of just NSS context to prepare for addition of command-specific
session-recording code.
---
 src/responder/nss/nsssrv_cmd.c | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 42eb57e..72faa13 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -418,7 +418,7 @@ done:
  *
  * @param packet        The packet to transfer user information to.
  * @param dom           Domain the users belong to.
- * @param nctx          NSS context the operation is carried within.
+ * @param cmdctx        The command context the operation is carried within.
  * @param filter_users  True if users present in negative cache should be
  *                      skipped, false otherwise.
  * @param pw_mmap_cache True if unskipped users should be stored in mmap
@@ -432,11 +432,12 @@ done:
  */
 static int fill_pwent(struct sss_packet *packet,
                       struct sss_domain_info *dom,
-                      struct nss_ctx *nctx,
+                      struct nss_cmd_ctx *cmdctx,
                       bool filter_users, bool pw_mmap_cache,
                       struct ldb_message **msgs,
                       int *count)
 {
+    struct nss_ctx *nctx;
     struct ldb_message *msg;
     uint8_t *body;
     const char *upn;
@@ -457,6 +458,8 @@ static int fill_pwent(struct sss_packet *packet,
     TALLOC_CTX *tmp_ctx = NULL;
     struct sss_nss_homedir_ctx homedir_ctx;
 
+    nctx = talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
+
     to_sized_string(&pwfield, nctx->pwfield);
 
     rp = 2*sizeof(uint32_t);
@@ -614,12 +617,10 @@ static int nss_cmd_getpw_send_reply(struct nss_dom_ctx *dctx, bool filter)
     struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
     struct cli_ctx *cctx = cmdctx->cctx;
     struct cli_protocol *pctx;
-    struct nss_ctx *nctx;
     int ret;
     int i;
 
     pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol);
-    nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
 
     ret = sss_packet_new(pctx->creq, 0,
                          sss_packet_get_cmd(pctx->creq->in),
@@ -631,7 +632,7 @@ static int nss_cmd_getpw_send_reply(struct nss_dom_ctx *dctx, bool filter)
 
     ret = fill_pwent(pctx->creq->out,
                      dctx->domain,
-                     nctx, filter, true,
+                     cmdctx, filter, true,
                      dctx->res->msgs, &i);
     if (ret) {
         return ret;
@@ -2559,7 +2560,7 @@ static int nss_cmd_getpwent(struct cli_ctx *cctx)
     return nss_cmd_getpwent_immediate(cmdctx);
 }
 
-static int nss_cmd_retpwent(struct cli_ctx *cctx, int num);
+static int nss_cmd_retpwent(struct nss_cmd_ctx *cmdctx, int num);
 static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx)
 {
     struct cli_protocol *pctx;
@@ -2585,7 +2586,7 @@ static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx)
         return ret;
     }
 
-    ret = nss_cmd_retpwent(cmdctx->cctx, num);
+    ret = nss_cmd_retpwent(cmdctx, num);
 
     sss_packet_set_error(pctx->creq->out, ret);
     sss_cmd_done(cmdctx->cctx, cmdctx);
@@ -2593,7 +2594,7 @@ static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx)
     return EOK;
 }
 
-static int nss_cmd_retpwent(struct cli_ctx *cctx, int num)
+static int nss_cmd_retpwent(struct nss_cmd_ctx *cmdctx, int num)
 {
     struct cli_protocol *pctx;
     struct nss_state_ctx *state_ctx;
@@ -2604,9 +2605,9 @@ static int nss_cmd_retpwent(struct cli_ctx *cctx, int num)
     int n = 0;
     int ret = ENOENT;
 
-    pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol);
-    state_ctx = talloc_get_type(cctx->state_ctx, struct nss_state_ctx);
-    nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+    pctx = talloc_get_type(cmdctx->cctx->protocol_ctx, struct cli_protocol);
+    state_ctx = talloc_get_type(cmdctx->cctx->state_ctx, struct nss_state_ctx);
+    nctx = talloc_get_type(cmdctx->cctx->rctx->pvt_ctx, struct nss_ctx);
     if (!nctx->pctx) goto none;
 
     gctx = nctx->pctx;
@@ -2639,7 +2640,7 @@ static int nss_cmd_retpwent(struct cli_ctx *cctx, int num)
 
         msgs = &(pdom->res->msgs[state_ctx->pwent.cur]);
 
-        ret = fill_pwent(pctx->creq->out, pdom->domain, nctx,
+        ret = fill_pwent(pctx->creq->out, pdom->domain, cmdctx,
                          true, false, msgs, &n);
 
         state_ctx->pwent.cur += n;
-- 
2.8.1

>From d04f4d67ea04b049227f4e2a492133c3ca6fcade Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Thu, 11 Aug 2016 14:10:44 +0300
Subject: [PATCH 09/12] config: Add session_recording section

Add information on "session_recording" config section, having three
options: "scope", "users", and "groups".

The section is intended for disabling session recording ("scope = none",
default), enabling session recording for all users ("scope = all"), and
enabling it for some specific users and/or groups ("scope = some",
"users = <users>", "groups = <groups>").
---
 src/confdb/confdb.h          | 6 ++++++
 src/config/SSSDConfigTest.py | 6 ++++--
 src/config/etc/sssd.api.conf | 6 ++++++
 3 files changed, 16 insertions(+), 2 deletions(-)

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
-- 
2.8.1

>From 63647ba98c0df9c63979bf8cd4d41c572d8d3aca Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Thu, 11 Aug 2016 14:15:55 +0300
Subject: [PATCH 10/12] BUILD: Support configuring session recording shell

Add support for specifying the shell used for recording user sessions,
at configure time.
---
 configure.ac       |  1 +
 src/conf_macros.m4 | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

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])],
-- 
2.8.1

>From 6eccd0de064485475dfe7346a716567725f57a12 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
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 <[email protected]>
+
+    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 <[email protected]>
+
+    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

>From f431ba55c15b95b830f8789120692c75971cc78f Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Thu, 11 Aug 2016 14:18:38 +0300
Subject: [PATCH 12/12] intg: Add session recording tests

---
 src/tests/intg/Makefile.am  |   1 +
 src/tests/intg/config.py.m4 |  21 ++--
 src/tests/intg/ldap_test.py | 239 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 251 insertions(+), 10 deletions(-)

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index b8cc5c0..7098a7e 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -23,6 +23,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 77aa47b..c2922f9 100644
--- a/src/tests/intg/config.py.m4
+++ b/src/tests/intg/config.py.m4
@@ -2,13 +2,14 @@
 Build configuration variables.
 """
 
-PREFIX              = "prefix"
-SYSCONFDIR          = "sysconfdir"
-NSS_MODULE_DIR      = PREFIX + "/lib"
-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"
+NSS_MODULE_DIR          = PREFIX + "/lib"
+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..955aa21 100644
--- a/src/tests/intg/ldap_test.py
+++ b/src/tests/intg/ldap_test.py
@@ -789,3 +789,242 @@ def test_vetoed_shells(vetoed_shells):
                  shell="/bin/default")
         )
     )
+
+
[email protected]
+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)
+
+
[email protected]
+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"),
+        )
+    )
+
+
[email protected]
+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_nam(session_recording_all):
+    """Test session recording "all" scope with getpwnam"""
+    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=config.SESSION_RECORDING_SHELL),
+    ))
+
+
+def test_session_recording_all_uid(session_recording_all):
+    """Test session recording "all" scope with getpwuid"""
+    ent.assert_each_passwd_by_uid({
+        1001:dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+        1002:dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+        1003:dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+    })
+
+
+def test_session_recording_all_ent(session_recording_all):
+    """Test session recording "all" scope with getpwent"""
+    ent.assert_passwd_list(
+        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),
+        )
+    )
+
+
[email protected]
+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"),
+        )
+    )
+
+
[email protected]
+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_nam(session_recording_some_users):
+    """Test session recording "some" scope with user list and getpwnam"""
+    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"),
+    ))
+
+
+def test_session_recording_some_users_uid(session_recording_some_users):
+    """Test session recording "some" scope with user list and getpwuid"""
+    ent.assert_each_passwd_by_uid({
+        1001:dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+        1002:dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+        1003:dict(name="user3", uid=1003, shell="/bin/sh"),
+    })
+
+
+def test_session_recording_some_users_ent(session_recording_some_users):
+    """Test session recording "some" scope with user list and getpwent"""
+    ent.assert_passwd_list(
+        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"),
+        )
+    )
+
+
[email protected]
+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_nam(session_recording_some_groups):
+    """Test session recording "some" scope with group list and getpwnam"""
+    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"),
+    ))
+
+
+def test_session_recording_some_groups_uid(session_recording_some_groups):
+    """Test session recording "some" scope with group list and getpwuid"""
+    ent.assert_each_passwd_by_uid({
+        1001:dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+        1002:dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+        1003:dict(name="user3", uid=1003, shell="/bin/sh"),
+    })
+
+
+def test_session_recording_some_groups_ent(session_recording_some_groups):
+    """Test session recording "some" scope with group list and getpwent"""
+    ent.assert_passwd_list(
+        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"),
+        )
+    )
+
+
[email protected]
+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_nam(
+                                    session_recording_some_users_and_groups):
+    """
+    Test session recording "some" scope with user and group lists and getpwnam
+    """
+    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),
+    ))
+
+
+def test_session_recording_some_users_and_groups_uid(
+                                    session_recording_some_users_and_groups):
+    """
+    Test session recording "some" scope with user and group lists and getpwuid
+    """
+    ent.assert_each_passwd_by_uid({
+        1001:dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+        1002:dict(name="user2", uid=1002, shell="/bin/sh"),
+        1003:dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+    })
+
+
+def test_session_recording_some_users_and_groups_ent(
+                                    session_recording_some_users_and_groups):
+    """
+    Test session recording "some" scope with user and group lists and getpwent
+    """
+    ent.assert_passwd_list(
+        ent.contains_only(
+            dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+            dict(name="user2", uid=1002, shell="/bin/sh"),
+            dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+        )
+    )
-- 
2.8.1

_______________________________________________
sssd-devel mailing list
[email protected]
https://lists.fedorahosted.org/admin/lists/[email protected]

Reply via email to