Blob Blame History Raw
From e08eb3e3b0586b331a688a4eded2d7f13611016c Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 20 Oct 2014 23:16:40 +0200
Subject: [PATCH 71/71] IPA: Move setting the SELinux context to a child
 process
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In order for the sssd_be process to run as unprivileged user, we need to
move the semanage processing to a process that runs as the root user
using setuid privileges.

Reviewed-by: Michal Židek <mzidek@redhat.com>
---
 Makefile.am                       |  27 +++
 contrib/sssd.spec.in              |   1 +
 src/providers/ipa/ipa_selinux.c   | 425 +++++++++++++++++++++++++++++++++++---
 src/providers/ipa/selinux_child.c | 272 ++++++++++++++++++++++++
 src/util/util_errors.c            |   1 +
 src/util/util_errors.h            |   1 +
 6 files changed, 699 insertions(+), 28 deletions(-)
 create mode 100644 src/providers/ipa/selinux_child.c

diff --git a/Makefile.am b/Makefile.am
index ea296c40f89c552d5825cbce8e95afdc517e8bb5..b85341f5845c3cffab8a2c95b1be1d32517316e8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -146,6 +146,9 @@ endif
 if BUILD_SAMBA
 sssdlibexec_PROGRAMS += gpo_child
 endif
+if BUILD_SEMANAGE
+sssdlibexec_PROGRAMS += selinux_child
+endif
 
 
 if BUILD_PAC_RESPONDER
@@ -2531,6 +2534,26 @@ ldap_child_LDADD = \
     $(DHASH_LIBS) \
     $(KRB5_LIBS)
 
+if BUILD_SEMANAGE
+selinux_child_SOURCES = \
+    src/providers/ipa/selinux_child.c \
+    src/util/sss_semanage.c \
+    src/util/atomic_io.c \
+    src/util/util.c \
+    $(NULL)
+selinux_child_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(POPT_CFLAGS) \
+    $(NULL)
+selinux_child_LDADD = \
+    libsss_debug.la \
+    $(TALLOC_LIBS) \
+    $(POPT_LIBS) \
+    $(DHASH_LIBS) \
+    $(SEMANAGE_LIBS) \
+    $(NULL)
+endif
+
 gpo_child_SOURCES = \
     src/providers/ad/ad_gpo_child.c \
     src/util/atomic_io.c \
@@ -2849,6 +2872,10 @@ endif
 if SSSD_USER
 	chgrp $(SSSD_USER) $(sssdlibexecdir)/ldap_child
 	chmod 4750 $(sssdlibexecdir)/ldap_child
+if BUILD_SEMANAGE
+	chgrp $(SSSD_USER) $(sssdlibexecdir)/selinux_child
+	chmod 4750 $(sssdlibexecdir)/selinux_child
+endif
 endif
 
 install-data-hook:
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index d2e6cec2610e4c00cb376683cf7e64eb5cdafc5c..5bfb16707c22dc65376581c88b8eb898949e726f 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -665,6 +665,7 @@ rm -rf $RPM_BUILD_ROOT
 %doc COPYING
 %attr(755,root,root) %dir %{pubconfpath}/krb5.include.d
 %{_libdir}/%{name}/libsss_ipa.so
+%attr(4750,root,sssd) %{_libexecdir}/%{servicename}/selinux_child
 %{_mandir}/man5/sssd-ipa.5*
 
 %files ad -f sssd_ad.lang
diff --git a/src/providers/ipa/ipa_selinux.c b/src/providers/ipa/ipa_selinux.c
index 7db1c6ed2981e4c4a85d892171bbfa60b006980e..b392d82a61cd523cd5e73e5246013bdbc448ddd5 100644
--- a/src/providers/ipa/ipa_selinux.c
+++ b/src/providers/ipa/ipa_selinux.c
@@ -24,6 +24,7 @@
 #include <security/pam_modules.h>
 
 #include "db/sysdb_selinux.h"
