Coverage Report

Created: 2025-11-24 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/plugins/sudoers/ldap_util.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2013, 2016, 2018-2024 Todd C. Miller <Todd.Miller@sudo.ws>
5
 *
6
 * This code is derived from software contributed by Aaron Spangler.
7
 *
8
 * Permission to use, copy, modify, and distribute this software for any
9
 * purpose with or without fee is hereby granted, provided that the above
10
 * copyright notice and this permission notice appear in all copies.
11
 *
12
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
 */
20
21
#include <config.h>
22
23
#include <sys/types.h>
24
#include <sys/socket.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
#include <ctype.h>
29
#include <netinet/in.h>
30
#include <arpa/inet.h>
31
32
#include <sudoers.h>
33
#include <interfaces.h>
34
#include <sudo_lbuf.h>
35
#include <sudo_ldap.h>
36
#include <sudo_digest.h>
37
#include <gram.h>
38
39
/*
40
 * Returns true if the string pointed to by valp begins with an
41
 * odd number of '!' characters.  Intervening blanks are ignored.
42
 * Stores the address of the string after '!' removal in valp.
43
 */
44
bool
45
sudo_ldap_is_negated(char **valp)
46
327k
{
47
327k
    char *val = *valp;
48
327k
    bool ret = false;
49
327k
    debug_decl(sudo_ldap_is_negated, SUDOERS_DEBUG_LDAP);
50
51
331k
    while (*val == '!') {
52
3.66k
  ret = !ret;
53
5.02k
  do {
54
5.02k
      val++;
55
5.02k
  } while (isblank((unsigned char)*val));
56
3.66k
    }
57
327k
    *valp = val;
58
327k
    debug_return_bool(ret);
59
327k
}
60
61
/*
62
 * Parse an option string into a defaults structure.
63
 * The members of def are pointers into optstr (which is modified).
64
 */
65
int
66
sudo_ldap_parse_option(char *optstr, char **varp, char **valp)
67
147k
{
68
147k
    char *cp, *val = NULL;
69
147k
    char *var = optstr;
70
147k
    int op;
71
147k
    debug_decl(sudo_ldap_parse_option, SUDOERS_DEBUG_LDAP);
72
73
    /* check for equals sign past first char */
74
147k
    cp = strchr(var, '=');
75
147k
    if (cp != NULL && cp > var) {
76
11.8k
  val = cp + 1;
77
11.8k
  op = cp[-1];  /* peek for += or -= cases */
78
11.8k
  if (op == '+' || op == '-') {
79
      /* case var+=val or var-=val */
80
2.76k
      cp--;
81
9.12k
  } else {
82
      /* case var=val */
83
9.12k
      op = true;
84
9.12k
  }
85
  /* Trim whitespace between var and operator. */
86
12.1k
  while (cp > var && isblank((unsigned char)cp[-1]))
87
219
      cp--;
88
  /* Truncate variable name. */
89
11.8k
  *cp = '\0';
90
  /* Trim leading whitespace from val. */
91
11.8k
  while (isblank((unsigned char)*val))
92
194
      val++;
93
  /* Strip double quotes if present. */
94
11.8k
  if (*val == '"') {
95
397
      char *ep = val + strlen(val);
96
397
      if (ep != val && ep[-1] == '"') {
97
194
    val++;
98
194
    ep[-1] = '\0';
99
194
      }
100
397
  }
101
135k
    } else {
102
  /* Boolean value, either true or false. */
103
135k
  op = sudo_ldap_is_negated(&var) ? false : true;
104
135k
    }
105
147k
    *varp = var;
106
147k
    *valp = val;
107
108
147k
    debug_return_int(op);
109
147k
}
110
111
/*
112
 * Convert an array of user/group names to a member list.
113
 * The caller is responsible for freeing the returned struct member_list.
114
 */
