]> granicus.if.org Git - sudo/blob - plugins/sudoers/parse_ldif.c
df8c610fb4c7434ef21fbdfd797f26f11e21e6d3
[sudo] / plugins / sudoers / parse_ldif.c
1 /*
2  * Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 /*
18  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
19  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
20  */
21
22 #include <config.h>
23
24 #include <sys/types.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #ifdef HAVE_STRING_H
28 # include <string.h>
29 #endif /* HAVE_STRING_H */
30 #ifdef HAVE_STRINGS_H
31 # include <strings.h>
32 #endif /* HAVE_STRINGS_H */
33
34 #include "sudoers.h"
35 #include "sudo_ldap.h"
36 #include "redblack.h"
37 #include "strlist.h"
38 #include <gram.h>
39
40 struct sudo_role {
41     STAILQ_ENTRY(sudo_role) entries;
42     char *cn;
43     char *notbefore;
44     char *notafter;
45     double order;
46     struct sudoers_str_list *cmnds;
47     struct sudoers_str_list *hosts;
48     struct sudoers_str_list *users;
49     struct sudoers_str_list *runasusers;
50     struct sudoers_str_list *runasgroups;
51     struct sudoers_str_list *options;
52 };
53 STAILQ_HEAD(sudo_role_list, sudo_role);
54
55 static void
56 sudo_role_free(struct sudo_role *role)
57 {
58     debug_decl(sudo_role_free, SUDOERS_DEBUG_UTIL)
59
60     if (role != NULL) {
61         free(role->cn);
62         free(role->notbefore);
63         free(role->notafter);
64         str_list_free(role->cmnds);
65         str_list_free(role->hosts);
66         str_list_free(role->users);
67         str_list_free(role->runasusers);
68         str_list_free(role->runasgroups);
69         str_list_free(role->options);
70         free(role);
71     }
72
73     debug_return;
74 }
75
76 static struct sudo_role *
77 sudo_role_alloc(void)
78 {
79     struct sudo_role *role;
80     debug_decl(sudo_role_alloc, SUDOERS_DEBUG_UTIL)
81
82     role = calloc(1, sizeof(*role));
83     if (role != NULL) {
84         role->cmnds = str_list_alloc();
85         role->hosts = str_list_alloc();
86         role->users = str_list_alloc();
87         role->runasusers = str_list_alloc();
88         role->runasgroups = str_list_alloc();
89         role->options = str_list_alloc();
90         if (role->cmnds == NULL || role->hosts == NULL ||
91             role->users == NULL || role->runasusers == NULL ||
92             role->runasgroups == NULL || role->options == NULL) {
93             sudo_role_free(role);
94             role = NULL;
95         }
96     }
97
98     debug_return_ptr(role);
99 }
100
101 /*
102  * Parse an LDIF attribute, including base64 support.
103  * See http://www.faqs.org/rfcs/rfc2849.html
104  */
105 static char *
106 ldif_parse_attribute(char *str)
107 {
108     bool encoded = false;
109     char *attr, *ep;
110     size_t len;
111     debug_decl(ldif_parse_attribute, SUDOERS_DEBUG_UTIL)
112
113     /* Check for foo:: base64str. */
114     if (*str == ':') {
115         encoded = true;
116         str++;
117     }
118
119     /* Trim leading and trailing space. */
120     while (*str == ' ')
121         str++;
122
123     ep = str + strlen(str);
124     while (ep > str && ep[-1] == ' ') {
125         ep--;
126         /* Don't trim escaped trailing space if not base64. */
127         if (!encoded && ep != str && ep[-1] == '\\')
128             break;
129         *ep = '\0';
130     }
131
132     attr = str;
133     if (encoded) {
134         /*
135          * Decode base64 inline and add NUL-terminator.
136          * The copy allows us to provide a useful message on error.
137          */
138         char *copy = strdup(str);
139         if (copy == NULL) {
140             sudo_fatalx(U_("%s: %s"), __func__,
141                 U_("unable to allocate memory"));
142         }
143         len = base64_decode(copy, (unsigned char *)attr, strlen(attr));
144         if (len == (size_t)-1) {
145             sudo_warnx(U_("ignoring invalid attribute value: %s"), copy);
146             free(copy);
147             debug_return_str(NULL);
148         }
149         attr[len] = '\0';
150         free(copy);
151     }
152
153     debug_return_str(attr);
154 }
155
156 /*
157  * Allocate a struct sudoers_string, store str in it and
158  * insert into the specified strlist.
159  */
160 static void
161 ldif_store_string(const char *str, struct sudoers_str_list *strlist, bool sorted)
162 {
163     struct sudoers_string *ls;
164     debug_decl(ldif_store_string, SUDOERS_DEBUG_UTIL)
165
166     if ((ls = sudoers_string_alloc(str)) == NULL) {
167         sudo_fatalx(U_("%s: %s"), __func__,
168             U_("unable to allocate memory"));
169     }
170     if (!sorted) {
171         STAILQ_INSERT_TAIL(strlist, ls, entries);
172     } else {
173         struct sudoers_string *prev, *next;
174
175         /* Insertion sort, list is small. */
176         prev = STAILQ_FIRST(strlist);
177         if (prev == NULL || strcasecmp(str, prev->str) <= 0) {
178             STAILQ_INSERT_HEAD(strlist, ls, entries);
179         } else {
180             while ((next = STAILQ_NEXT(prev, entries)) != NULL) {
181                 if (strcasecmp(str, next->str) <= 0)
182                     break;
183                 prev = next;
184             }
185             STAILQ_INSERT_AFTER(strlist, prev, ls, entries);
186         }
187     }
188
189     debug_return;
190 }
191
192 /*
193  * Iterator for sudo_ldap_role_to_priv().
194  * Takes a pointer to a struct sudoers_string *.
195  * Returns the string or NULL if we've reached the end.
196  */
197 static char *
198 sudoers_string_iter(void **vp)
199 {
200     struct sudoers_string *ls = *vp;
201
202     if (ls == NULL)
203         return NULL;
204
205     *vp = STAILQ_NEXT(ls, entries);
206
207     return ls->str;
208 }
209
210 static int
211 role_order_cmp(const void *va, const void *vb)
212 {
213     const struct sudo_role *a = *(const struct sudo_role **)va;
214     const struct sudo_role *b = *(const struct sudo_role **)vb;
215     debug_decl(role_order_cmp, SUDOERS_DEBUG_LDAP)
216
217     debug_return_int(a->order < b->order ? -1 :
218         (a->order > b->order ? 1 : 0));
219 }
220
221 /*
222  * Parse list of sudoOption and store in the parse tree's defaults list.
223  */
224 static void
225 ldif_store_options(struct sudoers_parse_tree *parse_tree,
226     struct sudoers_str_list *options)
227 {
228     struct defaults *d;
229     struct sudoers_string *ls;
230     char *var, *val;
231     debug_decl(ldif_store_options, SUDOERS_DEBUG_UTIL)
232
233     STAILQ_FOREACH(ls, options, entries) {
234         if ((d = calloc(1, sizeof(*d))) == NULL ||
235             (d->binding = malloc(sizeof(*d->binding))) == NULL) {
236             sudo_fatalx(U_("%s: %s"), __func__,
237                 U_("unable to allocate memory"));
238         }
239         TAILQ_INIT(d->binding);
240         d->type = DEFAULTS;
241         d->op = sudo_ldap_parse_option(ls->str, &var, &val);
242         if ((d->var = strdup(var)) == NULL) {
243             sudo_fatalx(U_("%s: %s"), __func__,
244                 U_("unable to allocate memory"));
245         }
246         if (val != NULL) {
247             if ((d->val = strdup(val)) == NULL) {
248                 sudo_fatalx(U_("%s: %s"), __func__,
249                     U_("unable to allocate memory"));
250             }
251         }
252         TAILQ_INSERT_TAIL(&parse_tree->defaults, d, entries);
253     }
254     debug_return;
255 }
256
257 static int
258 str_list_cmp(const void *aa, const void *bb)
259 {
260     const struct sudoers_str_list *a = aa;
261     const struct sudoers_str_list *b = bb;
262     const struct sudoers_string *lsa = STAILQ_FIRST(a);
263     const struct sudoers_string *lsb = STAILQ_FIRST(b);
264     int ret;
265
266     while (lsa != NULL && lsb != NULL) {
267         if ((ret = strcmp(lsa->str, lsb->str)) != 0)
268             return ret;
269         lsa = STAILQ_NEXT(lsa, entries);
270         lsb = STAILQ_NEXT(lsb, entries);
271     }
272     return lsa == lsb ? 0 : (lsa == NULL ? -1 : 1);
273 }
274
275 static int
276 str_list_cache(struct rbtree *cache, struct sudoers_str_list **strlistp)
277 {
278     struct sudoers_str_list *strlist = *strlistp;
279     struct rbnode *node;
280     int ret;
281     debug_decl(str_list_cache, SUDOERS_DEBUG_UTIL)
282
283     ret = rbinsert(cache, strlist, &node);
284     switch (ret) {
285     case 0:
286         /* new entry, take a ref for the cache */
287         strlist->refcnt++;
288         break;
289     case 1:
290         /* already exists, use existing and take a ref. */
291         str_list_free(strlist);
292         strlist = node->data;
293         strlist->refcnt++;
294         *strlistp = strlist;
295         break;
296     }
297     debug_return_int(ret);
298 }
299
300 /*
301  * Convert a sudoRole to sudoers format and store in the parse tree.
302  */
303 static void
304 role_to_sudoers(struct sudoers_parse_tree *parse_tree, struct sudo_role *role,
305     bool store_options, bool reuse_userspec, bool reuse_privilege,
306     bool reuse_runas)
307 {
308     struct privilege *priv;
309     struct sudoers_string *ls;
310     struct userspec *us;
311     struct member *m;
312     debug_decl(role_to_sudoers, SUDOERS_DEBUG_UTIL)
313
314     /*
315      * TODO: use cn to create a UserAlias if multiple users in it?
316      */
317
318     if (reuse_userspec) {
319         /* Re-use the previous userspec */
320         us = TAILQ_LAST(&parse_tree->userspecs, userspec_list);
321     } else {
322         /* Allocate a new userspec and fill in the user list. */
323         if ((us = calloc(1, sizeof(*us))) == NULL) {
324             sudo_fatalx(U_("%s: %s"), __func__,
325                 U_("unable to allocate memory"));
326         }
327         TAILQ_INIT(&us->privileges);
328         TAILQ_INIT(&us->users);
329         STAILQ_INIT(&us->comments);
330
331         STAILQ_FOREACH(ls, role->users, entries) {
332             char *user = ls->str;
333
334             if ((m = calloc(1, sizeof(*m))) == NULL) {
335                 sudo_fatalx(U_("%s: %s"), __func__,
336                     U_("unable to allocate memory"));
337             }
338             m->negated = sudo_ldap_is_negated(&user);
339             m->name = strdup(user);
340             if (m->name == NULL) {
341                 sudo_fatalx(U_("%s: %s"), __func__,
342                     U_("unable to allocate memory"));
343             }
344             if (strcmp(user, "ALL") == 0) {
345                 m->type = ALL;
346             } else if (*user == '+') {
347                 m->type = NETGROUP;
348             } else if (*user == '%') {
349                 m->type = USERGROUP;
350             } else {
351                 m->type = WORD;
352             }
353             TAILQ_INSERT_TAIL(&us->users, m, entries);
354         }
355     }
356
357     /* Add source role as a comment. */
358     if (role->cn != NULL) {
359         struct sudoers_comment *comment = NULL;
360         if (reuse_userspec) {
361             /* Try to re-use comment too. */
362             STAILQ_FOREACH(comment, &us->comments, entries) {
363                 if (strncmp(comment->str, "sudoRole ", 9) == 0) {
364                     char *tmpstr;
365                     if (asprintf(&tmpstr, "%s, %s", comment->str, role->cn) == -1) {
366                         sudo_fatalx(U_("%s: %s"), __func__,
367                             U_("unable to allocate memory"));
368                     }
369                     free(comment->str);
370                     comment->str = tmpstr;
371                     break;
372                 }
373             }
374         }
375         if (comment == NULL) {
376             /* Create a new comment. */
377             if ((comment = malloc(sizeof(*comment))) == NULL) {
378                 sudo_fatalx(U_("%s: %s"), __func__,
379                     U_("unable to allocate memory"));
380             }
381             if (asprintf(&comment->str, "sudoRole %s", role->cn) == -1) {
382                 sudo_fatalx(U_("%s: %s"), __func__,
383                     U_("unable to allocate memory"));
384             }
385             STAILQ_INSERT_TAIL(&us->comments, comment, entries);
386         }
387     }
388
389     /* Convert role to sudoers privilege. */
390     priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts),
391         STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups),
392         STAILQ_FIRST(role->cmnds), STAILQ_FIRST(role->options),
393         role->notbefore, role->notafter, true, store_options,
394         sudoers_string_iter);
395     if (priv == NULL) {
396         sudo_fatalx(U_("%s: %s"), __func__,
397             U_("unable to allocate memory"));
398     }
399
400     if (reuse_privilege) {
401         /* Hostspec unchanged, append cmndlist to previous privilege. */
402         struct privilege *prev_priv = TAILQ_LAST(&us->privileges, privilege_list);
403         if (reuse_runas) {
404             /* Runas users and groups same if as in previous privilege. */
405             struct member_list *runasuserlist =
406                 TAILQ_FIRST(&prev_priv->cmndlist)->runasuserlist;
407             struct member_list *runasgrouplist =
408                 TAILQ_FIRST(&prev_priv->cmndlist)->runasgrouplist;
409             struct cmndspec *cmndspec = TAILQ_FIRST(&priv->cmndlist);
410
411             /* Free duplicate runas lists. */
412             if (cmndspec->runasuserlist != NULL) {
413                 free_members(cmndspec->runasuserlist);
414                 free(cmndspec->runasuserlist);
415             }
416             if (cmndspec->runasgrouplist != NULL) {
417                 free_members(cmndspec->runasgrouplist);
418                 free(cmndspec->runasgrouplist);
419             }
420
421             /* Update cmndspec with previous runas lists. */
422             TAILQ_FOREACH(cmndspec, &priv->cmndlist, entries) {
423                 cmndspec->runasuserlist = runasuserlist;
424                 cmndspec->runasgrouplist = runasgrouplist;
425             }
426         }
427         TAILQ_CONCAT(&prev_priv->cmndlist, &priv->cmndlist, entries);
428         free_privilege(priv);
429     } else {
430         TAILQ_INSERT_TAIL(&us->privileges, priv, entries);
431     }
432
433     /* Add finished userspec to the list if new. */
434     if (!reuse_userspec)
435         TAILQ_INSERT_TAIL(&parse_tree->userspecs, us, entries);
436
437     debug_return;
438 }
439
440 /*
441  * Convert the list of sudoRoles to sudoers format and store in the parse tree.
442  */
443 static void
444 ldif_to_sudoers(struct sudoers_parse_tree *parse_tree,
445     struct sudo_role_list *roles, unsigned int numroles, bool store_options)
446 {
447     struct sudo_role **role_array, *role = NULL;
448     unsigned int n;
449     debug_decl(ldif_to_sudoers, SUDOERS_DEBUG_UTIL)
450
451     /* Convert from list of roles to array and sort by order. */
452     role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array));
453     for (n = 0; n < numroles; n++) {
454         if ((role = STAILQ_FIRST(roles)) == NULL)
455             break;      /* cannot happen */
456         STAILQ_REMOVE_HEAD(roles, entries);
457         role_array[n] = role;
458     }
459     role_array[n] = NULL;
460     qsort(role_array, numroles, sizeof(*role_array), role_order_cmp);
461
462     /*
463      * Iterate over roles in sorted order, converting to sudoers.
464      */
465     for (n = 0; n < numroles; n++) {
466         bool reuse_userspec = false;
467         bool reuse_privilege = false;
468         bool reuse_runas = false;
469
470         role = role_array[n];
471
472         /* Check whether we can reuse the previous user and host specs */
473         if (n > 0 && role->users == role_array[n - 1]->users) {
474             reuse_userspec = true;
475
476             /*
477              * Since options are stored per-privilege we can't
478              * append to the previous privilege's cmndlist if
479              * we are storing options.
480              */
481             if (!store_options) {
482                 if (role->hosts == role_array[n - 1]->hosts) {
483                     reuse_privilege = true;
484
485                     /* Reuse runasusers and runasgroups if possible. */
486                     if (role->runasusers == role_array[n - 1]->runasusers &&
487                         role->runasgroups == role_array[n - 1]->runasgroups)
488                         reuse_runas = true;
489                 }
490             }
491         }
492
493         role_to_sudoers(parse_tree, role, store_options, reuse_userspec,
494             reuse_privilege, reuse_runas);
495     }
496
497     /* Clean up. */
498     for (n = 0; n < numroles; n++)
499         sudo_role_free(role_array[n]);
500     free(role_array);
501
502     debug_return;
503 }
504
505 /*
506  * Given a cn with possible quoted characters, return a copy of
507  * the cn with quote characters ('\\') removed.
508  * The caller is responsible for freeing the returned string.
509  */
510 static
511 char *unquote_cn(const char *src)
512 {
513     char *dst, *new_cn;
514     size_t len;
515     debug_decl(unquote_cn, SUDOERS_DEBUG_UTIL)
516
517     len = strlen(src);
518     if ((new_cn = malloc(len + 1)) == NULL)
519         debug_return_str(NULL);
520
521     for (dst = new_cn; *src != '\0';) {
522         if (src[0] == '\\' && src[1] != '\0')
523             src++;
524         *dst++ = *src++;
525     }
526     *dst = '\0';
527
528     debug_return_str(new_cn);
529 }
530
531 /*
532  * Parse a sudoers file in LDIF format, https://tools.ietf.org/html/rfc2849
533  * Parsed sudoRole objects are stored in the specified parse_tree which
534  * must already be initialized.
535  */
536 bool
537 sudoers_parse_ldif(struct sudoers_parse_tree *parse_tree,
538     FILE *fp, const char *sudoers_base, bool store_options)
539 {
540     struct sudo_role_list roles = STAILQ_HEAD_INITIALIZER(roles);
541     struct sudo_role *role = NULL;
542     struct rbtree *usercache, *groupcache, *hostcache;
543     unsigned numroles = 0;
544     bool in_role = false;
545     size_t linesize = 0;
546     char *attr, *line = NULL, *savedline = NULL;
547     ssize_t savedlen = 0;
548     bool mismatch = false;
549     debug_decl(sudoers_parse_ldif, SUDOERS_DEBUG_UTIL)
550
551     /* Free old contents of the parse tree (if any). */
552     free_parse_tree(parse_tree);
553
554     /*
555      * We cache user, group and host lists to make it eay to detect when there
556      * are identical lists (simple pointer compare).  This makes it possible
557      * to merge multiplpe sudoRole objects into a single UserSpec and/or
558      * Privilege.  The lists are sorted since LDAP order is arbitrary.
559      */
560     usercache = rbcreate(str_list_cmp);
561     groupcache = rbcreate(str_list_cmp);
562     hostcache = rbcreate(str_list_cmp);
563     if (usercache == NULL || groupcache == NULL || hostcache == NULL)
564         sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
565
566     /* Read through input, parsing into sudo_roles and global defaults. */
567     for (;;) {
568         int ch;
569         ssize_t len = getdelim(&line, &linesize, '\n', fp);
570
571         /* Trim trailing return or newline. */
572         while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
573             line[--len] = '\0';
574
575         /* Blank line or EOF terminates an entry. */
576         if (len <= 0) {
577             if (in_role) {
578                 if (role->cn != NULL && strcmp(role->cn, "defaults") == 0) {
579                     ldif_store_options(parse_tree, role->options);
580                     sudo_role_free(role);
581                 } else if (STAILQ_EMPTY(role->users) ||
582                     STAILQ_EMPTY(role->hosts) || STAILQ_EMPTY(role->cmnds)) {
583                     /* Incomplete role. */
584                     sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
585                         role->cn ? role->cn : "UNKNOWN");
586                     sudo_role_free(role);
587                 } else {
588                     /* Cache users, hosts, runasusers and runasgroups. */
589                     if (str_list_cache(usercache, &role->users) == -1 ||
590                         str_list_cache(hostcache, &role->hosts) == -1 ||
591                         str_list_cache(usercache, &role->runasusers) == -1 ||
592                         str_list_cache(groupcache, &role->runasgroups) == -1) {
593                         sudo_fatalx(U_("%s: %s"), __func__,
594                             U_("unable to allocate memory"));
595                     }
596
597                     /* Store finished role. */
598                     STAILQ_INSERT_TAIL(&roles, role, entries);
599                     numroles++;
600                 }
601                 role = NULL;
602                 in_role = false;
603             }
604             if (len == -1) {
605                 /* EOF */
606                 break;
607             }
608             mismatch = false;
609             continue;
610         }
611
612         if (savedline != NULL) {
613             char *tmp;
614
615             /* Append to saved line. */
616             linesize = savedlen + len + 1;
617             if ((tmp = realloc(savedline, linesize)) == NULL) {
618                 sudo_fatalx(U_("%s: %s"), __func__,
619                     U_("unable to allocate memory"));
620             }
621             memcpy(tmp + savedlen, line, len + 1);
622             free(line);
623             line = tmp;
624             savedline = NULL;
625         } else {
626             /* Skip comment lines or records that don't match the base. */
627             if (*line == '#' || mismatch)
628                 continue;
629         }
630
631         /* Check for folded line */
632         if ((ch = getc(fp)) == ' ') {
633             /* folded line, append to the saved portion. */
634             savedlen = len;
635             savedline = line;
636             line = NULL;
637             linesize = 0;
638             continue;
639         } else {
640             /* not folded, push back ch */
641             ungetc(ch, fp);
642         }
643
644         /* Parse dn and objectClass. */
645         if (strncasecmp(line, "dn:", 3) == 0) {
646             /* Compare dn to base, if specified. */
647             if (sudoers_base != NULL) {
648                 attr = ldif_parse_attribute(line + 3);
649                 if (attr == NULL) {
650                     /* invalid attribute */
651                     mismatch = true;
652                     continue;
653                 }
654                 /* Skip over cn if present. */
655                 if (strncasecmp(attr, "cn=", 3) == 0) {
656                     for (attr += 3; *attr != '\0'; attr++) {
657                         /* Handle escaped ',' chars. */
658                         if (*attr == '\\')
659                             attr++;
660                         if (*attr == ',') {
661                             attr++;
662                             break;
663                         }
664                     }
665                 }
666                 if (strcasecmp(attr, sudoers_base) != 0) {
667                     /* Doesn't match base, skip the rest of it. */
668                     mismatch = true;
669                     continue;
670                 }
671             }
672         } else if (strncmp(line, "objectClass:", 12) == 0) {
673             attr = ldif_parse_attribute(line + 12);
674             if (attr != NULL && strcmp(attr, "sudoRole") == 0) {
675                 /* Allocate new role as needed. */
676                 if (role == NULL) {
677                     if ((role = sudo_role_alloc()) == NULL) {
678                         sudo_fatalx(U_("%s: %s"), __func__,
679                             U_("unable to allocate memory"));
680                     }
681                 }
682                 in_role = true;
683             }
684         }
685
686         /* Not in a sudoRole, keep reading. */
687         if (!in_role)
688             continue;
689
690         /* Part of a sudoRole, parse it. */
691         if (strncmp(line, "cn:", 3) == 0) {
692             attr = ldif_parse_attribute(line + 3);
693             if (attr != NULL) {
694                 free(role->cn);
695                 role->cn = unquote_cn(attr);
696                 if (role->cn == NULL) {
697                     sudo_fatalx(U_("%s: %s"), __func__,
698                         U_("unable to allocate memory"));
699                 }
700             }
701         } else if (strncmp(line, "sudoUser:", 9) == 0) {
702             attr = ldif_parse_attribute(line + 9);
703             if (attr != NULL)
704                 ldif_store_string(attr, role->users, true);
705         } else if (strncmp(line, "sudoHost:", 9) == 0) {
706             attr = ldif_parse_attribute(line + 9);
707             if (attr != NULL)
708                 ldif_store_string(attr, role->hosts, true);
709         } else if (strncmp(line, "sudoRunAs:", 10) == 0) {
710             attr = ldif_parse_attribute(line + 10);
711             if (attr != NULL)
712                 ldif_store_string(attr, role->runasusers, true);
713         } else if (strncmp(line, "sudoRunAsUser:", 14) == 0) {
714             attr = ldif_parse_attribute(line + 14);
715             if (attr != NULL)
716                 ldif_store_string(attr, role->runasusers, true);
717         } else if (strncmp(line, "sudoRunAsGroup:", 15) == 0) {
718             attr = ldif_parse_attribute(line + 15);
719             if (attr != NULL)
720                 ldif_store_string(attr, role->runasgroups, true);
721         } else if (strncmp(line, "sudoCommand:", 12) == 0) {
722             attr = ldif_parse_attribute(line + 12);
723             if (attr != NULL)
724                 ldif_store_string(attr, role->cmnds, false);
725         } else if (strncmp(line, "sudoOption:", 11) == 0) {
726             attr = ldif_parse_attribute(line + 11);
727             if (attr != NULL)
728                 ldif_store_string(attr, role->options, false);
729         } else if (strncmp(line, "sudoOrder:", 10) == 0) {
730             char *ep;
731             attr = ldif_parse_attribute(line + 10);
732             if (attr != NULL) {
733                 role->order = strtod(attr, &ep);
734                 if (ep == attr || *ep != '\0')
735                     sudo_warnx(U_("invalid sudoOrder attribute: %s"), attr);
736             }
737         } else if (strncmp(line, "sudoNotBefore:", 14) == 0) {
738             attr = ldif_parse_attribute(line + 14);
739             if (attr != NULL) {
740                 free(role->notbefore);
741                 role->notbefore = strdup(attr);
742                 if (role->notbefore == NULL) {
743                     sudo_fatalx(U_("%s: %s"), __func__,
744                         U_("unable to allocate memory"));
745                 }
746             }
747         } else if (strncmp(line, "sudoNotAfter:", 13) == 0) {
748             attr = ldif_parse_attribute(line + 13);
749             if (attr != NULL) {
750                 free(role->notafter);
751                 role->notafter = strdup(attr);
752                 if (role->notafter == NULL) {
753                     sudo_fatalx(U_("%s: %s"), __func__,
754                         U_("unable to allocate memory"));
755                 }
756             }
757         }
758     }
759     sudo_role_free(role);
760     free(line);
761
762     /* Convert from roles to sudoers data structures. */
763     ldif_to_sudoers(parse_tree, &roles, numroles, store_options);
764
765     /* Clean up. */
766     rbdestroy(usercache, str_list_free);
767     rbdestroy(groupcache, str_list_free);
768     rbdestroy(hostcache, str_list_free);
769
770     if (fp != stdin)
771         fclose(fp);
772
773     debug_return_bool(true);
774 }