+#include "util/child_common.h"
 #include "util/sss_selinux.h"
 #include "providers/ldap/sdap_async.h"
 #include "providers/ipa/ipa_common.h"
@@ -37,8 +38,23 @@
 #include "providers/ipa/ipa_subdomains.h"
 
 #if defined HAVE_SELINUX && defined HAVE_SELINUX_LOGIN_DIR
+
+#ifndef SELINUX_CHILD_DIR
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#endif  /* SSSD_LIBEXEC_PATH */
+
+#define SELINUX_CHILD_DIR SSSD_LIBEXEC_PATH
+#endif /* SELINUX_CHILD_DIR */
+
+#define SELINUX_CHILD SELINUX_CHILD_DIR"/selinux_child"
+#define SELINUX_CHILD_LOG_FILE "selinux_child"
+
 #include <selinux/selinux.h>
 
+/* fd used by the selinux_child process for logging */
+int selinux_child_debug_fd = -1;
+
 static struct tevent_req *
 ipa_get_selinux_send(TALLOC_CTX *mem_ctx,
                      struct be_ctx *be_ctx,
@@ -274,12 +290,27 @@ struct map_order_ctx {
 
 static errno_t init_map_order_ctx(TALLOC_CTX *mem_ctx, const char *map_order,
                                   struct map_order_ctx **_mo_ctx);
-static errno_t choose_best_seuser(struct sysdb_attrs **usermaps,
+
+struct selinux_child_input {
+    const char *seuser;
+    const char *mls_range;
+    const char *username;
+};
+
+static errno_t choose_best_seuser(TALLOC_CTX *mem_ctx,
+                                  struct sysdb_attrs **usermaps,
                                   struct pam_data *pd,
                                   struct sss_domain_info *user_domain,
                                   struct map_order_ctx *mo_ctx,
-                                  const char *default_user);
+                                  const char *default_user,
+                                  struct selinux_child_input **_sci);
 
+static struct tevent_req *selinux_child_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct selinux_child_input *sci);
+static errno_t selinux_child_recv(struct tevent_req *req);
+
+static void ipa_selinux_child_done(struct tevent_req *child_req);
 
 static void ipa_selinux_handler_done(struct tevent_req *req)
 {
@@ -299,6 +330,8 @@ static void ipa_selinux_handler_done(struct tevent_req *req)
     struct sysdb_attrs **hbac_rules = 0;
     struct sysdb_attrs **best_match_maps;
     struct map_order_ctx *map_order_ctx;
+    struct selinux_child_input *sci;
+    struct tevent_req *child_req;
 
     ret = ipa_get_selinux_recv(req, breq, &map_count, &maps,
                                &hbac_count, &hbac_rules,
@@ -360,14 +393,61 @@ static void ipa_selinux_handler_done(struct tevent_req *req)
         goto fail;
     }
 
-    ret = choose_best_seuser(best_match_maps, pd, op_ctx->user_domain,
-                             map_order_ctx, default_user);
+    ret = choose_best_seuser(breq,
+                             best_match_maps, pd, op_ctx->user_domain,
+                             map_order_ctx, default_user, &sci);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "Failed to evaluate ordered SELinux users array.\n");
         goto fail;
     }
 
