Coverage Report

Created: 2026-02-26 06:09

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
81.1k
{
53
81.1k
    debug_decl(sudo_role_free, SUDOERS_DEBUG_UTIL);
54
55
81.1k
    if (role != NULL) {
56
76.4k
  free(role->cn);
57
76.4k
  free(role->notbefore);
58
76.4k
  free(role->notafter);
59
76.4k
  str_list_free(role->cmnds);
60
76.4k
  str_list_free(role->hosts);
61
76.4k
  str_list_free(role->users);
62
76.4k
  str_list_free(role->runasusers);
63
76.4k
  str_list_free(role->runasgroups);
64
76.4k
  str_list_free(role->options);
65
76.4k
  free(role);
66
76.4k
    }
67
68
81.1k
    debug_return;
69
81.1k
}
70
71
static struct sudo_role *
72
sudo_role_alloc(void)
73
76.4k
{
74
76.4k
    struct sudo_role *role;
75
76.4k
    debug_decl(sudo_role_alloc, SUDOERS_DEBUG_UTIL);
76
77
76.4k
    role = calloc(1, sizeof(*role));
78
76.4k
    if (role != NULL) {
79
76.4k
  role->cmnds = str_list_alloc();
80
76.4k
  role->hosts = str_list_alloc();
81
76.4k
  role->users = str_list_alloc();
82
76.4k
  role->runasusers = str_list_alloc();
83
76.4k
  role->runasgroups = str_list_alloc();
84
76.4k
  role->options = str_list_alloc();
85
76.4k
  if (role->cmnds == NULL || role->hosts == NULL ||
86
76.4k
      role->users == NULL || role->runasusers == NULL ||
87
76.4k
      role->runasgroups == NULL || role->options == NULL) {
88
0
      sudo_role_free(role);
89
0
      role = NULL;
90
0
  }
91
76.4k
    }
92
93
76.4k
    debug_return_ptr(role);
94
76.4k
}
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.23M
{
104
1.23M
    bool encoded = false;
105
1.23M
    char *attr, *cp, *ep, *colon;
106
1.23M
    size_t len;
107
1.23M
    debug_decl(ldif_parse_attribute, SUDOERS_DEBUG_UTIL);
108
109
    /* Parse attribute name: [a-zA-Z][a-zA-Z0-9-]*: */
110
1.23M
    if (!isalpha((unsigned char)*line))
111
668k
  debug_return_bool(false);
112
5.82M
    for (cp = line + 1; *cp != ':' && *cp != '\0'; cp++) {
113
5.27M
  if (!isalnum((unsigned char)*cp) && *cp != '-')
114
15.4k
      debug_return_bool(false);
115
5.27M
    }
116
547k
    if (*cp != ':')
117
21.8k
  debug_return_bool(false);
118
525k
    colon = cp++;
119
120
    /* Check for foo:: base64str. */
121
525k
    if (*cp == ':') {
122
1.14k
  encoded = true;
123
1.14k
  cp++;
124
1.14k
    }
125
126
    /* Trim leading and trailing space. */
127
608k
    while (*cp == ' ')
128
83.0k
  cp++;
129
130
525k
    ep = cp + strlen(cp);
131
527k
    while (ep > cp && ep[-1] == ' ') {
132
1.62k
  ep--;
133
  /* Don't trim escaped trailing space if not base64. */
134
1.62k
  if (!encoded && ep != cp && ep[-1] == '\\')
135
203
      break;
136
1.41k
  *ep = '\0';
137
1.41k
    }
138
139
525k
    attr = cp;
140
525k
    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.14k
  char *copy = strdup(attr);
146
1.14k
  if (copy == NULL) {
147
0
      sudo_fatalx(U_("%s: %s"), __func__,
148
0
    U_("unable to allocate memory"));
149
0
  }
150
1.14k
  len = sudo_base64_decode(attr, (unsigned char *)copy, strlen(copy));
151
1.14k
  if (len == (size_t)-1) {
152
337
      free(copy);
153
337
      debug_return_bool(false);
154
337
  }
155
804
  memcpy(attr, copy, len);
156
804
  attr[len] = '\0';
157
804
  free(copy);
158
804
    }
159
160
525k
    *colon = '\0';
