Move ldif -> sudoers conversion code into parse_ldif.c
This commit is contained in:
1
MANIFEST
1
MANIFEST
@@ -332,6 +332,7 @@ plugins/sudoers/mkdefaults
|
|||||||
plugins/sudoers/mkdir_parents.c
|
plugins/sudoers/mkdir_parents.c
|
||||||
plugins/sudoers/parse.c
|
plugins/sudoers/parse.c
|
||||||
plugins/sudoers/parse.h
|
plugins/sudoers/parse.h
|
||||||
|
plugins/sudoers/parse_ldif.c
|
||||||
plugins/sudoers/po/README
|
plugins/sudoers/po/README
|
||||||
plugins/sudoers/po/ca.mo
|
plugins/sudoers/po/ca.mo
|
||||||
plugins/sudoers/po/ca.po
|
plugins/sudoers/po/ca.po
|
||||||
|
@@ -164,7 +164,7 @@ VISUDO_OBJS = editor.lo find_path.lo goodpath.lo locale.lo stubs.o \
|
|||||||
sudo_printf.o visudo.o
|
sudo_printf.o visudo.o
|
||||||
|
|
||||||
CVTSUDOERS_OBJS = cvtsudoers.o cvtsudoers_json.o cvtsudoers_ldif.o \
|
CVTSUDOERS_OBJS = cvtsudoers.o cvtsudoers_json.o cvtsudoers_ldif.o \
|
||||||
cvtsudoers_pwutil.o fmtsudoers.lo locale.lo \
|
cvtsudoers_pwutil.o fmtsudoers.lo locale.lo parse_ldif.o \
|
||||||
strlist.o stubs.o sudo_printf.o ldap_util.lo
|
strlist.o stubs.o sudo_printf.o ldap_util.lo
|
||||||
|
|
||||||
REPLAY_OBJS = getdate.o sudoreplay.o iolog_util.lo
|
REPLAY_OBJS = getdate.o sudoreplay.o iolog_util.lo
|
||||||
@@ -1133,6 +1133,17 @@ parse.lo: $(srcdir)/parse.c $(devdir)/def_data.h $(devdir)/gram.h \
|
|||||||
$(srcdir)/sudo_nss.h $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
|
$(srcdir)/sudo_nss.h $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
|
||||||
$(top_builddir)/config.h $(top_builddir)/pathnames.h
|
$(top_builddir)/config.h $(top_builddir)/pathnames.h
|
||||||
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/parse.c
|
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/parse.c
|
||||||
|
parse_ldif.o: $(srcdir)/parse_ldif.c $(devdir)/def_data.h $(devdir)/gram.h \
|
||||||
|
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
||||||
|
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
|
||||||
|
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
|
||||||
|
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
|
||||||
|
$(incdir)/sudo_util.h $(srcdir)/defaults.h $(srcdir)/logging.h \
|
||||||
|
$(srcdir)/parse.h $(srcdir)/redblack.h $(srcdir)/strlist.h \
|
||||||
|
$(srcdir)/sudo_ldap.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
|
||||||
|
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
|
||||||
|
$(top_builddir)/pathnames.h
|
||||||
|
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/parse_ldif.c
|
||||||
passwd.lo: $(authdir)/passwd.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
|
passwd.lo: $(authdir)/passwd.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
|
||||||
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
|
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
|
||||||
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
|
$(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \
|
||||||
|
@@ -80,6 +80,7 @@ static void help(void) __attribute__((__noreturn__));
|
|||||||
static void usage(int);
|
static void usage(int);
|
||||||
static bool convert_sudoers_sudoers(struct sudoers_parse_tree *parse_tree, const char *output_file, struct cvtsudoers_config *conf);
|
static bool convert_sudoers_sudoers(struct sudoers_parse_tree *parse_tree, const char *output_file, struct cvtsudoers_config *conf);
|
||||||
static bool parse_sudoers(const char *input_file, struct cvtsudoers_config *conf);
|
static bool parse_sudoers(const char *input_file, struct cvtsudoers_config *conf);
|
||||||
|
static bool parse_ldif(struct sudoers_parse_tree *parse_tree, const char *input_file, struct cvtsudoers_config *conf);
|
||||||
static bool cvtsudoers_parse_filter(char *expression);
|
static bool cvtsudoers_parse_filter(char *expression);
|
||||||
static struct cvtsudoers_config *cvtsudoers_conf_read(const char *conf_file);
|
static struct cvtsudoers_config *cvtsudoers_conf_read(const char *conf_file);
|
||||||
static void cvtsudoers_conf_free(struct cvtsudoers_config *conf);
|
static void cvtsudoers_conf_free(struct cvtsudoers_config *conf);
|
||||||
@@ -581,6 +582,24 @@ cvtsudoers_parse_filter(char *expression)
|
|||||||
debug_return_bool(true);
|
debug_return_bool(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_ldif(struct sudoers_parse_tree *parse_tree, const char *input_file,
|
||||||
|
struct cvtsudoers_config *conf)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
debug_decl(parse_ldif, SUDOERS_DEBUG_UTIL)
|
||||||
|
|
||||||
|
/* Open LDIF file and parse it. */
|
||||||
|
if (strcmp(input_file, "-") == 0) {
|
||||||
|
fp = stdin;
|
||||||
|
input_file = "stdin";
|
||||||
|
} else if ((fp = fopen(input_file, "r")) == NULL)
|
||||||
|
sudo_fatal(U_("unable to open %s"), input_file);
|
||||||
|
|
||||||
|
debug_return_bool(sudoers_parse_ldif(parse_tree, fp, conf->sudoers_base,
|
||||||
|
conf->store_options));
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_sudoers(const char *input_file, struct cvtsudoers_config *conf)
|
parse_sudoers(const char *input_file, struct cvtsudoers_config *conf)
|
||||||
{
|
{
|
||||||
|
@@ -83,8 +83,6 @@ bool convert_sudoers_json(struct sudoers_parse_tree *parse_tree, const char *out
|
|||||||
|
|
||||||
/* cvtsudoers_ldif.c */
|
/* cvtsudoers_ldif.c */
|
||||||
bool convert_sudoers_ldif(struct sudoers_parse_tree *parse_tree, const char *output_file, struct cvtsudoers_config *conf);
|
bool convert_sudoers_ldif(struct sudoers_parse_tree *parse_tree, const char *output_file, struct cvtsudoers_config *conf);
|
||||||
bool parse_ldif(struct sudoers_parse_tree *parse_tree, const char *input_file, struct cvtsudoers_config *conf);
|
|
||||||
void get_hostname(void);
|
|
||||||
|
|
||||||
/* cvtsudoers_pwutil.c */
|
/* cvtsudoers_pwutil.c */
|
||||||
struct cache_item *cvtsudoers_make_pwitem(uid_t uid, const char *name);
|
struct cache_item *cvtsudoers_make_pwitem(uid_t uid, const char *name);
|
||||||
@@ -92,4 +90,7 @@ struct cache_item *cvtsudoers_make_gritem(gid_t gid, const char *name);
|
|||||||
struct cache_item *cvtsudoers_make_gidlist_item(const struct passwd *pw, char * const *unused1, unsigned int type);
|
struct cache_item *cvtsudoers_make_gidlist_item(const struct passwd *pw, char * const *unused1, unsigned int type);
|
||||||
struct cache_item *cvtsudoers_make_grlist_item(const struct passwd *pw, char * const *unused1);
|
struct cache_item *cvtsudoers_make_grlist_item(const struct passwd *pw, char * const *unused1);
|
||||||
|
|
||||||
|
/* stubs.c */
|
||||||
|
void get_hostname(void);
|
||||||
|
|
||||||
#endif /* SUDOERS_CVTSUDOERS_H */
|
#endif /* SUDOERS_CVTSUDOERS_H */
|
||||||
|
@@ -654,746 +654,3 @@ convert_sudoers_ldif(struct sudoers_parse_tree *parse_tree,
|
|||||||
|
|
||||||
debug_return_bool(ret);
|
debug_return_bool(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parse an LDIF attribute, including base64 support.
|
|
||||||
* See http://www.faqs.org/rfcs/rfc2849.html
|
|
||||||
*/
|
|
||||||
static char *
|
|
||||||
ldif_parse_attribute(char *str)
|
|
||||||
{
|
|
||||||
bool encoded = false;
|
|
||||||
char *attr, *ep;
|
|
||||||
size_t len;
|
|
||||||
debug_decl(ldif_parse_attribute, SUDOERS_DEBUG_UTIL)
|
|
||||||
|
|
||||||
/* Check for foo:: base64str. */
|
|
||||||
if (*str == ':') {
|
|
||||||
encoded = true;
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trim leading and trailing space. */
|
|
||||||
while (*str == ' ')
|
|
||||||
str++;
|
|
||||||
|
|
||||||
ep = str + strlen(str);
|
|
||||||
while (ep > str && ep[-1] == ' ') {
|
|
||||||
/* Don't trim ecaped trailing space if not base64. */
|
|
||||||
if (!encoded && ep != str && ep[-2] == '\\')
|
|
||||||
break;
|
|
||||||
*--ep = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
attr = str;
|
|
||||||
if (encoded) {
|
|
||||||
/*
|
|
||||||
* Decode base64 inline and add NUL-terminator.
|
|
||||||
* The copy allows us to provide a useful message on error.
|
|
||||||
*/
|
|
||||||
char *copy = strdup(str);
|
|
||||||
if (copy == NULL) {
|
|
||||||
sudo_fatalx(U_("%s: %s"), __func__,
|
|
||||||
U_("unable to allocate memory"));
|
|
||||||
}
|
|
||||||
len = base64_decode(copy, (unsigned char *)attr, strlen(attr));
|
|
||||||
if (len == (size_t)-1) {
|
|
||||||
sudo_warnx(U_("ignoring invalid attribute value: %s"), copy);
|
|
||||||
free(copy);
|
|
||||||
debug_return_str(NULL);
|
|
||||||
}
|
|
||||||
attr[len] = '\0';
|
|
||||||
free(copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_return_str(attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allocate a struct sudoers_string, store str in it and
|
|
||||||
* insert into the specified strlist.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* Insertion sort, list is small. */
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Iterator for sudo_ldap_role_to_priv().
|
|
||||||
* Takes a pointer to a struct sudoers_string *.
|
|
||||||
* Returns the string or NULL if we've reached the end.
|
|
||||||
*/
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parse list of sudoOption and store in global defaults list.
|
|
||||||
*/
|
|
||||||
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 = strcmp(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:
|
|
||||||
/* new entry, take a ref for the cache */
|
|
||||||
strlist->refcnt++;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
/* already exists, use existing and take a ref. */
|
|
||||||
str_list_free(strlist);
|
|
||||||
strlist = node->data;
|
|
||||||
strlist->refcnt++;
|
|
||||||
*strlistp = strlist;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
debug_return_int(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a sudoRole to sudoers format and store in the global sudoers
|
|
||||||
* data structures.
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: use cn to create a UserAlias if multiple users in it?
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (reuse_userspec) {
|
|
||||||
/* Re-use the previous userspec */
|
|
||||||
us = TAILQ_LAST(&parse_tree->userspecs, userspec_list);
|
|
||||||
} else {
|
|
||||||
/* Allocate a new userspec and fill in the user list. */
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add source role as a comment. */
|
|
||||||
if (role->cn != NULL) {
|
|
||||||
struct sudoers_comment *comment = NULL;
|
|
||||||
if (reuse_userspec) {
|
|
||||||
/* Try to re-use comment too. */
|
|
||||||
STAILQ_FOREACH(comment, &us->comments, entries) {
|
|
||||||
if (strncmp(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) {
|
|
||||||
/* Create a new comment. */
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert role to sudoers privilege. */
|
|
||||||
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) {
|
|
||||||
/* Hostspec unchanged, append cmndlist to previous privilege. */
|
|
||||||
struct privilege *prev_priv = TAILQ_LAST(&us->privileges, privilege_list);
|
|
||||||
if (reuse_runas) {
|
|
||||||
/* Runas users and groups same if as in previous privilege. */
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* Free duplicate runas lists. */
|
|
||||||
if (cmndspec->runasuserlist != NULL)
|
|
||||||
free_members(cmndspec->runasuserlist);
|
|
||||||
if (cmndspec->runasgrouplist != NULL)
|
|
||||||
free_members(cmndspec->runasgrouplist);
|
|
||||||
|
|
||||||
/* Update cmndspec with previous runas lists. */
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add finished userspec to the list if new. */
|
|
||||||
if (!reuse_userspec)
|
|
||||||
TAILQ_INSERT_TAIL(&parse_tree->userspecs, us, entries);
|
|
||||||
|
|
||||||
debug_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert the list of sudoRoles to sudoers format and
|
|
||||||
* store in the global sudoers data structures.
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
|
|
||||||
/* Convert from list of roles to array and sort by order. */
|
|
||||||
role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array));
|
|
||||||
for (n = 0; n < numroles; n++) {
|
|
||||||
if ((role = STAILQ_FIRST(roles)) == NULL)
|
|
||||||
break; /* cannot happen */
|
|
||||||
STAILQ_REMOVE_HEAD(roles, entries);
|
|
||||||
role_array[n] = role;
|
|
||||||
}
|
|
||||||
role_array[n] = NULL;
|
|
||||||
qsort(role_array, numroles, sizeof(*role_array), role_order_cmp);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Iterate over roles in sorted order, converting to sudoers.
|
|
||||||
*/
|
|
||||||
for (n = 0; n < numroles; n++) {
|
|
||||||
bool reuse_userspec = false;
|
|
||||||
bool reuse_privilege = false;
|
|
||||||
bool reuse_runas = false;
|
|
||||||
|
|
||||||
role = role_array[n];
|
|
||||||
|
|
||||||
/* Check whether we can reuse the previous user and host specs */
|
|
||||||
if (n > 0 && role->users == role_array[n - 1]->users) {
|
|
||||||
reuse_userspec = true;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Since options are stored per-privilege we can't
|
|
||||||
* append to the previous privilege's cmndlist if
|
|
||||||
* we are storing options.
|
|
||||||
*/
|
|
||||||
if (!store_options) {
|
|
||||||
if (role->hosts == role_array[n - 1]->hosts) {
|
|
||||||
reuse_privilege = true;
|
|
||||||
|
|
||||||
/* Reuse runasusers and runasgroups if possible. */
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clean up. */
|
|
||||||
for (n = 0; n < numroles; n++)
|
|
||||||
sudo_role_free(role_array[n]);
|
|
||||||
free(role_array);
|
|
||||||
|
|
||||||
debug_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Given a cn with possible quoted characters, return a copy of
|
|
||||||
* the cn with quote characters ('\\') removed.
|
|
||||||
* The caller is responsible for freeing the returned string.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parse a sudoers file in LDIF format, https://tools.ietf.org/html/rfc2849
|
|
||||||
* Parsed sudoRole objects are stored in the specified parse_tree which
|
|
||||||
* must already be initialized.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
parse_ldif(struct sudoers_parse_tree *parse_tree, const char *input_file,
|
|
||||||
struct cvtsudoers_config *conf)
|
|
||||||
{
|
|
||||||
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, *line = NULL, *savedline = NULL;
|
|
||||||
ssize_t savedlen = 0;
|
|
||||||
bool mismatch = false;
|
|
||||||
FILE *fp;
|
|
||||||
debug_decl(parse_ldif, SUDOERS_DEBUG_UTIL)
|
|
||||||
|
|
||||||
/* Open LDIF file and parse it. */
|
|
||||||
if (strcmp(input_file, "-") == 0) {
|
|
||||||
fp = stdin;
|
|
||||||
input_file = "stdin";
|
|
||||||
} else if ((fp = fopen(input_file, "r")) == NULL)
|
|
||||||
sudo_fatal(U_("unable to open %s"), input_file);
|
|
||||||
|
|
||||||
/* Free old contents of the parse tree (if any). */
|
|
||||||
free_parse_tree(parse_tree);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We cache user, group and host lists to make it eay to detect when there
|
|
||||||
* are identical lists (simple pointer compare). This makes it possible
|
|
||||||
* to merge multiplpe sudoRole objects into a single UserSpec and/or
|
|
||||||
* Privilege. The lists are sorted since LDAP order is arbitrary.
|
|
||||||
*/
|
|
||||||
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"));
|
|
||||||
|
|
||||||
/* Read through input, parsing into sudo_roles and global defaults. */
|
|
||||||
for (;;) {
|
|
||||||
int ch;
|
|
||||||
ssize_t len = getline(&line, &linesize, fp);
|
|
||||||
|
|
||||||
/* Trim trailing return or newline. */
|
|
||||||
while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
|
|
||||||
line[--len] = '\0';
|
|
||||||
|
|
||||||
/* Blank line or EOF terminates an entry. */
|
|
||||||
if (len <= 0) {
|
|
||||||
if (in_role) {
|
|
||||||
if (role->cn != NULL && strcmp(role->cn, "defaults") == 0) {
|
|
||||||
ldif_store_options(parse_tree, role->options);
|
|
||||||
sudo_role_free(role);
|
|
||||||
role = NULL;
|
|
||||||
} else if (STAILQ_EMPTY(role->users) ||
|
|
||||||
STAILQ_EMPTY(role->hosts) || STAILQ_EMPTY(role->cmnds)) {
|
|
||||||
/* Incomplete role. */
|
|
||||||
sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
|
|
||||||
role->cn ? role->cn : "UNKNOWN");
|
|
||||||
sudo_role_free(role);
|
|
||||||
role = NULL;
|
|
||||||
} else {
|
|
||||||
/* Cache users, hosts, runasusers and runasgroups. */
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Store finished role. */
|
|
||||||
STAILQ_INSERT_TAIL(&roles, role, entries);
|
|
||||||
numroles++;
|
|
||||||
}
|
|
||||||
role = NULL;
|
|
||||||
in_role = false;
|
|
||||||
}
|
|
||||||
if (len == -1) {
|
|
||||||
/* EOF */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mismatch = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedline != NULL) {
|
|
||||||
char *tmp;
|
|
||||||
|
|
||||||
/* Append to saved line. */
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
/* Skip comment lines or records that don't match the base. */
|
|
||||||
if (*line == '#' || mismatch)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for folded line */
|
|
||||||
if ((ch = getc(fp)) == ' ') {
|
|
||||||
/* folded line, append to the saved portion. */
|
|
||||||
savedlen = len;
|
|
||||||
savedline = line;
|
|
||||||
line = NULL;
|
|
||||||
linesize = 0;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
/* not folded, push back ch */
|
|
||||||
ungetc(ch, fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate new role as needed. */
|
|
||||||
if (role == NULL) {
|
|
||||||
if ((role = sudo_role_alloc()) == NULL) {
|
|
||||||
sudo_fatalx(U_("%s: %s"), __func__,
|
|
||||||
U_("unable to allocate memory"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse dn and objectClass. */
|
|
||||||
if (strncasecmp(line, "dn:", 3) == 0) {
|
|
||||||
/* Compare dn to base, if specified. */
|
|
||||||
if (conf->sudoers_base != NULL) {
|
|
||||||
attr = ldif_parse_attribute(line + 3);
|
|
||||||
if (attr == NULL) {
|
|
||||||
/* invalid attribute */
|
|
||||||
mismatch = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* Skip over cn if present. */
|
|
||||||
if (strncasecmp(attr, "cn=", 3) == 0) {
|
|
||||||
for (attr += 3; *attr != '\0'; attr++) {
|
|
||||||
/* Handle escaped ',' chars. */
|
|
||||||
if (*attr == '\\')
|
|
||||||
attr++;
|
|
||||||
if (*attr == ',') {
|
|
||||||
attr++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (strcasecmp(attr, conf->sudoers_base) != 0) {
|
|
||||||
/* Doesn't match base, skip the rest of it. */
|
|
||||||
mismatch = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strncmp(line, "objectClass:", 12) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 12);
|
|
||||||
if (attr != NULL && strcmp(attr, "sudoRole") == 0)
|
|
||||||
in_role = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Not in a sudoRole, keep reading. */
|
|
||||||
if (!in_role)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Part of a sudoRole, parse it. */
|
|
||||||
if (strncmp(line, "cn:", 3) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 3);
|
|
||||||
if (attr != NULL) {
|
|
||||||
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 (strncmp(line, "sudoUser:", 9) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 9);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->users, true);
|
|
||||||
} else if (strncmp(line, "sudoHost:", 9) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 9);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->hosts, true);
|
|
||||||
} else if (strncmp(line, "sudoRunAs:", 10) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 10);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->runasusers, true);
|
|
||||||
} else if (strncmp(line, "sudoRunAsUser:", 14) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 14);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->runasusers, true);
|
|
||||||
} else if (strncmp(line, "sudoRunAsGroup:", 15) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 15);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->runasgroups, true);
|
|
||||||
} else if (strncmp(line, "sudoCommand:", 12) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 12);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->cmnds, false);
|
|
||||||
} else if (strncmp(line, "sudoOption:", 11) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 11);
|
|
||||||
if (attr != NULL)
|
|
||||||
ldif_store_string(attr, role->options, false);
|
|
||||||
} else if (strncmp(line, "sudoOrder:", 10) == 0) {
|
|
||||||
char *ep;
|
|
||||||
attr = ldif_parse_attribute(line + 10);
|
|
||||||
if (attr != NULL) {
|
|
||||||
role->order = strtod(attr, &ep);
|
|
||||||
if (ep == attr || *ep != '\0')
|
|
||||||
sudo_warnx(U_("invalid sudoOrder attribute: %s"), attr);
|
|
||||||
}
|
|
||||||
} else if (strncmp(line, "sudoNotBefore:", 14) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 14);
|
|
||||||
if (attr != NULL) {
|
|
||||||
free(role->notbefore);
|
|
||||||
role->notbefore = strdup(attr);
|
|
||||||
if (role->notbefore == NULL) {
|
|
||||||
sudo_fatalx(U_("%s: %s"), __func__,
|
|
||||||
U_("unable to allocate memory"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strncmp(line, "sudoNotAfter:", 13) == 0) {
|
|
||||||
attr = ldif_parse_attribute(line + 13);
|
|
||||||
if (attr != NULL) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* Convert from roles to sudoers data structures. */
|
|
||||||
ldif_to_sudoers(parse_tree, &roles, numroles, conf->store_options);
|
|
||||||
|
|
||||||
/* Clean up. */
|
|
||||||
rbdestroy(usercache, str_list_free);
|
|
||||||
rbdestroy(groupcache, str_list_free);
|
|
||||||
rbdestroy(hostcache, str_list_free);
|
|
||||||
|
|
||||||
if (fp != stdin)
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
debug_return_bool(true);
|
|
||||||
}
|
|
||||||
|
@@ -346,6 +346,9 @@ int sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int validated,
|
|||||||
int display_privs(struct sudo_nss_list *snl, struct passwd *pw, bool verbose);
|
int display_privs(struct sudo_nss_list *snl, struct passwd *pw, bool verbose);
|
||||||
int display_cmnd(struct sudo_nss_list *snl, struct passwd *pw);
|
int display_cmnd(struct sudo_nss_list *snl, struct passwd *pw);
|
||||||
|
|
||||||
|
/* parse_ldif.c */
|
||||||
|
bool sudoers_parse_ldif(struct sudoers_parse_tree *parse_tree, FILE *fp, const char *sudoers_base, bool store_options);
|
||||||
|
|
||||||
/* fmtsudoers.c */
|
/* fmtsudoers.c */
|
||||||
struct sudo_lbuf;
|
struct sudo_lbuf;
|
||||||
bool sudoers_format_cmndspec(struct sudo_lbuf *lbuf, struct sudoers_parse_tree *parse_tree, struct cmndspec *cs, struct cmndspec *prev_cs, struct cmndtag tags, bool expand_aliases);
|
bool sudoers_format_cmndspec(struct sudo_lbuf *lbuf, struct sudoers_parse_tree *parse_tree, struct cmndspec *cs, struct cmndspec *prev_cs, struct cmndtag tags, bool expand_aliases);
|
||||||
|
766
plugins/sudoers/parse_ldif.c
Normal file
766
plugins/sudoers/parse_ldif.c
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#ifdef HAVE_STRING_H
|
||||||
|
# include <string.h>
|
||||||
|
#endif /* HAVE_STRING_H */
|
||||||
|
#ifdef HAVE_STRINGS_H
|
||||||
|
# include <strings.h>
|
||||||
|
#endif /* HAVE_STRINGS_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse an LDIF attribute, including base64 support.
|
||||||
|
* See http://www.faqs.org/rfcs/rfc2849.html
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
ldif_parse_attribute(char *str)
|
||||||
|
{
|
||||||
|
bool encoded = false;
|
||||||
|
char *attr, *ep;
|
||||||
|
size_t len;
|
||||||
|
debug_decl(ldif_parse_attribute, SUDOERS_DEBUG_UTIL)
|
||||||
|
|
||||||
|
/* Check for foo:: base64str. */
|
||||||
|
if (*str == ':') {
|
||||||
|
encoded = true;
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trim leading and trailing space. */
|
||||||
|
while (*str == ' ')
|
||||||
|
str++;
|
||||||
|
|
||||||
|
ep = str + strlen(str);
|
||||||
|
while (ep > str && ep[-1] == ' ') {
|
||||||
|
/* Don't trim ecaped trailing space if not base64. */
|
||||||
|
if (!encoded && ep != str && ep[-2] == '\\')
|
||||||
|
break;
|
||||||
|
*--ep = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
attr = str;
|
||||||
|
if (encoded) {
|
||||||
|
/*
|
||||||
|
* Decode base64 inline and add NUL-terminator.
|
||||||
|
* The copy allows us to provide a useful message on error.
|
||||||
|
*/
|
||||||
|
char *copy = strdup(str);
|
||||||
|
if (copy == NULL) {
|
||||||
|
sudo_fatalx(U_("%s: %s"), __func__,
|
||||||
|
U_("unable to allocate memory"));
|
||||||
|
}
|
||||||
|
len = base64_decode(copy, (unsigned char *)attr, strlen(attr));
|
||||||
|
if (len == (size_t)-1) {
|
||||||
|
sudo_warnx(U_("ignoring invalid attribute value: %s"), copy);
|
||||||
|
free(copy);
|
||||||
|
debug_return_str(NULL);
|
||||||
|
}
|
||||||
|
attr[len] = '\0';
|
||||||
|
free(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_str(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate a struct sudoers_string, store str in it and
|
||||||
|
* insert into the specified strlist.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* Insertion sort, list is small. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterator for sudo_ldap_role_to_priv().
|
||||||
|
* Takes a pointer to a struct sudoers_string *.
|
||||||
|
* Returns the string or NULL if we've reached the end.
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse list of sudoOption and store in the parse tree's defaults list.
|
||||||
|
*/
|
||||||
|
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 = strcmp(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:
|
||||||
|
/* new entry, take a ref for the cache */
|
||||||
|
strlist->refcnt++;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* already exists, use existing and take a ref. */
|
||||||
|
str_list_free(strlist);
|
||||||
|
strlist = node->data;
|
||||||
|
strlist->refcnt++;
|
||||||
|
*strlistp = strlist;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
debug_return_int(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a sudoRole to sudoers format and store in the parse tree.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: use cn to create a UserAlias if multiple users in it?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (reuse_userspec) {
|
||||||
|
/* Re-use the previous userspec */
|
||||||
|
us = TAILQ_LAST(&parse_tree->userspecs, userspec_list);
|
||||||
|
} else {
|
||||||
|
/* Allocate a new userspec and fill in the user list. */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add source role as a comment. */
|
||||||
|
if (role->cn != NULL) {
|
||||||
|
struct sudoers_comment *comment = NULL;
|
||||||
|
if (reuse_userspec) {
|
||||||
|
/* Try to re-use comment too. */
|
||||||
|
STAILQ_FOREACH(comment, &us->comments, entries) {
|
||||||
|
if (strncmp(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) {
|
||||||
|
/* Create a new comment. */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert role to sudoers privilege. */
|
||||||
|
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) {
|
||||||
|
/* Hostspec unchanged, append cmndlist to previous privilege. */
|
||||||
|
struct privilege *prev_priv = TAILQ_LAST(&us->privileges, privilege_list);
|
||||||
|
if (reuse_runas) {
|
||||||
|
/* Runas users and groups same if as in previous privilege. */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Free duplicate runas lists. */
|
||||||
|
if (cmndspec->runasuserlist != NULL)
|
||||||
|
free_members(cmndspec->runasuserlist);
|
||||||
|
if (cmndspec->runasgrouplist != NULL)
|
||||||
|
free_members(cmndspec->runasgrouplist);
|
||||||
|
|
||||||
|
/* Update cmndspec with previous runas lists. */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add finished userspec to the list if new. */
|
||||||
|
if (!reuse_userspec)
|
||||||
|
TAILQ_INSERT_TAIL(&parse_tree->userspecs, us, entries);
|
||||||
|
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the list of sudoRoles to sudoers format and store in the parse tree.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
|
||||||
|
/* Convert from list of roles to array and sort by order. */
|
||||||
|
role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array));
|
||||||
|
for (n = 0; n < numroles; n++) {
|
||||||
|
if ((role = STAILQ_FIRST(roles)) == NULL)
|
||||||
|
break; /* cannot happen */
|
||||||
|
STAILQ_REMOVE_HEAD(roles, entries);
|
||||||
|
role_array[n] = role;
|
||||||
|
}
|
||||||
|
role_array[n] = NULL;
|
||||||
|
qsort(role_array, numroles, sizeof(*role_array), role_order_cmp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterate over roles in sorted order, converting to sudoers.
|
||||||
|
*/
|
||||||
|
for (n = 0; n < numroles; n++) {
|
||||||
|
bool reuse_userspec = false;
|
||||||
|
bool reuse_privilege = false;
|
||||||
|
bool reuse_runas = false;
|
||||||
|
|
||||||
|
role = role_array[n];
|
||||||
|
|
||||||
|
/* Check whether we can reuse the previous user and host specs */
|
||||||
|
if (n > 0 && role->users == role_array[n - 1]->users) {
|
||||||
|
reuse_userspec = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since options are stored per-privilege we can't
|
||||||
|
* append to the previous privilege's cmndlist if
|
||||||
|
* we are storing options.
|
||||||
|
*/
|
||||||
|
if (!store_options) {
|
||||||
|
if (role->hosts == role_array[n - 1]->hosts) {
|
||||||
|
reuse_privilege = true;
|
||||||
|
|
||||||
|
/* Reuse runasusers and runasgroups if possible. */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clean up. */
|
||||||
|
for (n = 0; n < numroles; n++)
|
||||||
|
sudo_role_free(role_array[n]);
|
||||||
|
free(role_array);
|
||||||
|
|
||||||
|
debug_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a cn with possible quoted characters, return a copy of
|
||||||
|
* the cn with quote characters ('\\') removed.
|
||||||
|
* The caller is responsible for freeing the returned string.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse a sudoers file in LDIF format, https://tools.ietf.org/html/rfc2849
|
||||||
|
* Parsed sudoRole objects are stored in the specified parse_tree which
|
||||||
|
* must already be initialized.
|
||||||
|
*/
|
||||||
|
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, *line = NULL, *savedline = NULL;
|
||||||
|
ssize_t savedlen = 0;
|
||||||
|
bool mismatch = false;
|
||||||
|
debug_decl(sudoers_parse_ldif, SUDOERS_DEBUG_UTIL)
|
||||||
|
|
||||||
|
/* Free old contents of the parse tree (if any). */
|
||||||
|
free_parse_tree(parse_tree);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We cache user, group and host lists to make it eay to detect when there
|
||||||
|
* are identical lists (simple pointer compare). This makes it possible
|
||||||
|
* to merge multiplpe sudoRole objects into a single UserSpec and/or
|
||||||
|
* Privilege. The lists are sorted since LDAP order is arbitrary.
|
||||||
|
*/
|
||||||
|
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"));
|
||||||
|
|
||||||
|
/* Read through input, parsing into sudo_roles and global defaults. */
|
||||||
|
for (;;) {
|
||||||
|
int ch;
|
||||||
|
ssize_t len = getline(&line, &linesize, fp);
|
||||||
|
|
||||||
|
/* Trim trailing return or newline. */
|
||||||
|
while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
|
||||||
|
line[--len] = '\0';
|
||||||
|
|
||||||
|
/* Blank line or EOF terminates an entry. */
|
||||||
|
if (len <= 0) {
|
||||||
|
if (in_role) {
|
||||||
|
if (role->cn != NULL && strcmp(role->cn, "defaults") == 0) {
|
||||||
|
ldif_store_options(parse_tree, role->options);
|
||||||
|
sudo_role_free(role);
|
||||||
|
role = NULL;
|
||||||
|
} else if (STAILQ_EMPTY(role->users) ||
|
||||||
|
STAILQ_EMPTY(role->hosts) || STAILQ_EMPTY(role->cmnds)) {
|
||||||
|
/* Incomplete role. */
|
||||||
|
sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
|
||||||
|
role->cn ? role->cn : "UNKNOWN");
|
||||||
|
sudo_role_free(role);
|
||||||
|
role = NULL;
|
||||||
|
} else {
|
||||||
|
/* Cache users, hosts, runasusers and runasgroups. */
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store finished role. */
|
||||||
|
STAILQ_INSERT_TAIL(&roles, role, entries);
|
||||||
|
numroles++;
|
||||||
|
}
|
||||||
|
role = NULL;
|
||||||
|
in_role = false;
|
||||||
|
}
|
||||||
|
if (len == -1) {
|
||||||
|
/* EOF */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mismatch = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedline != NULL) {
|
||||||
|
char *tmp;
|
||||||
|
|
||||||
|
/* Append to saved line. */
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
/* Skip comment lines or records that don't match the base. */
|
||||||
|
if (*line == '#' || mismatch)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for folded line */
|
||||||
|
if ((ch = getc(fp)) == ' ') {
|
||||||
|
/* folded line, append to the saved portion. */
|
||||||
|
savedlen = len;
|
||||||
|
savedline = line;
|
||||||
|
line = NULL;
|
||||||
|
linesize = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
/* not folded, push back ch */
|
||||||
|
ungetc(ch, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate new role as needed. */
|
||||||
|
if (role == NULL) {
|
||||||
|
if ((role = sudo_role_alloc()) == NULL) {
|
||||||
|
sudo_fatalx(U_("%s: %s"), __func__,
|
||||||
|
U_("unable to allocate memory"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse dn and objectClass. */
|
||||||
|
if (strncasecmp(line, "dn:", 3) == 0) {
|
||||||
|
/* Compare dn to base, if specified. */
|
||||||
|
if (sudoers_base != NULL) {
|
||||||
|
attr = ldif_parse_attribute(line + 3);
|
||||||
|
if (attr == NULL) {
|
||||||
|
/* invalid attribute */
|
||||||
|
mismatch = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Skip over cn if present. */
|
||||||
|
if (strncasecmp(attr, "cn=", 3) == 0) {
|
||||||
|
for (attr += 3; *attr != '\0'; attr++) {
|
||||||
|
/* Handle escaped ',' chars. */
|
||||||
|
if (*attr == '\\')
|
||||||
|
attr++;
|
||||||
|
if (*attr == ',') {
|
||||||
|
attr++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (strcasecmp(attr, sudoers_base) != 0) {
|
||||||
|
/* Doesn't match base, skip the rest of it. */
|
||||||
|
mismatch = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strncmp(line, "objectClass:", 12) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 12);
|
||||||
|
if (attr != NULL && strcmp(attr, "sudoRole") == 0)
|
||||||
|
in_role = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not in a sudoRole, keep reading. */
|
||||||
|
if (!in_role)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Part of a sudoRole, parse it. */
|
||||||
|
if (strncmp(line, "cn:", 3) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 3);
|
||||||
|
if (attr != NULL) {
|
||||||
|
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 (strncmp(line, "sudoUser:", 9) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 9);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->users, true);
|
||||||
|
} else if (strncmp(line, "sudoHost:", 9) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 9);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->hosts, true);
|
||||||
|
} else if (strncmp(line, "sudoRunAs:", 10) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 10);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->runasusers, true);
|
||||||
|
} else if (strncmp(line, "sudoRunAsUser:", 14) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 14);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->runasusers, true);
|
||||||
|
} else if (strncmp(line, "sudoRunAsGroup:", 15) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 15);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->runasgroups, true);
|
||||||
|
} else if (strncmp(line, "sudoCommand:", 12) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 12);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->cmnds, false);
|
||||||
|
} else if (strncmp(line, "sudoOption:", 11) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 11);
|
||||||
|
if (attr != NULL)
|
||||||
|
ldif_store_string(attr, role->options, false);
|
||||||
|
} else if (strncmp(line, "sudoOrder:", 10) == 0) {
|
||||||
|
char *ep;
|
||||||
|
attr = ldif_parse_attribute(line + 10);
|
||||||
|
if (attr != NULL) {
|
||||||
|
role->order = strtod(attr, &ep);
|
||||||
|
if (ep == attr || *ep != '\0')
|
||||||
|
sudo_warnx(U_("invalid sudoOrder attribute: %s"), attr);
|
||||||
|
}
|
||||||
|
} else if (strncmp(line, "sudoNotBefore:", 14) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 14);
|
||||||
|
if (attr != NULL) {
|
||||||
|
free(role->notbefore);
|
||||||
|
role->notbefore = strdup(attr);
|
||||||
|
if (role->notbefore == NULL) {
|
||||||
|
sudo_fatalx(U_("%s: %s"), __func__,
|
||||||
|
U_("unable to allocate memory"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strncmp(line, "sudoNotAfter:", 13) == 0) {
|
||||||
|
attr = ldif_parse_attribute(line + 13);
|
||||||
|
if (attr != NULL) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Convert from roles to sudoers data structures. */
|
||||||
|
ldif_to_sudoers(parse_tree, &roles, numroles, store_options);
|
||||||
|
|
||||||
|
/* Clean up. */
|
||||||
|
rbdestroy(usercache, str_list_free);
|
||||||
|
rbdestroy(groupcache, str_list_free);
|
||||||
|
rbdestroy(hostcache, str_list_free);
|
||||||
|
|
||||||
|
if (fp != stdin)
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
debug_return_bool(true);
|
||||||
|
}
|
Reference in New Issue
Block a user