From 29641ad3770d8236074c8bf3e7a828e37c5f23dd Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Tue, 30 Mar 2010 15:26:58 +0200
Subject: [PATCH 2/2] SELinux login management

Adds a new option -Z to sss_useradd and sss_usermod. This option allows
user to specify the SELinux login context for the user. On deleting the
user with sss_userdel, the login mapping is deleted, so subsequent
adding of the same user would result in the default login context unless
-Z is specified again.

MLS security is not supported as of this patch.
---
 contrib/sssd.spec.in      |    2 +
 contrib/suse/sssd.spec.in |    1 +
 src/Makefile.am           |    7 +
 src/conf_macros.m4        |   16 ++
 src/configure.ac          |    5 +
 src/external/selinux.m4   |   12 ++
 src/man/sss_useradd.8.xml |   12 ++
 src/man/sss_usermod.8.xml |   11 ++
 src/tools/selinux.c       |  338 +++++++++++++++++++++++++++++++++++++++++++++
 src/tools/sss_useradd.c   |   11 ++
 src/tools/sss_userdel.c   |    9 ++
 src/tools/sss_usermod.c   |   11 ++
 src/tools/tools_util.h    |    2 +
 13 files changed, 437 insertions(+), 0 deletions(-)

diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index c6c91db..c5e05e3 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -72,6 +72,8 @@ BuildRequires: c-ares-devel
 BuildRequires: python-devel
 BuildRequires: check-devel
 BuildRequires: doxygen
+BuildRequires: libselinux-devel
+BuildRequires: libsemanage-devel
 
 %description
 Provides a set of daemons to manage access to remote directories and
diff --git a/contrib/suse/sssd.spec.in b/contrib/suse/sssd.spec.in
index eff7ff7..8fce0b1 100644
--- a/contrib/suse/sssd.spec.in
+++ b/contrib/suse/sssd.spec.in
@@ -110,6 +110,7 @@ KRB5_LIBS=-lkrb5 \
     --with-init-dir=%{_initrddir} \
     --enable-nsslibdir=/%{_lib} \
     --without-selinux \
+    --without-semanage \
     --with-os=suse \
     --disable-static
 
diff --git a/src/Makefile.am b/src/Makefile.am
index e5c12df..e56d7b1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -295,6 +295,9 @@ TOOLS_LIBS = \
 if BUILD_SELINUX
     TOOLS_LIBS += $(SELINUX_LIBS)
 endif
+if BUILD_SEMANAGE
+    TOOLS_LIBS += $(SEMANAGE_LIBS)
+endif
 
 dist_noinst_HEADERS = \
     monitor/monitor.h \
@@ -529,11 +532,15 @@ FILES_TESTS_LIBS = \
 if BUILD_SELINUX
     FILES_TESTS_LIBS += $(SELINUX_LIBS)
 endif
+if BUILD_SEMANAGE
+    FILES_TESTS_LIBS += $(SEMANAGE_LIBS)
+endif
 
 files_tests_SOURCES = \
     $(SSSD_DEBUG_OBJ) \
     tests/files-tests.c \
     util/check_and_open.c \
+    tools/selinux.c \
     tools/files.c
 files_tests_CFLAGS = \
     $(AM_CFLAGS) \
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index 4a41da2..4630f31 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -217,3 +217,19 @@ AC_DEFUN([WITH_NSCD],
     fi
   ])
 
+AC_DEFUN([WITH_SEMANAGE],
+  [ AC_ARG_WITH([semanage],
+                [AC_HELP_STRING([--with-semanage],
+                                [Whether to build with SELinux user management support [yes]]
+                               )
+                ],
+                [],
+                with_semanage=yes
+               )
+    if test x"$with_semanage" == xyes; then
+        HAVE_SEMANAGE=1
+        AC_SUBST(HAVE_SEMANAGE)
+        AC_DEFINE_UNQUOTED(HAVE_SEMANAGE, 1, [Build with SELinux support])
+    fi
+    AM_CONDITIONAL([BUILD_SEMANAGE], [test x"$with_semanage" = xyes])
+  ])
diff --git a/src/configure.ac b/src/configure.ac
index 52162b7..40dac37 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -72,6 +72,7 @@ WITH_KRB5_PLUGIN_PATH
 WITH_PYTHON_BINDINGS
 WITH_SELINUX
 WITH_NSCD