161
525k
    *name = line;
162
525k
    *value = attr;
163
164
525k
    debug_return_bool(true);
165
525k
}
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
375k
{
174
375k
    struct sudoers_string *ls;
175
375k
    debug_decl(ldif_store_string, SUDOERS_DEBUG_UTIL);
176
177
375k
    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
375k
    if (!sorted) {
182
175k
  STAILQ_INSERT_TAIL(strlist, ls, entries);
183
200k
    } else {
184
200k
  struct sudoers_string *prev, *next;
185
186
  /* Insertion sort, list is small. */
187
200k
  prev = STAILQ_FIRST(strlist);
188
200k
  if (prev == NULL || strcasecmp(str, prev->str) <= 0) {
189
188k
      STAILQ_INSERT_HEAD(strlist, ls, entries);
190
188k
  } else {
191
31.5k
      while ((next = STAILQ_NEXT(prev, entries)) != NULL) {
192
25.6k
    if (strcasecmp(str, next->str) <= 0)
193
5.61k
        break;
194
20.0k
    prev = next;
195
20.0k
      }
196
11.4k
      STAILQ_INSERT_AFTER(strlist, prev, ls, entries);
197
11.4k
  }
198
200k
    }
199
200
375k
    debug_return;
201
375k
}
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
449k
{
211
449k
    struct sudoers_string *ls = *vp;
212
213
449k
    if (ls == NULL)
214
168k
  return NULL;
215
216
280k
    *vp = STAILQ_NEXT(ls, entries);
217
218
280k
    return ls->str;
219
449k
}
220
221
static int
222
role_order_cmp(const void *va, const void *vb)
223
383k
{
224
383k
    const struct sudo_role *a = *(const struct sudo_role **)va;
225
383k
    const struct sudo_role *b = *(const struct sudo_role **)vb;
226
383k
    debug_decl(role_order_cmp, SUDOERS_DEBUG_LDAP);
227
228
383k
    debug_return_int(a->order < b->order ? -1 :
229
383k
        (a->order > b->order ? 1 : 0));
230
383k
}
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
581
{
239
581
    struct defaults *d;
240
581
    struct sudoers_string *ls;
241
581
    char *var, *val;
242
581
    debug_decl(ldif_store_options, SUDOERS_DEBUG_UTIL);
243
244
2.12k
    STAILQ_FOREACH(ls, options, entries) {
245
2.12k
  if ((d = calloc(1, sizeof(*d))) == NULL ||
246
2.12k
      (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.12k
  TAILQ_INIT(&d->binding->members);
251
2.12k
  d->binding->refcnt = 1;
252
2.12k
  d->type = DEFAULTS;
253
2.12k
  d->op = sudo_ldap_parse_option(ls->str, &var, &val);
254
2.12k
  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.12k
  if (val != NULL) {
259
1.04k
      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.04k
  }
264
2.12k
  TAILQ_INSERT_TAIL(&parse_tree->defaults, d, entries);
265
2.12k
    }
266
581
    debug_return;
267
581
}
268
269
static int
270
str_list_cmp(const void *aa, const void *bb)
271
669k
{
272
669k
    const struct sudoers_str_list *a = aa;
273
669k
    const struct sudoers_str_list *b = bb;
274
669k
    const struct sudoers_string *lsa = STAILQ_FIRST(a);
275
669k
    const struct sudoers_string *lsb = STAILQ_FIRST(b);
276
669k
    int ret;
277
278
927k
    while (lsa != NULL && lsb != NULL) {
279
464k
  if ((ret = strcasecmp(lsa->str, lsb->str)) != 0)
280
206k
      return ret;
281
257k
  lsa = STAILQ_NEXT(lsa, entries);
282
257k
  lsb = STAILQ_NEXT(lsb, entries);
283
257k
    }
284
463k
    return lsa == lsb ? 0 : (lsa == NULL ? -1 : 1);
285
669k
}
286
287
static int
288
str_list_cache(struct rbtree *cache, struct sudoers_str_list **strlistp)
289
281k
{
290
281k
    struct sudoers_str_list *strlist = *strlistp;
291
281k
    struct rbnode *node;
292
281k
    int ret;
293
281k
    debug_decl(str_list_cache, SUDOERS_DEBUG_UTIL);
294
295
281k
    ret = rbinsert(cache, strlist, &node);
296
281k
    switch (ret) {
297
31.9k
    case 0:
298
  /* new entry, take a ref for the cache */
299
31.9k
  strlist->refcnt++;
300
31.9k
  break;
301
249k
    case 1:
302
  /* already exists, use existing and take a ref. */
303
249k
  str_list_free(strlist);
304
249k
  strlist = node->data;
305
249k
  strlist->refcnt++;
306
249k
  *strlistp = strlist;
307
249k
  break;
308
281k
    }
309
281k
    debug_return_int(ret);
310
281k
}
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
70.4k
{
320
70.4k
    struct privilege *priv;
321
70.4k
    struct sudoers_string *ls;
322
70.4k
    struct userspec *us;
323
70.4k
    struct member *m;
324
70.4k
    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
70.4k
    if (reuse_userspec) {
331
  /* Reuse the previous userspec */
332
50.8k
  us = TAILQ_LAST(&parse_tree->userspecs, userspec_list);
333
50.8k
    } else {
334
  /* Allocate a new userspec and fill in the user list. */
335
19.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
19.6k
  TAILQ_INIT(&us->privileges);
340
19.6k
  TAILQ_INIT(&us->users);
341
19.6k
  STAILQ_INIT(&us->comments);
342
343
29.2k
  STAILQ_FOREACH(ls, role->users, entries) {
344
29.2k
      char *user = ls->str;
345
346
29.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
29.2k
      m->negated = sudo_ldap_is_negated(&user);
351
29.2k
      switch (*user) {
352
9.46k
      case '\0':
353
    /* Empty RunAsUser means run as the invoking user. */
354
9.46k
    m->type = MYSELF;
355
9.46k
    break;
356
405
      case '+':
357
405
    m->type = NETGROUP;
358
405
    break;
359
2.12k
      case '%':
360
2.12k
    m->type = USERGROUP;
361
2.12k
    break;
362
620
      case 'A':
363
620
    if (strcmp(user, "ALL") == 0) {
364
231
        m->type = ALL;
365
231
        break;
366
231
    }
367
389
    FALLTHROUGH;
368
17.0k
      default:
369
17.0k
    m->type = WORD;
370
17.0k
    break;
371
29.2k
      }
372
29.2k
      if (m->type != ALL && m->type != MYSELF) {
373
19.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
19.5k
      }
378
29.2k
      TAILQ_INSERT_TAIL(&us->users, m, entries);
379
29.2k
  }
380
19.6k
    }
381
382
    /* Add source role as a comment. */
383
70.4k
    if (role->cn != NULL) {
384
7.46k
  struct sudoers_comment *comment = NULL;
385
7.46k
  if (reuse_userspec) {
386
      /* Try to reuse comment too. */
387
1.86k
      STAILQ_FOREACH(comment, &us->comments, entries) {
388
1.19k
    if (strncasecmp(comment->str, "sudoRole ", 9) == 0) {
389
1.19k
        char *tmpstr;
390
1.19k
        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.19k
        free(comment->str);
395
1.19k
        comment->str = tmpstr;
396
1.19k
        break;
397
1.19k
    }
398
1.19k
      }
399
1.86k
  }
400
7.46k
  if (comment == NULL) {
401
      /* Create a new comment. */
402
6.27k
      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.27k
      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.27k
      STAILQ_INSERT_TAIL(&us->comments, comment, entries);
411
6.27k
  }
412
7.46k
    }
413
414
    /* Convert role to sudoers privilege. */
415
70.4k
    priv = sudo_ldap_role_to_priv(role->cn, STAILQ_FIRST(role->hosts),
416
70.4k
  STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups),
417
70.4k
  STAILQ_FIRST(role->cmnds), STAILQ_FIRST(role->options),
418
70.4k
  role->notbefore, role->notafter, true, store_options,
419
70.4k
  sudoers_string_iter);
420
70.4k
    if (priv == NULL) {
421
0
  sudo_fatalx(U_("%s: %s"), __func__,
422
0
      U_("unable to allocate memory"));
423
0
    }
424
425
70.4k
    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
70.4k
    } else {
455
70.4k
  TAILQ_INSERT_TAIL(&us->privileges, priv, entries);
456
70.4k
    }
457
458
    /* Add finished userspec to the list if new. */
459
70.4k
    if (!reuse_userspec)
460
19.6k
  TAILQ_INSERT_TAIL(&parse_tree->userspecs, us, entries);
461
462
70.4k
    debug_return;
463
70.4k
}
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.87k
{
472
2.87k
    struct sudo_role **role_array, *role = NULL;
473
2.87k
    unsigned int n;
474
2.87k
    debug_decl(ldif_to_sudoers, SUDOERS_DEBUG_UTIL);
475
476
    /* Convert from list of roles to array and sort by order. */
477
2.87k
    role_array = reallocarray(NULL, numroles + 1, sizeof(*role_array));
478
2.87k
    if (role_array == NULL)
479
0
  sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
480
73.3k
    for (n = 0; n < numroles; n++) {
481
70.4k
  if ((role = STAILQ_FIRST(roles)) == NULL)
482
0
      break; /* cannot happen */
483
70.4k
  STAILQ_REMOVE_HEAD(roles, entries);
484
70.4k
  role_array[n] = role;
485
70.4k
    }
486
2.87k
    role_array[n] = NULL;
487
2.87k
    qsort(role_array, numroles, sizeof(*role_array), role_order_cmp);
488
489
    /*
490
     * Iterate over roles in sorted order, converting to sudoers.
491
     */
492
73.3k
    for (n = 0, role = NULL; n < numroles; n++) {
493
70.4k
  bool reuse_userspec = false;
494
70.4k
  bool reuse_privilege = false;
495
70.4k
  bool reuse_runas = false;
496
70.4k
  struct sudo_role *prev_role = role;
497
498
70.4k
  role = role_array[n];
499
500
  /* Check whether we can reuse the previous user and host specs */
501
70.4k
  if (prev_role != NULL && role->users == prev_role->users) {
502
50.8k
      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
50.8k
      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
50.8k
  }
520
521
70.4k
  role_to_sudoers(parse_tree, role, store_options, reuse_userspec,
522
70.4k
      reuse_privilege, reuse_runas);
523
70.4k
    }
524
525
    /* Clean up. */
526
73.3k
    for (n = 0; n < numroles; n++)
527
70.4k
  sudo_role_free(role_array[n]);
528
2.87k
    free(role_array);
529
530
2.87k
    debug_return;
531
2.87k
}
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
12.2k
{
541
12.2k
    char *dst, *new_cn;
542
12.2k
    size_t len;
543
12.2k
    debug_decl(unquote_cn, SUDOERS_DEBUG_UTIL);
544
545
12.2k
    len = strlen(src);
546
12.2k
    if ((new_cn = malloc(len + 1)) == NULL)
547
0
  debug_return_str(NULL);
548
549
1.43M
    for (dst = new_cn; *src != '\0';) {
550
1.42M
  if (src[0] == '\\' && src[1] != '\0')
551
757
      src++;
552
1.42M
  *dst++ = *src++;
553
1.42M
    }
554
12.2k
    *dst = '\0';
555
556
12.2k
    debug_return_str(new_cn);
557
12.2k
}
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.69k
{
568
4.69k
    struct sudo_role_list roles = STAILQ_HEAD_INITIALIZER(roles);
569
4.69k
    struct sudo_role *role = NULL;
570
4.69k
    struct rbtree *usercache, *groupcache, *hostcache;
571
4.69k
    unsigned numroles = 0;
572
4.69k
    bool in_role = false;
573
4.69k
    size_t linesize = 0;
574
4.69k
    char *attr, *name, *line = NULL, *savedline = NULL;
575
4.69k
    size_t savedlen = 0;
576
4.69k
    bool mismatch = false;
577
4.69k
    int errors = 0;
578
4.69k
    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.69k
    usercache = rbcreate(str_list_cmp);
587
4.69k
    groupcache = rbcreate(str_list_cmp);
588
4.69k
    hostcache = rbcreate(str_list_cmp);
589
4.69k
    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.32M
    for (;;) {
594
1.32M
  int ch;
595
1.32M
  ssize_t len = getdelim(&line, &linesize, '\n', fp);
596
597
  /* Trim trailing return or newline. */
598
2.78M
  while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
599
1.45M
      line[--len] = '\0';
600
601
  /* Blank line or EOF terminates an entry. */
602
1.32M
  if (len <= 0) {
603
87.6k
      if (in_role) {
604
76.4k
    if (role->cn != NULL && strcasecmp(role->cn, "defaults") == 0) {
605
581
        ldif_store_options(parse_tree, role->options);
606
581
        sudo_role_free(role);
607
75.8k
    } else if (STAILQ_EMPTY(role->users) ||
608
73.1k
        STAILQ_EMPTY(role->hosts) || STAILQ_EMPTY(role->cmnds)) {
609
        /* Incomplete role. */
610
5.41k
        sudo_warnx(U_("ignoring incomplete sudoRole: cn: %s"),
611
5.41k
      role->cn ? role->cn : "UNKNOWN");
612
5.41k
        sudo_role_free(role);
613
70.4k
    } else {
614
        /* Cache users, hosts, runasusers and runasgroups. */
615
70.4k
        if (str_list_cache(usercache, &role->users) == -1 ||
616
70.4k
      str_list_cache(hostcache, &role->hosts) == -1 ||
617
70.4k
      str_list_cache(usercache, &role->runasusers) == -1 ||
618
70.4k
      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
70.4k
        STAILQ_INSERT_TAIL(&roles, role, entries);
625
70.4k
        numroles++;
626
70.4k
    }
627
76.4k
    role = NULL;
628
76.4k
    in_role = false;
629
76.4k
      }
630
87.6k
      if (len == -1) {
631
    /* EOF */
632
4.69k
    break;
633
4.69k
      }
634
82.9k
      mismatch = false;
635
82.9k
      continue;
636
87.6k
  }
637
638
1.23M
  if (savedline != NULL) {
639
1.04k
      char *tmp;
640
641
      /* Append to saved line. */
642
1.04k
      linesize = savedlen + (size_t)len + 1;
643
1.04k
      if ((tmp = realloc(savedline, linesize)) == NULL) {
644
0
    sudo_fatalx(U_("%s: %s"), __func__,
645
0
        U_("unable to allocate memory"));
646
0
      }
647
1.04k
      memcpy(tmp + savedlen, line, (size_t)len + 1);
648
1.04k
      free(line);
649
1.04k
      line = tmp;
650
1.04k
      savedline = NULL;
651
1.04k
  }
652
653
  /* Check for folded line */
654
1.23M
  if ((ch = getc(fp)) == ' ') {
655
      /* folded line, append to the saved portion. */
656
1.04k
      savedlen = (size_t)len;
657
1.04k
      savedline = line;
658
1.04k
      line = NULL;
659
1.04k
      linesize = 0;
660
1.04k
      continue;
661
1.04k
  }
662
1.23M
  ungetc(ch, fp);   /* not folded, push back ch */
663
664
  /* Skip comment lines or records that don't match the base. */
665
1.23M
  if (*line == '#' || mismatch)
666
6.27k
      continue;
667
668
  /* Reject invalid LDIF. */
669
1.23M
  if (!ldif_parse_attribute(line, &name, &attr)) {
670
706k
      sudo_warnx(U_("invalid LDIF attribute: %s"), line);
671
706k
      errors++;
672
706k
      continue;
673
706k
  }
674
675
  /* Parse dn and objectClass. */
676
525k
  if (strcasecmp(name, "dn") == 0) {
677
      /* Compare dn to base, if specified. */
678
4.96k
      if (sudoers_base != NULL) {
679
    /* Skip over cn if present. */
680
4.96k
    if (strncasecmp(attr, "cn=", 3) == 0) {
681
28.6k
        for (attr += 3; *attr != '\0'; attr++) {
682
      /* Handle escaped ',' chars. */
683
28.0k
      if (*attr == '\\' && attr[1] != '\0')
684
584
          attr++;
685
28.0k
      if (*attr == ',') {
686
3.59k
          attr++;
687
3.59k
          break;
688
3.59k
      }
689
28.0k
        }
690
4.24k
    }
691
4.96k
    if (strcasecmp(attr, sudoers_base) != 0) {
692
        /* Doesn't match base, skip the rest of it. */
693
1.74k
        mismatch = true;
694
1.74k
        continue;
695
1.74k
    }
696
4.96k
      }
697
520k
  } else if (strcasecmp(name, "objectClass") == 0) {
698
84.9k
      if (strcasecmp(attr, "sudoRole") == 0) {
699
    /* Allocate new role as needed. */
700
78.4k
    if (role == NULL) {
701
76.4k
        if ((role = sudo_role_alloc()) == NULL) {
702
0
      sudo_fatalx(U_("%s: %s"), __func__,
703
0
          U_("unable to allocate memory"));
704
0
        }
705
76.4k
    }
706
78.4k
    in_role = true;
707
78.4k
      }
708
84.9k
  }
709
710
  /* Not in a sudoRole, keep reading. */
711
523k
  if (!in_role)
712
29.8k
      continue;
713
714
  /* Part of a sudoRole, parse it. */
715
493k
  if (strcasecmp(name, "cn") == 0) {
716
12.2k
      free(role->cn);
717
12.2k
      role->cn = unquote_cn(attr);
718
12.2k
      if (role->cn == NULL) {
719
0
    sudo_fatalx(U_("%s: %s"), __func__,
720
0
        U_("unable to allocate memory"));
721
0
      }
722
481k
  } else if (strcasecmp(name, "sudoUser") == 0) {
723
84.8k
      ldif_store_string(attr, role->users, true);
724
396k
  } else if (strcasecmp(name, "sudoHost") == 0) {
725
87.0k
      ldif_store_string(attr, role->hosts, true);
726
309k
  } else if (strcasecmp(name, "sudoRunAs") == 0) {
727
20.3k
      ldif_store_string(attr, role->runasusers, true);
728
289k
  } else if (strcasecmp(name, "sudoRunAsUser") == 0) {
729
4.88k
      ldif_store_string(attr, role->runasusers, true);
730
284k
  } else if (strcasecmp(name, "sudoRunAsGroup") == 0) {
731
3.22k
      ldif_store_string(attr, role->runasgroups, true);
732
281k
  } else if (strcasecmp(name, "sudoCommand") == 0) {
733
82.7k
      ldif_store_string(attr, role->cmnds, false);
734
198k
  } else if (strcasecmp(name, "sudoOption") == 0) {
735
92.5k
      ldif_store_string(attr, role->options, false);
736
105k
  } else if (strcasecmp(name, "sudoOrder") == 0) {
737
2.94k
      char *ep;
738
2.94k
      role->order = strtod(attr, &ep);
739
2.94k
      if (ep == attr || *ep != '\0') {
740
667
    sudo_warnx(U_("invalid sudoOrder attribute: %s"), attr);
741
667
    errors++;
742
667
      }
743
102k
  } else if (strcasecmp(name, "sudoNotBefore") == 0) {
744
2.90k
      free(role->notbefore);
745
2.90k
      role->notbefore = strdup(attr);
746
2.90k
      if (role->notbefore == NULL) {
747
0
    sudo_fatalx(U_("%s: %s"), __func__,
748
0
        U_("unable to allocate memory"));
749
0
      }
750
100k
  } else if (strcasecmp(name, "sudoNotAfter") == 0) {
751
3.03k
      free(role->notafter);
752
3.03k
      role->notafter = strdup(attr);
753
3.03k
      if (role->notafter == NULL) {
754
0
    sudo_fatalx(U_("%s: %s"), __func__,
755
0
        U_("unable to allocate memory"));
756
0
      }
757
3.03k
  }
758
493k
    }
759
4.69k
    sudo_role_free(role);
760
4.69k
    free(line);
761
4.69k
    free(savedline);
762
763
    /* Convert from roles to sudoers data structures. */
764
4.69k
    if (numroles > 0)
765
2.87k
  ldif_to_sudoers(parse_tree, &roles, numroles, store_options);
766
767
    /* Clean up. */
768
4.69k
    rbdestroy(usercache, str_list_free);
769
4.69k
    rbdestroy(groupcache, str_list_free);
770
4.69k
    rbdestroy(hostcache, str_list_free);
771
772
    debug_return_bool(errors == 0);
773
4.69k
}