From 809f1e89b1fa9e163b2fb34585ab645f5bfdfff6 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 17 May 2011 16:49:19 +0200
Subject: [PATCH 3/3] Add new options to override shell value

https://fedorahosted.org/sssd/ticket/742
---
 configure.ac                   |    1 +
 src/conf_macros.m4             |   14 +++++++
 src/confdb/confdb.h            |    3 +
 src/config/SSSDConfig.py       |    2 +
 src/config/etc/sssd.api.conf   |    2 +
 src/man/sssd.conf.5.xml        |   44 +++++++++++++++++++++
 src/responder/nss/nsssrv.c     |   82 ++++++++++++++++++++++++++++++++++++++++
 src/responder/nss/nsssrv.h     |    3 +
 src/responder/nss/nsssrv_cmd.c |   39 ++++++++++++++++++-
 9 files changed, 189 insertions(+), 1 deletions(-)

diff --git a/configure.ac b/configure.ac
index ba1c03b..b96c42a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -95,6 +95,7 @@ WITH_SELINUX
 WITH_NSCD
 WITH_SEMANAGE
 WITH_LIBNL
+WITH_NOLOGIN_SHELL
 
 m4_include([src/external/pkg.m4])
 m4_include([src/external/libpopt.m4])
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index 31048d3..af9cb4f 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -317,3 +317,17 @@ AC_DEFUN([WITH_CRYPTO],
       AM_CONDITIONAL([HAVE_NSS], [test x"$cryptolib" = xnss])
       AM_CONDITIONAL([HAVE_LIBCRYPTO], [test x"$cryptolib" = xlibcrypto])
     ])
+
+AC_DEFUN([WITH_NOLOGIN_SHELL],
+  [ AC_ARG_WITH([nologin-shell],
+                [AC_HELP_STRING([--with-nologin-shell=PATH],
+                                [The shell used to deny access to users [/sbin/nologin]]
+                               )
+                ]
+               )
+    nologin_shell="/sbin/nologin"
+    if test x"$with_nologin_shell" != x; then
+        nologin_shell=$with_nologin_shell
+    fi
+    AC_DEFINE_UNQUOTED(NOLOGIN_SHELL, "$nologin_shell", [The shell used to deny access to users])
+  ])
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 20e82b4..c2ae9fc 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -41,6 +41,7 @@
 #define CONFDB_DEFAULT_CONFIG_FILE SSSD_CONF_DIR"/sssd.conf"
 #define SSSD_MIN_ID 1
 #define SSSD_LOCAL_MINID 1000
+#define CONFDB_DEFAULT_SHELL_FALLBACK "/bin/sh"
 
 
 /* Configuration options */
@@ -73,6 +74,8 @@
 #define CONFDB_NSS_FILTER_GROUPS "filter_groups"
 #define CONFDB_NSS_PWFIELD  "pwfield"
 #define CONFDB_NSS_OVERRIDE_HOMEDIR "override_homedir"
+#define CONFDB_NSS_ALLOWED_SHELL "allowed_shells"
+#define CONFDB_NSS_SHELL_FALLBACK "shell_fallback"
 
 /* PAM */
 #define CONFDB_PAM_CONF_ENTRY "config/pam"
diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index 8e962f3..5a26e31 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -59,6 +59,8 @@ option_strings = {
     'filter_users_in_groups' : _('Should filtered users appear in groups'),
     'pwfield' : _('The value of the password field the NSS provider should return'),
     'override_homedir' : _('Override homedir value from the identity provider with this value'),
+    'allowed_shells' : _('The list of shells users are allowed to log in with'),
+    'shell_fallback' : _('If a shell stored in central directory is allowed but not available, use this fallback'),
 
     # [pam]
     'offline_credentials_expiration' : _('How long to allow cached logins between online logins (days)'),
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 6f15495..ae9ba55 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -28,6 +28,8 @@ filter_groups = list, str, false
 filter_users_in_groups = bool, None, false
 pwfield = str, None, false
 override_homedir = str, None, false
+allowed_shells = list, str, false
+shell_fallback = str, None, false
 
 [pam]
 # Authentication service
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 57454dd..b4f3846 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -391,6 +391,50 @@
                         </para>
                     </listitem>
                 </varlistentry>
+                <varlistentry>
+                    <term>allowed_shells (string)</term>
+                    <listitem>
+                        <para>
+                            Restrict user shell to one of the listed values. The order of evaluation is:
+                        </para>
+                        <para>
+                            1. If the shell is present in
+                            <quote>/etc/shells</quote>, it is used.
+                        </para>
+                        <para>
+                            2. If the shell is in the allowed_shells list but
+                            not in <quote>/etc/shells</quote>, use the
+                            value of the shell_fallback parameter.
+                        </para>
+                        <para>
+                            3. If the shell is not in the allowed_shells list and
+                            not in <quote>/etc/shells</quote>, a nologin shell
+                            is used.
+                        </para>
+                        <para>
+                            An empty string for shell is passed as-is to libc.
+                        </para>
+                        <para>
+                            The <quote>/etc/shells</quote> is only read on SSSD start up, which means that
+                            a restart of the SSSD is required in case a new shell is installed.
+                        </para>
+                        <para>
+                            Default: Not set. The user shell is automatically used.
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>shell_fallback (string)</term>
+                    <listitem>
+                        <para>
+                            The default shell to use if an allowed shell is not
+                            installed on the machine.
+                        </para>
+                        <para>
+                            Default: /bin/sh
+                        </para>
+                    </listitem>
+                </varlistentry>
             </variablelist>
         </refsect2>
         <refsect2 id='PAM'>
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
index a5323bd..b9d548e 100644
--- a/src/responder/nss/nsssrv.c
+++ b/src/responder/nss/nsssrv.c
@@ -48,6 +48,9 @@
 
 #define DEFAULT_PWFIELD "*"
 
+#define SHELL_REALLOC_INCREMENT 5
+#define SHELL_REALLOC_MAX       50
+
 struct sbus_method monitor_nss_methods[] = {
     { MON_CLI_METHOD_PING, monitor_common_pong },
     { MON_CLI_METHOD_RES_INIT, monitor_common_res_init },
@@ -63,6 +66,71 @@ struct sbus_interface monitor_nss_interface = {
     NULL
 };
 
+static errno_t nss_get_etc_shells(TALLOC_CTX *mem_ctx, char ***_shells)
+{
+    int i = 0;
+    char *sh;
+    char **shells = NULL;
+    TALLOC_CTX *tmp_ctx;
+    errno_t ret;
+    int size;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    shells = talloc_array(tmp_ctx, char *, SHELL_REALLOC_INCREMENT);
+    if (!shells) {
+        ret = ENOMEM;
+        goto done;
+    }
+    size = SHELL_REALLOC_INCREMENT;
+
+    setusershell();
+    while ((sh = getusershell())) {
+        shells[i] = talloc_strdup(shells, sh);
+        if (!shells[i]) {
+            endusershell();
+            ret = ENOMEM;
+            goto done;
+        }
+        DEBUG(6, ("Found shell %s in /etc/shells\n", shells[i]));
+        i++;
+
+        if (i == size) {
+            size += SHELL_REALLOC_INCREMENT;
+            if (size > SHELL_REALLOC_MAX) {
+                DEBUG(0, ("Reached maximum number of shells [%d]. "
+                          "Users may be denied access. "
+                          "Please check /etc/shells for sanity\n",
+                          SHELL_REALLOC_MAX));
+                break;
+            }
+            shells = talloc_realloc(NULL, shells, char *,
+                                    size);
+            if (!shells) {
+                ret = ENOMEM;
+                goto done;
+            }
+        }
+    }
+    endusershell();
+
+    if (i + 1 < size) {
+        shells = talloc_realloc(NULL, shells, char *, i + 1);
+        if (!shells) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+    shells[i] = NULL;
+
+    *_shells = talloc_move(mem_ctx, &shells);
+    ret = EOK;
+done:
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
+
 static int nss_get_config(struct nss_ctx *nctx,
                           struct resp_ctx *rctx,
                           struct confdb_ctx *cdb)
@@ -115,6 +183,20 @@ static int nss_get_config(struct nss_ctx *nctx,
                             &nctx->override_homedir);
     if (ret != EOK) goto done;
 
+    ret = confdb_get_string_as_list(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+                                    CONFDB_NSS_ALLOWED_SHELL,
+                                    &nctx->allowed_shells);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    ret = nss_get_etc_shells(nctx, &nctx->etc_shells);
+    if (ret != EOK) goto done;
+
+    ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+                            CONFDB_NSS_SHELL_FALLBACK,
+                            CONFDB_DEFAULT_SHELL_FALLBACK,
+                            &nctx->shell_fallback);
+    if (ret != EOK) goto done;
+
     ret = 0;
 done:
     talloc_free(tmpctx);
diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h
index e3e774f..f9aff56 100644
--- a/src/responder/nss/nsssrv.h
+++ b/src/responder/nss/nsssrv.h
@@ -59,6 +59,9 @@ struct nss_ctx {
     char *pwfield;
 
     char *override_homedir;
+    char **allowed_shells;
+    char **etc_shells;
+    char *shell_fallback;
 };
 
 struct nss_packet;
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 4386da7..e698b5e 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -305,6 +305,43 @@ static const char *get_homedir_override(TALLOC_CTX *mem_ctx,
             ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL));
 }
 
+static const char *get_shell_override(TALLOC_CTX *mem_ctx,
+                                      struct ldb_message *msg,
+                                      struct nss_ctx *nctx)
+{
+    const char *user_shell;
+    int i;
+
+    user_shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL);
+    if (!user_shell) return NULL;
+    if (!nctx->allowed_shells) return talloc_strdup(mem_ctx, user_shell);
+
+    for (i=0; nctx->etc_shells[i]; i++) {
+        if (strcmp(user_shell, nctx->etc_shells[i]) == 0) {
+            DEBUG(9, ("Shell %s found in /etc/shells\n",
+                      nctx->etc_shells[i]));
+            break;
+        }
+    }
+
+    if (nctx->etc_shells[i]) {
+        DEBUG(9, ("Using original shell '%s'\n", user_shell));
+        return talloc_strdup(mem_ctx, user_shell);
+    }
+
+    for (i=0; nctx->allowed_shells[i]; i++) {
+        if (strcmp(nctx->allowed_shells[i], user_shell) == 0) {
+            DEBUG(5, ("The shell '%s' is allowed but does not exist. "
+                      "Using fallback\n", user_shell));
+            return talloc_strdup(mem_ctx, nctx->shell_fallback);
+        }
+    }
+
+    DEBUG(5, ("The shell '%s' is not allowed and does not exist.\n",
+              user_shell));
+    return talloc_strdup(mem_ctx, NOLOGIN_SHELL);
+}
+
 static int fill_pwent(struct sss_packet *packet,
                       struct sss_domain_info *dom,
                       struct nss_ctx *nctx,
@@ -373,7 +410,7 @@ static int fill_pwent(struct sss_packet *packet,
 
         gecos = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL);
         homedir = get_homedir_override(tmp_ctx, msg, nctx, dom, name, uid);
-        shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL);
+        shell = get_shell_override(tmp_ctx, msg, nctx);
 
         if (!gecos) gecos = "";
         if (!homedir) homedir = "/";
-- 
1.7.4.4