115
static struct member_list *
116
array_to_member_list(void *a, sudo_ldap_iter_t iter)
117
22.8k
{
118
22.8k
    struct member_list negated_members =
119
22.8k
  TAILQ_HEAD_INITIALIZER(negated_members);
120
22.8k
    struct member_list *members;
121
22.8k
    struct member *m;
122
22.8k
    char *val;
123
22.8k
    debug_decl(bv_to_member_list, SUDOERS_DEBUG_LDAP);
124
125
22.8k
    if ((members = calloc(1, sizeof(*members))) == NULL)
126
0
  return NULL;
127
22.8k
    TAILQ_INIT(members);                      
128
129
47.9k
    while ((val = iter(&a)) != NULL) {
130
25.1k
  if ((m = calloc(1, sizeof(*m))) == NULL)
131
0
      goto bad;
132
25.1k
  m->negated = sudo_ldap_is_negated(&val);
133
134
25.1k
  switch (val[0]) {
135
19.2k
  case '\0':
136
      /* Empty RunAsUser means run as the invoking user. */
137
19.2k
      m->type = MYSELF;
138
19.2k
      break;
139
478
  case '+':
140
478
      m->type = NETGROUP;
141
478
      break;
142
319
  case '%':
143
319
      m->type = USERGROUP;
144
319
      break;
145
743
  case 'A':
146
743
      if (strcmp(val, "ALL") == 0) {
147
346
    m->type = ALL;
148
346
    break;
149
346
      }
150
397
      FALLTHROUGH;
151
4.74k
  default:
152
4.74k
      m->type = WORD;
153
4.74k
      break;
154
25.1k
  }
155
25.1k
  if (m->type != ALL && m->type != MYSELF) {
156
5.53k
      if ((m->name = strdup(val)) == NULL) {
157
0
    free(m);
158
0
    goto bad;
159
0
      }
160
5.53k
  }
161
25.1k
  if (m->negated)
162
558
      TAILQ_INSERT_TAIL(&negated_members, m, entries);
163
24.5k
  else
164
24.5k
      TAILQ_INSERT_TAIL(members, m, entries);
165
25.1k
    }
166
167
    /* Negated members take precedence so we insert them at the end. */
168
22.8k
    TAILQ_CONCAT(members, &negated_members, entries);
169
22.8k
    debug_return_ptr(members);
170
0
bad:
171
0
    free_members(&negated_members);
172
0
    free_members(members);
173
0
    free(members);
174
0
    debug_return_ptr(NULL);
175
0
}
176
177
static bool
178
is_address(char *host)
179
72.7k
{
180
72.7k
    union sudo_in_addr_un addr;
181
72.7k
    bool ret = false;
182
72.7k
    char *slash;
183
72.7k
    debug_decl(is_address, SUDOERS_DEBUG_LDAP);
184
185
    /* Check for mask, not currently parsed. */
186
72.7k
    if ((slash = strchr(host, '/')) != NULL)
187
293
  *slash = '\0';
188
189
72.7k
    if (inet_pton(AF_INET, host, &addr.ip4) == 1)
190
212
  ret = true;
191
72.4k
#ifdef HAVE_STRUCT_IN6_ADDR
192
72.4k
    else if (inet_pton(AF_INET6, host, &addr.ip6) == 1)
193
205
  ret = true;
194
72.7k
#endif
195
196
72.7k
    if (slash != NULL)
197
293
  *slash = '/';
198
199
72.7k
    debug_return_bool(ret);
200
72.7k
}
201
202
static struct member *
203
host_to_member(char *host)
204
73.9k
{
205
73.9k
    struct member *m;
206
73.9k
    debug_decl(host_to_member, SUDOERS_DEBUG_LDAP);
207
208
73.9k
    if ((m = calloc(1, sizeof(*m))) == NULL)
209
0
  goto oom;
210
73.9k
    m->negated = sudo_ldap_is_negated(&host);
211
73.9k
    switch (*host) {
212
241
    case '+':
213
241
  m->type = NETGROUP;
214
241
  break;
215
2.32k
    case 'A':
216
2.32k
  if (strcmp(host, "ALL") == 0) {
217
997
      m->type = ALL;
218
997
      break;
219
997
  }
220
1.33k
  FALLTHROUGH;
221
72.7k
    default:
222
72.7k
  if (is_address(host)) {
223
417
      m->type = NTWKADDR;
224
72.2k
  } else {
225
72.2k
      m->type = WORD;
226
72.2k
  }
227
72.7k
  break;
228
73.9k
    }
229
73.9k
    if (m->type != ALL) {
230
72.9k
  if ((m->name = strdup(host)) == NULL)
231
0
      goto oom;
232
72.9k
    }
233
234
73.9k
    debug_return_ptr(m);
235
0
oom:
236
0
    free(m);
237
0
    debug_return_ptr(NULL);
238
0
}
239
240
/*
241
 * If a digest prefix is present, add it to struct command_digest_list
242
 * and update cmnd to point to the command after the digest.
243
 * Returns 1 if a digest was parsed, 0 if not and -1 on error.
244
 */
