summaryrefslogtreecommitdiff
path: root/src/providers/ipa/ipa_selinux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ipa/ipa_selinux.c')
-rw-r--r--src/providers/ipa/ipa_selinux.c412
1 files changed, 381 insertions, 31 deletions
diff --git a/src/providers/ipa/ipa_selinux.c b/src/providers/ipa/ipa_selinux.c
index 29e98870..0e65a377 100644
--- a/src/providers/ipa/ipa_selinux.c
+++ b/src/providers/ipa/ipa_selinux.c
@@ -21,7 +21,7 @@
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 <selinux/selinux.h>
#include <security/pam_modules.h>
#include "db/sysdb_selinux.h"
@@ -37,6 +37,8 @@
#include "providers/ipa/ipa_selinux_common.h"
#include "providers/ipa/ipa_selinux_maps.h"
+#ifdef HAVE_SELINUX_LOGIN_DIR
+
static struct tevent_req *
ipa_get_selinux_send(TALLOC_CTX *mem_ctx,
struct be_ctx *be_ctx,
@@ -65,12 +67,14 @@ static void ipa_get_config_step(struct tevent_req *req);
static void ipa_get_selinux_config_done(struct tevent_req *subreq);
static void ipa_get_selinux_maps_done(struct tevent_req *subreq);
static void ipa_get_selinux_hbac_done(struct tevent_req *subreq);
-static errno_t ipa_selinux_process_maps(struct sysdb_attrs *user,
+static errno_t ipa_selinux_process_maps(TALLOC_CTX *mem_ctx,
+ struct sysdb_attrs *user,
struct sysdb_attrs *host,
struct sysdb_attrs **selinux_maps,
size_t selinux_map_count,
struct sysdb_attrs **hbac_rules,
- size_t hbac_rule_count);
+ size_t hbac_rule_count,
+ struct sysdb_attrs ***usermaps);
struct ipa_selinux_op_ctx {
struct be_req *be_req;
@@ -184,6 +188,14 @@ fail:
return NULL;
}
+static errno_t create_order_array(TALLOC_CTX *mem_ctx, const char *map_order,
+ char ***_order_array, size_t *_order_count);
+static errno_t choose_best_seuser(struct sysdb_attrs **usermaps,
+ struct pam_data *pd,
+ char **order_array, int order_count,
+ const char *default_user);
+
+
static void ipa_selinux_handler_done(struct tevent_req *req)
{
struct ipa_selinux_op_ctx *op_ctx = tevent_req_callback_data(req, struct ipa_selinux_op_ctx);
@@ -199,6 +211,9 @@ static void ipa_selinux_handler_done(struct tevent_req *req)
char *map_order = NULL;
size_t hbac_count = 0;
struct sysdb_attrs **hbac_rules = 0;
+ struct sysdb_attrs **best_match_maps;
+ size_t order_count;
+ char **order_array;
ret = ipa_get_selinux_recv(req, breq, &map_count, &maps,
&hbac_count, &hbac_rules,
@@ -207,10 +222,31 @@ static void ipa_selinux_handler_done(struct tevent_req *req)
goto fail;
}
- ret = ipa_selinux_process_maps(op_ctx->user, op_ctx->host,
+ /* Process the maps and return list of best matches (maps with
+ * highest priority). The input maps are also parent memory
+ * context for the output list of best matches. The best match
+ * maps should never be freed explicitly but always through
+ * their parent (or any indirect parent) */
+ ret = ipa_selinux_process_maps(maps, op_ctx->user, op_ctx->host,
maps, map_count,
- hbac_rules, hbac_count);
+ hbac_rules, hbac_count, &best_match_maps);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ret = create_order_array(op_ctx, map_order,
+ &order_array, &order_count);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ ("Failed to create ordered SELinux users array.\n"));
+ goto fail;
+ }
+
+ ret = choose_best_seuser(best_match_maps, pd, order_array, order_count,
+ default_user);
if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ ("Failed to evaluate ordered SELinux users array.\n"));
goto fail;
}
@@ -272,22 +308,30 @@ ipa_selinux_process_seealso_maps(struct sysdb_attrs *user,
struct sysdb_attrs **seealso_rules,
size_t seealso_rules_count,
struct sysdb_attrs **hbac_rules,
- size_t hbac_rule_count);
+ size_t hbac_rule_count,
+ uint32_t top_priority,
+ struct sysdb_attrs **usermaps,
+ size_t best_match_maps_cnt);
static errno_t
-ipa_selinux_process_maps(struct sysdb_attrs *user,
+ipa_selinux_process_maps(TALLOC_CTX *mem_ctx,
+ struct sysdb_attrs *user,
struct sysdb_attrs *host,
struct sysdb_attrs **selinux_maps,
size_t selinux_map_count,
struct sysdb_attrs **hbac_rules,
- size_t hbac_rule_count)
+ size_t hbac_rule_count,
+ struct sysdb_attrs ***_usermaps)
{
TALLOC_CTX *tmp_ctx;
int i;
errno_t ret;
uint32_t priority = 0;
+ uint32_t top_priority = 0;
struct sysdb_attrs **seealso_rules;
- size_t num_seealso_rules;
+ size_t num_seealso_rules = 0;
const char *seealso_str;
+ struct sysdb_attrs **usermaps;
+ size_t best_match_maps_cnt = 0;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
@@ -300,23 +344,36 @@ ipa_selinux_process_maps(struct sysdb_attrs *user,
ret = ENOMEM;
goto done;
}
- num_seealso_rules = 0;
+
+ usermaps = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, selinux_map_count + 1);
+ if (usermaps == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
for (i = 0; i < selinux_map_count; i++) {
- if (sss_selinux_match(selinux_maps[i], user,
- host, &priority)) {
- priority &= ~(SELINUX_PRIORITY_USER_NAME |
- SELINUX_PRIORITY_USER_GROUP |
- SELINUX_PRIORITY_USER_CAT);
- ret = sysdb_attrs_add_uint32(selinux_maps[i],
- SYSDB_SELINUX_HOST_PRIORITY,
- priority);
- if (ret != EOK) {
- goto done;
+ if (sss_selinux_match(selinux_maps[i], user, host, &priority)) {
+ if (priority < top_priority) {
+ /* This rule has lower priority than what we already have,
+ * skip it. */
+ continue;
+ } else if (priority > top_priority) {
+ /* This rule has higher priority, drop what we already have */
+ while (best_match_maps_cnt > 0) {
+ best_match_maps_cnt--;
+ usermaps[best_match_maps_cnt] = NULL;
+ }
+ top_priority = priority;
}
+
+ usermaps[best_match_maps_cnt] = selinux_maps[i];
+ best_match_maps_cnt++;
+
continue;
}
+ /* SELinux map did not matched -> check sealso attribute for
+ * possible HBAC match */
ret = sysdb_attrs_get_string(selinux_maps[i],
SYSDB_SELINUX_SEEALSO, &seealso_str);
if (ret == ENOENT) {
@@ -331,11 +388,14 @@ ipa_selinux_process_maps(struct sysdb_attrs *user,
ret = ipa_selinux_process_seealso_maps(user, host,
seealso_rules, num_seealso_rules,
- hbac_rules, hbac_rule_count);
+ hbac_rules, hbac_rule_count,
+ top_priority, usermaps, best_match_maps_cnt);
if (ret != EOK) {
goto done;
}
+ *_usermaps = talloc_steal(mem_ctx, usermaps);
+
ret = EOK;
done:
talloc_free(tmp_ctx);
@@ -348,15 +408,18 @@ ipa_selinux_process_seealso_maps(struct sysdb_attrs *user,
struct sysdb_attrs **seealso_rules,
size_t seealso_rules_count,
struct sysdb_attrs **hbac_rules,
- size_t hbac_rule_count)
+ size_t hbac_rule_count,
+ uint32_t top_priority,
+ struct sysdb_attrs **usermaps,
+ size_t best_match_maps_cnt)
{
int i, j;
errno_t ret;
struct ldb_message_element *el;
- uint32_t priority = 0;
struct sysdb_attrs *usermap;
const char *seealso_dn;
const char *hbac_dn;
+ uint32_t priority;
for (i = 0; i < hbac_rule_count; i++) {
ret = sysdb_attrs_get_string(hbac_rules[i], SYSDB_ORIG_DN, &hbac_dn);
@@ -399,16 +462,24 @@ ipa_selinux_process_seealso_maps(struct sysdb_attrs *user,
DEBUG(SSSDBG_TRACE_FUNC, ("HBAC rule [%s] matched, copying its"
"attributes to SELinux user map [%s]\n",
hbac_dn, seealso_dn));
- priority &= ~(SELINUX_PRIORITY_USER_NAME |
- SELINUX_PRIORITY_USER_GROUP |
- SELINUX_PRIORITY_USER_CAT);
- ret = sysdb_attrs_add_uint32(usermap,
- SYSDB_SELINUX_HOST_PRIORITY,
- priority);
- if (ret != EOK) {
- return ret;
+
+ /* Selinux maps priority evaluation removed --DELETE this comment before pushing*/
+ if (priority < top_priority) {
+ /* This rule has lower priority than what we already have,
+ * skip it. */
+ continue;
+ } else if (priority > top_priority) {
+ /* This rule has higher priority, drop what we already have */
+ while (best_match_maps_cnt > 0) {
+ best_match_maps_cnt--;
+ usermaps[best_match_maps_cnt] = NULL;
+ }
+ top_priority = priority;
}
+ usermaps[best_match_maps_cnt] = usermap;
+ best_match_maps_cnt++;
+
ret = sysdb_attrs_copy_values(hbac_rules[i], usermap, SYSDB_ORIG_MEMBER_USER);
if (ret != EOK) {
return ret;
@@ -428,6 +499,271 @@ ipa_selinux_process_seealso_maps(struct sysdb_attrs *user,
return EOK;
}
+static errno_t create_order_array(TALLOC_CTX *mem_ctx, const char *map_order,
+ char ***_order_array, size_t *_order_count)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *order = NULL;
+ char **order_array;
+ errno_t ret;
+ int i;
+ int len;
+ size_t order_count;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ order = talloc_strdup(tmp_ctx, map_order);
+ if (order == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ len = strlen(order);
+
+ /* The "order" string contains one or more SELinux user records
+ * separated by $. Now we need to create an array of string from
+ * this one string. First find out how many elements in the array
+ * will be. This way only one alloc will be necessary for the array
+ */
+ order_count = 1;
+ for (i = 0; i < len; i++) {
+ if (order[i] == '$') order_count++;
+ }
+
+ order_array = talloc_array(tmp_ctx, char *, order_count);
+ if (order_array == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Now fill the array with pointers to the original string. Also
+ * use binary zeros to make multiple string out of the one.
+ */
+ order_array[0] = order;
+ order_count = 1;
+ for (i = 0; i < len; i++) {
+ if (order[i] == '$') {
+ order[i] = '\0';
+ order_array[order_count] = &order[i+1];
+ order_count++;
+ }
+ }
+
+ *_order_array = talloc_steal(mem_ctx, order_array);
+ *_order_count = order_count;
+
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t write_selinux_login_file(const char *username, char *string);
+static errno_t remove_selinux_login_file(const char *username);
+
+/* 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,
+ struct pam_data *pd,
+ char **order_array, int order_count,
+ const char *default_user)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *file_content = NULL;
+ const char *tmp_str;
+ errno_t ret, err;
+ int i, j;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n"));
+ return ENOMEM;
+ }
+
+ /* If no maps match, we'll use the default SELinux user from the
+ * config */
+ file_content = talloc_strdup(tmp_ctx, default_user);
+ if (file_content == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Iterate through the order array and try to find SELinux users
+ * in fetched maps. The order array contains all SELinux users
+ * allowed in the domain in the same order they should appear
+ * in the SELinux config file. If any user from the order array
+ * is not in fetched user maps, it means it should not be allowed
+ * for the user who is just logging in.
+ *
+ * Right now we have empty content of the SELinux config file,
+ * we shall add only those SELinux users that are present both in
+ * the order array and user maps applicable to the user who is
+ * logging in.
+ */
+ for (i = 0; i < order_count; i++) {
+ for (j = 0; usermaps[j] != NULL; j++) {
+ tmp_str = sss_selinux_map_get_seuser(usermaps[j]);
+
+ if (tmp_str && !strcasecmp(tmp_str, order_array[i])) {
+ /* If file_content contained something, overwrite it.
+ * This record has higher priority.
+ */
+ talloc_zfree(file_content);
+ file_content = talloc_strdup(tmp_ctx, tmp_str);
+ if (file_content == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ break;
+ }
+ }
+ }
+
+ ret = write_selinux_login_file(pd->user, file_content);
+done:
+ if (!file_content) {
+ err = remove_selinux_login_file(pd->user);
+ /* Don't overwrite original error condition if there was one */
+ if (ret == EOK) ret = err;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t write_selinux_login_file(const char *username, char *string)
+{
+ char *path = NULL;
+ char *tmp_path = NULL;
+ ssize_t written;
+ int len;
+ int fd = -1;
+ mode_t oldmask;
+ TALLOC_CTX *tmp_ctx;
+ char *full_string = NULL;
+ int enforce;
+ errno_t ret = EOK;
+
+ len = strlen(string);
+ if (len == 0) {
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n"));
+ return ENOMEM;
+ }
+
+ path = selogin_path(tmp_ctx, username);
+ if (path == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tmp_path = talloc_asprintf(tmp_ctx, "%sXXXXXX", path);
+ if (tmp_path == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ oldmask = umask(022);
+ fd = mkstemp(tmp_path);
+ ret = errno;
+ umask(oldmask);
+ if (fd < 0) {
+ if (ret == ENOENT) {
+ /* if selinux is disabled and selogin dir does not exist,
+ * just ignore the error */
+ if (selinux_getenforcemode(&enforce) == 0 && enforce == -1) {
+ ret = EOK;
+ goto done;
+ }
+
+ /* continue if we can't get enforce mode or selinux is enabled */
+ }
+
+ DEBUG(SSSDBG_OP_FAILURE, ("unable to create temp file [%s] "
+ "for SELinux data [%d]: %s\n", tmp_path, ret, strerror(ret)));
+ goto done;
+ }
+
+ full_string = talloc_asprintf(tmp_ctx, "%s:%s", ALL_SERVICES, string);
+ if (full_string == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ len = strlen(full_string);
+
+ errno = 0;
+ written = sss_atomic_write_s(fd, full_string, len);
+ if (written == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE, ("writing to SELinux data file %s"
+ "failed [%d]: %s", tmp_path, ret,
+ strerror(ret)));
+ goto done;
+ }
+
+ if (written != len) {
+ DEBUG(SSSDBG_OP_FAILURE, ("Expected to write %d bytes, wrote %d",
+ written, len));
+ ret = EIO;
+ goto done;
+ }
+
+ errno = 0;
+ if (rename(tmp_path, path) < 0) {
+ ret = errno;
+ } else {
+ ret = EOK;
+ }
+ close(fd);
+ fd = -1;
+
+done:
+ if (fd != -1) {
+ close(fd);
+ if (unlink(tmp_path) < 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE, ("Could not remove file [%s]",
+ tmp_path));
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t remove_selinux_login_file(const char *username)
+{
+ char *path;
+ errno_t ret;
+
+ path = selogin_path(NULL, username);
+ if (!path) return ENOMEM;
+
+ errno = 0;
+ ret = unlink(path);
+ if (ret < 0) {
+ ret = errno;
+ if (ret == ENOENT) {
+ /* Just return success if the file was not there */
+ ret = EOK;
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Could not remove login file %s [%d]: %s\n",
+ path, ret, strerror(ret)));
+ }
+ }
+
+ talloc_free(path);
+ return ret;
+}
+
+
/* A more generic request to gather all SELinux and HBAC rules. Updates
* cache if necessary
*/
@@ -912,3 +1248,17 @@ ipa_get_selinux_recv(struct tevent_req *req,
return EOK;
}
+
+#else
+/* Simply return success if HAVE_SELINUX_LOGIN_DIR is not defined. */
+void ipa_selinux_handler(struct be_req *be_req)
+{
+ struct be_ctx *be_ctx = be_req_get_be_ctx(be_req);
+ struct pam_data *pd;
+
+ pd = talloc_get_type(be_req_get_data(be_req), struct pam_data);
+
+ pd->pam_status = PAM_SUCCESS;
+ be_req_terminate(be_req, DP_ERR_OK, EOK, "Success");
+}
+#endif