/* Author: Mark Goldman <mgoldman@tresys.com>
|
* Paul Rosenfeld <prosenfeld@tresys.com>
|
* Todd C. Miller <tmiller@tresys.com>
|
*
|
* Copyright (C) 2007 Tresys Technology, LLC
|
*
|
* This library is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU Lesser General Public License as
|
* published by the Free Software Foundation; either version 2.1 of the
|
* License, or (at your option) any later version.
|
*
|
* This library 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
|
* Lesser General Public License for more details.
|
*
|
* You should have received a copy of the GNU Lesser General Public
|
* License along with this library; if not, write to the Free Software
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
* 02110-1301 USA
|
*/
|
|
#include <semanage/handle.h>
|
#include <semanage/seusers_policy.h>
|
#include <semanage/users_policy.h>
|
#include <semanage/user_record.h>
|
#include <semanage/fcontext_record.h>
|
#include <semanage/fcontexts_policy.h>
|
#include <sepol/context.h>
|
#include <sepol/context_record.h>
|
#include "semanage_store.h"
|
#include "seuser_internal.h"
|
#include "debug.h"
|
|
#include "utilities.h"
|
#include "genhomedircon.h"
|
|
#include <assert.h>
|
#include <ctype.h>
|
#include <limits.h>
|
#include <stdio.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <sys/types.h>
|
#include <sys/stat.h>
|
#include <fcntl.h>
|
#include <pwd.h>
|
#include <errno.h>
|
#include <unistd.h>
|
#include <regex.h>
|
#include <grp.h>
|
#include <search.h>
|
|
/* paths used in get_home_dirs() */
|
#define PATH_ETC_USERADD "/etc/default/useradd"
|
#define PATH_ETC_LIBUSER "/etc/libuser.conf"
|
#define PATH_DEFAULT_HOME "/home"
|
#define PATH_EXPORT_HOME "/export/home"
|
#define PATH_ETC_LOGIN_DEFS "/etc/login.defs"
|
|
/* other paths */
|
#define PATH_SHELLS_FILE "/etc/shells"
|
#define PATH_NOLOGIN_SHELL "/sbin/nologin"
|
|
/* comments written to context file */
|
#define COMMENT_FILE_CONTEXT_HEADER "#\n#\n# " \
|
"User-specific file contexts, generated via libsemanage\n" \
|
"# use semanage command to manage system users to change" \
|
" the file_context\n#\n#\n"
|
|
#define COMMENT_USER_HOME_CONTEXT "\n\n#\n# Home Context for user %s" \
|
"\n#\n\n"
|
|
/* placeholders used in the template file
|
which are searched for and replaced */
|
#define TEMPLATE_HOME_ROOT "HOME_ROOT"
|
#define TEMPLATE_HOME_DIR "HOME_DIR"
|
/* these are legacy */
|
#define TEMPLATE_USER "USER"
|
#define TEMPLATE_ROLE "ROLE"
|
/* new names */
|
#define TEMPLATE_USERNAME "%{USERNAME}"
|
#define TEMPLATE_USERID "%{USERID}"
|
|
#define FALLBACK_SENAME "user_u"
|
#define FALLBACK_PREFIX "user"
|
#define FALLBACK_LEVEL "s0"
|
#define FALLBACK_NAME "[^/]+"
|
#define FALLBACK_UIDGID "[0-9]+"
|
#define DEFAULT_LOGIN "__default__"
|
|
#define CONTEXT_NONE "<<none>>"
|
|
typedef struct user_entry {
|
char *name;
|
char *uid;
|
char *gid;
|
char *sename;
|
char *prefix;
|
char *home;
|
char *level;
|
char *login;
|
char *homedir_role;
|
struct user_entry *next;
|
} genhomedircon_user_entry_t;
|
|
typedef struct {
|
const char *fcfilepath;
|
int usepasswd;
|
const char *homedir_template_path;
|
genhomedircon_user_entry_t *fallback;
|
semanage_handle_t *h_semanage;
|
sepol_policydb_t *policydb;
|
} genhomedircon_settings_t;
|
|
typedef struct {
|
const char *search_for;
|
const char *replace_with;
|
} replacement_pair_t;
|
|
typedef struct {
|
const char *dir;
|
int matched;
|
} fc_match_handle_t;
|
|
typedef struct IgnoreDir {
|
struct IgnoreDir *next;
|
char *dir;
|
} ignoredir_t;
|
|
ignoredir_t *ignore_head = NULL;
|
|
static void ignore_free(void) {
|
ignoredir_t *next;
|
|
while (ignore_head) {
|
next = ignore_head->next;
|
free(ignore_head->dir);
|
free(ignore_head);
|
ignore_head = next;
|
}
|
}
|
|
static int ignore_setup(char *ignoredirs) {
|
char *tok;
|
ignoredir_t *ptr = NULL;
|
|
tok = strtok(ignoredirs, ";");
|
while(tok) {
|
ptr = calloc(sizeof(ignoredir_t),1);
|
if (!ptr)
|
goto err;
|
ptr->dir = strdup(tok);
|
if (!ptr->dir)
|
goto err;
|
|
ptr->next = ignore_head;
|
ignore_head = ptr;
|
|
tok = strtok(NULL, ";");
|
}
|
|
return 0;
|
err:
|
free(ptr);
|
ignore_free();
|
return -1;
|
}
|
|
static int ignore(const char *homedir) {
|
ignoredir_t *ptr = ignore_head;
|
while (ptr) {
|
if (strcmp(ptr->dir, homedir) == 0) {
|
return 1;
|
}
|
ptr = ptr->next;
|
}
|
return 0;
|
}
|
|
static int prefix_is_homedir_role(const semanage_user_t *user,
|
const char *prefix)
|
{
|
return strcmp(OBJECT_R, prefix) == 0 ||
|
semanage_user_has_role(user, prefix);
|
}
|
|
static semanage_list_t *default_shell_list(void)
|
{
|
semanage_list_t *list = NULL;
|
|
if (semanage_list_push(&list, "/bin/csh")
|
|| semanage_list_push(&list, "/bin/tcsh")
|
|| semanage_list_push(&list, "/bin/ksh")
|
|| semanage_list_push(&list, "/bin/bsh")
|
|| semanage_list_push(&list, "/bin/ash")
|
|| semanage_list_push(&list, "/usr/bin/ksh")
|
|| semanage_list_push(&list, "/usr/bin/pdksh")
|
|| semanage_list_push(&list, "/bin/zsh")
|
|| semanage_list_push(&list, "/bin/sh")
|
|| semanage_list_push(&list, "/bin/bash"))
|
goto fail;
|
|
return list;
|
|
fail:
|
semanage_list_destroy(&list);
|
return NULL;
|
}
|
|
static semanage_list_t *get_shell_list(void)
|
{
|
FILE *shells;
|
char *temp = NULL;
|
semanage_list_t *list = NULL;
|
size_t buff_len = 0;
|
ssize_t len;
|
|
shells = fopen(PATH_SHELLS_FILE, "r");
|
if (!shells)
|
return default_shell_list();
|
while ((len = getline(&temp, &buff_len, shells)) > 0) {
|
if (temp[len-1] == '\n') temp[len-1] = 0;
|
if (strcmp(temp, PATH_NOLOGIN_SHELL)) {
|
if (semanage_list_push(&list, temp)) {
|
free(temp);
|
semanage_list_destroy(&list);
|
return default_shell_list();
|
}
|
}
|
}
|
free(temp);
|
|
return list;
|
}
|
|
/* Helper function called via semanage_fcontext_iterate() */
|
static int fcontext_matches(const semanage_fcontext_t *fcontext, void *varg)
|
{
|
const char *oexpr = semanage_fcontext_get_expr(fcontext);
|
fc_match_handle_t *handp = varg;
|
char *expr = NULL;
|
regex_t re;
|
int type, retval = -1;
|
size_t len;
|
|
/* Only match ALL or DIR */
|
type = semanage_fcontext_get_type(fcontext);
|
if (type != SEMANAGE_FCONTEXT_ALL && type != SEMANAGE_FCONTEXT_DIR)
|
return 0;
|
|
len = strlen(oexpr);
|
/* Define a macro to strip a literal string from the end of oexpr */
|
#define rstrip_oexpr_len(cstr, cstrlen) \
|
do { \
|
if (len >= (cstrlen) && !strncmp(oexpr + len - (cstrlen), (cstr), (cstrlen))) \
|
len -= (cstrlen); \
|
} while (0)
|
#define rstrip_oexpr(cstr) rstrip_oexpr_len(cstr, sizeof(cstr) - 1)
|
|
rstrip_oexpr(".+");
|
rstrip_oexpr(".*");
|
rstrip_oexpr("(/.*)?");
|
rstrip_oexpr("/");
|
|
#undef rstrip_oexpr_len
|
#undef rstrip_oexpr
|
|
/* Anchor oexpr at the beginning and append pattern to eat up trailing slashes */
|
if (asprintf(&expr, "^%.*s/*$", (int)len, oexpr) < 0)
|
return -1;
|
|
/* Check dir against expr */
|
if (regcomp(&re, expr, REG_EXTENDED) != 0)
|
goto done;
|
if (regexec(&re, handp->dir, 0, NULL, 0) == 0)
|
handp->matched = 1;
|
regfree(&re);
|
|
retval = 0;
|
|
done:
|
free(expr);
|
|
return retval;
|
}
|
|
static semanage_list_t *get_home_dirs(genhomedircon_settings_t * s)
|
{
|
semanage_list_t *homedir_list = NULL;
|
semanage_list_t *shells = NULL;
|
fc_match_handle_t hand;
|
char *path = NULL;
|
uid_t temp, minuid = 500, maxuid = 60000;
|
int minuid_set = 0;
|
struct passwd *pwbuf;
|
struct stat buf;
|
|
path = semanage_findval(PATH_ETC_USERADD, "HOME", "=");
|
if (path && *path) {
|
if (semanage_list_push(&homedir_list, path))
|
goto fail;
|
}
|
free(path);
|
|
path = semanage_findval(PATH_ETC_LIBUSER, "LU_HOMEDIRECTORY", "=");
|
if (path && *path) {
|
if (semanage_list_push(&homedir_list, path))
|
goto fail;
|
}
|
free(path);
|
path = NULL;
|
|
if (!homedir_list) {
|
if (semanage_list_push(&homedir_list, PATH_DEFAULT_HOME)) {
|
goto fail;
|
}
|
}
|
|
if (!stat(PATH_EXPORT_HOME, &buf)) {
|
if (S_ISDIR(buf.st_mode)) {
|
if (semanage_list_push(&homedir_list, PATH_EXPORT_HOME)) {
|
goto fail;
|
}
|
}
|
}
|
|
if (!(s->usepasswd))
|
return homedir_list;
|
|
shells = get_shell_list();
|
assert(shells);
|
|
path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MIN", NULL);
|
if (path && *path) {
|
temp = atoi(path);
|
minuid = temp;
|
minuid_set = 1;
|
}
|
free(path);
|
path = NULL;
|
|
path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MAX", NULL);
|
if (path && *path) {
|
temp = atoi(path);
|
maxuid = temp;
|
}
|
free(path);
|
path = NULL;
|
|
path = semanage_findval(PATH_ETC_LIBUSER, "LU_UIDNUMBER", "=");
|
if (path && *path) {
|
temp = atoi(path);
|
if (!minuid_set || temp < minuid) {
|
minuid = temp;
|
minuid_set = 1;
|
}
|
}
|
free(path);
|
path = NULL;
|
|
errno = 0;
|
setpwent();
|
while (1) {
|
errno = 0;
|
pwbuf = getpwent();
|
if (pwbuf == NULL)
|
break;
|
if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid)
|
continue;
|
if (!semanage_list_find(shells, pwbuf->pw_shell))
|
continue;
|
int len = strlen(pwbuf->pw_dir) -1;
|
for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) {
|
pwbuf->pw_dir[len] = '\0';
|
}
|
if (strcmp(pwbuf->pw_dir, "/") == 0)
|
continue;
|
if (ignore(pwbuf->pw_dir))
|
continue;
|
if (semanage_str_count(pwbuf->pw_dir, '/') <= 1)
|
continue;
|
if (!(path = strdup(pwbuf->pw_dir))) {
|
break;
|
}
|
|
semanage_rtrim(path, '/');
|
|
if (!semanage_list_find(homedir_list, path)) {
|
/*
|
* Now check for an existing file context that matches
|
* so we don't label a non-homedir as a homedir.
|
*/
|
hand.dir = path;
|
hand.matched = 0;
|
if (semanage_fcontext_iterate(s->h_semanage,
|
fcontext_matches, &hand) == STATUS_ERR)
|
goto fail;
|
|
/* NOTE: old genhomedircon printed a warning on match */
|
if (hand.matched) {
|
WARN(s->h_semanage, "%s homedir %s or its parent directory conflicts with a file context already specified in the policy. This usually indicates an incorrectly defined system account. If it is a system account please make sure its uid is less than %u or greater than %u or its login shell is /sbin/nologin.", pwbuf->pw_name, pwbuf->pw_dir, minuid, maxuid);
|
} else {
|
if (semanage_list_push(&homedir_list, path))
|
goto fail;
|
}
|
}
|
free(path);
|
path = NULL;
|
}
|
|
if (errno) {
|
WARN(s->h_semanage, "Error while fetching users. "
|
"Returning list so far.");
|
}
|
|
if (semanage_list_sort(&homedir_list))
|
goto fail;
|
|
endpwent();
|
semanage_list_destroy(&shells);
|
|
return homedir_list;
|
|
fail:
|
endpwent();
|
free(path);
|
semanage_list_destroy(&homedir_list);
|
semanage_list_destroy(&shells);
|
return NULL;
|
}
|
|
/**
|
* @param out the FILE to put all the output in.
|
* @return 0 on success
|
*/
|
static int write_file_context_header(FILE * out)
|
{
|
if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) {
|
return STATUS_ERR;
|
}
|
|
return STATUS_SUCCESS;
|
}
|
|
/* Predicates for use with semanage_slurp_file_filter() the homedir_template
|
* file currently contains lines that serve as the template for a user's
|
* homedir.
|
*
|
* It also contains lines that are the template for the parent of a
|
* user's home directory.
|
*
|
* Currently, the only lines that apply to the the root of a user's home
|
* directory are all prefixed with the string "HOME_ROOT". All other
|
* lines apply to a user's home directory. If this changes the
|
* following predicates need to change to reflect that.
|
*/
|
static int HOME_ROOT_PRED(const char *string)
|
{
|
return semanage_is_prefix(string, TEMPLATE_HOME_ROOT);
|
}
|
|
static int HOME_DIR_PRED(const char *string)
|
{
|
return semanage_is_prefix(string, TEMPLATE_HOME_DIR);
|
}
|
|
/* new names */
|
static int USERNAME_CONTEXT_PRED(const char *string)
|
{
|
return (int)(
|
(strstr(string, TEMPLATE_USERNAME) != NULL) ||
|
(strstr(string, TEMPLATE_USERID) != NULL)
|
);
|
}
|
|
/* This will never match USER if USERNAME or USERID are found. */
|
static int USER_CONTEXT_PRED(const char *string)
|
{
|
if (USERNAME_CONTEXT_PRED(string))
|
return 0;
|
|
return (int)(strstr(string, TEMPLATE_USER) != NULL);
|
}
|
|
static int STR_COMPARATOR(const void *a, const void *b)
|
{
|
return strcmp((const char *) a, (const char *) b);
|
}
|
|
/* make_tempate
|
* @param s the settings holding the paths to various files
|
* @param pred function pointer to function to use as filter for slurp
|
* file filter
|
* @return a list of lines from the template file with inappropriate
|
* lines filtered out.
|
*/
|
static semanage_list_t *make_template(genhomedircon_settings_t * s,
|
int (*pred) (const char *))
|
{
|
FILE *template_file = NULL;
|
semanage_list_t *template_data = NULL;
|
|
template_file = fopen(s->homedir_template_path, "r");
|
if (!template_file)
|
return NULL;
|
template_data = semanage_slurp_file_filter(template_file, pred);
|
fclose(template_file);
|
|
return template_data;
|
}
|
|
static char *replace_all(const char *str, const replacement_pair_t * repl)
|
{
|
char *retval, *retval2;
|
int i;
|
|
if (!str || !repl)
|
return NULL;
|
|
retval = strdup(str);
|
for (i = 0; retval != NULL && repl[i].search_for; i++) {
|
retval2 = semanage_str_replace(repl[i].search_for,
|
repl[i].replace_with, retval, 0);
|
free(retval);
|
retval = retval2;
|
}
|
return retval;
|
}
|
|
static const char *extract_context(const char *line)
|
{
|
const char *p = line;
|
size_t off;
|
|
off = strlen(p);
|
p += off;
|
/* consider trailing whitespaces */
|
while (off > 0) {
|
p--;
|
off--;
|
if (!isspace(*p))
|
break;
|
}
|
if (off == 0)
|
return NULL;
|
|
/* find the last field in line */
|
while (off > 0 && !isspace(*(p - 1))) {
|
p--;
|
off--;
|
}
|
return p;
|
}
|
|
static int check_line(genhomedircon_settings_t * s, const char *line)
|
{
|
sepol_context_t *ctx_record = NULL;
|
const char *ctx_str;
|
int result;
|
|
ctx_str = extract_context(line);
|
if (!ctx_str)
|
return STATUS_ERR;
|
|
result = sepol_context_from_string(s->h_semanage->sepolh,
|
ctx_str, &ctx_record);
|
if (result == STATUS_SUCCESS && ctx_record != NULL) {
|
result = sepol_context_check(s->h_semanage->sepolh,
|
s->policydb, ctx_record);
|
sepol_context_free(ctx_record);
|
}
|
return result;
|
}
|
|
static int write_replacements(genhomedircon_settings_t * s, FILE * out,
|
const semanage_list_t * tpl,
|
const replacement_pair_t *repl)
|
{
|
char *line;
|
|
for (; tpl; tpl = tpl->next) {
|
line = replace_all(tpl->data, repl);
|
if (!line)
|
goto fail;
|
if (check_line(s, line) == STATUS_SUCCESS) {
|
if (fprintf(out, "%s\n", line) < 0)
|
goto fail;
|
}
|
free(line);
|
}
|
return STATUS_SUCCESS;
|
|
fail:
|
free(line);
|
return STATUS_ERR;
|
}
|
|
static int write_contexts(genhomedircon_settings_t *s, FILE *out,
|
semanage_list_t *tpl, const replacement_pair_t *repl,
|
const genhomedircon_user_entry_t *user)
|
{
|
char *line, *temp;
|
sepol_context_t *context;
|
char *new_context_str;
|
|
for (; tpl; tpl = tpl->next) {
|
context = NULL;
|
new_context_str = NULL;
|
line = replace_all(tpl->data, repl);
|
if (!line) {
|
goto fail;
|
}
|
|
const char *old_context_str = extract_context(line);
|
if (!old_context_str) {
|
goto fail;
|
}
|
|
if (strcmp(old_context_str, CONTEXT_NONE) == 0) {
|
if (check_line(s, line) == STATUS_SUCCESS &&
|
fprintf(out, "%s\n", line) < 0) {
|
goto fail;
|
}
|
free(line);
|
continue;
|
}
|
|
sepol_handle_t *sepolh = s->h_semanage->sepolh;
|
|
if (sepol_context_from_string(sepolh, old_context_str,
|
&context) < 0) {
|
goto fail;
|
}
|
|
if (sepol_context_set_user(sepolh, context, user->sename) < 0) {
|
goto fail;
|
}
|
|
if (sepol_policydb_mls_enabled(s->policydb) &&
|
sepol_context_set_mls(sepolh, context, user->level) < 0) {
|
goto fail;
|
}
|
|
if (user->homedir_role &&
|
sepol_context_set_role(sepolh, context, user->homedir_role) < 0) {
|
goto fail;
|
}
|
|
if (sepol_context_to_string(sepolh, context,
|
&new_context_str) < 0) {
|
goto fail;
|
}
|
|
temp = semanage_str_replace(old_context_str, new_context_str,
|
line, 1);
|
if (!temp) {
|
goto fail;
|
}
|
free(line);
|
line = temp;
|
|
if (check_line(s, line) == STATUS_SUCCESS) {
|
if (fprintf(out, "%s\n", line) < 0)
|
goto fail;
|
}
|
|
free(line);
|
sepol_context_free(context);
|
free(new_context_str);
|
}
|
|
return STATUS_SUCCESS;
|
fail:
|
free(line);
|
sepol_context_free(context);
|
free(new_context_str);
|
return STATUS_ERR;
|
}
|
|
static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out,
|
semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
|
{
|
replacement_pair_t repl[] = {
|
{.search_for = TEMPLATE_HOME_DIR,.replace_with = user->home},
|
{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
|
{NULL, NULL}
|
};
|
|
if (strcmp(user->name, FALLBACK_NAME) == 0) {
|
if (fprintf(out, COMMENT_USER_HOME_CONTEXT, FALLBACK_SENAME) < 0)
|
return STATUS_ERR;
|
} else {
|
if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user->name) < 0)
|
return STATUS_ERR;
|
}
|
|
return write_contexts(s, out, tpl, repl, user);
|
}
|
|
static int write_home_root_context(genhomedircon_settings_t * s, FILE * out,
|
semanage_list_t * tpl, char *homedir)
|
{
|
replacement_pair_t repl[] = {
|
{.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir},
|
{NULL, NULL}
|
};
|
|
return write_replacements(s, out, tpl, repl);
|
}
|
|
static int write_username_context(genhomedircon_settings_t * s, FILE * out,
|
semanage_list_t * tpl,
|
const genhomedircon_user_entry_t *user)
|
{
|
replacement_pair_t repl[] = {
|
{.search_for = TEMPLATE_USERNAME,.replace_with = user->name},
|
{.search_for = TEMPLATE_USERID,.replace_with = user->uid},
|
{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
|
{NULL, NULL}
|
};
|
|
return write_contexts(s, out, tpl, repl, user);
|
}
|
|
static int write_user_context(genhomedircon_settings_t * s, FILE * out,
|
semanage_list_t * tpl, const genhomedircon_user_entry_t *user)
|
{
|
replacement_pair_t repl[] = {
|
{.search_for = TEMPLATE_USER,.replace_with = user->name},
|
{.search_for = TEMPLATE_ROLE,.replace_with = user->prefix},
|
{NULL, NULL}
|
};
|
|
return write_contexts(s, out, tpl, repl, user);
|
}
|
|
static int seuser_sort_func(const void *arg1, const void *arg2)
|
{
|
const semanage_seuser_t **u1 = (const semanage_seuser_t **) arg1;
|
const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;;
|
const char *name1 = semanage_seuser_get_name(*u1);
|
const char *name2 = semanage_seuser_get_name(*u2);
|
|
if (name1[0] == '%' && name2[0] == '%') {
|
return 0;
|
} else if (name1[0] == '%') {
|
return 1;
|
} else if (name2[0] == '%') {
|
return -1;
|
}
|
|
return strcmp(name1, name2);
|
}
|
|
static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2)
|
{
|
return strcmp(semanage_user_get_name(*arg1),
|
semanage_user_get_name(*arg2));
|
}
|
|
static int name_user_cmp(char *key, semanage_user_t ** val)
|
{
|
return strcmp(key, semanage_user_get_name(*val));
|
}
|
|
static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n,
|
const char *u, const char *g, const char *sen,
|
const char *pre, const char *h, const char *l,
|
const char *ln, const char *hd_role)
|
{
|
genhomedircon_user_entry_t *temp = NULL;
|
char *name = NULL;
|
char *uid = NULL;
|
char *gid = NULL;
|
char *sename = NULL;
|
char *prefix = NULL;
|
char *home = NULL;
|
char *level = NULL;
|
char *lname = NULL;
|
char *homedir_role = NULL;
|
|
temp = malloc(sizeof(genhomedircon_user_entry_t));
|
if (!temp)
|
goto cleanup;
|
name = strdup(n);
|
if (!name)
|
goto cleanup;
|
uid = strdup(u);
|
if (!uid)
|
goto cleanup;
|
gid = strdup(g);
|
if (!gid)
|
goto cleanup;
|
sename = strdup(sen);
|
if (!sename)
|
goto cleanup;
|
prefix = strdup(pre);
|
if (!prefix)
|
goto cleanup;
|
home = strdup(h);
|
if (!home)
|
goto cleanup;
|
level = strdup(l);
|
if (!level)
|
goto cleanup;
|
lname = strdup(ln);
|
if (!lname)
|
goto cleanup;
|
if (hd_role) {
|
homedir_role = strdup(hd_role);
|
if (!homedir_role)
|
goto cleanup;
|
}
|
|
temp->name = name;
|
temp->uid = uid;
|
temp->gid = gid;
|
temp->sename = sename;
|
temp->prefix = prefix;
|
temp->home = home;
|
temp->level = level;
|
temp->login = lname;
|
temp->homedir_role = homedir_role;
|
temp->next = (*list);
|
(*list) = temp;
|
|
return STATUS_SUCCESS;
|
|
cleanup:
|
free(name);
|
free(uid);
|
free(gid);
|
free(sename);
|
free(prefix);
|
free(home);
|
free(level);
|
free(lname);
|
free(homedir_role);
|
free(temp);
|
return STATUS_ERR;
|
}
|
|
static void pop_user_entry(genhomedircon_user_entry_t ** list)
|
{
|
genhomedircon_user_entry_t *temp;
|
|
if (!list || !(*list))
|
return;
|
|
temp = *list;
|
*list = temp->next;
|
free(temp->name);
|
free(temp->uid);
|
free(temp->gid);
|
free(temp->sename);
|
free(temp->prefix);
|
free(temp->home);
|
free(temp->level);
|
free(temp->login);
|
free(temp->homedir_role);
|
free(temp);
|
}
|
|
static int setup_fallback_user(genhomedircon_settings_t * s)
|
{
|
semanage_seuser_t **seuser_list = NULL;
|
unsigned int nseusers = 0;
|
semanage_user_key_t *key = NULL;
|
semanage_user_t *u = NULL;
|
const char *name = NULL;
|
const char *seuname = NULL;
|
const char *prefix = NULL;
|
const char *level = NULL;
|
const char *homedir_role = NULL;
|
unsigned int i;
|
int retval;
|
int errors = 0;
|
|
retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
|
if (retval < 0 || (nseusers < 1)) {
|
/* if there are no users, this function can't do any other work */
|
return errors;
|
}
|
|
for (i = 0; i < nseusers; i++) {
|
name = semanage_seuser_get_name(seuser_list[i]);
|
if (strcmp(name, DEFAULT_LOGIN) == 0) {
|
seuname = semanage_seuser_get_sename(seuser_list[i]);
|
|
/* find the user structure given the name */
|
if (semanage_user_key_create(s->h_semanage, seuname,
|
&key) < 0) {
|
errors = STATUS_ERR;
|
break;
|
}
|
if (semanage_user_query(s->h_semanage, key, &u) < 0)
|
{
|
prefix = name;
|
level = FALLBACK_LEVEL;
|
}
|
else
|
{
|
prefix = semanage_user_get_prefix(u);
|
level = semanage_user_get_mlslevel(u);
|
if (!level)
|
level = FALLBACK_LEVEL;
|
}
|
|
if (prefix_is_homedir_role(u, prefix)) {
|
homedir_role = prefix;
|
}
|
|
if (push_user_entry(&(s->fallback), FALLBACK_NAME,
|
FALLBACK_UIDGID, FALLBACK_UIDGID,
|
seuname, prefix, "", level,
|
FALLBACK_NAME, homedir_role) != 0)
|
errors = STATUS_ERR;
|
semanage_user_key_free(key);
|
if (u)
|
semanage_user_free(u);
|
break;
|
}
|
}
|
|
for (i = 0; i < nseusers; i++)
|
semanage_seuser_free(seuser_list[i]);
|
free(seuser_list);
|
|
return errors;
|
}
|
|
static genhomedircon_user_entry_t *find_user(genhomedircon_user_entry_t *head,
|
const char *name)
|
{
|
for(; head; head = head->next) {
|
if (strcmp(head->name, name) == 0) {
|
return head;
|
}
|
}
|
|
return NULL;
|
}
|
|
static int add_user(genhomedircon_settings_t * s,
|
genhomedircon_user_entry_t **head,
|
semanage_user_t *user,
|
const char *name,
|
const char *sename,
|
const char *selogin)
|
{
|
if (selogin[0] == '%') {
|
genhomedircon_user_entry_t *orig = find_user(*head, name);
|
if (orig != NULL && orig->login[0] == '%') {
|
ERR(s->h_semanage, "User %s is already mapped to"
|
" group %s, but also belongs to group %s. Add an"
|
" explicit mapping for this user to"
|
" override group mappings.",
|
name, orig->login + 1, selogin + 1);
|
return STATUS_ERR;
|
} else if (orig != NULL) {
|
// user mappings take precedence
|
return STATUS_SUCCESS;
|
}
|
}
|
|
int retval = STATUS_ERR;
|
|
char *rbuf = NULL;
|
long rbuflen;
|
struct passwd pwstorage, *pwent = NULL;
|
const char *prefix = NULL;
|
const char *level = NULL;
|
const char *homedir_role = NULL;
|
char uid[11];
|
char gid[11];
|
|
errno = 0;
|
/* Allocate space for the getpwnam_r buffer */
|
rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
|
if (rbuflen == -1 && errno == 0)
|
/* sysconf returning -1 with no errno means indeterminate size */
|
rbuflen = 1024;
|
else if (rbuflen <= 0)
|
goto cleanup;
|
rbuf = malloc(rbuflen);
|
if (rbuf == NULL)
|
goto cleanup;
|
|
if (user) {
|
prefix = semanage_user_get_prefix(user);
|
level = semanage_user_get_mlslevel(user);
|
|
if (!level) {
|
level = FALLBACK_LEVEL;
|
}
|
} else {
|
prefix = name;
|
level = FALLBACK_LEVEL;
|
}
|
|
if (prefix_is_homedir_role(user, prefix)) {
|
homedir_role = prefix;
|
}
|
|
retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
|
if (retval != 0 || pwent == NULL) {
|
if (retval != 0 && retval != ENOENT) {
|
goto cleanup;
|
}
|
|
WARN(s->h_semanage,
|
"user %s not in password file", name);
|
retval = STATUS_SUCCESS;
|
goto cleanup;
|
}
|
|
int len = strlen(pwent->pw_dir) -1;
|
for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
|
pwent->pw_dir[len] = '\0';
|
}
|
|
if (strcmp(pwent->pw_dir, "/") == 0) {
|
/* don't relabel / genhomdircon checked to see if root
|
* was the user and if so, set his home directory to
|
* /root */
|
retval = STATUS_SUCCESS;
|
goto cleanup;
|
}
|
|
if (ignore(pwent->pw_dir)) {
|
retval = STATUS_SUCCESS;
|
goto cleanup;
|
}
|
|
len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid);
|
if (len < 0 || len >= (int)sizeof(uid)) {
|
goto cleanup;
|
}
|
|
len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid);
|
if (len < 0 || len >= (int)sizeof(gid)) {
|
goto cleanup;
|
}
|
|
retval = push_user_entry(head, name, uid, gid, sename, prefix,
|
pwent->pw_dir, level, selogin, homedir_role);
|
cleanup:
|
free(rbuf);
|
return retval;
|
}
|
|
static int get_group_users(genhomedircon_settings_t * s,
|
genhomedircon_user_entry_t **head,
|
semanage_user_t *user,
|
const char *sename,
|
const char *selogin)
|
{
|
int retval = STATUS_ERR;
|
unsigned int i;
|
|
long grbuflen;
|
char *grbuf = NULL;
|
struct group grstorage, *group = NULL;
|
struct passwd *pw = NULL;
|
|
errno = 0;
|
grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
|
if (grbuflen == -1 && errno == 0)
|
/* sysconf returning -1 with no errno means indeterminate size */
|
grbuflen = 1024;
|
else if (grbuflen <= 0)
|
goto cleanup;
|
grbuf = malloc(grbuflen);
|
if (grbuf == NULL)
|
goto cleanup;
|
|
const char *grname = selogin + 1;
|
|
errno = 0;
|
while (
|
(retval = getgrnam_r(grname, &grstorage, grbuf, (size_t) grbuflen, &group)) != 0 &&
|
errno == ERANGE
|
) {
|
char *new_grbuf;
|
grbuflen *= 2;
|
if (grbuflen < 0)
|
/* the member list could exceed 2Gb on a system with a 32-bit CPU (where
|
* sizeof(long) = 4) - if this ever happened, the loop would become infinite. */
|
goto cleanup;
|
new_grbuf = realloc(grbuf, grbuflen);
|
if (new_grbuf == NULL)
|
goto cleanup;
|
grbuf = new_grbuf;
|
}
|
if (retval != 0)
|
goto cleanup;
|
|
if (group == NULL) {
|
ERR(s->h_semanage, "Can't find group named %s\n", grname);
|
goto cleanup;
|
}
|
|
size_t nmembers = 0;
|
char **members = group->gr_mem;
|
|
while (*members != NULL) {
|
nmembers++;
|
members++;
|
}
|
|
for (i = 0; i < nmembers; i++) {
|
const char *uname = group->gr_mem[i];
|
|
if (add_user(s, head, user, uname, sename, selogin) < 0) {
|
goto cleanup;
|
}
|
}
|
|
setpwent();
|
while (1) {
|
errno = 0;
|
pw = getpwent();
|
if (pw == NULL)
|
break;
|
// skip users who also have this group as their
|
// primary group
|
if (lfind(pw->pw_name, group->gr_mem, &nmembers,
|
sizeof(char *), &STR_COMPARATOR)) {
|
continue;
|
}
|
|
if (group->gr_gid == pw->pw_gid) {
|
if (add_user(s, head, user, pw->pw_name,
|
sename, selogin) < 0) {
|
goto cleanup;
|
}
|
}
|
}
|
|
retval = STATUS_SUCCESS;
|
cleanup:
|
endpwent();
|
free(grbuf);
|
|
return retval;
|
}
|
|
static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
|
int *errors)
|
{
|
genhomedircon_user_entry_t *head = NULL;
|
semanage_seuser_t **seuser_list = NULL;
|
unsigned int nseusers = 0;
|
semanage_user_t **user_list = NULL;
|
unsigned int nusers = 0;
|
semanage_user_t **u = NULL;
|
const char *name = NULL;
|
const char *seuname = NULL;
|
unsigned int i;
|
int retval;
|
|
*errors = 0;
|
retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
|
if (retval < 0 || (nseusers < 1)) {
|
/* if there are no users, this function can't do any other work */
|
return NULL;
|
}
|
|
if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
|
nusers = 0;
|
}
|
|
qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *),
|
&seuser_sort_func);
|
qsort(user_list, nusers, sizeof(semanage_user_t *),
|
(int (*)(const void *, const void *))&user_sort_func);
|
|
for (i = 0; i < nseusers; i++) {
|
seuname = semanage_seuser_get_sename(seuser_list[i]);
|
name = semanage_seuser_get_name(seuser_list[i]);
|
|
if (strcmp(name, DEFAULT_LOGIN) == 0)
|
continue;
|
|
/* find the user structure given the name */
|
u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
|
(int (*)(const void *, const void *))
|
&name_user_cmp);
|
|
/* %groupname syntax */
|
if (name[0] == '%') {
|
retval = get_group_users(s, &head, *u, seuname,
|
name);
|
} else {
|
retval = add_user(s, &head, *u, name,
|
seuname, name);
|
}
|
|
if (retval != 0) {
|
*errors = STATUS_ERR;
|
goto cleanup;
|
}
|
}
|
|
cleanup:
|
if (*errors) {
|
for (; head; pop_user_entry(&head)) {
|
/* the pop function takes care of all the cleanup
|
so the loop body is just empty */
|
}
|
}
|
for (i = 0; i < nseusers; i++) {
|
semanage_seuser_free(seuser_list[i]);
|
}
|
free(seuser_list);
|
|
for (i = 0; i < nusers; i++) {
|
semanage_user_free(user_list[i]);
|
}
|
free(user_list);
|
|
return head;
|
}
|
|
static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
|
semanage_list_t * username_context_tpl,
|
semanage_list_t * user_context_tpl,
|
semanage_list_t * homedir_context_tpl)
|
{
|
genhomedircon_user_entry_t *users;
|
int errors = 0;
|
|
users = get_users(s, &errors);
|
if (!users && errors) {
|
return STATUS_ERR;
|
}
|
|
for (; users; pop_user_entry(&users)) {
|
if (write_home_dir_context(s, out, homedir_context_tpl, users))
|
goto err;
|
if (write_username_context(s, out, username_context_tpl, users))
|
goto err;
|
if (write_user_context(s, out, user_context_tpl, users))
|
goto err;
|
}
|
|
return STATUS_SUCCESS;
|
err:
|
for (; users; pop_user_entry(&users)) {
|
/* the pop function takes care of all the cleanup
|
* so the loop body is just empty */
|
}
|
|
return STATUS_ERR;
|
}
|
|
/**
|
* @param s settings structure, stores various paths etc. Must never be NULL
|
* @param out the FILE to put all the output in.
|
* @return 0 on success
|
*/
|
static int write_context_file(genhomedircon_settings_t * s, FILE * out)
|
{
|
semanage_list_t *homedirs = NULL;
|
semanage_list_t *h = NULL;
|
semanage_list_t *homedir_context_tpl = NULL;
|
semanage_list_t *homeroot_context_tpl = NULL;
|
semanage_list_t *username_context_tpl = NULL;
|
semanage_list_t *user_context_tpl = NULL;
|
int retval = STATUS_SUCCESS;
|
|
homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
|
homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
|
username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED);
|
user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
|
|
if (!homedir_context_tpl
|
&& !homeroot_context_tpl
|
&& !username_context_tpl
|
&& !user_context_tpl)
|
goto done;
|
|
if (write_file_context_header(out) != STATUS_SUCCESS) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
if (setup_fallback_user(s) != 0) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
if (homedir_context_tpl || homeroot_context_tpl) {
|
homedirs = get_home_dirs(s);
|
if (!homedirs) {
|
WARN(s->h_semanage,
|
"no home directories were available, exiting without writing");
|
goto done;
|
}
|
|
for (h = homedirs; h; h = h->next) {
|
char *temp = NULL;
|
|
if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
free(s->fallback->home);
|
s->fallback->home = temp;
|
|
if (write_home_dir_context(s, out, homedir_context_tpl,
|
s->fallback) != STATUS_SUCCESS) {
|
free(temp);
|
s->fallback->home = NULL;
|
retval = STATUS_ERR;
|
goto done;
|
}
|
if (write_home_root_context(s, out,
|
homeroot_context_tpl,
|
h->data) != STATUS_SUCCESS) {
|
free(temp);
|
s->fallback->home = NULL;
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
free(temp);
|
s->fallback->home = NULL;
|
}
|
}
|
if (user_context_tpl || username_context_tpl) {
|
if (write_username_context(s, out, username_context_tpl,
|
s->fallback) != STATUS_SUCCESS) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
if (write_user_context(s, out, user_context_tpl,
|
s->fallback) != STATUS_SUCCESS) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
if (write_gen_home_dir_context(s, out, username_context_tpl,
|
user_context_tpl, homedir_context_tpl)
|
!= STATUS_SUCCESS) {
|
retval = STATUS_ERR;
|
}
|
}
|
|
done:
|
/* Cleanup */
|
semanage_list_destroy(&homedirs);
|
semanage_list_destroy(&username_context_tpl);
|
semanage_list_destroy(&user_context_tpl);
|
semanage_list_destroy(&homedir_context_tpl);
|
semanage_list_destroy(&homeroot_context_tpl);
|
|
return retval;
|
}
|
|
int semanage_genhomedircon(semanage_handle_t * sh,
|
sepol_policydb_t * policydb,
|
int usepasswd,
|
char *ignoredirs)
|
{
|
genhomedircon_settings_t s;
|
FILE *out = NULL;
|
int retval = 0;
|
|
assert(sh);
|
|
s.homedir_template_path =
|
semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
|
s.fcfilepath =
|
semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS);
|
|
s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t));
|
if (s.fallback == NULL) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
s.fallback->name = strdup(FALLBACK_NAME);
|
s.fallback->sename = strdup(FALLBACK_SENAME);
|
s.fallback->prefix = strdup(FALLBACK_PREFIX);
|
s.fallback->level = strdup(FALLBACK_LEVEL);
|
if (s.fallback->name == NULL
|
|| s.fallback->sename == NULL
|
|| s.fallback->prefix == NULL
|
|| s.fallback->level == NULL) {
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
if (ignoredirs) ignore_setup(ignoredirs);
|
|
s.usepasswd = usepasswd;
|
s.h_semanage = sh;
|
s.policydb = policydb;
|
|
if (!(out = fopen(s.fcfilepath, "w"))) {
|
/* couldn't open output file */
|
ERR(sh, "Could not open the file_context file for writing");
|
retval = STATUS_ERR;
|
goto done;
|
}
|
|
retval = write_context_file(&s, out);
|
|
done:
|
if (out != NULL)
|
fclose(out);
|
|
while (s.fallback)
|
pop_user_entry(&(s.fallback));
|
|
ignore_free();
|
|
return retval;
|
}
|