summaryrefslogtreecommitdiffstats
path: root/plugins/sudoers/parse_ldif.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:37:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:37:38 +0000
commitae581a19fbe896a797450b9d9573fb66f2735227 (patch)
tree56c40be8518a29c9351364d13a9676aa83932dc0 /plugins/sudoers/parse_ldif.c
parentInitial commit. (diff)
downloadsudo-ae581a19fbe896a797450b9d9573fb66f2735227.tar.xz
sudo-ae581a19fbe896a797450b9d9573fb66f2735227.zip
Adding upstream version 1.9.13p3.upstream/1.9.13p3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/sudoers/parse_ldif.c')
-rw-r--r--plugins/sudoers/parse_ldif.c781
1 files changed, 781 insertions, 0 deletions
diff --git a/plugins/sudoers/parse_ldif.c b/plugins/sudoers/parse_ldif.c
new file mode 100644
index 0000000..2b71092
--- /dev/null
+++ b/plugins/sudoers/parse_ldif.c
@@ -0,0 +1,781 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2018-2020 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#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);
+}
+
+/*
+ * Parse an LDIF line, filling in attribute name and value.
+ * Modifies line, decodes base64 attribute values if present.
+ * See http://www.faqs.org/rfcs/rfc2849.html
+ */
+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);
+
+ /* Parse attribute name: [a-zA-Z][a-zA-Z0-9-]*: */
+ 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++;
+
+ /* Check for foo:: base64str. */
+ if (*cp == ':') {
+ encoded = true;
+ cp++;
+ }
+
+ /* Trim leading and trailing space. */
+ while (*cp == ' ')
+ cp++;
+
+ ep = cp + strlen(cp);
+ while (ep > cp && ep[-1] == ' ') {
+ ep--;
+ /* Don't trim escaped trailing space if not base64. */
+ if (!encoded && ep != cp && ep[-1] == '\\')
+ break;
+ *ep = '\0';
+ }
+
+ attr = cp;
+ if (encoded) {
+ /*
+ * Decode base64 inline and add NUL-terminator.
+ * The copy allows us to provide a useful message on error.
+ */
+ 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);
+}
+
+/*
+ * 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->members);
+ d->binding->refcnt = 1;
+ 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:
+ /* 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);
+ switch (*user) {
+ case '\0':
+ /* Empty RunAsUser means run as the invoking user. */
+ m->type = MYSELF;
+ break;
+ case '+':
+ m->type = NETGROUP;
+ break;
+ case '%':
+ m->type = USERGROUP;
+ break;
+ case 'A':
+ if (strcmp(user, "ALL") == 0) {
+ m->type = ALL;
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ m->type = WORD;
+ break;
+ }
+ if (m->type != ALL && m->type != MYSELF) {
+ if ((m->name = strdup(user)) == NULL) {
+ sudo_fatalx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ }
+ }
+ 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 (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) {
+ /* 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 cmndspec *cmndspec = TAILQ_FIRST(&priv->cmndlist);
+ const struct cmndspec *prev_cmndspec =
+ TAILQ_LAST(&prev_priv->cmndlist, cmndspec_list);
+ struct member_list *runasuserlist = prev_cmndspec->runasuserlist;
+ struct member_list *runasgrouplist = prev_cmndspec->runasgrouplist;
+
+ /* Free duplicate runas lists. */
+ if (cmndspec->runasuserlist != NULL) {
+ free_members(cmndspec->runasuserlist);
+ free(cmndspec->runasuserlist);
+ }
+ if (cmndspec->runasgrouplist != NULL) {
+ free_members(cmndspec->runasgrouplist);
+ free(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));
+ if (role_array == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ 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, role = NULL; n < numroles; n++) {
+ bool reuse_userspec = false;
+ bool reuse_privilege = false;
+ bool reuse_runas = false;
+ struct sudo_role *prev_role = role;
+
+ role = role_array[n];
+
+ /* Check whether we can reuse the previous user and host specs */
+ if (prev_role != NULL && role->users == prev_role->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 == prev_role->hosts) {
+ reuse_privilege = true;
+
+ /* Reuse runasusers and runasgroups if possible. */
+ if (role->runasusers == prev_role->runasusers &&
+ role->runasgroups == prev_role->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, *name, *line = NULL, *savedline = NULL;
+ ssize_t savedlen = 0;
+ bool mismatch = false;
+ int errors = 0;
+ 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 = getdelim(&line, &linesize, '\n', 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 && 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)) {
+ /* Incomplete role. */
+ sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
+ role->cn ? role->cn : "UNKNOWN");
+ sudo_role_free(role);
+ } 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;
+ }
+
+ /* Check for folded line */
+ if ((ch = getc(fp)) == ' ') {
+ /* folded line, append to the saved portion. */
+ savedlen = len;
+ savedline = line;
+ line = NULL;
+ linesize = 0;
+ continue;
+ }
+ ungetc(ch, fp); /* not folded, push back ch */
+
+ /* Skip comment lines or records that don't match the base. */
+ if (*line == '#' || mismatch)
+ continue;
+
+ /* Reject invalid LDIF. */
+ if (!ldif_parse_attribute(line, &name, &attr)) {
+ sudo_warnx(U_("invalid LDIF attribute: %s"), line);
+ errors++;
+ continue;
+ }
+
+ /* Parse dn and objectClass. */
+ if (strcasecmp(name, "dn") == 0) {
+ /* Compare dn to base, if specified. */
+ if (sudoers_base != NULL) {
+ /* Skip over cn if present. */
+ if (strncasecmp(attr, "cn=", 3) == 0) {
+ for (attr += 3; *attr != '\0'; attr++) {
+ /* Handle escaped ',' chars. */
+ if (*attr == '\\' && attr[1] != '\0')
+ 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 (strcasecmp(name, "objectClass") == 0) {
+ if (strcasecmp(attr, "sudoRole") == 0) {
+ /* 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"));
+ }
+ }
+ in_role = true;
+ }
+ }
+
+ /* Not in a sudoRole, keep reading. */
+ if (!in_role)
+ continue;
+
+ /* Part of a sudoRole, parse it. */
+ 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);
+ free(savedline);
+
+ /* Convert from roles to sudoers data structures. */
+ if (numroles > 0)
+ 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);
+
+ debug_return_bool(errors == 0);
+}