/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 | } |