+    /* Update the SELinux context in a privileged child as the back end is
+     * running unprivileged
+     */
+    child_req = selinux_child_send(breq, be_ctx->ev, sci);
+    if (child_req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "selinux_child_send() failed\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(child_req, ipa_selinux_child_done, op_ctx);
+    return;
+
+fail:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n");
+        }
+    }
+    if (ret == EAGAIN) {
+        be_req_terminate(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
+    } else {
+        be_req_terminate(breq, DP_ERR_FATAL, ret, NULL);
+    }
+}
+
+static void ipa_selinux_child_done(struct tevent_req *child_req)
+{
+    errno_t ret;
+    struct ipa_selinux_op_ctx *op_ctx;
+    struct be_req *breq;
+    struct pam_data *pd;
+    struct be_ctx *be_ctx;
+
+    op_ctx = tevent_req_callback_data(child_req, struct ipa_selinux_op_ctx);
+    breq = op_ctx->be_req;
+    pd = talloc_get_type(be_req_get_data(breq), struct pam_data);
+    be_ctx = be_req_get_be_ctx(breq);
+
+    ret = selinux_child_recv(child_req);
+    talloc_free(child_req);
+    if (ret != EOK) {
+        be_req_terminate(breq, DP_ERR_FATAL, ret, NULL);
+        return;
+    }
+
     /* If we got here in online mode, set last_update to current time */
     if (!be_is_offline(be_ctx)) {
         op_ctx->selinux_ctx->last_update = time(NULL);
@@ -375,20 +455,6 @@ static void ipa_selinux_handler_done(struct tevent_req *req)
 
     pd->pam_status = PAM_SUCCESS;
     be_req_terminate(breq, DP_ERR_OK, EOK, "Success");
-    return;
-
-fail:
-    if (in_transaction) {
-        sret = sysdb_transaction_cancel(sysdb);
-        if (sret != EOK) {
-            DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n");
-        }
-    }
-    if (ret == EAGAIN) {
-        be_req_terminate(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
-    } else {
-        be_req_terminate(breq, DP_ERR_FATAL, ret, NULL);
-    }
 }
 
 static errno_t
@@ -652,24 +718,28 @@ done:
     return ret;
 }
 
-static errno_t
-set_seuser_helper(const char *orig_name, struct sss_domain_info *dom,
-                  const char *seuser_mls_string);
-
+static errno_t selinux_child_setup(TALLOC_CTX *mem_ctx,
+                                   const char *orig_name,
+                                   struct sss_domain_info *dom,
+                                   const char *seuser_mls_string,
+                                   struct selinux_child_input **_sci);
 
 /* Choose best selinux user based on given order and write
  * the user to selinux login file. */
-static errno_t choose_best_seuser(struct sysdb_attrs **usermaps,
+static errno_t choose_best_seuser(TALLOC_CTX *mem_ctx,
+                                  struct sysdb_attrs **usermaps,
                                   struct pam_data *pd,
                                   struct sss_domain_info *user_domain,
                                   struct map_order_ctx *mo_ctx,
-                                  const char *default_user)
+                                  const char *default_user,
+                                  struct selinux_child_input **_sci)
 {
     TALLOC_CTX *tmp_ctx;
     char *seuser_mls_str = NULL;
     const char *tmp_str;
     errno_t ret;
     int i, j;
+    struct selinux_child_input *sci;
 
     tmp_ctx = talloc_new(NULL);
     if (tmp_ctx == NULL) {
@@ -716,15 +786,25 @@ static errno_t choose_best_seuser(struct sysdb_attrs **usermaps,
         }
     }
 
-    ret = set_seuser_helper(pd->user, user_domain, seuser_mls_str);
+    ret = selinux_child_setup(tmp_ctx, pd->user, user_domain, seuser_mls_str, &sci);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot set up child input buffer");
+        goto done;
+    }
+
+    *_sci = talloc_steal(mem_ctx, sci);
+    ret = EOK;
 done:
     talloc_free(tmp_ctx);
     return ret;
 }
 
 static errno_t