245
static int
246
sudo_ldap_extract_digest(const char *cmnd, char **endptr,
247
    struct command_digest_list *digests)
248
76.9k
{
249
76.9k
    const char *ep, *cp = cmnd;
250
76.9k
    struct command_digest *digest;
251
76.9k
    unsigned int digest_type = SUDO_DIGEST_INVALID;
252
76.9k
    debug_decl(sudo_ldap_extract_digest, SUDOERS_DEBUG_LDAP);
253
254
    /*
255
     * Check for and extract a digest prefix, e.g.
256
     * sha224:d06a2617c98d377c250edd470fd5e576327748d82915d6e33b5f8db1 /bin/ls
257
     */
258
76.9k
    if (cp[0] == 's' && cp[1] == 'h' && cp[2] == 'a') {
259
5.74k
  switch (cp[3]) {
260
2.60k
  case '2':
261
2.60k
      if (cp[4] == '2' && cp[5] == '4')
262
702
    digest_type = SUDO_DIGEST_SHA224;
263
1.90k
      else if (cp[4] == '5' && cp[5] == '6')
264
1.00k
    digest_type = SUDO_DIGEST_SHA256;
265
2.60k
      break;
266
1.68k
  case '3':
267
1.68k
      if (cp[4] == '8' && cp[5] == '4')
268
1.20k
    digest_type = SUDO_DIGEST_SHA384;
269
1.68k
      break;
270
1.12k
  case '5':
271
1.12k
      if (cp[4] == '1' && cp[5] == '2')
272
704
    digest_type = SUDO_DIGEST_SHA512;
273
1.12k
      break;
274
5.74k
  }
275
5.74k
  if (digest_type != SUDO_DIGEST_INVALID) {
276
3.61k
      cp += 6;
277
3.61k
      while (isblank((unsigned char)*cp))
278
550
    cp++;
279
3.61k
      if (*cp == ':') {
280
3.30k
    cp++;
281
3.30k
    while (isblank((unsigned char)*cp))
282
610
        cp++;
283
3.30k
    ep = cp;
284
4.79k
    while (*ep != '\0' && !isblank((unsigned char)*ep) && *ep != ',')
285
1.49k
        ep++;
286
3.30k
    if (isblank((unsigned char)*ep) || *ep == ',') {
287
2.19k
        if ((digest = malloc(sizeof(*digest))) == NULL) {
288
0
      sudo_warnx(U_("%s: %s"), __func__,
289
0
          U_("unable to allocate memory"));
290
0
      debug_return_int(-1);
291
0
        }
292
2.19k
        digest->digest_type = digest_type;
293
2.19k
        digest->digest_str = strndup(cp, (size_t)(ep - cp));
294
2.19k
        if (digest->digest_str == NULL) {
295
0
      sudo_warnx(U_("%s: %s"), __func__,
296
0
          U_("unable to allocate memory"));
297
0
      free(digest);
298
0
      debug_return_int(-1);
299
0
        }
300
2.19k
        while (isblank((unsigned char)*ep))
301
616
      ep++;
302
2.19k
        *endptr = (char *)ep;
303
2.19k
        sudo_debug_printf(SUDO_DEBUG_INFO,
304
2.19k
      "%s digest %s for %s",
305
2.19k
      digest_type_to_name(digest_type),
306
2.19k
      digest->digest_str, cp);
307
2.19k
        TAILQ_INSERT_TAIL(digests, digest, entries);
308
2.19k
        debug_return_int(1);
309
2.19k
    }
310
3.30k
      }
311
3.61k
  }
312
5.74k
    }
313
74.7k
    debug_return_int(0);
314
74.7k
}
315
316
/*
317
 * If a digest list is present, fill in struct command_digest_list
318
 * and update cmnd to point to the command after the digest.
319
 * Returns false on error, else true.
320
 */
