Coverage Report

Created: 2025-08-29 07:10

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