Coverage Report

Created: 2026-02-14 06:05

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