321
static bool
322
sudo_ldap_extract_digests(char **cmnd, struct command_digest_list *digests)
323
74.9k
{
324
74.9k
    char *cp = *cmnd;
325
74.9k
    int rc;
326
74.9k
    debug_decl(sudo_ldap_extract_digests, SUDOERS_DEBUG_LDAP);
327
328
76.9k
    for (;;) {
329
76.9k
  rc = sudo_ldap_extract_digest(cp, &cp, digests);
330
76.9k
  if (rc != 1)
331
74.7k
      break;
332
333
  /* Check for additional digestspecs, separated by a comma. */
334
2.19k
  if (*cp != ',')
335
244
      break;
336
2.14k
  do {
337
2.14k
      cp++;
338
2.14k
  } while (isblank((unsigned char)*cp));
339
1.95k
    }
340
74.9k
    *cmnd = cp;
341
342
74.9k
    debug_return_bool(rc != -1);
343
74.9k
}
344
345
/*
346
 * Convert an LDAP sudoRole to a sudoers privilege.
347
 * Pass in struct berval ** for LDAP or char *** for SSSD.
348
 */
349
struct privilege *
350
sudo_ldap_role_to_priv(const char *cn, void *hosts, void *runasusers,
351
    void *runasgroups, void *cmnds, void *opts, const char *notbefore,
352
    const char *notafter, bool warnings, bool store_options,
353
    sudo_ldap_iter_t iter)