-set_seuser_helper(const char *orig_name, struct sss_domain_info *dom,
-                  const char *seuser_mls_string)
+selinux_child_setup(TALLOC_CTX *mem_ctx,
+                    const char *orig_name,
+                    struct sss_domain_info *dom,
+                    const char *seuser_mls_string,
+                    struct selinux_child_input **_sci)
 {
     errno_t ret;
     char *seuser;
@@ -733,6 +813,7 @@ set_seuser_helper(const char *orig_name, struct sss_domain_info *dom,
     char *username;
     char *username_final;
     TALLOC_CTX *tmp_ctx;
+    struct selinux_child_input *sci;
 
     tmp_ctx = talloc_new(NULL);
     if (tmp_ctx == NULL) {
@@ -778,12 +859,300 @@ set_seuser_helper(const char *orig_name, struct sss_domain_info *dom,
         username_final = username;
     }
 
-    ret = set_seuser(username_final, seuser, mls_range);
+    sci = talloc(tmp_ctx, struct selinux_child_input);
+    if (sci == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    sci->seuser = talloc_strdup(sci, seuser);
+    sci->mls_range = talloc_strdup(sci, mls_range);
+    sci->username = talloc_strdup(sci, username);
+    if (sci->seuser == NULL || sci->mls_range == NULL
+        || sci->username == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    *_sci = talloc_steal(mem_ctx, sci);
+    ret = EOK;
 done:
     talloc_free(tmp_ctx);
     return ret;
 }
 
+struct selinux_child_state {
+    struct selinux_child_input *sci;
+    struct tevent_context *ev;
+    struct io_buffer *buf;
+    struct child_io_fds *io;
+};
+
+static errno_t selinux_child_init(void);
+static errno_t selinux_child_create_buffer(struct selinux_child_state *state);
+static errno_t selinux_fork_child(struct selinux_child_state *state);
+static void selinux_child_step(struct tevent_req *subreq);
+static void selinux_child_done(struct tevent_req *subreq);
+static errno_t selinux_child_parse_response(uint8_t *buf, ssize_t len,
+                                            uint32_t *_child_result);
+
+static struct tevent_req *selinux_child_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct selinux_child_input *sci)
+{
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct selinux_child_state *state;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct selinux_child_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+        return NULL;
+    }
+
+    state->sci = sci;
+    state->ev = ev;
+    state->io = talloc(state, struct child_io_fds);
+    state->buf = talloc(state, struct io_buffer);
+    if (state->io == NULL || state->buf == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    state->io->write_to_child_fd = -1;
+    state->io->read_from_child_fd = -1;
+    talloc_set_destructor((void *) state->io, child_io_destructor);
+
+    ret = selinux_child_init();
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to init the child\n");
+        goto immediately;
+    }
+
+    ret = selinux_child_create_buffer(state);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to create the send buffer\n");
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    ret = selinux_fork_child(state);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to fork the child\n");
+        goto immediately;
+    }
+
+    subreq = write_pipe_send(state, ev, state->buf->data, state->buf->size,
+                             state->io->write_to_child_fd);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+    tevent_req_set_callback(subreq, selinux_child_step, req);
+
+    ret = EOK;
+immediately:
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, ev);
+    }
+    return req;
+}
+
+static errno_t selinux_child_init(void)
+{
+    return child_debug_init(SELINUX_CHILD_LOG_FILE, &selinux_child_debug_fd);
+}
+
+static errno_t selinux_child_create_buffer(struct selinux_child_state *state)
+{
+    size_t rp;
+    size_t seuser_len;
+    size_t mls_range_len;
+    size_t username_len;
+
+    seuser_len = strlen(state->sci->seuser);
+    mls_range_len = strlen(state->sci->mls_range);
+    username_len = strlen(state->sci->username);
+
+    state->buf->size = 3 * sizeof(uint32_t);
+    state->buf->size += seuser_len + mls_range_len + username_len;
+
+    DEBUG(SSSDBG_TRACE_ALL, "buffer size: %zu\n", state->buf->size);
+
+    state->buf->data = talloc_size(state->buf, state->buf->size);
+    if (state->buf->data == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    rp = 0;
+
+    /* seuser */
+    SAFEALIGN_SET_UINT32(&state->buf->data[rp], seuser_len, &rp);
+    safealign_memcpy(&state->buf->data[rp], state->sci->seuser,
+                     seuser_len, &rp);
+
+    /* mls_range */
+    SAFEALIGN_SET_UINT32(&state->buf->data[rp], mls_range_len, &rp);
+    safealign_memcpy(&state->buf->data[rp], state->sci->mls_range,
+                     mls_range_len, &rp);
+
+    /* username */
+    SAFEALIGN_SET_UINT32(&state->buf->data[rp], username_len, &rp);
+    safealign_memcpy(&state->buf->data[rp], state->sci->username,
+                     username_len, &rp);
+
+    return EOK;
+}
+
+static errno_t selinux_fork_child(struct selinux_child_state *state)
+{
+    int pipefd_to_child[2];
+    int pipefd_from_child[2];
+    pid_t pid;
+    errno_t ret;
+
+    ret = pipe(pipefd_from_child);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "pipe failed [%d][%s].\n", errno, sss_strerror(errno));
+        return ret;
+    }
+
+    ret = pipe(pipefd_to_child);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "pipe failed [%d][%s].\n", errno, sss_strerror(errno));
+        return ret;
+    }
+
+    pid = fork();
+
+    if (pid == 0) { /* child */
+        ret = exec_child(state,
+                         pipefd_to_child, pipefd_from_child,
+                         SELINUX_CHILD, selinux_child_debug_fd);
+        DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec selinux_child: [%d][%s].\n",
+              ret, sss_strerror(ret));
+        return ret;
+    } else if (pid > 0) { /* parent */
+        state->io->read_from_child_fd = pipefd_from_child[0];
+        close(pipefd_from_child[1]);
+        state->io->write_to_child_fd = pipefd_to_child[1];
+        close(pipefd_to_child[0]);
+        fd_nonblocking(state->io->read_from_child_fd);
+        fd_nonblocking(state->io->write_to_child_fd);
+
+        ret = child_handler_setup(state->ev, pid, NULL, NULL, NULL);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Could not set up child signal handler\n");
+            return ret;
+        }
+    } else { /* error */
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "fork failed [%d][%s].\n", errno, sss_strerror(errno));
+        return ret;
+    }
+
+    return EOK;
+}
+
+static void selinux_child_step(struct tevent_req *subreq)
+{
+    struct tevent_req *req;
+    errno_t ret;
+    struct selinux_child_state *state;
+
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct selinux_child_state);
+
+    ret = write_pipe_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    close(state->io->write_to_child_fd);
+    state->io->write_to_child_fd = -1;
+
+    subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd);
+    if (subreq == NULL) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, selinux_child_done, req);
+}
+
+static void selinux_child_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req;
+    struct selinux_child_state *state;
+    uint32_t child_result;
+    errno_t ret;
+    ssize_t len;
+    uint8_t *buf;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct selinux_child_state);
+
+    ret = read_pipe_recv(subreq, state, &buf, &len);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    close(state->io->read_from_child_fd);
+    state->io->read_from_child_fd = -1;
+
+    ret = selinux_child_parse_response(buf, len, &child_result);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "selinux_child_parse_response failed: [%d][%s]\n",
+              ret, strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    } else if (child_result != 0){
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Error in selinux_child: [%d][%s]\n",
+              child_result, strerror(child_result));
+        tevent_req_error(req, ERR_SELINUX_CONTEXT);
+        return;
+    }
+
+    tevent_req_done(req);
+    return;
+}
+
+static errno_t selinux_child_parse_response(uint8_t *buf,
+                                            ssize_t len,
+                                            uint32_t *_child_result)
+{
+    size_t p = 0;
+    uint32_t child_result;
+
+    /* semanage retval */
+    SAFEALIGN_COPY_UINT32_CHECK(&child_result, buf + p, len, &p);
+
+    *_child_result = child_result;
+    return EOK;
+}
+
+static errno_t selinux_child_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
 /* A more generic request to gather all SELinux and HBAC rules. Updates
  * cache if necessary
  */