+WITH_SEMANAGE
 
 m4_include([external/platform.m4])
 m4_include([external/pkg.m4])
@@ -130,6 +131,10 @@ if test x$HAVE_SELINUX != x; then
     AM_CHECK_SELINUX
 fi
 
+if test x$HAVE_SEMANAGE != x -a x$HAVE_SELINUX != x; then
+    AM_CHECK_SEMANAGE
+fi
+
 AC_CHECK_HEADERS([sys/inotify.h])
 
 AC_CHECK_HEADERS([sasl/sasl.h],,AC_MSG_ERROR([Could not find SASL headers]))
diff --git a/src/external/selinux.m4 b/src/external/selinux.m4
index 0c5d529..d1b961a 100644
--- a/src/external/selinux.m4
+++ b/src/external/selinux.m4
@@ -11,3 +11,15 @@ AC_DEFUN([AM_CHECK_SELINUX],
     AC_SUBST(SELINUX_LIBS)
 ])
 
+dnl A macro to check the availability of SELinux management library
+AC_DEFUN([AM_CHECK_SEMANAGE],
+[
+    AC_CHECK_HEADERS(semanage/semanage.h,
+                     [AC_CHECK_LIB(semanage, semanage_handle_create,
+                                            [SEMANAGE_LIBS="-lsemanage"],
+                                            [AC_MSG_ERROR([libsemanage is missing])]
+                                  )
+                     ],
+                     [AC_MSG_ERROR([libsemanage is missing])])
+    AC_SUBST(SEMANAGE_LIBS)
+])
diff --git a/src/man/sss_useradd.8.xml b/src/man/sss_useradd.8.xml
index 7620ffd..4b745ab 100644
--- a/src/man/sss_useradd.8.xml
+++ b/src/man/sss_useradd.8.xml
@@ -160,6 +160,18 @@
                     </para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>
+                    <option>-Z</option>,<option>--selinux-user</option>
+                    <replaceable>SELINUX_USER</replaceable>
+                </term>
+                <listitem>
+                    <para>
+                        The SELinux user for the user's login. If not specified,
+                        the system default will be used.
+                    </para>
+                </listitem>
+            </varlistentry>
             <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
         </variablelist>
     </refsect1>
diff --git a/src/man/sss_usermod.8.xml b/src/man/sss_usermod.8.xml
index b94fc73..2b43708 100644
--- a/src/man/sss_usermod.8.xml
+++ b/src/man/sss_usermod.8.xml
@@ -119,6 +119,17 @@
                     </para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>
+                    <option>-Z</option>,<option>--selinux-user</option>
+                    <replaceable>SELINUX_USER</replaceable>
+                </term>
+                <listitem>
+                    <para>
+                        The SELinux user for the user's login.
+                    </para>
+                </listitem>
+            </varlistentry>
             <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
         </variablelist>
     </refsect1>
diff --git a/src/tools/selinux.c b/src/tools/selinux.c
index 4ce6d99..4ac29e1 100644
--- a/src/tools/selinux.c
+++ b/src/tools/selinux.c
@@ -21,12 +21,23 @@
 
 #include "config.h"
 
+#define _GNU_SOURCE
+#include <stdio.h>
+
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
 #endif
 
+#ifdef HAVE_SEMANAGE
+#include <semanage/semanage.h>
+#endif
+
 #include "util/util.h"
 
+#ifndef DEFAULT_SERANGE
+#define DEFAULT_SERANGE "s0"
+#endif
+
 #ifdef HAVE_SELINUX
 /*
  * selinux_file_context - Set the security context before any file or
@@ -79,3 +90,330 @@ int reset_selinux_file_context(void)
     return EOK;
 }
 #endif  /* HAVE_SELINUX */