354
67.9k
{
355
67.9k
    struct cmndspec_list negated_cmnds = TAILQ_HEAD_INITIALIZER(negated_cmnds);
356
67.9k
    struct member_list negated_hosts = TAILQ_HEAD_INITIALIZER(negated_hosts);
357
67.9k
    struct cmndspec *prev_cmndspec = NULL;
358
67.9k
    struct privilege *priv;
359
67.9k
    struct member *m;
360
67.9k
    char *cmnd;
361
67.9k
    debug_decl(sudo_ldap_role_to_priv, SUDOERS_DEBUG_LDAP);
362
363
67.9k
    if ((priv = calloc(1, sizeof(*priv))) == NULL)
364
0
  goto oom;
365
67.9k
    TAILQ_INIT(&priv->hostlist);
366
67.9k
    TAILQ_INIT(&priv->cmndlist);
367
67.9k
    TAILQ_INIT(&priv->defaults);
368
369
67.9k
    priv->ldap_role = strdup(cn ? cn : "UNKNOWN");
370
67.9k
    if (priv->ldap_role == NULL)
371
0
  goto oom;
372
373
67.9k
    if (hosts == NULL) {
374
  /* The host has already matched, use ALL as wildcard. */
375
0
  if ((m = sudo_ldap_new_member_all()) == NULL)
376
0
      goto oom;
377
0
  TAILQ_INSERT_TAIL(&priv->hostlist, m, entries);
378
67.9k
    } else {
379
67.9k
  char *host;
380
141k
  while ((host = iter(&hosts)) != NULL) {
381
73.9k
      if ((m = host_to_member(host)) == NULL)
382
0
    goto oom;
383
73.9k
      if (m->negated)
384
917
    TAILQ_INSERT_TAIL(&negated_hosts, m, entries);
385
73.0k
      else
386
73.0k
    TAILQ_INSERT_TAIL(&priv->hostlist, m, entries);
387
73.9k
  }
388
  /* Negated hosts take precedence so we insert them at the end. */
389
67.9k
  TAILQ_CONCAT(&priv->hostlist, &negated_hosts, entries);
390
67.9k
    }
391
392
    /*
393
     * Parse sudoCommands and add to cmndlist.
394
     */
395
142k
    while ((cmnd = iter(&cmnds)) != NULL) {
396
74.9k
  bool negated = sudo_ldap_is_negated(&cmnd);
397
74.9k
  struct sudo_command *c = NULL;
398
74.9k
  struct cmndspec *cmndspec;
399
400
  /* Allocate storage upfront. */
401
74.9k
  if ((cmndspec = calloc(1, sizeof(*cmndspec))) == NULL)
402
0
      goto oom;
403
74.9k
  if ((m = calloc(1, sizeof(*m))) == NULL) {
404
0
      free(cmndspec);
405
0
      goto oom;
406
0
  }
407
74.9k
  if ((c = calloc(1, sizeof(*c))) == NULL) {
408
0
      free(cmndspec);
409
0
      free(m);
410
0
      goto oom;
411
0
  }
412
74.9k
  m->name = (char *)c;
413
74.9k
  TAILQ_INIT(&c->digests);
414
415
  /* Negated commands have precedence so insert them at the end. */
416
74.9k
  if (negated)
417
495
      TAILQ_INSERT_TAIL(&negated_cmnds, cmndspec, entries);
418
74.5k
  else
419
74.5k
      TAILQ_INSERT_TAIL(&priv->cmndlist, cmndspec, entries);
420
421
  /* Initialize cmndspec */
422
74.9k
  TAGS_INIT(&cmndspec->tags);
423
74.9k
  cmndspec->notbefore = UNSPEC;
424
74.9k
  cmndspec->notafter = UNSPEC;
425
74.9k
  cmndspec->timeout = UNSPEC;
426
74.9k
  cmndspec->cmnd = m;
427
428
74.9k
  if (prev_cmndspec != NULL) {
429
      /* Inherit values from prior cmndspec (common to the sudoRole). */
430
7.00k
      cmndspec->runasuserlist = prev_cmndspec->runasuserlist;
431
7.00k
      cmndspec->runasgrouplist = prev_cmndspec->runasgrouplist;
432
7.00k
      cmndspec->notbefore = prev_cmndspec->notbefore;
433
7.00k
      cmndspec->notafter = prev_cmndspec->notafter;
434
7.00k
      cmndspec->timeout = prev_cmndspec->timeout;
435
7.00k
      cmndspec->runchroot = prev_cmndspec->runchroot;
436
7.00k
      cmndspec->runcwd = prev_cmndspec->runcwd;
437
7.00k
      cmndspec->role = prev_cmndspec->role;
438
7.00k
      cmndspec->type = prev_cmndspec->type;
439
7.00k
      cmndspec->apparmor_profile = prev_cmndspec->apparmor_profile;
440
7.00k
      cmndspec->privs = prev_cmndspec->privs;
441
7.00k
      cmndspec->limitprivs = prev_cmndspec->limitprivs;
442
7.00k
      cmndspec->tags = prev_cmndspec->tags;
443
7.00k
      if (cmndspec->tags.setenv == IMPLIED)
444
477
    cmndspec->tags.setenv = UNSPEC;
445
67.9k
  } else {
446
      /* Parse sudoRunAsUser / sudoRunAs */
447
67.9k
      if (runasusers != NULL) {
448
21.7k
    cmndspec->runasuserlist =
449
21.7k
        array_to_member_list(runasusers, iter);
450
21.7k
    if (cmndspec->runasuserlist == NULL)
451
0
        goto oom;
452
21.7k
      }
453
454
      /* Parse sudoRunAsGroup */
455
67.9k
      if (runasgroups != NULL) {
456
1.11k
    cmndspec->runasgrouplist =
457
1.11k
        array_to_member_list(runasgroups, iter);
458
1.11k
    if (cmndspec->runasgrouplist == NULL)
459
0
        goto oom;
460
1.11k
      }
461
462
      /* Parse sudoNotBefore / sudoNotAfter */
463
67.9k
      if (notbefore != NULL)
464
1.72k
    cmndspec->notbefore = parse_gentime(notbefore);
465
67.9k
      if (notafter != NULL)
466
1.87k
    cmndspec->notafter = parse_gentime(notafter);
467
468
      /* Parse sudoOptions. */
469
67.9k
      if (opts != NULL) {
470
3.52k
    char *opt, *source = NULL;
471
472
3.52k
    if (store_options) {
473
        /* Use sudoRole in place of file name in defaults. */
474
3.52k
        size_t slen = sizeof("sudoRole ") - 1 + strlen(priv->ldap_role);
475
3.52k
        if ((source = sudo_rcstr_alloc(slen)) == NULL)
476
0
      goto oom;
477
3.52k
        if ((size_t)snprintf(source, slen + 1, "sudoRole %s", priv->ldap_role) != slen) {
478
0
      sudo_warnx(U_("internal error, %s overflow"), __func__);
479
0
      sudo_rcstr_delref(source);
480
0
      goto bad;
481
0
        }
482
3.52k
    }
483
484
148k
    while ((opt = iter(&opts)) != NULL) {
485
145k
        char *var, *val;
486
145k
        int op;
487
488
145k
        op = sudo_ldap_parse_option(opt, &var, &val);
489
145k
        if (strcmp(var, "command_timeout") == 0 && val != NULL) {
490
3.43k
      if (cmndspec->timeout != UNSPEC) {
491
877
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
492
877
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
493
877
      }
494
3.43k
      cmndspec->timeout = parse_timeout(val);
495
141k
        } else if (strcmp(var, "runchroot") == 0 && val != NULL) {
496
804
      if (cmndspec->runchroot != NULL) {
497
575
          free(cmndspec->runchroot);
498
575
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
499
575
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
500
575
      }
501
804
      if ((cmndspec->runchroot = strdup(val)) == NULL)
502
0
          break;
503
141k
        } else if (strcmp(var, "runcwd") == 0 && val != NULL) {
504
1.02k
      if (cmndspec->runcwd != NULL) {
505
766
          free(cmndspec->runcwd);
506
766
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
507
766
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
508
766
      }
509
1.02k
      if ((cmndspec->runcwd = strdup(val)) == NULL)
510
0
          break;
511
140k
        } else if (strcmp(var, "role") == 0 && val != NULL) {
512
801
      if (cmndspec->role != NULL) {
513
575
          free(cmndspec->role);
514
575
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
515
575
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
516
575
      }
517
801
      if ((cmndspec->role = strdup(val)) == NULL)
518
0
          break;
519
139k
        } else if (strcmp(var, "type") == 0 && val != NULL) {
520
788
      if (cmndspec->type != NULL) {
521
569
          free(cmndspec->type);
522
569
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
523
569
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
524
569
      }
525
788
      if ((cmndspec->type = strdup(val)) == NULL)
526
0
          break;
527
138k
        } else if (strcmp(var, "apparmor_profile") == 0 && val != NULL) {
528
1.01k
      if (cmndspec->apparmor_profile != NULL) {
529
578
          free(cmndspec->apparmor_profile);
530
578
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
531
578
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
532
578
      }
533
1.01k
      if ((cmndspec->apparmor_profile = strdup(val)) == NULL)
534
0
          break;
535
137k
        } else if (strcmp(var, "privs") == 0 && val != NULL) {
536
1.31k
      if (cmndspec->privs != NULL) {
537
862
          free(cmndspec->privs);
538
862
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
539
862
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
540
862
      }
541
1.31k
      if ((cmndspec->privs = strdup(val)) == NULL)
542
0
          break;
543
136k
        } else if (strcmp(var, "limitprivs") == 0 && val != NULL) {
544
1.02k
      if (cmndspec->limitprivs != NULL) {
545
796
          free(cmndspec->limitprivs);
546
796
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
547
796
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
548
796
      }
549
1.02k
      if ((cmndspec->limitprivs = strdup(val)) == NULL)
550
0
          break;
551
135k
        } else if (store_options) {
552
135k
      if (!append_default(var, val, op, source,
553
135k
          &priv->defaults)) {
554
0
          break;
555
0
      }
556
135k
        } else {
557
      /* Convert to tags. */
558
0
      bool converted = sudoers_defaults_to_tags(var, val, op,
559
0
          &cmndspec->tags);
560
0
      if (!converted) {
561
0
          if (warnings) {
562
        /* XXX - callback to process unsupported options. */
563
0
        if (val != NULL) {
564
0
            sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), var, op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
565
0
        } else {
566
0
            sudo_warnx(U_("unable to convert sudoOption: %s%s%s"), op == false ? "!" : "", var, "");
567
0
        }
568
0
          }
569
0
          continue;
570
0
      }
571
0
        }
572
145k
    }
573
3.52k
    sudo_rcstr_delref(source);
574
3.52k
    if (opt != NULL) {
575
        /* Defer oom until we drop the ref on source. */
576
0
        goto oom;
577
0
    }
578
3.52k
      }
579
580
      /* So we can inherit previous values. */
581
67.9k
      prev_cmndspec = cmndspec;
582
67.9k
  }
583
584
  /* Fill in command member now that options have been processed. */
585
74.9k
  m->negated = negated;
586
74.9k
  if (!sudo_ldap_extract_digests(&cmnd, &c->digests))
587
0
      goto oom;
588
74.9k
  if (strcmp(cmnd, "ALL") == 0) {
589
566
      if (cmndspec->tags.setenv == UNSPEC)
590
566
    cmndspec->tags.setenv = IMPLIED;
591
566
      m->type = ALL;
592
74.4k
  } else {
593
74.4k
      char *args = strpbrk(cmnd, " \t");
594
74.4k
      if (args != NULL) {
595
1.20k
    *args++ = '\0';
596
1.20k
    if ((c->args = strdup(args)) == NULL)
597
0
        goto oom;
598
1.20k
      }
599
74.4k
      if ((c->cmnd = strdup(cmnd)) == NULL)
600
0
    goto oom;
601
74.4k
      m->type = COMMAND;
602
74.4k
  }
603
74.9k
    }
