/*
* Generalized labeling frontend for userspace object managers.
*
* Author : Eamon Walsh <ewalsh@epoch.ncsc.mil>
*/
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <selinux/selinux.h>
#include "callbacks.h"
#include "label_internal.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
typedef int (*selabel_initfunc)(struct selabel_handle *rec,
const struct selinux_opt *opts,
unsigned nopts);
static selabel_initfunc initfuncs[] = {
&selabel_file_init,
&selabel_media_init,
&selabel_x_init,
&selabel_db_init,
&selabel_property_init,
};
static void selabel_subs_fini(struct selabel_sub *ptr)
{
struct selabel_sub *next;
while (ptr) {
next = ptr->next;
free(ptr->src);
free(ptr->dst);
free(ptr);
ptr = next;
}
}
static char *selabel_sub(struct selabel_sub *ptr, const char *src)
{
char *dst = NULL;
int len;
while (ptr) {
if (strncmp(src, ptr->src, ptr->slen) == 0 ) {
if (src[ptr->slen] == '/' ||
src[ptr->slen] == 0) {
if ((src[ptr->slen] == '/') &&
(strcmp(ptr->dst, "/") == 0))
len = ptr->slen + 1;
else
len = ptr->slen;
if (asprintf(&dst, "%s%s", ptr->dst, &src[len]) < 0)
return NULL;
return dst;
}
}
ptr = ptr->next;
}
return NULL;
}
struct selabel_sub *selabel_subs_init(const char *path,
struct selabel_sub *list,
struct selabel_digest *digest)
{
char buf[1024];
FILE *cfg = fopen(path, "r");
struct selabel_sub *sub = NULL;
struct stat sb;
if (!cfg)
return list;
if (fstat(fileno(cfg), &sb) < 0)
return list;
while (fgets_unlocked(buf, sizeof(buf) - 1, cfg)) {
char *ptr = NULL;
char *src = buf;
char *dst = NULL;
while (*src && isspace(*src))
src++;
if (src[0] == '#') continue;
ptr = src;
while (*ptr && ! isspace(*ptr))
ptr++;
*ptr++ = '\0';
if (! *src) continue;
dst = ptr;
while (*dst && isspace(*dst))
dst++;
ptr=dst;
while (*ptr && ! isspace(*ptr))
ptr++;
*ptr='\0';
if (! *dst)
continue;
sub = malloc(sizeof(*sub));
if (! sub)
goto err;
memset(sub, 0, sizeof(*sub));
sub->src=strdup(src);
if (! sub->src)
goto err;
sub->dst=strdup(dst);
if (! sub->dst)
goto err;
sub->slen = strlen(src);
sub->next = list;
list = sub;
}
if (digest_add_specfile(digest, cfg, NULL, sb.st_size, path) < 0)
goto err;
out:
fclose(cfg);
return list;
err:
if (sub)
free(sub->src);
free(sub);
goto out;
}
static inline struct selabel_digest *selabel_is_digest_set
(const struct selinux_opt *opts,
unsigned n,
struct selabel_digest *entry)
{
struct selabel_digest *digest = NULL;
while (n--) {
if (opts[n].type == SELABEL_OPT_DIGEST &&
opts[n].value == (char *)1) {
digest = calloc(1, sizeof(*digest));
if (!digest)
goto err;
digest->digest = calloc(1, DIGEST_SPECFILE_SIZE + 1);
if (!digest->digest)
goto err;
digest->specfile_list = calloc(DIGEST_FILES_MAX,
sizeof(char *));
if (!digest->specfile_list)
goto err;
entry = digest;
return entry;
}
}
return NULL;
err:
free(digest->digest);
free(digest->specfile_list);
free(digest);
return NULL;
}
static void selabel_digest_fini(struct selabel_digest *ptr)
{
int i;
free(ptr->digest);
free(ptr->hashbuf);
if (ptr->specfile_list) {
for (i = 0; ptr->specfile_list[i]; i++)
free(ptr->specfile_list[i]);
free(ptr->specfile_list);
}
free(ptr);
}
/*
* Validation functions
*/
static inline int selabel_is_validate_set(const struct selinux_opt *opts,
unsigned n)
{
while (n--)
if (opts[n].type == SELABEL_OPT_VALIDATE)
return !!opts[n].value;
return 0;
}
int selabel_validate(struct selabel_handle *rec,
struct selabel_lookup_rec *contexts)
{
int rc = 0;
if (!rec->validating || contexts->validated)
goto out;
rc = selinux_validate(&contexts->ctx_raw);
if (rc < 0)
goto out;
contexts->validated = 1;
out:
return rc;
}
/* Public API helpers */
static char *selabel_sub_key(struct selabel_handle *rec, const char *key)
{
char *ptr = NULL;
char *dptr = NULL;
ptr = selabel_sub(rec->subs, key);
if (ptr) {
dptr = selabel_sub(rec->dist_subs, ptr);
if (dptr) {
free(ptr);
ptr = dptr;
}
} else {
ptr = selabel_sub(rec->dist_subs, key);
}
if (ptr)
return ptr;
return NULL;
}
static int selabel_fini(struct selabel_handle *rec,
struct selabel_lookup_rec *lr,
int translating)
{
if (compat_validate(rec, lr, rec->spec_file, 0))
return -1;
if (translating && !lr->ctx_trans &&
selinux_raw_to_trans_context(lr->ctx_raw, &lr->ctx_trans))
return -1;
return 0;
}
static struct selabel_lookup_rec *
selabel_lookup_common(struct selabel_handle *rec, int translating,
const char *key, int type)
{
struct selabel_lookup_rec *lr;
char *ptr = NULL;
if (key == NULL) {
errno = EINVAL;
return NULL;
}
ptr = selabel_sub_key(rec, key);
if (ptr) {
lr = rec->func_lookup(rec, ptr, type);
free(ptr);
} else {
lr = rec->func_lookup(rec, key, type);
}
if (!lr)
return NULL;
if (selabel_fini(rec, lr, translating))
return NULL;
return lr;
}
static struct selabel_lookup_rec *
selabel_lookup_bm_common(struct selabel_handle *rec, int translating,
const char *key, int type, const char **aliases)
{
struct selabel_lookup_rec *lr;
char *ptr = NULL;
if (key == NULL) {
errno = EINVAL;
return NULL;
}
ptr = selabel_sub_key(rec, key);
if (ptr) {
lr = rec->func_lookup_best_match(rec, ptr, aliases, type);
free(ptr);
} else {
lr = rec->func_lookup_best_match(rec, key, aliases, type);
}
if (!lr)
return NULL;
if (selabel_fini(rec, lr, translating))
return NULL;
return lr;
}
/*
* Public API
*/
struct selabel_handle *selabel_open(unsigned int backend,
const struct selinux_opt *opts,
unsigned nopts)
{
struct selabel_handle *rec = NULL;
if (backend >= ARRAY_SIZE(initfuncs)) {
errno = EINVAL;
goto out;
}
rec = (struct selabel_handle *)malloc(sizeof(*rec));
if (!rec)
goto out;
memset(rec, 0, sizeof(*rec));
rec->backend = backend;
rec->validating = selabel_is_validate_set(opts, nopts);
rec->subs = NULL;
rec->dist_subs = NULL;
rec->digest = selabel_is_digest_set(opts, nopts, rec->digest);
if ((*initfuncs[backend])(rec, opts, nopts)) {
free(rec->spec_file);
free(rec);
rec = NULL;
}
out:
return rec;
}
int selabel_lookup(struct selabel_handle *rec, char **con,
const char *key, int type)
{
struct selabel_lookup_rec *lr;
lr = selabel_lookup_common(rec, 1, key, type);
if (!lr)
return -1;
*con = strdup(lr->ctx_trans);
return *con ? 0 : -1;
}
int selabel_lookup_raw(struct selabel_handle *rec, char **con,
const char *key, int type)
{
struct selabel_lookup_rec *lr;
lr = selabel_lookup_common(rec, 0, key, type);
if (!lr)
return -1;
*con = strdup(lr->ctx_raw);
return *con ? 0 : -1;
}
bool selabel_partial_match(struct selabel_handle *rec, const char *key)
{
char *ptr;
bool ret;
if (!rec->func_partial_match) {
/*
* If the label backend does not support partial matching,
* then assume a match is possible.
*/
return true;
}
ptr = selabel_sub_key(rec, key);
if (ptr) {
ret = rec->func_partial_match(rec, ptr);
free(ptr);
} else {
ret = rec->func_partial_match(rec, key);
}
return ret;
}
int selabel_lookup_best_match(struct selabel_handle *rec, char **con,
const char *key, const char **aliases, int type)
{
struct selabel_lookup_rec *lr;
if (!rec->func_lookup_best_match) {
errno = ENOTSUP;
return -1;
}
lr = selabel_lookup_bm_common(rec, 1, key, type, aliases);
if (!lr)
return -1;
*con = strdup(lr->ctx_trans);
return *con ? 0 : -1;
}
int selabel_lookup_best_match_raw(struct selabel_handle *rec, char **con,
const char *key, const char **aliases, int type)
{
struct selabel_lookup_rec *lr;
if (!rec->func_lookup_best_match) {
errno = ENOTSUP;
return -1;
}
lr = selabel_lookup_bm_common(rec, 0, key, type, aliases);
if (!lr)
return -1;
*con = strdup(lr->ctx_raw);
return *con ? 0 : -1;
}
enum selabel_cmp_result selabel_cmp(struct selabel_handle *h1,
struct selabel_handle *h2)
{
if (!h1->func_cmp || h1->func_cmp != h2->func_cmp)
return SELABEL_INCOMPARABLE;
return h1->func_cmp(h1, h2);
}
int selabel_digest(struct selabel_handle *rec,
unsigned char **digest, size_t *digest_len,
char ***specfiles, size_t *num_specfiles)
{
if (!rec->digest) {
errno = EINVAL;
return -1;
}
*digest = rec->digest->digest;
*digest_len = DIGEST_SPECFILE_SIZE;
*specfiles = rec->digest->specfile_list;
*num_specfiles = rec->digest->specfile_cnt;
return 0;
}
void selabel_close(struct selabel_handle *rec)
{
selabel_subs_fini(rec->subs);
selabel_subs_fini(rec->dist_subs);
if (rec->digest)
selabel_digest_fini(rec->digest);
rec->func_close(rec);
free(rec->spec_file);
free(rec);
}
void selabel_stats(struct selabel_handle *rec)
{
rec->func_stats(rec);
}