+
+#ifdef HAVE_SEMANAGE
+/* turn libselinux messages into SSSD DEBUG() calls */
+static void sss_semanage_error_callback(void *varg,
+                                        semanage_handle_t *handle,
+                                        const char *fmt, ...)
+{
+    int level = -1;
+    int ret;
+    char * message = NULL;
+    va_list ap;
+
+    switch (semanage_msg_get_level(handle)) {
+        case SEMANAGE_MSG_ERR:
+            level = 1;
+            break;
+        case SEMANAGE_MSG_WARN:
+            level = 4;
+            break;
+        case SEMANAGE_MSG_INFO:
+            level = 6;
+            break;
+    }
+
+    va_start(ap, fmt);
+    ret = vasprintf(&message, fmt, ap);
+    if (ret < 0) {
+        /* ENOMEM */
+        return;
+    }
+    va_end(ap);
+
+    if (level <= debug_level) {
+        if (debug_timestamps) {
+            time_t rightnow = time(NULL);
+            char stamp[25];
+            memcpy(stamp, ctime(&rightnow), 24);
+            stamp[24] = '\0';
+            debug_fn("(%s) [%s] [libsemanage] (%d): %s\n",
+                     stamp, debug_prg_name, level, message);
+        } else {
+            debug_fn("[%s] [libsemanage] (%d): %s\n",
+                     debug_prg_name, level, message);
+        }
+    }
+    free(message);
+}
+
+static semanage_handle_t *sss_semanage_init(void)
+{
+    int ret;
+    semanage_handle_t *handle = NULL;
+
+    handle = semanage_handle_create();
+    if (!handle) {
+        DEBUG(1, ("Cannot create SELinux management handle\n"));
+        return NULL;
+    }
+
+    semanage_msg_set_callback(handle,
+                              sss_semanage_error_callback,
+                              NULL);
+
+    ret = semanage_is_managed(handle);
+    if (ret != 1) {
+        DEBUG(1, ("SELinux policy not managed\n"));
+        goto fail;
+    }
+
+    ret = semanage_access_check(handle);
+    if (ret < SEMANAGE_CAN_READ) {
+        DEBUG(1, ("Cannot read SELinux policy store\n"));
+        goto fail;
+    }
+
+    ret = semanage_connect(handle);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot estabilish SELinux management connection\n"));
+        goto fail;
+    }
+
+    ret = semanage_begin_transaction(handle);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot begin SELinux transaction\n"));
+        goto fail;
+    }
+
+    return handle;
+fail:
+    semanage_handle_destroy(handle);
+    return NULL;
+}
+
+static int sss_semanage_user_add(semanage_handle_t *handle,
+                                 semanage_seuser_key_t *key,
+                                 const char *login_name,
+                                 const char *seuser_name)
+{
+    int ret;
+    semanage_seuser_t *seuser = NULL;
+
+    ret = semanage_seuser_create(handle, &seuser);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot create SELinux login mapping for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_set_name(handle, seuser, login_name);
+    if (ret != 0) {
+        DEBUG(1, ("Could not set name for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_set_mlsrange(handle, seuser, DEFAULT_SERANGE);
+    if (ret != 0) {
+        DEBUG(1, ("Could not set serange for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_set_sename(handle, seuser, seuser_name);
+    if (ret != 0) {
+        DEBUG(1, ("Could not set SELinux user for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_modify_local(handle, key, seuser);
+    if (ret != 0) {
+        DEBUG(1, ("Could not add login mapping for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    semanage_seuser_free(seuser);
+    return ret;
+}
+
+static int sss_semanage_user_mod(semanage_handle_t *handle,
+                                 semanage_seuser_key_t *key,
+                                 const char *login_name,
+                                 const char *seuser_name)
+{
+    int ret;
+    semanage_seuser_t *seuser = NULL;
+
+    semanage_seuser_query(handle, key, &seuser);
+    if (seuser == NULL) {
+        DEBUG(1, ("Could not query seuser for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_set_mlsrange(handle, seuser, DEFAULT_SERANGE);
+    if (ret != 0) {
+        DEBUG(1, ("Could not set serange for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_set_sename(handle, seuser, seuser_name);
+    if (ret != 0) {
+        DEBUG(1, ("Could not set sename for %s\n", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_modify_local(handle, key, seuser);
+    if (ret != 0) {
+        DEBUG(1, (("Could not modify login mapping for %s\n"), login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    semanage_seuser_free(seuser);
+    return ret;
+}
+
+int set_seuser(const char *login_name, const char *seuser_name)
+{
+    semanage_handle_t *handle = NULL;
+    semanage_seuser_key_t *key = NULL;
+    int ret;
+    int seuser_exists = 0;
+
+    if (seuser_name == NULL) {
+        /* don't care, just let system pick the defaults */
+        return EOK;
+    }
+
+    handle = sss_semanage_init();
+    if (!handle) {
+        DEBUG(1, ("Cannot init SELinux management\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_key_create(handle, login_name, &key);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot create SELinux user key\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_exists(handle, key, &seuser_exists);
+    if (ret < 0) {
+        DEBUG(1, ("Cannot verify the SELinux user\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    if (seuser_exists) {
+        ret = sss_semanage_user_mod(handle, key, login_name, seuser_name);
+        if (ret != 0) {
+            DEBUG(1, ("Cannot modify SELinux user mapping\n"));
+            ret = EIO;
+            goto done;
+        }
+    } else {
+        ret = sss_semanage_user_add(handle, key, login_name, seuser_name);
+        if (ret != 0) {
+            DEBUG(1, ("Cannot add SELinux user mapping\n"));
+            ret = EIO;
+            goto done;
+        }
+    }
+
+    ret = semanage_commit(handle);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot commit SELinux transaction\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    semanage_seuser_key_free(key);
+    semanage_handle_destroy(handle);
+    return ret;
+}
+
+int del_seuser(const char *login_name)
+{
+    semanage_handle_t *handle = NULL;
+    semanage_seuser_key_t *key = NULL;
+    int ret;
+    int exists = 0;
+
+    handle = sss_semanage_init();
+    if (!handle) {
+        DEBUG(1, ("Cannot init SELinux management\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_key_create(handle, login_name, &key);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot create SELinux user key\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_seuser_exists(handle, key, &exists);
+    if (ret < 0) {
+        DEBUG(1, ("Cannot verify the SELinux user\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    if (!exists) {
+        DEBUG(5, ("Login mapping for %s is not defined, OK if default mapping "
+                  "was used\n", login_name));
+        ret = EOK;  /* probably default mapping */
+        goto done;
+    }
+
+    ret = semanage_seuser_exists_local(handle, key, &exists);
+    if (ret < 0) {
+        DEBUG(1, ("Cannot verify the SELinux user\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    if (!exists) {
+        DEBUG(1, ("Login mapping for %s is defined in policy, "
+                  "cannot be deleted", login_name));
+        ret = ENOENT;
+        goto done;
+    }
+
+    ret = semanage_seuser_del_local(handle, key);
+    if (ret != 0) {
+        DEBUG(1, ("Could not delete login mapping for %s", login_name));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = semanage_commit(handle);
+    if (ret != 0) {
+        DEBUG(1, ("Cannot commit SELinux transaction\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    semanage_handle_destroy(handle);
+    return ret;
+}
+
+#else /* HAVE_SEMANAGE */
+int set_seuser(const char *login_name, const char *seuser_name)
+{
+    return EOK;
+}
+
+int del_seuser(const char *login_name)
+{
+    return EOK;
+}
+#endif  /* HAVE_SEMANAGE */
diff --git a/src/tools/sss_useradd.c b/src/tools/sss_useradd.c
index 2d88e75..6c6b585 100644
--- a/src/tools/sss_useradd.c
+++ b/src/tools/sss_useradd.c
@@ -109,6 +109,7 @@ int main(int argc, const char **argv)
     int pc_create_home = 0;
     const char *pc_username = NULL;
     const char *pc_skeldir = NULL;
+    const char *pc_selinux_user = NULL;
     struct poptOption long_options[] = {
         POPT_AUTOHELP
         { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL },
@@ -121,6 +122,7 @@ int main(int argc, const char **argv)
         { "create-home", 'm', POPT_ARG_NONE, NULL, 'm', _("Create user's directory if it does not exist"), NULL },
         { "no-create-home", 'M', POPT_ARG_NONE, NULL, 'M', _("Never create user's directory, overrides config"), NULL },
         { "skel", 'k', POPT_ARG_STRING, &pc_skeldir, 0, _("Specify an alternative skeleton directory"), NULL },
+        { "selinux-user", 'Z', POPT_ARG_STRING, &pc_selinux_user, 0, _("The SELinux user for user's login"), NULL },
         POPT_TABLEEND
     };
     poptContext pc = NULL;
@@ -270,6 +272,15 @@ int main(int argc, const char **argv)
 
     end_transaction(tctx);
 
+    /* Set SELinux login context - must be done after transaction is done
+     * b/c libselinux calls getpwnam */
+    ret = set_seuser(tctx->octx->name, pc_selinux_user);
+    if (ret != EOK) {
+        ERROR("Cannot set SELinux login context\n");
+        ret = EXIT_FAILURE;
+        goto fini;
+    }
+
     /* Create user's home directory and/or mail spool */
     if (tctx->octx->create_homedir) {
         /* We need to know the UID and GID of the user, if
diff --git a/src/tools/sss_userdel.c b/src/tools/sss_userdel.c
index e74424d..464c22e 100644
--- a/src/tools/sss_userdel.c
+++ b/src/tools/sss_userdel.c
@@ -278,6 +278,15 @@ int main(int argc, const char **argv)
 
     end_transaction(tctx);
 
+    /* Set SELinux login context - must be done after transaction is done
+     * b/c libselinux calls getpwnam */
+    ret = del_seuser(tctx->octx->name);
+    if (ret != EOK) {
+        ERROR("Cannot reset SELinux login context\n");
+        ret = EXIT_FAILURE;
+        goto fini;
+    }
+
     if (!pc_kick) {
         ret = is_logged_in(tctx, tctx->octx->uid);
         switch(ret) {
diff --git a/src/tools/sss_usermod.c b/src/tools/sss_usermod.c
index a272bc5..65431fa 100644
--- a/src/tools/sss_usermod.c
+++ b/src/tools/sss_usermod.c
@@ -41,6 +41,7 @@ int main(int argc, const char **argv)
     char *pc_home = NULL;
     char *pc_shell = NULL;
     int pc_debug = 0;
+    const char *pc_selinux_user = NULL;
     struct poptOption long_options[] = {
         POPT_AUTOHELP
         { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL },
@@ -53,6 +54,7 @@ int main(int argc, const char **argv)
         { "remove-group", 'r', POPT_ARG_STRING, NULL, 'r', _("Groups to remove this user from"), NULL },
         { "lock", 'L', POPT_ARG_NONE, NULL, 'L', _("Lock the account"), NULL },
         { "unlock", 'U', POPT_ARG_NONE, NULL, 'U', _("Unlock the account"), NULL },
+        { "selinux-user", 'Z', POPT_ARG_STRING, &pc_selinux_user, 0, _("The SELinux user for user's login"), NULL },
         POPT_TABLEEND
     };
     poptContext pc = NULL;
@@ -233,6 +235,15 @@ int main(int argc, const char **argv)
 
     end_transaction(tctx);
 
+    /* Set SELinux login context - must be done after transaction is done
+     * b/c libselinux calls getpwnam */
+    ret = set_seuser(tctx->octx->name, pc_selinux_user);
+    if (ret != EOK) {
+        ERROR("Cannot set SELinux login context\n");
+        ret = EXIT_FAILURE;
+        goto fini;
+    }
+
 done:
     if (tctx->error) {
         ret = tctx->error;
diff --git a/src/tools/tools_util.h b/src/tools/tools_util.h
index 2ac1853..ac88286 100644
--- a/src/tools/tools_util.h
+++ b/src/tools/tools_util.h
@@ -115,5 +115,7 @@ int flush_nscd_cache(TALLOC_CTX *mem_ctx, enum nscd_db flush_db);
 /* from selinux.c */
 int selinux_file_context(const char *dst_name);
 int reset_selinux_file_context(void);
+int set_seuser(const char *login_name, const char *seuser_name);
+int del_seuser(const char *login_name);
 
 #endif  /* __TOOLS_UTIL_H__ */
-- 
1.6.6.1