604
    /* Negated commands take precedence so we insert them at the end. */
605
67.9k
    TAILQ_CONCAT(&priv->cmndlist, &negated_cmnds, entries);
606
607
67.9k
    debug_return_ptr(priv);
608
609
0
oom:
610
0
    sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
611
0
bad:
612
0
    if (priv != NULL) {
613
0
  TAILQ_CONCAT(&priv->hostlist, &negated_hosts, entries);
614
0
  TAILQ_CONCAT(&priv->cmndlist, &negated_cmnds, entries);
615
0
  free_privilege(priv);
616
0
    }
617
0
    debug_return_ptr(NULL);
618
0
}
619
620
/* So ldap.c and sssd.c don't need to include gram.h */
621
struct member *
622
sudo_ldap_new_member_all(void)
623
0
{
624
0
    struct member *m;
625
0
    debug_decl(sudo_ldap_new_member_all, SUDOERS_DEBUG_LDAP);
626
627
0
    if ((m = calloc(1, sizeof(*m))) != NULL)
628
0
  m->type = ALL;
629
0
    debug_return_ptr(m);
630
0
}
631
632
/*
633
 * Determine length of query value after escaping characters
634
 * as per RFC 4515.
635
 */
636
size_t
637
sudo_ldap_value_len(const char *value)
638
0
{
639
0
    const char *s;
640
0
    size_t len = 0;
641
642
0
    for (s = value; *s != '\0'; s++) {
643
0
  switch (*s) {
644
0
  case '\\':
645
0
  case '(':
646
0
  case ')':
647
0
  case '*':
648
0
      len += 2;
649
0
      break;
650
0
  }
651
0
    }
652
0
    len += (size_t)(s - value);
653
0
    return len;
654
0
}
655
656
/*
657
 * Like strlcat() but escapes characters as per RFC 4515.
658
 */
