Hi everyone,

Please find attached proof-of-concept patches for a part of NSS integration
with tlog. Namely, addition of shell substitution for getpwnam requests.

The code is supposed to replace a user's shell with /usr/bin/tlog-rec, if
session recording is enabled for all users, if it is enabled for that
particular user, or for a group that it belongs to.

The configuration is done in a dedicated section of sssd.conf named
"session_recording", which can contain three options "scope", "users", and
"groups". Those correspond to the scope of session recording: "none", "some",
and "all", corresponding in order to: disabled session recording, session
recording enabled for the specified users/groups, and session recording
enabled for all users handled by SSSD.

An example of a configuration can be:

    [session_recording]
    ; Disabled
    scope = none

or

    [session_recording]
    ; Enabled for everyone
    scope = all

or

    [session_recording]
    ; Enabled for some users and groups
    scope = some
    users = user1, user2
    groups = group1, group2

The parts to be done still are adding support for getpwuid and getpwent
requests, exporting of the original shell in pam_sss, and of course cleaning
it up and doing it according to your comments and requirements.

The code has some documentation in doxygen format, which I can change later if
we decide on some other format, or no documentation at all.

Please, tell me if I'm doing anything wrong this far already, or suggest
better ways to do it.

Thank you!

Nick

P.S. I'm on PTO for two weeks starting next week, so might not be able to
     answer quickly.
>From 163c4ce42fbce3137b624e32699999aa70b739b3 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Fri, 15 Jul 2016 13:13:52 +0300
Subject: [PATCH 1/6] 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 1ae1796..f0291cc 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 79c7b72..fb78d38 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 c8b74fd60ef713522a25d876ac2d8163d27942fd Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:08:40 +0300
Subject: [PATCH 2/6] NSS: Extract getbynam search and reply function

Extract the duplicte getbynam search and reply parts into a function to
avoid maintenance cost and prepare for insertion of an extra async step.
---
 src/responder/nss/nsssrv_cmd.c | 161 ++++++++++++++++-------------------------
 1 file changed, 61 insertions(+), 100 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index f0291cc..480c35b 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1534,6 +1534,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)
 {
 
@@ -1645,9 +1704,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) {
@@ -1671,53 +1727,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);
@@ -1758,9 +1768,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) {
@@ -1773,53 +1780,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);
-- 
2.8.1

>From 6b2c387df91d64f3a68bbd68ae463f70c725a600 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:16:27 +0300
Subject: [PATCH 3/6] 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 480c35b..4baacf3 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1762,12 +1762,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) {
@@ -1780,6 +1774,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 7ca57841172471dc31e3646e9387ca795500a04d Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:24:52 +0300
Subject: [PATCH 4/6] 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 | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 4baacf3..dfe1fe8 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -1528,7 +1528,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);
@@ -1678,7 +1678,8 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
             ret = ENOMEM;
         } else {
             dctx->rawname = rawname;
-            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;
@@ -1694,7 +1695,8 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
             ret = ENOMEM;
         } else {
             dctx->rawname = rawname;
-            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;
@@ -1720,7 +1722,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;
@@ -1733,7 +1736,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;
@@ -1924,7 +1927,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);
@@ -2034,7 +2037,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;
@@ -2072,7 +2076,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;
@@ -5360,7 +5364,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 f4250565b07eec698820c0428cd05e66ab0e0ccb Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
Date: Wed, 20 Jul 2016 18:53:57 +0300
Subject: [PATCH 5/6] 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 dfe1fe8..2e1bc93 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;
@@ -2567,7 +2568,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;
@@ -2593,7 +2594,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);
@@ -2601,7 +2602,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;
@@ -2612,9 +2613,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;
@@ -2647,7 +2648,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 92a8255206ff8bc5500f85d4c87a18dfef7d6aa7 Mon Sep 17 00:00:00 2001
From: Nikolai Kondrashov <[email protected]>
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 <[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    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 <[email protected]>
+
+    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")
         )
     )
+
+
[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(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),
+        )
+    )
+
+
[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(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"),
+        )
+    )
+
+
[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)
+
+
[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(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"),
+        )
+    )
+
+
[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(
+                                        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

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

Reply via email to