diff --git a/src/providers/ipa/selinux_child.c b/src/providers/ipa/selinux_child.c
new file mode 100644
index 0000000000000000000000000000000000000000..a624cfd30d6795c305b7673d849213c74de95cd8
--- /dev/null
+++ b/src/providers/ipa/selinux_child.c
@@ -0,0 +1,272 @@
+/*
+    SSSD
+
+    IPA back end -- set SELinux context in a child module
+
+    Authors:
+        Jakub Hrozek <jhrozek@redhat.com>
+
+    Copyright (C) 2014 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include "util/util.h"
+#include "util/child_common.h"
+#include "providers/dp_backend.h"
+
+struct input_buffer {
+    const char *seuser;
+    const char *mls_range;
+    const char *username;
+};
+
+static errno_t unpack_buffer(uint8_t *buf,
+                             size_t size,
+                             struct input_buffer *ibuf)
+{
+    size_t p = 0;
+    uint32_t len;
+
+    /* seuser */
+    SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+    DEBUG(SSSDBG_TRACE_INTERNAL, "seuser length: %d\n", len);
+    if (len == 0) {
+        return EINVAL;
+    } else {
+        if ((p + len ) > size) return EINVAL;
+        ibuf->seuser = talloc_strndup(ibuf, (char *)(buf + p), len);
+        if (ibuf->seuser == NULL) return ENOMEM;
+        DEBUG(SSSDBG_TRACE_INTERNAL, "seuser: %s\n", ibuf->seuser);
+        p += len;
+    }
+
+    /* MLS range */
+    SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+    DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range length: %d\n", len);
+    if (len == 0) {
+        return EINVAL;
+    } else {
+        if ((p + len ) > size) return EINVAL;
+        ibuf->mls_range = talloc_strndup(ibuf, (char *)(buf + p), len);
+        if (ibuf->mls_range == NULL) return ENOMEM;
+        DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range: %s\n", ibuf->mls_range);
+        p += len;
+    }
+
+    /* username */
+    SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+    DEBUG(SSSDBG_TRACE_INTERNAL, "username length: %d\n", len);
+    if (len == 0) {
+        return EINVAL;
+    } else {
+        if ((p + len ) > size) return EINVAL;
+        ibuf->username = talloc_strndup(ibuf, (char *)(buf + p), len);
+        if (ibuf->username == NULL) return ENOMEM;
+        DEBUG(SSSDBG_TRACE_INTERNAL, "username: %s\n", ibuf->username);
+        p += len;
+    }
+
+    return EOK;
+}
+
+static errno_t pack_buffer(struct response *r, int result)
+{
+    size_t p = 0;
+
+    /* A buffer with the following structure must be created:
+     *   uint32_t status of the request (required)
+     */
+    r->size =  sizeof(uint32_t);
+
+    r->buf = talloc_array(r, uint8_t, r->size);
+    if(r->buf == NULL) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "result [%d]\n", result);
+
+    /* result */
+    SAFEALIGN_SET_UINT32(&r->buf[p], result, &p);
+
+    return EOK;
+}
+
+static errno_t prepare_response(TALLOC_CTX *mem_ctx,
+                                int result,
+                                struct response **rsp)
+{
+    int ret;
+    struct response *r = NULL;
+
+    r = talloc_zero(mem_ctx, struct response);
+    if (r == NULL) {
+        return ENOMEM;
+    }
+
+    r->buf = NULL;
+    r->size = 0;
+
+    ret = pack_buffer(r, result);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n");
+        return ret;
+    }
+
+    *rsp = r;
+    DEBUG(SSSDBG_TRACE_ALL, "r->size: %zu\n", r->size);
+    return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    int debug_fd = -1;
+    errno_t ret;
+    TALLOC_CTX *main_ctx = NULL;
+    uint8_t *buf = NULL;
+    ssize_t len = 0;
+    struct input_buffer *ibuf = NULL;
+    struct response *resp = NULL;
+    size_t written;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+         _("Debug level"), NULL},
+        {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+         _("Add debug timestamps"), NULL},
+        {"debug-microseconds", 0, POPT_ARG_INT, &debug_microseconds, 0,
+         _("Show timestamps with microseconds"), NULL},
+        {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+         _("An open file descriptor for the debug logs"), NULL},
+        {"debug-to-stderr", 0, POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN,
+         &debug_to_stderr, 0,
+         _("Send the debug output to stderr directly."), NULL },
+        POPT_TABLEEND
+    };
+
+    /* Set debug level to invalid value so we can decide if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+        fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                  poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            _exit(-1);
+        }
+    }
+
+    poptFreeContext(pc);
+
+    DEBUG_INIT(debug_level);
+
+    debug_prg_name = talloc_asprintf(NULL, "[sssd[selinux_child[%d]]]", getpid());
+    if (debug_prg_name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
+        goto fail;
+    }
+
+    if (debug_fd != -1) {
+        ret = set_debug_file_from_fd(debug_fd);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "set_debug_file_from_fd failed.\n");
+        }
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "selinux_child started.\n");
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          "Running as [%"SPRIuid"][%"SPRIgid"].\n", geteuid(), getegid());
+
+    main_ctx = talloc_new(NULL);
+    if (main_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n");
+        talloc_free(discard_const(debug_prg_name));
+        goto fail;
+    }
+    talloc_steal(main_ctx, debug_prg_name);
+
+    buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE);
+    if (buf == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n");
+        goto fail;
+    }
+
+    ibuf = talloc_zero(main_ctx, struct input_buffer);
+    if (ibuf == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+        goto fail;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "context initialized\n");
+
+    errno = 0;
+    len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE);
+    if (len == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret));
+        goto fail;
+    }
+
+    close(STDIN_FILENO);
+
+    ret = unpack_buffer(buf, len, ibuf);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret));
+        goto fail;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "performing selinux operations\n");
+
+    ret = set_seuser(ibuf->username, ibuf->seuser, ibuf->mls_range);
+
+    ret = prepare_response(main_ctx, ret, &resp);
+
+    errno = 0;
+
+    written = sss_atomic_write_s(STDOUT_FILENO, resp->buf, resp->size);
+    if (written == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret,
+                    strerror(ret));
+        goto fail;
+    }
+
+    if (written != resp->size) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n",
+              resp->size, written);
+        goto fail;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "selinux_child completed successfully\n");
+    close(STDOUT_FILENO);
+    talloc_free(main_ctx);
+    return EXIT_SUCCESS;
+fail:
+    DEBUG(SSSDBG_CRIT_FAILURE, "selinux_child failed!\n");
+    close(STDOUT_FILENO);
+    talloc_free(main_ctx);
+    return EXIT_FAILURE;
+}
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 5b36780ffcdc6733241cdb942865ecdf38da3bca..d5da64622eebe7f779816c7f2090da5b9a9b13f0 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -62,6 +62,7 @@ struct err_string error_to_str[] = {
     { "Bus method not supported" }, /* ERR_SBUS_NOSUP */
     { "Cannot connect to system bus" }, /* ERR_NO_SYSBUS */
     { "LDAP search returned a referral" }, /* ERR_REFERRAL */
+    { "Error setting SELinux user context" }, /* ERR_SELINUX_CONTEXT */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index e040ba903b27d06ec75cea31485d2f3111ca5302..2bc576605e613d674d38b54aae1604c0b044635f 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -84,6 +84,7 @@ enum sssd_errors {
     ERR_SBUS_NOSUP,
     ERR_NO_SYSBUS,
     ERR_REFERRAL,
+    ERR_SELINUX_CONTEXT,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
1.9.3