659
size_t
660
sudo_ldap_value_cat(char * restrict dst, const char * restrict src, size_t size)
661
0
{
662
0
    char *d = dst;
663
0
    const char *s = src;
664
0
    size_t n = size;
665
0
    size_t dlen;
666
667
    /* Find the end of dst and adjust bytes left but don't go past end */
668
0
    while (n-- != 0 && *d != '\0')
669
0
  d++;
670
0
    dlen = (size_t)(d - dst);
671
0
    n = size - dlen;
672
673
0
    if (n == 0)
674
0
  return dlen + strlen(s);
675
0
    while (*s != '\0') {
676
0
  switch (*s) {
677
0
  case '\\':
678
0
      if (n < 3)
679
0
    goto done;
680
0
      *d++ = '\\';
681
0
      *d++ = '5';
682
0
      *d++ = 'c';
683
0
      n -= 3;
684
0
      break;
685
0
  case '(':
686
0
      if (n < 3)
687
0
    goto done;
688
0
      *d++ = '\\';
689
0
      *d++ = '2';
690
0
      *d++ = '8';
691
0
      n -= 3;
692
0
      break;
693
0
  case ')':
694
0
      if (n < 3)
695
0
    goto done;
696
0
      *d++ = '\\';
697
0
      *d++ = '2';
698
0
      *d++ = '9';
699
0
      n -= 3;
700
0
      break;
701
0
  case '*':
702
0
      if (n < 3)
703
0
    goto done;
704
0
      *d++ = '\\';
705
0
      *d++ = '2';
706
0
      *d++ = 'a';
707
0
      n -= 3;
708
0
      break;
709
0
  default:
710
0
      if (n < 1)
711
0
    goto done;
712
0
      *d++ = *s;
713
0
      n--;
714
0
      break;
715
0
  }
716
0
  s++;
717
0
    }
718
0
done:
719
0
    *d = '\0';
720
0
    while (*s != '\0')
721
0
  s++;
722
0
    return dlen + (size_t)(s - src);  /* count does not include NUL */
723
0
}
724
725
/*
726
 * Like strdup() but escapes characters as per RFC 4515.
727
 */
728
char *
729
sudo_ldap_value_dup(const char *src)
730
0
{
731
0
    char *dst;
732
0
    size_t size;
733
734
0
    size = sudo_ldap_value_len(src) + 1;
735
0
    dst = malloc(size);
736
0
    if (dst == NULL)
737
0
  return NULL;
738
739
0
    *dst = '\0';
740
0
    if (sudo_ldap_value_cat(dst, src, size) >= size) {
741
  /* Should not be possible... */
742
0
  free(dst);
743
  dst = NULL;
744
0
    }
745
0
    return dst;
746
0
}