#include <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <ctype.h>
#include "sudoers.h"
#include "sudo_ldap.h"
#include "redblack.h"
#include "strlist.h"
#include <gram.h>
struct sudo_role {
STAILQ_ENTRY(sudo_role) entries;
char *cn;
char *notbefore;
char *notafter;
double order;
struct sudoers_str_list *cmnds;
struct sudoers_str_list *hosts;
struct sudoers_str_list *users;
struct sudoers_str_list *runasusers;
struct sudoers_str_list *runasgroups;
struct sudoers_str_list *options;
};
STAILQ_HEAD(sudo_role_list, sudo_role);
static void
sudo_role_free(struct sudo_role *role)
{
debug_decl(sudo_role_free, SUDOERS_DEBUG_UTIL)
if (role != NULL) {
free(role->cn);
free(role->notbefore);
free(role->notafter);
str_list_free(role->cmnds);
str_list_free(role->hosts);
str_list_free(role->users);
str_list_free(role->runasusers);
str_list_free(role->runasgroups);
str_list_free(role->options);
free(role);
}
debug_return;
}
static struct sudo_role *
sudo_role_alloc(void)
{
struct sudo_role *role;
debug_decl(sudo_role_alloc, SUDOERS_DEBUG_UTIL)
role = calloc(1, sizeof(*role));
if (role != NULL) {
role->cmnds = str_list_alloc();
role->hosts = str_list_alloc();
role->users = str_list_alloc();
role->runasusers = str_list_alloc();
role->runasgroups = str_list_alloc();
role->options = str_list_alloc();
if (role->cmnds == NULL || role->hosts == NULL ||
role->users == NULL || role->runasusers == NULL ||
role->runasgroups == NULL || role->options == NULL) {
sudo_role_free(role);
role = NULL;
}
}
debug_return_ptr(role);
}
static bool
ldif_parse_attribute(char *line, char **name, char **value)
{
bool encoded = false;
char *attr, *cp, *ep, *colon;
size_t len;
debug_decl(ldif_parse_attribute, SUDOERS_DEBUG_UTIL)
if (!isalpha((unsigned char)*line))
debug_return_bool(false);
for (cp = line + 1; *cp != ':' && *cp != '\0'; cp++) {
if (!isalnum((unsigned char)*cp) && *cp != '-')
debug_return_bool(false);
}
if (*cp != ':')
debug_return_bool(false);
colon = cp++;
if (*cp == ':') {
encoded = true;
cp++;
}
while (*cp == ' ')
cp++;
ep = cp + strlen(cp);
while (ep > cp && ep[-1] == ' ') {
ep--;
if (!encoded && ep != cp && ep[-1] == '\\')
break;
*ep = '\0';
}
attr = cp;
if (encoded) {
char *copy = strdup(attr);
if (copy == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
len = base64_decode(attr, (unsigned char *)copy, strlen(copy));
if (len == (size_t)-1) {
free(copy);
debug_return_bool(false);
}
memcpy(attr, copy, len);
attr[len] = '\0';
free(copy);
}
*colon = '\0';
*name = line;
*value = attr;
debug_return_bool(true);
}
static void
ldif_store_string(const char *str, struct sudoers_str_list *strlist, bool sorted)
{
struct sudoers_string *ls;
debug_decl(ldif_store_string, SUDOERS_DEBUG_UTIL)
if ((ls = sudoers_string_alloc(str)) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (!sorted) {
STAILQ_INSERT_TAIL(strlist, ls, entries);
} else {
struct sudoers_string *prev, *next;
prev = STAILQ_FIRST(strlist);
if (prev == NULL || strcasecmp(str, prev->str) <= 0) {
STAILQ_INSERT_HEAD(strlist, ls, entries);
} else {
while ((next = STAILQ_NEXT(prev, entries)) != NULL) {
if (strcasecmp(str, next->str) <= 0)
break;
prev = next;
}
STAILQ_INSERT_AFTER(strlist, prev, ls, entries);
}
}
debug_return;
}
static char *
sudoers_string_iter(void **vp)
{
struct sudoers_string *ls = *vp;
if (ls == NULL)
return NULL;
*vp = STAILQ_NEXT(ls, entries);
return ls->str;
}
static int
role_order_cmp(const void *va, const void *vb)
{
const struct sudo_role *a = *(const struct sudo_role **)va;
const struct sudo_role *b = *(const struct sudo_role **)vb;
debug_decl(role_order_cmp, SUDOERS_DEBUG_LDAP)
debug_return_int(a->order < b->order ? -1 :
(a->order > b->order ? 1 : 0));
}
static void
ldif_store_options(struct sudoers_parse_tree *parse_tree,
struct sudoers_str_list *options)
{
struct defaults *d;
struct sudoers_string *ls;
char *var, *val;
debug_decl(ldif_store_options, SUDOERS_DEBUG_UTIL)
STAILQ_FOREACH(ls, options, entries) {
if ((d = calloc(1, sizeof(*d))) == NULL ||
(d->binding = malloc(sizeof(*d->binding))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
TAILQ_INIT(d->binding);
d->type = DEFAULTS;
d->op = sudo_ldap_parse_option(ls->str, &var, &val);
if ((d->var = strdup(var)) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (val != NULL) {
if ((d->val = strdup(val)) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
}
TAILQ_INSERT_TAIL(&parse_tree->defaults, d, entries);
}
debug_return;
}
static int
str_list_cmp(const void *aa, const void *bb)
{
const struct sudoers_str_list *a = aa;
const struct sudoers_str_list *b = bb;
const struct sudoers_string *lsa = STAILQ_FIRST(a);
const struct sudoers_string *lsb = STAILQ_FIRST(b);
int ret;
while (lsa != NULL && lsb != NULL) {
if ((ret = strcasecmp(lsa->str, lsb->str)) != 0)
return ret;
lsa = STAILQ_NEXT(lsa, entries);
lsb = STAILQ_NEXT(lsb, entries);
}
return lsa == lsb ? 0 : (lsa == NULL ? -1 : 1);
}
static int
str_list_cache(struct rbtree *cache, struct sudoers_str_list **strlistp)
{
struct sudoers_str_list *strlist = *strlistp;
struct rbnode *node;
int ret;
debug_decl(str_list_cache, SUDOERS_DEBUG_UTIL)
ret = rbinsert(cache, strlist, &node);
switch (ret) {
case 0:
strlist->refcnt++;
break;
case 1:
str_list_free(strlist);
strlist = node->data;
strlist->refcnt++;
*strlistp = strlist;
break;
}
debug_return_int(ret);
}
static void
role_to_sudoers(struct sudoers_parse_tree *parse_tree, struct sudo_role *role,
bool store_options, bool reuse_userspec, bool reuse_privilege,
bool reuse_runas)
{
struct privilege *priv;
struct sudoers_string *ls;
struct userspec *us;
struct member *m;
debug_decl(role_to_sudoers, SUDOERS_DEBUG_UTIL)
if (reuse_userspec) {
us = TAILQ_LAST(&parse_tree->userspecs, userspec_list);
} else {
if ((us = calloc(1, sizeof(*us))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
TAILQ_INIT(&us->privileges);
TAILQ_INIT(&us->users);
STAILQ_INIT(&us->comments);
STAILQ_FOREACH(ls, role->users, entries) {
char *user = ls->str;
if ((m = calloc(1, sizeof(*m))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
m->negated = sudo_ldap_is_negated(&user);
m->name = strdup(user);
if (m->name == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (strcmp(user, "ALL") == 0) {
m->type = ALL;
} else if (*user == '+') {
m->type = NETGROUP;
} else if (*user == '%') {
m->type = USERGROUP;
} else {
m->type = WORD;
}
TAILQ_INSERT_TAIL(&us->users, m, entries);
}
}
if (role->cn != NULL) {
struct sudoers_comment *comment = NULL;
if (reuse_userspec) {
STAILQ_FOREACH(comment, &us->comments, entries) {
if (strncasecmp(comment->str, "sudoRole ", 9) == 0) {
char *tmpstr;
if (asprintf(&tmpstr, "%s, %s", comment->str, role->cn) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
free(comment->str);
comment->str = tmpstr;
break;
}
}
}
if (comment == NULL) {
if ((comment = malloc(sizeof(*comment))) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (asprintf(&comment->str, "sudoRole %s", role->cn) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
STAILQ_INSERT_TAIL(&us->comments, comment, entries);
}
}
priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts),
STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups),
STAILQ_FIRST(role->cmnds), STAILQ_FIRST(role->options),
role->notbefore, role->notafter, true, store_options,
sudoers_string_iter);
if (priv == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
if (reuse_privilege) {
struct privilege *prev_priv = TAILQ_LAST(&us->privileges, privilege_list);
if (reuse_runas) {
struct member_list *runasuserlist =
TAILQ_FIRST(&prev_priv->cmndlist)->runasuserlist;
struct member_list *runasgrouplist =
TAILQ_FIRST(&prev_priv->cmndlist)->runasgrouplist;
struct cmndspec *cmndspec = TAILQ_FIRST(&priv->cmndlist);
if (cmndspec->runasuserlist != NULL) {
free_members(cmndspec->runasuserlist);
free(cmndspec->runasuserlist);
}
if (cmndspec->runasgrouplist != NULL) {
free_members(cmndspec->runasgrouplist);
free(cmndspec->runasgrouplist);
}
TAILQ_FOREACH(cmndspec, &priv->cmndlist, entries) {
cmndspec->runasuserlist = runasuserlist;
cmndspec->runasgrouplist = runasgrouplist;
}
}
TAILQ_CONCAT(&prev_priv->cmndlist, &priv->cmndlist, entries);
free_privilege(priv);
} else {
TAILQ_INSERT_TAIL(&us->privileges, priv, entries);
}
if (!reuse_userspec)
TAILQ_INSERT_TAIL(&parse_tree->userspecs, us, entries);
debug_return;
}
static void
ldif_to_sudoers(struct sudoers_parse_tree *parse_tree,
struct sudo_role_list *roles, unsigned int numroles, bool store_options)
{
struct sudo_role **role_array, *role = NULL;
unsigned int n;
debug_decl(ldif_to_sudoers, SUDOERS_DEBUG_UTIL)
role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array));
for (n = 0; n < numroles; n++) {
if ((role = STAILQ_FIRST(roles)) == NULL)
break;
STAILQ_REMOVE_HEAD(roles, entries);
role_array[n] = role;
}
role_array[n] = NULL;
qsort(role_array, numroles, sizeof(*role_array), role_order_cmp);
for (n = 0; n < numroles; n++) {
bool reuse_userspec = false;
bool reuse_privilege = false;
bool reuse_runas = false;
role = role_array[n];
if (n > 0 && role->users == role_array[n - 1]->users) {
reuse_userspec = true;
if (!store_options) {
if (role->hosts == role_array[n - 1]->hosts) {
reuse_privilege = true;
if (role->runasusers == role_array[n - 1]->runasusers &&
role->runasgroups == role_array[n - 1]->runasgroups)
reuse_runas = true;
}
}
}
role_to_sudoers(parse_tree, role, store_options, reuse_userspec,
reuse_privilege, reuse_runas);
}
for (n = 0; n < numroles; n++)
sudo_role_free(role_array[n]);
free(role_array);
debug_return;
}
static
char *unquote_cn(const char *src)
{
char *dst, *new_cn;
size_t len;
debug_decl(unquote_cn, SUDOERS_DEBUG_UTIL)
len = strlen(src);
if ((new_cn = malloc(len + 1)) == NULL)
debug_return_str(NULL);
for (dst = new_cn; *src != '\0';) {
if (src[0] == '\\' && src[1] != '\0')
src++;
*dst++ = *src++;
}
*dst = '\0';
debug_return_str(new_cn);
}
bool
sudoers_parse_ldif(struct sudoers_parse_tree *parse_tree,
FILE *fp, const char *sudoers_base, bool store_options)
{
struct sudo_role_list roles = STAILQ_HEAD_INITIALIZER(roles);
struct sudo_role *role = NULL;
struct rbtree *usercache, *groupcache, *hostcache;
unsigned numroles = 0;
bool in_role = false;
size_t linesize = 0;
char *attr, *name, *line = NULL, *savedline = NULL;
ssize_t savedlen = 0;
bool mismatch = false;
int errors = 0;
debug_decl(sudoers_parse_ldif, SUDOERS_DEBUG_UTIL)
free_parse_tree(parse_tree);
usercache = rbcreate(str_list_cmp);
groupcache = rbcreate(str_list_cmp);
hostcache = rbcreate(str_list_cmp);
if (usercache == NULL || groupcache == NULL || hostcache == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
for (;;) {
int ch;
ssize_t len = getdelim(&line, &linesize, '\n', fp);
while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
line[--len] = '\0';
if (len <= 0) {
if (in_role) {
if (role->cn != NULL && strcasecmp(role->cn, "defaults") == 0) {
ldif_store_options(parse_tree, role->options);
sudo_role_free(role);
} else if (STAILQ_EMPTY(role->users) ||
STAILQ_EMPTY(role->hosts) || STAILQ_EMPTY(role->cmnds)) {
sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
role->cn ? role->cn : "UNKNOWN");
sudo_role_free(role);
} else {
if (str_list_cache(usercache, &role->users) == -1 ||
str_list_cache(hostcache, &role->hosts) == -1 ||
str_list_cache(usercache, &role->runasusers) == -1 ||
str_list_cache(groupcache, &role->runasgroups) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
STAILQ_INSERT_TAIL(&roles, role, entries);
numroles++;
}
role = NULL;
in_role = false;
}
if (len == -1) {
break;
}
mismatch = false;
continue;
}
if (savedline != NULL) {
char *tmp;
linesize = savedlen + len + 1;
if ((tmp = realloc(savedline, linesize)) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
memcpy(tmp + savedlen, line, len + 1);
free(line);
line = tmp;
savedline = NULL;
}
if ((ch = getc(fp)) == ' ') {
savedlen = len;
savedline = line;
line = NULL;
linesize = 0;
continue;
}
ungetc(ch, fp);
if (*line == '#' || mismatch)
continue;
if (!ldif_parse_attribute(line, &name, &attr)) {
sudo_warnx(U_("invalid LDIF attribute: %s"), line);
errors++;
continue;
}
if (strcasecmp(name, "dn") == 0) {
if (sudoers_base != NULL) {
if (strncasecmp(attr, "cn=", 3) == 0) {
for (attr += 3; *attr != '\0'; attr++) {
if (*attr == '\\')
attr++;
if (*attr == ',') {
attr++;
break;
}
}
}
if (strcasecmp(attr, sudoers_base) != 0) {
mismatch = true;
continue;
}
}
} else if (strcasecmp(name, "objectClass") == 0) {
if (strcasecmp(attr, "sudoRole") == 0) {
if (role == NULL) {
if ((role = sudo_role_alloc()) == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
}
in_role = true;
}
}
if (!in_role)
continue;
if (strcasecmp(name, "cn") == 0) {
free(role->cn);
role->cn = unquote_cn(attr);
if (role->cn == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
} else if (strcasecmp(name, "sudoUser") == 0) {
ldif_store_string(attr, role->users, true);
} else if (strcasecmp(name, "sudoHost") == 0) {
ldif_store_string(attr, role->hosts, true);
} else if (strcasecmp(name, "sudoRunAs") == 0) {
ldif_store_string(attr, role->runasusers, true);
} else if (strcasecmp(name, "sudoRunAsUser") == 0) {
ldif_store_string(attr, role->runasusers, true);
} else if (strcasecmp(name, "sudoRunAsGroup") == 0) {
ldif_store_string(attr, role->runasgroups, true);
} else if (strcasecmp(name, "sudoCommand") == 0) {
ldif_store_string(attr, role->cmnds, false);
} else if (strcasecmp(name, "sudoOption") == 0) {
ldif_store_string(attr, role->options, false);
} else if (strcasecmp(name, "sudoOrder") == 0) {
char *ep;
role->order = strtod(attr, &ep);
if (ep == attr || *ep != '\0') {
sudo_warnx(U_("invalid sudoOrder attribute: %s"), attr);
errors++;
}
} else if (strcasecmp(name, "sudoNotBefore") == 0) {
free(role->notbefore);
role->notbefore = strdup(attr);
if (role->notbefore == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
} else if (strcasecmp(name, "sudoNotAfter") == 0) {
free(role->notafter);
role->notafter = strdup(attr);
if (role->notafter == NULL) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
}
}
sudo_role_free(role);
free(line);
ldif_to_sudoers(parse_tree, &roles, numroles, store_options);
rbdestroy(usercache, str_list_free);
rbdestroy(groupcache, str_list_free);
rbdestroy(hostcache, str_list_free);
if (fp != stdin)
fclose(fp);
debug_return_bool(errors == 0);
}