Coverage Report

Created: 2026-06-30 06:13

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
433k
{
47
433k
    char *val = *valp;
48
433k
    bool ret = false;
49
433k
    debug_decl(sudo_ldap_is_negated, SUDOERS_DEBUG_LDAP);
50
51
446k
    while (*val == '!') {
52
13.0k
  ret = !ret;
53
15.5k
  do {
54
15.5k
      val++;
55
15.5k
  } while (isblank((unsigned char)*val));
56
13.0k
    }
57
433k
    *valp = val;
58
433k
    debug_return_bool(ret);
59
433k
}
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
174k
{
68
174k
    char *cp, *val = NULL;
69
174k
    char *var = optstr;
70
174k
    int op;
71
174k
    debug_decl(sudo_ldap_parse_option, SUDOERS_DEBUG_LDAP);
72
73
    /* check for equals sign past first char */
74
174k
    cp = strchr(var, '=');
75
174k
    if (cp != NULL && cp > var) {
76
30.7k
  val = cp + 1;
77
30.7k
  op = cp[-1];  /* peek for += or -= cases */
78
30.7k
  if (op == '+' || op == '-') {
79
      /* case var+=val or var-=val */
80
4.39k
      cp--;
81
26.3k
  } else {
82
      /* case var=val */
83
26.3k
      op = true;
84
26.3k
  }
85
  /* Trim whitespace between var and operator. */
86
31.1k
  while (cp > var && isblank((unsigned char)cp[-1]))
87
421
      cp--;
88
  /* Truncate variable name. */
89
30.7k
  *cp = '\0';
90
  /* Trim leading whitespace from val. */
91
30.7k
  while (isblank((unsigned char)*val))
92
274
      val++;
93
  /* Strip double quotes if present. */
94
30.7k
  if (*val == '"') {
95
2.48k
      char *ep = val + strlen(val);
96
2.48k
      if (ep != val && ep[-1] == '"') {
97
874
    val++;
98
874
    ep[-1] = '\0';
99
874
      }
100
2.48k
  }
101
143k
    } else {
102
  /* Boolean value, either true or false. */
103
143k
  op = sudo_ldap_is_negated(&var) ? false : true;
104
143k
    }
105
174k
    *varp = var;
106
174k
    *valp = val;
107
108
174k
    debug_return_int(op);
109
174k
}
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
48.1k
{
118
48.1k
    struct member_list negated_members =
119
48.1k
  TAILQ_HEAD_INITIALIZER(negated_members);
120
48.1k
    struct member_list *members;
121
48.1k
    struct member *m;
122
48.1k
    char *val;
123
48.1k
    debug_decl(bv_to_member_list, SUDOERS_DEBUG_LDAP);
124
125
48.1k
    if ((members = calloc(1, sizeof(*members))) == NULL)
126
0
  return NULL;
127
48.1k
    TAILQ_INIT(members);                      
128
129
102k
    while ((val = iter(&a)) != NULL) {
130
54.5k
  if ((m = calloc(1, sizeof(*m))) == NULL)
131
0
      goto bad;
132
54.5k
  m->negated = sudo_ldap_is_negated(&val);
133
134
54.5k
  switch (val[0]) {
135
38.4k
  case '\0':
136
      /* Empty RunAsUser means run as the invoking user. */
137
38.4k
      m->type = MYSELF;
138
38.4k
      break;
139
915
  case '+':
140
915
      m->type = NETGROUP;
141
915
      break;
142
714
  case '%':
143
714
      m->type = USERGROUP;
144
714
      break;
145
3.59k
  case 'A':
146
3.59k
      if (strcmp(val, "ALL") == 0) {
147
2.48k
    m->type = ALL;
148
2.48k
    break;
149
2.48k
      }
150
1.11k
      FALLTHROUGH;
151
11.9k
  default:
152
11.9k
      m->type = WORD;
153
11.9k
      break;
154
54.5k
  }
155
54.5k
  if (m->type != ALL && m->type != MYSELF) {
156
13.5k
      if ((m->name = strdup(val)) == NULL) {
157
0
    free(m);
158
0
    goto bad;
159
0
      }
160
13.5k
  }
161
54.5k
  if (m->negated)
162
1.41k
      TAILQ_INSERT_TAIL(&negated_members, m, entries);
163
53.1k
  else
164
53.1k
      TAILQ_INSERT_TAIL(members, m, entries);
165
54.5k
    }
166
167
    /* Negated members take precedence so we insert them at the end. */
168
48.1k
    TAILQ_CONCAT(members, &negated_members, entries);
169
48.1k
    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
88.6k
{
180
88.6k
    union sudo_in_addr_un addr;
181
88.6k
    bool ret = false;
182
88.6k
    char *slash;
183
88.6k
    debug_decl(is_address, SUDOERS_DEBUG_LDAP);
184
185
    /* Check for mask, not currently parsed. */
186
88.6k
    if ((slash = strchr(host, '/')) != NULL)
187
988
  *slash = '\0';
188
189
88.6k
    if (inet_pton(AF_INET, host, &addr.ip4) == 1)
190
662
  ret = true;
191
87.9k
#ifdef HAVE_STRUCT_IN6_ADDR
192
87.9k
    else if (inet_pton(AF_INET6, host, &addr.ip6) == 1)
193
235
  ret = true;
194
88.6k
#endif
195
196
88.6k
    if (slash != NULL)
197
988
  *slash = '/';
198
199
88.6k
    debug_return_bool(ret);
200
88.6k
}
201
202
static struct member *
203
host_to_member(char *host)
204
93.3k
{
205
93.3k
    struct member *m;
206
93.3k
    debug_decl(host_to_member, SUDOERS_DEBUG_LDAP);
207
208
93.3k
    if ((m = calloc(1, sizeof(*m))) == NULL)
209
0
  goto oom;
210
93.3k
    m->negated = sudo_ldap_is_negated(&host);
211
93.3k
    switch (*host) {
212
443
    case '+':
213
443
  m->type = NETGROUP;
214
443
  break;
215
8.03k
    case 'A':
216
8.03k
  if (strcmp(host, "ALL") == 0) {
217
4.27k
      m->type = ALL;
218
4.27k
      break;
219
4.27k
  }
220
3.75k
  FALLTHROUGH;
221
88.6k
    default:
222
88.6k
  if (is_address(host)) {
223
897
      m->type = NTWKADDR;
224
87.7k
  } else {
225
87.7k
      m->type = WORD;
226
87.7k
  }
227
88.6k
  break;
228
93.3k
    }
229
93.3k
    if (m->type != ALL) {
230
89.0k
  if ((m->name = strdup(host)) == NULL)
231
0
      goto oom;
232
89.0k
    }
233
234
93.3k
    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
94.5k
{
249
94.5k
    const char *ep, *cp = cmnd;
250
94.5k
    struct command_digest *digest;
251
94.5k
    unsigned int digest_type = SUDO_DIGEST_INVALID;
252
94.5k
    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
94.5k
    if (cp[0] == 's' && cp[1] == 'h' && cp[2] == 'a') {
259
9.23k
  switch (cp[3]) {
260
2.94k
  case '2':
261
2.94k
      if (cp[4] == '2' && cp[5] == '4')
262
1.03k
    digest_type = SUDO_DIGEST_SHA224;
263
1.91k
      else if (cp[4] == '5' && cp[5] == '6')
264
718
    digest_type = SUDO_DIGEST_SHA256;
265
2.94k
      break;
266
4.30k
  case '3':
267
4.30k
      if (cp[4] == '8' && cp[5] == '4')
268
3.23k
    digest_type = SUDO_DIGEST_SHA384;
269
4.30k
      break;
270
1.44k
  case '5':
271
1.44k
      if (cp[4] == '1' && cp[5] == '2')
272
675
    digest_type = SUDO_DIGEST_SHA512;
273
1.44k
      break;
274
9.23k
  }
275
9.23k
  if (digest_type != SUDO_DIGEST_INVALID) {
276
5.66k
      cp += 6;
277
5.66k
      while (isblank((unsigned char)*cp))
278
194
    cp++;
279
5.66k
      if (*cp == ':') {
280
5.01k
    cp++;
281
5.01k
    while (isblank((unsigned char)*cp))
282
202
        cp++;
283
5.01k
    ep = cp;
284
10.7k
    while (*ep != '\0' && !isblank((unsigned char)*ep) && *ep != ',')
285
5.69k
        ep++;
286
5.01k
    if (isblank((unsigned char)*ep) || *ep == ',') {
287
1.95k
        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
1.95k
        digest->digest_type = digest_type;
293
1.95k
        digest->digest_str = strndup(cp, (size_t)(ep - cp));
294
1.95k
        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
1.95k
        while (isblank((unsigned char)*ep))
301
804
      ep++;
302
1.95k
        *endptr = (char *)ep;
303
1.95k
        sudo_debug_printf(SUDO_DEBUG_INFO,
304
1.95k
      "%s digest %s for %s",
305
1.95k
      digest_type_to_name(digest_type),
306
1.95k
      digest->digest_str, cp);
307
1.95k
        TAILQ_INSERT_TAIL(digests, digest, entries);
308
1.95k
        debug_return_int(1);
309
1.95k
    }
310
5.01k
      }
311
5.66k
  }
312
9.23k
    }
313
92.5k
    debug_return_int(0);
314
92.5k
}
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
93.0k
{
324
93.0k
    char *cp = *cmnd;
325
93.0k
    int rc;
326
93.0k
    debug_decl(sudo_ldap_extract_digests, SUDOERS_DEBUG_LDAP);
327
328
94.5k
    for (;;) {
329
94.5k
  rc = sudo_ldap_extract_digest(cp, &cp, digests);
330
94.5k
  if (rc != 1)
331
92.5k
      break;
332
333
  /* Check for additional digestspecs, separated by a comma. */
334
1.95k
  if (*cp != ',')
335
424
      break;
336
1.86k
  do {
337
1.86k
      cp++;
338
1.86k
  } while (isblank((unsigned char)*cp));
339
1.53k
    }
340
93.0k
    *cmnd = cp;
341
342
93.0k
    debug_return_bool(rc != -1);
343
93.0k
}
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
76.4k
{
355
76.4k
    struct cmndspec_list negated_cmnds = TAILQ_HEAD_INITIALIZER(negated_cmnds);
356
76.4k
    struct member_list negated_hosts = TAILQ_HEAD_INITIALIZER(negated_hosts);
357
76.4k
    struct cmndspec *prev_cmndspec = NULL;
358
76.4k
    struct privilege *priv;
359
76.4k
    struct member *m;
360
76.4k
    char *cmnd;
361
76.4k
    debug_decl(sudo_ldap_role_to_priv, SUDOERS_DEBUG_LDAP);
362
363
76.4k
    if ((priv = calloc(1, sizeof(*priv))) == NULL)
364
0
  goto oom;
365
76.4k
    TAILQ_INIT(&priv->hostlist);
366
76.4k
    TAILQ_INIT(&priv->cmndlist);
367
76.4k
    TAILQ_INIT(&priv->defaults);
368
369
76.4k
    priv->ldap_role = strdup(cn ? cn : "UNKNOWN");
370
76.4k
    if (priv->ldap_role == NULL)
371
0
  goto oom;
372
373
76.4k
    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
76.4k
    } else {
379
76.4k
  char *host;
380
169k
  while ((host = iter(&hosts)) != NULL) {
381
93.3k
      if ((m = host_to_member(host)) == NULL)
382
0
    goto oom;
383
93.3k
      if (m->negated)
384
2.84k
    TAILQ_INSERT_TAIL(&negated_hosts, m, entries);
385
90.5k
      else
386
90.5k
    TAILQ_INSERT_TAIL(&priv->hostlist, m, entries);
387
93.3k
  }
388
  /* Negated hosts take precedence so we insert them at the end. */
389
76.4k
  TAILQ_CONCAT(&priv->hostlist, &negated_hosts, entries);
390
76.4k
    }
391
392
    /*
393
     * Parse sudoCommands and add to cmndlist.
394
     */
395
169k
    while ((cmnd = iter(&cmnds)) != NULL) {
396
93.0k
  bool negated = sudo_ldap_is_negated(&cmnd);
397
93.0k
  struct sudo_command *c = NULL;
398
93.0k
  struct cmndspec *cmndspec;
399
400
  /* Allocate storage upfront. */
401
93.0k
  if ((cmndspec = calloc(1, sizeof(*cmndspec))) == NULL)
402
0
      goto oom;
403
93.0k
  if ((m = calloc(1, sizeof(*m))) == NULL) {
404
0
      free(cmndspec);
405
0
      goto oom;
406
0
  }
407
93.0k
  if ((c = calloc(1, sizeof(*c))) == NULL) {
408
0
      free(cmndspec);
409
0
      free(m);
410
0
      goto oom;
411
0
  }
412
93.0k
  m->name = (char *)c;
413
93.0k
  TAILQ_INIT(&c->digests);
414
415
  /* Negated commands have precedence so insert them at the end. */
416
93.0k
  if (negated)
417
1.11k
      TAILQ_INSERT_TAIL(&negated_cmnds, cmndspec, entries);
418
91.8k
  else
419
91.8k
      TAILQ_INSERT_TAIL(&priv->cmndlist, cmndspec, entries);
420
421
  /* Initialize cmndspec */
422
93.0k
  TAGS_INIT(&cmndspec->tags);
423
93.0k
  cmndspec->notbefore = UNSPEC;
424
93.0k
  cmndspec->notafter = UNSPEC;
425
93.0k
  cmndspec->timeout = UNSPEC;
426
93.0k
  cmndspec->cmnd = m;
427
428
93.0k
  if (prev_cmndspec != NULL) {
429
      /* Inherit values from prior cmndspec (common to the sudoRole). */
430
16.5k
      cmndspec->runasuserlist = prev_cmndspec->runasuserlist;
431
16.5k
      cmndspec->runasgrouplist = prev_cmndspec->runasgrouplist;
432
16.5k
      cmndspec->notbefore = prev_cmndspec->notbefore;
433
16.5k
      cmndspec->notafter = prev_cmndspec->notafter;
434
16.5k
      cmndspec->timeout = prev_cmndspec->timeout;
435
16.5k
      cmndspec->runchroot = prev_cmndspec->runchroot;
436
16.5k
      cmndspec->runcwd = prev_cmndspec->runcwd;
437
16.5k
      cmndspec->role = prev_cmndspec->role;
438
16.5k
      cmndspec->type = prev_cmndspec->type;
439
16.5k
      cmndspec->apparmor_profile = prev_cmndspec->apparmor_profile;
440
16.5k
      cmndspec->privs = prev_cmndspec->privs;
441
16.5k
      cmndspec->limitprivs = prev_cmndspec->limitprivs;
442
16.5k
      cmndspec->tags = prev_cmndspec->tags;
443
16.5k
      if (cmndspec->tags.setenv == IMPLIED)
444
1.73k
    cmndspec->tags.setenv = UNSPEC;
445
76.4k
  } else {
446
      /* Parse sudoRunAsUser / sudoRunAs */
447
76.4k
      if (runasusers != NULL) {
448
45.1k
    cmndspec->runasuserlist =
449
45.1k
        array_to_member_list(runasusers, iter);
450
45.1k
    if (cmndspec->runasuserlist == NULL)
451
0
        goto oom;
452
45.1k
      }
453
454
      /* Parse sudoRunAsGroup */
455
76.4k
      if (runasgroups != NULL) {
456
2.95k
    cmndspec->runasgrouplist =
457
2.95k
        array_to_member_list(runasgroups, iter);
458
2.95k
    if (cmndspec->runasgrouplist == NULL)
459
0
        goto oom;
460
2.95k
      }
461
462
      /* Parse sudoNotBefore / sudoNotAfter */
463
76.4k
      if (notbefore != NULL)
464
3.18k
    cmndspec->notbefore = parse_gentime(notbefore);
465
76.4k
      if (notafter != NULL)
466
2.93k
    cmndspec->notafter = parse_gentime(notafter);
467
468
      /* Parse sudoOptions. */
469
76.4k
      if (opts != NULL) {
470
6.20k
    char *opt, *source = NULL;
471
472
6.20k
    if (store_options) {
473
        /* Use sudoRole in place of file name in defaults. */
474
6.20k
        size_t slen = sizeof("sudoRole ") - 1 + strlen(priv->ldap_role);
475
6.20k
        if ((source = sudo_rcstr_alloc(slen)) == NULL)
476
0
      goto oom;
477
6.20k
        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
6.20k
    }
483
484
178k
    while ((opt = iter(&opts)) != NULL) {
485
172k
        char *var, *val;
486
172k
        int op;
487
488
172k
        op = sudo_ldap_parse_option(opt, &var, &val);
489
172k
        if (strcmp(var, "command_timeout") == 0 && val != NULL) {
490
10.9k
      if (cmndspec->timeout != UNSPEC) {
491
1.61k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
492
1.61k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
493
1.61k
      }
494
10.9k
      cmndspec->timeout = parse_timeout(val);
495
161k
        } else if (strcmp(var, "runchroot") == 0 && val != NULL) {
496
1.30k
      if (cmndspec->runchroot != NULL) {
497
913
          free(cmndspec->runchroot);
498
913
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
499
913
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
500
913
      }
501
1.30k
      if ((cmndspec->runchroot = strdup(val)) == NULL)
502
0
          break;
503
159k
        } else if (strcmp(var, "runcwd") == 0 && val != NULL) {
504
2.68k
      if (cmndspec->runcwd != NULL) {
505
1.93k
          free(cmndspec->runcwd);
506
1.93k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
507
1.93k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
508
1.93k
      }
509
2.68k
      if ((cmndspec->runcwd = strdup(val)) == NULL)
510
0
          break;
511
157k
        } else if (strcmp(var, "role") == 0 && val != NULL) {
512
1.32k
      if (cmndspec->role != NULL) {
513
872
          free(cmndspec->role);
514
872
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
515
872
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
516
872
      }
517
1.32k
      if ((cmndspec->role = strdup(val)) == NULL)
518
0
          break;
519
155k
        } else if (strcmp(var, "type") == 0 && val != NULL) {
520
1.70k
      if (cmndspec->type != NULL) {
521
1.35k
          free(cmndspec->type);
522
1.35k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
523
1.35k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
524
1.35k
      }
525
1.70k
      if ((cmndspec->type = strdup(val)) == NULL)
526
0
          break;
527
154k
        } else if (strcmp(var, "apparmor_profile") == 0 && val != NULL) {
528
1.59k
      if (cmndspec->apparmor_profile != NULL) {
529
1.11k
          free(cmndspec->apparmor_profile);
530
1.11k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
531
1.11k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
532
1.11k
      }
533
1.59k
      if ((cmndspec->apparmor_profile = strdup(val)) == NULL)
534
0
          break;
535
152k
        } else if (strcmp(var, "privs") == 0 && val != NULL) {
536
1.81k
      if (cmndspec->privs != NULL) {
537
1.17k
          free(cmndspec->privs);
538
1.17k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
539
1.17k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
540
1.17k
      }
541
1.81k
      if ((cmndspec->privs = strdup(val)) == NULL)
542
0
          break;
543
150k
        } else if (strcmp(var, "limitprivs") == 0 && val != NULL) {
544
2.42k
      if (cmndspec->limitprivs != NULL) {
545
1.33k
          free(cmndspec->limitprivs);
546
1.33k
          sudo_warnx(U_("duplicate sudoOption: %s%s%s"), var,
547
1.33k
        op == '+' ? "+=" : op == '-' ? "-=" : "=", val);
548
1.33k
      }
549
2.42k
      if ((cmndspec->limitprivs = strdup(val)) == NULL)
550
0
          break;
551
148k
        } else if (store_options) {
552
148k
      if (!append_default(var, val, op, source,
553
148k
          &priv->defaults)) {
554
0
          break;
555
0
      }
556
148k
        } 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
172k
    }
573
6.20k
    sudo_rcstr_delref(source);
574
6.20k
    if (opt != NULL) {
575
        /* Defer oom until we drop the ref on source. */
576
0
        goto oom;
577
0
    }
578
6.20k
      }
579
580
      /* So we can inherit previous values. */
581
76.4k
      prev_cmndspec = cmndspec;
582
76.4k
  }
583
584
  /* Fill in command member now that options have been processed. */
585
93.0k
  m->negated = negated;
586
93.0k
  if (!sudo_ldap_extract_digests(&cmnd, &c->digests))
587
0
      goto oom;
588
93.0k
  if (strcmp(cmnd, "ALL") == 0) {
589
3.07k
      if (cmndspec->tags.setenv == UNSPEC)
590
3.07k
    cmndspec->tags.setenv = IMPLIED;
591
3.07k
      m->type = ALL;
592
89.9k
  } else {
593
89.9k
      char *args = strpbrk(cmnd, " \t");
594
89.9k
      if (args != NULL) {
595
5.03k
    *args++ = '\0';
596
5.03k
    if ((c->args = strdup(args)) == NULL)
597
0
        goto oom;
598
5.03k
      }
599
89.9k
      if ((c->cmnd = strdup(cmnd)) == NULL)
600
0
    goto oom;
601
89.9k
      m->type = COMMAND;
602
89.9k
  }
603
93.0k
    }
604
    /* Negated commands take precedence so we insert them at the end. */
605
76.4k
    TAILQ_CONCAT(&priv->cmndlist, &negated_cmnds, entries);
606
607
76.4k
    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
}