From: Todd C. Miller Date: Mon, 27 Aug 2018 02:02:49 +0000 (-0600) Subject: Move ldif -> sudoers conversion code into parse_ldif.c X-Git-Tag: SUDO_1_8_25^2~21 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4782b22a479cee2338fe5c3b0747ce714e775e44;p=sudo Move ldif -> sudoers conversion code into parse_ldif.c --- diff --git a/MANIFEST b/MANIFEST index 105030924..6f1cc104c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -332,6 +332,7 @@ plugins/sudoers/mkdefaults plugins/sudoers/mkdir_parents.c plugins/sudoers/parse.c plugins/sudoers/parse.h +plugins/sudoers/parse_ldif.c plugins/sudoers/po/README plugins/sudoers/po/ca.mo plugins/sudoers/po/ca.po diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 9a2e1e184..e23ccb3b8 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -164,7 +164,7 @@ VISUDO_OBJS = editor.lo find_path.lo goodpath.lo locale.lo stubs.o \ sudo_printf.o visudo.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 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 \ $(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 +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 \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ diff --git a/plugins/sudoers/cvtsudoers.c b/plugins/sudoers/cvtsudoers.c index 3adf52153..0a320496f 100644 --- a/plugins/sudoers/cvtsudoers.c +++ b/plugins/sudoers/cvtsudoers.c @@ -80,6 +80,7 @@ static void help(void) __attribute__((__noreturn__)); 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 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 struct cvtsudoers_config *cvtsudoers_conf_read(const char *conf_file); static void cvtsudoers_conf_free(struct cvtsudoers_config *conf); @@ -581,6 +582,24 @@ cvtsudoers_parse_filter(char *expression) 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 parse_sudoers(const char *input_file, struct cvtsudoers_config *conf) { diff --git a/plugins/sudoers/cvtsudoers.h b/plugins/sudoers/cvtsudoers.h index 8d6d48407..562da5c96 100644 --- a/plugins/sudoers/cvtsudoers.h +++ b/plugins/sudoers/cvtsudoers.h @@ -83,8 +83,6 @@ bool convert_sudoers_json(struct sudoers_parse_tree *parse_tree, const char *out /* cvtsudoers_ldif.c */ 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 */ 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_grlist_item(const struct passwd *pw, char * const *unused1); +/* stubs.c */ +void get_hostname(void); + #endif /* SUDOERS_CVTSUDOERS_H */ diff --git a/plugins/sudoers/cvtsudoers_ldif.c b/plugins/sudoers/cvtsudoers_ldif.c index 2d5d2edae..75064467e 100644 --- a/plugins/sudoers/cvtsudoers_ldif.c +++ b/plugins/sudoers/cvtsudoers_ldif.c @@ -654,746 +654,3 @@ convert_sudoers_ldif(struct sudoers_parse_tree *parse_tree, 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); -} diff --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h index b8b2497ad..426144c53 100644 --- a/plugins/sudoers/parse.h +++ b/plugins/sudoers/parse.h @@ -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_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 */ 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); diff --git a/plugins/sudoers/parse_ldif.c b/plugins/sudoers/parse_ldif.c new file mode 100644 index 000000000..0b07b7c4c --- /dev/null +++ b/plugins/sudoers/parse_ldif.c @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2018 Todd C. Miller + * + * 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 + +#include +#include +#include +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ + +#include "sudoers.h" +#include "sudo_ldap.h" +#include "redblack.h" +#include "strlist.h" +#include + +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); +}