1 /*
2 * Copyright 2004-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdbool.h>
13 #include <stdint.h> // uint32_t
14 #include <stdio.h>
15 #include <sys/types.h>
16 #include <pwd.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20
21 #include <libxml/tree.h> // xmlNode, etc.
22 #include <libxml/xmlstring.h> // xmlChar
23 #include <libxml/xpath.h> // xmlXPathObject, etc.
24
25 #include <crm/crm.h>
26 #include <crm/common/xml.h>
27 #include "crmcommon_private.h"
28
29 typedef struct {
30 enum pcmk__xml_flags mode;
31 gchar *xpath;
32 } xml_acl_t;
33
34 /*!
35 * \internal
36 * \brief Free an \c xml_acl_t object
37 *
38 * \param[in,out] data \c xml_acl_t object to free
39 *
40 * \note This is a \c GDestroyNotify function.
41 */
42 static void
43 free_acl(void *data)
44 {
45 xml_acl_t *acl = data;
46
47 if (acl == NULL) {
48 return;
49 }
50
51 g_free(acl->xpath);
52 free(acl);
53 }
54
55 /*!
56 * \internal
57 * \brief Free a list of \c xml_acl_t objects
58 *
59 * \param[in,out] acls List of \c xml_acl_t objects
60 */
61 void
62 pcmk__free_acls(GList *acls)
63 {
64 g_list_free_full(acls, free_acl);
65 }
66
67 /*!
68 * \internal
69 * \brief Get readable description of an ACL mode
70 *
71 * \param[in] mode ACL mode (one of \c pcmk__xf_acl_read,
72 * \c pcmk__xf_acl_write, \c pcmk__xf_acl_deny, or
73 * \c pcmk__xf_acl_create
74 *
75 * \return Static string describing \p mode, or \c "none" if \p mode is invalid
76 */
77 static const char *
78 acl_mode_text(enum pcmk__xml_flags mode)
79 {
80 switch (mode) {
81 case pcmk__xf_acl_read:
82 return "read";
83
84 case pcmk__xf_acl_create:
85 case pcmk__xf_acl_write:
86 return "read/write";
87
88 case pcmk__xf_acl_deny:
89 return "deny";
90
91 default:
92 return "none";
93 }
94 }
95
96 /*!
97 * \internal
98 * \brief Parse an ACL mode from a string
99 *
100 * \param[in] text String to parse
101 *
102 * \return ACL mode corresponding to \p text
103 */
104 static enum pcmk__xml_flags
105 parse_acl_mode(const char *text)
106 {
107 if (pcmk__str_eq(text, PCMK_VALUE_READ, pcmk__str_none)) {
108 return pcmk__xf_acl_read;
109 }
110
111 if (pcmk__str_eq(text, PCMK_VALUE_WRITE, pcmk__str_none)) {
112 return pcmk__xf_acl_write;
113 }
114
115 if (pcmk__str_eq(text, PCMK_VALUE_DENY, pcmk__str_none)) {
116 return pcmk__xf_acl_deny;
117 }
118
119 return pcmk__xf_none;
120 }
121
122 /*!
123 * \internal
124 * \brief Set a config warning if ACL permission specifiers are mismatched
125 *
126 * The schema requires exactly one of \c PCMK_XA_XPATH, \c PCMK_XA_REFERENCE,
127 * or \c PCMK_XA_OBJECT_TYPE. Additionally, \c PCMK_XA_ATTRIBUTE may be used
128 * only with \c PCMK_XA_OBJECT_TYPE.
129 *
130 * We've handled these in a very permissive and inconsistent manner thus far. To
131 * avoid breaking backward compatibility, the best we can do for now is to set
132 * configuration warnings and log how we will behave if the specifiers are set
133 * incorrectly.
134 *
135 * The caller has already ensured that at least one of \c PCMK_XA_XPATH,
136 * \c PCMK_XA_REFERENCE, or \c PCMK_XA_OBJECT_TYPE is set.
137 */
138 static void
139 warn_on_specifier_mismatch(const xmlNode *xml, const char *xpath,
140 const char *ref, const char *tag, const char *attr)
141 {
142 // @COMPAT Let's be more strict at a compatibility break... please...
143
144 const char *id = pcmk__s(pcmk__xe_id(xml), "without ID");
145 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
146 const char *parent_type = (const char *) xml->parent->name;
147
148 if ((xpath != NULL) && (ref == NULL) && (tag == NULL) && (attr == NULL)) {
149 return;
150 }
151
152 if ((xpath == NULL) && (ref != NULL) && (tag == NULL) && (attr == NULL)) {
153 return;
154 }
155
156 if ((xpath == NULL) && (ref == NULL) && (tag != NULL)) {
157 return;
158 }
159
160 // Remaining cases are not possible with schema validation enabled
161
162 if (xpath != NULL) {
163 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
164 "(in <%s> %s) has " PCMK_XA_XPATH " set along with "
165 PCMK_XA_REFERENCE ", " PCMK_XA_OBJECT_TYPE ", or "
166 PCMK_XA_ATTRIBUTE ". Using " PCMK_XA_XPATH " and "
167 "ignoring the rest.", id, parent_type, parent_id);
168 return;
169 }
170
171 // Log both of the below if appropriate
172
173 if ((tag == NULL) && (attr != NULL)) {
174 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
175 "(in <%s> %s) has " PCMK_XA_ATTRIBUTE " set without "
176 PCMK_XA_OBJECT_TYPE ". Using '*' for "
177 PCMK_XA_OBJECT_TYPE ".", id, parent_type, parent_id);
178 }
179
180 if (ref != NULL) {
181 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
182 "(in <%s> %s) has " PCMK_XA_REFERENCE " set along "
183 "with " PCMK_XA_OBJECT_TYPE " or "
184 PCMK_XA_ATTRIBUTE ". Using all of these criteria "
185 "together, but support may be removed in a future "
186 "release.", id, parent_type, parent_id);
187 }
188 }
189
190 /*!
191 * \internal
192 * \brief Create an ACL based on an ACL permission XML element
193 *
194 * The \c PCMK_XE_ACL_PERMISSION element should have already been validated for
195 * unrecoverable schema violations when this function is called. There may be
196 * recoverable violations, but the element should be well-formed enough to
197 * create an \c xml_acl_t.
198 *
199 * \param[in] xml \c PCMK_XE_ACL_PERMISSION element
200 * \param[in] mode One of \c pcmk__xf_acl_read, \c pcmk__xf_acl_write, or
201 * \c pcmk__xf_acl_deny
202 *
203 * \return Newly allocated ACL object (guaranteed not to be \c NULL)
204 *
205 * \note The caller is responsible for freeing the return value using
206 * \c free_acl().
207 */
208 static xml_acl_t *
209 create_acl(const xmlNode *xml, enum pcmk__xml_flags mode)
210 {
211 const char *tag = pcmk__xe_get(xml, PCMK_XA_OBJECT_TYPE);
212 const char *ref = pcmk__xe_get(xml, PCMK_XA_REFERENCE);
213 const char *xpath = pcmk__xe_get(xml, PCMK_XA_XPATH);
214 const char *attr = pcmk__xe_get(xml, PCMK_XA_ATTRIBUTE);
215
216 GString *buf = NULL;
217 xml_acl_t *acl = pcmk__assert_alloc(1, sizeof (xml_acl_t));
218
219 warn_on_specifier_mismatch(xml, xpath, ref, tag, attr);
220
221 acl->mode = mode;
222
223 if (xpath != NULL) {
224 acl->xpath = g_strdup(xpath);
225 return acl;
226 }
227
228 buf = g_string_sized_new(128);
229
230 g_string_append_printf(buf, "//%s", pcmk__s(tag, "*"));
231
232 if ((ref != NULL) && (attr != NULL)) {
233 // Not possible with schema validation enabled
234 g_string_append_printf(buf, "[@" PCMK_XA_ID "='%s' and @%s]", ref,
235 attr);
236
237 } else if (ref != NULL) {
238 g_string_append_printf(buf, "[@" PCMK_XA_ID "='%s']", ref);
239
240 } else if (attr != NULL) {
241 g_string_append_printf(buf, "[@%s]", attr);
242 }
243
244 acl->xpath = g_string_free(buf, FALSE);
245 return acl;
246 }
247
248 /*!
249 * \internal
250 * \brief Unpack a \c PCMK_XE_ACL_PERMISSION element to an \c xml_acl_t
251 *
252 * Append the new \c xml_acl_t object to a list.
253 *
254 * \param[in] xml Permission element to unpack
255 * \param[in,out] user_data List of ACLs to append to (<tt>GList **</tt>)
256 *
257 * \return \c pcmk_rc_ok (to keep iterating)
258 *
259 * \note The caller is responsible for freeing \p *user_data using
260 * \c pcmk__free_acls().
261 * \note This is used as a callback for \c pcmk__xe_foreach_child().
262 */
263 static int
264 unpack_acl_permission(xmlNode *xml, void *user_data)
265 {
266 GList **acls = user_data;
267 const char *id = pcmk__xe_id(xml);
268 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
269 const char *parent_type = (const char *) xml->parent->name;
270
271 const char *kind_s = pcmk__xe_get(xml, PCMK_XA_KIND);
272 enum pcmk__xml_flags kind = pcmk__xf_none;
273 xml_acl_t *acl = NULL;
274
275 if (id == NULL) {
276 // Not possible with schema validation enabled
277 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element in <%s> %s has "
278 "no " PCMK_XA_ID " attribute", parent_type,
279 parent_id);
280
281 // Set a value to use for logging and continue unpacking
282 id = "without ID";
283 }
284
285 if (kind_s == NULL) {
286 // Not possible with schema validation enabled
287 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
288 "(in <%s> %s) with no " PCMK_XA_KIND " attribute", id,
289 parent_type, parent_id);
290 return pcmk_rc_ok;
291 }
292
293 kind = parse_acl_mode(kind_s);
294 if (kind == pcmk__xf_none) {
295 // Not possible with schema validation enabled
296 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
297 "(in <%s> %s) with unknown ACL kind '%s'", id,
298 parent_type, parent_id, kind_s);
299 return pcmk_rc_ok;
300 }
301
302 if ((pcmk__xe_get(xml, PCMK_XA_OBJECT_TYPE) == NULL)
303 && (pcmk__xe_get(xml, PCMK_XA_REFERENCE) == NULL)
304 && (pcmk__xe_get(xml, PCMK_XA_XPATH) == NULL)) {
305
306 // Not possible with schema validation enabled
307 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
308 "(in <%s> %s) without selection criteria. Exactly one "
309 "of the following attributes is required: "
310 PCMK_XA_OBJECT_TYPE ", " PCMK_XA_REFERENCE ", "
311 PCMK_XA_XPATH ".", id, parent_type, parent_id);
312 return pcmk_rc_ok;
313 }
314
315 acl = create_acl(xml, kind);
316 *acls = g_list_append(*acls, acl);
317
318 pcmk__trace("Unpacked <" PCMK_XE_ACL_PERMISSION "> element %s "
319 "(in <%s> %s) with " PCMK_XA_KIND "='%s' as XPath '%s'", id,
320 parent_type, parent_id, kind_s, acl->xpath);
321
322 return pcmk_rc_ok;
323 }
324
325 /*!
326 * \internal
327 * \brief Get the ACL role whose ID matches a role reference
328 *
329 * If there are multiple matches (not allowed by the schema), return the first
330 * one for backward compatibility and set a config warning.
331 *
332 * \param[in] xml \c PCMK_XE_ROLE element (an ACL role reference)
333 *
334 * \return \c PCMK_XE_ACL_ROLE element whose \c PCMK_XA_ID attribute matches
335 * that of \p xml, or \c NULL if none is found
336 */
337 static xmlNode *
338 resolve_acl_role_ref(xmlNode *xml)
339 {
340 const char *id = pcmk__xe_id(xml);
341 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
342 const char *parent_type = (const char *) xml->parent->name;
343
344 xmlNode *result = NULL;
345 char *xpath = pcmk__assert_asprintf("//" PCMK_XE_ACL_ROLE
346 "[@" PCMK_XA_ID "='%s']", id);
347 xmlXPathObject *xpath_obj = pcmk__xpath_search(xml->doc, xpath);
348 const int num_results = pcmk__xpath_num_results(xpath_obj);
349
350 switch (num_results) {
351 case 0:
352 // Caller calls pcmk__config_err()
353 break;
354
355 case 1:
356 // Success
357 result = pcmk__xpath_result(xpath_obj, 0);
358 break;
359
360 default:
361 /* Not possible with schema validation enabled.
362 *
363 * @COMPAT At a compatibility break, use pcmk__xpath_find_one(),
364 * treat this as an error, and return NULL. For now, return the
365 * first match.
366 */
367 result = pcmk__xpath_result(xpath_obj, 0);
368 pcmk__config_warn("Multiple <" PCMK_XE_ACL_ROLE "> elements have "
369 PCMK_XA_ID "='%s'. Returning the first one for "
370 "<" PCMK_XE_ROLE "> in <%s> %s.", id, parent_type,
371 parent_id);
372 break;
373 }
374
375 free(xpath);
376 xmlXPathFreeObject(xpath_obj);
377 return result;
378 }
379
380 /*!
381 * \internal
382 * \brief Unpack an ACL role reference to a list of \c xml_acl_t
383 *
384 * Unpack a \c PCMK_XE_ROLE element within a \c PCMK_XE_ACL_TARGET or
385 * \c PCMK_XE_ACL_GROUP element. This element is a role reference. Its
386 * \c PCMK_XA_ID attribute is an IDREF; it must match the ID of a
387 * \c PCMK_XE_ACL_ROLE child of the \c PCMK_XE_ACLS element.
388 *
389 * The referenced \c PCMK_XE_ACL_ROLE contains zero or more
390 * \c PCMK_XE_ACL_PERMISSION children. Unpack those children to \c xml_acl_t
391 * objects and append them to a list.
392 *
393 * \param[in] xml Role reference element to unpack
394 * \param[in,out] acls List of ACLs to append to (\c NULL to start a new list)
395 *
396 * \return On success, \p acls with the new items appended, or a new list
397 * containing only the new items if \p acls is \c NULL. On failure,
398 * \p acls (unmodified).
399 *
400 * \note The caller is responsible for freeing the return value using
401 * \c pcmk__free_acls().
402 */
403 static GList *
404 unpack_acl_role_ref(xmlNode *xml, GList *acls)
405 {
406 const char *id = pcmk__xe_id(xml);
407 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
408 const char *parent_type = (const char *) xml->parent->name;
409
410 xmlNode *role = NULL;
411
412 if (id == NULL) {
413 // Not possible with schema validation enabled
414 pcmk__config_err("Ignoring <" PCMK_XE_ROLE "> element in <%s> %s with "
415 "no " PCMK_XA_ID " attribute", parent_type, parent_id);
416
417 // There is no reference role ID to match and unpack
418 return acls;
419 }
420
421 role = resolve_acl_role_ref(xml);
422 if (role == NULL) {
423 // Not possible with schema validation enabled
424 pcmk__config_err("Ignoring <" PCMK_XE_ROLE "> element %s in <%s> %s: "
425 "no <" PCMK_XE_ACL_ROLE "> with matching "
426 PCMK_XA_ID " found", id, parent_type, parent_id);
427 return acls;
428 }
429
430 pcmk__trace("Unpacking role '%s' referenced in <%s> element %s", id,
431 parent_type, parent_id);
432
433 pcmk__xe_foreach_child(role, PCMK_XE_ACL_PERMISSION, unpack_acl_permission,
434 &acls);
435 return acls;
436 }
437
438 /*!
439 * \internal
440 * \brief Unpack a child of an ACL target or group to a list of \c xml_acl_t
441 *
442 * \param[in] xml Child of a \c PCMK_XE_ACL_TARGET or
443 * \c PCMK_XE_ACL_GROUP
444 * \param[in,out] user_data List of ACLs to append to (<tt>GList **</tt>)
445 *
446 * \return \c pcmk_rc_ok (to keep iterating)
447 *
448 * \note The caller is responsible for freeing \p *user_data using
449 * \c pcmk__free_acls().
450 * \note This is used as a callback for \c pcmk__xe_foreach_child().
451 */
452 static int
453 unpack_acl_role_ref_or_perm(xmlNode *xml, void *user_data)
454 {
455 GList **acls = user_data;
456 const char *id = pcmk__s(pcmk__xe_id(xml), "without ID");
457 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
458 const char *parent_type = (const char *) xml->parent->name;
459
460 if (pcmk__xe_is(xml, PCMK_XE_ROLE)) {
461 *acls = unpack_acl_role_ref(xml, *acls);
462 return pcmk_rc_ok;
463 }
464
465 if (!pcmk__xe_is(xml, PCMK_XE_ACL_PERMISSION)) {
466 return pcmk_rc_ok;
467 }
468
469 /* Not possible with schema validation enabled.
470 *
471 * @COMPAT Drop this support at a compatibility break. A PCMK_XE_ACL_TARGET
472 * or PCMK_XE_ACL_GROUP element should contain only PCMK_XE_ROLE elements as
473 * children.
474 */
475 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s is a child of "
476 "<%s> %s. It should be a child of an "
477 "<" PCMK_XE_ACL_ROLE ">, and the parent should reference "
478 "that <" PCMK_XE_ACL_ROLE ">.", id, parent_type,
479 parent_id);
480
481 unpack_acl_permission(xml, acls);
482 return pcmk_rc_ok;
483 }
484
485 /*!
486 * \internal
487 * \brief Unpack an ACL target (user) element to a list of \c xml_acl_t
488 *
489 * \param[in] xml \c PCMK_XE_ACL_TARGET element to unpack
490 * \param[in,out] docpriv XML document private data whose \c acls field to
491 * append to
492 */
493 static void
494 unpack_acl_target(xmlNode *xml, xml_doc_private_t *docpriv)
495 {
496 const char *id = pcmk__s(pcmk__xe_get(xml, PCMK_XA_NAME), pcmk__xe_id(xml));
497
498 if (id == NULL) {
499 // Not possible with schema validation enabled
500 pcmk__config_err("Ignoring <" PCMK_XE_ACL_TARGET "> element with no "
501 PCMK_XA_NAME " or " PCMK_XA_ID " attribute");
502
503 // There is no user ID for the current ACL user to match
504 return;
505 }
506
507 if (!pcmk__str_eq(id, docpriv->acl_user, pcmk__str_none)) {
508 return;
509 }
510
511 pcmk__trace("Unpacking ACLs for user '%s'", id);
512 pcmk__xe_foreach_child(xml, NULL, unpack_acl_role_ref_or_perm,
513 &docpriv->acls);
514 }
515
516 /*!
517 * \internal
518 * \brief Unpack an ACL group element to a list of \c xml_acl_t
519 *
520 * \param[in] xml \c PCMK_XE_ACL_TARGET element to unpack
521 * \param[in,out] docpriv XML document private data whose \c acls field to
522 * append to
523 */
524 static void
525 unpack_acl_group(xmlNode *xml, xml_doc_private_t *docpriv)
526 {
527 const char *id = pcmk__s(pcmk__xe_get(xml, PCMK_XA_NAME), pcmk__xe_id(xml));
528
529 if (id == NULL) {
530 // Not possible with schema validation enabled
531 pcmk__config_err("Ignoring <" PCMK_XE_ACL_GROUP "> element with no "
532 PCMK_XA_NAME " or " PCMK_XA_ID " attribute");
533
534 // There is no group ID for the current ACL user to match
535 return;
536 }
537
538 if (!pcmk__is_user_in_group(docpriv->acl_user, id)) {
539 return;
540 }
541
542 pcmk__trace("Unpacking ACLs for group '%s' (user '%s')", id,
543 docpriv->acl_user);
544 pcmk__xe_foreach_child(xml, NULL, unpack_acl_role_ref_or_perm,
545 &docpriv->acls);
546 }
547
548 /*!
549 * \internal
550 * \brief Unpack an ACL target (user) or group element to a list of \c xml_acl_t
551 *
552 * \param[in] xml Element to unpack (\c PCMK_XE_ACL_TARGET
553 * or \c PCMK_XE_ACL_GROUP)
554 * \param[in,out] user_data XML document private data whose \c acls field to
555 * append to (<tt>xml_doc_private_t *</tt>)
556 *
557 * \return \c pcmk_rc_ok (to keep iterating)
558 *
559 * \note The caller is responsible for freeing \p user_data->acls using
560 * \c pcmk__free_acls().
561 * \note This is used as a callback for \c pcmk__xe_foreach_child().
562 */
563 static int
564 unpack_acl_target_or_group(xmlNode *xml, void *user_data)
565 {
566 if (pcmk__xe_is(xml, PCMK_XE_ACL_TARGET)) {
567 unpack_acl_target(xml, user_data);
568 return pcmk_rc_ok;
569 }
570
571 if (pcmk__xe_is(xml, PCMK_XE_ACL_GROUP)) {
572 unpack_acl_group(xml, user_data);
573 return pcmk_rc_ok;
574 }
575
576 return pcmk_rc_ok;
577 }
578
579 /*!
580 * \internal
581 * \brief Add a user's ACLs to a target XML document's private data
582 *
583 * Unpack the ACLs that apply to the user from the \c PCMK_XE_ACLS element in
584 * the source document to the \c acls list in the target document. If that list
585 * is already non-empty or if the user doesn't require ACLs, do nothing.
586 *
587 * Also set the target document's \c acl_user field to the given user.
588 *
589 * \param[in] source XML document whose ACL definitions to use
590 * \param[in,out] target XML document private data whose \c acls field to set
591 * \param[in] user User whose ACLs to unpack
592 */
593 void
594 pcmk__unpack_acls(xmlDoc *source, xml_doc_private_t *target, const char *user)
595 {
596 xmlNode *acls = NULL;
597
598 pcmk__assert(target != NULL);
599
600 if ((target->acls != NULL) || !pcmk_acl_required(user)) {
601 return;
602 }
603
604 pcmk__str_update(&target->acl_user, user);
605
606 acls = pcmk__xpath_find_one(source, "//" PCMK_XE_ACLS, PCMK__LOG_NEVER);
607 pcmk__xe_foreach_child(acls, NULL, unpack_acl_target_or_group, target);
608 }
609
610 /*!
611 * \internal
612 * \brief Set an ACL's mode on a node that matches its XPath expression
613 *
614 * Given a node that matches an ACL's XPath expression, get the corresponding
615 * XML element. Then set the ACL's mode flag in the private data of that
616 * element. For details, see comment in the body below, as well as the doc
617 * comment for \c pcmk__xpath_match_element().
618 *
619 * \param[in,out] match Node matched by the ACL's XPath expression
620 * \param[in] user_data ACL object (<tt>xml_acl_t *</tt>)
621 */
622 static void
623 apply_acl_to_match(xmlNode *match, void *user_data)
624 {
625 const xml_acl_t *acl = user_data;
626 xml_node_private_t *nodepriv = NULL;
627 GString *path = NULL;
628
629 /* @COMPAT If the ACL's XPath matches a node that is neither an element nor
630 * a document, we apply the ACL to the parent element rather than to the
631 * matched node. For example, if the XPath matches a "score" attribute, then
632 * it applies to every element that contains a "score" attribute. That is,
633 * the XPath expression "//@score" matches all attributes named "score", but
634 * we apply the ACL to all elements containing such an attribute.
635 *
636 * This behavior is incorrect from an XPath standpoint and is thus confusing
637 * and counterintuitive. The correct way to match all elements containing a
638 * "score" attribute is to use an XPath predicate: "// *[@score]". (Space
639 * inserted after slashes so that GCC doesn't throw an error about nested
640 * comments.)
641 *
642 * Additionally, if an XPath expression matches the entire document (for
643 * example, "/"), then the ACL applies to the document's root element if it
644 * exists.
645 *
646 * These behaviors should be changed so that the ACL applies to the nodes
647 * matched by the XPath expression, or so that it doesn't apply at all if
648 * applying an ACL to an attribute doesn't make sense.
649 *
650 * Unfortunately, we document in Pacemaker Explained that matching
651 * attributes is a valid way to match elements: "Attributes may be specified
652 * in the XPath to select particular elements, but the permissions apply to
653 * the entire element."
654 *
655 * So we have to keep this behavior at least until a compatibility break.
656 * Even then, it's not feasible in the general case to transform such XPath
657 * expressions using XSLT.
658 */
659 match = pcmk__xpath_match_element(match);
660 if (match == NULL) {
661 return;
662 }
663
664 nodepriv = match->_private;
665 pcmk__set_xml_flags(nodepriv, acl->mode);
666
667 path = pcmk__element_xpath(match);
668 pcmk__trace("Applied %s ACL to %s matched by %s", acl_mode_text(acl->mode),
669 path->str, acl->xpath);
670 g_string_free(path, TRUE);
671 }
672
673 /*!
674 * \internal
675 * \brief Apply an ACL to each matching node in an XML document
676 *
677 * See \c apply_acl_to_match() for details on applying the ACL.
678 *
679 * \param[in,out] data ACL object to apply (<tt>xml_acl_t *</tt>)
680 * \param[in,out] user_data XML document to match against (<tt>xmlDoc *</tt>)
681 */
682 static void
683 apply_acl_to_doc(void *data, void *user_data)
684 {
685 xml_acl_t *acl = data;
686 xmlDoc *doc = user_data;
687
688 pcmk__xpath_foreach_result(doc, acl->xpath, apply_acl_to_match, acl);
689 }
690
691 /*!
692 * \internal
693 * \brief Apply all of an XML document's ACLs
694 *
695 * For each ACL in the document's \c acls list, search the document for nodes
696 * that match the ACL's XPath expression. Then apply the ACL to each matching
697 * node.
698 *
699 * See \c apply_acl_to_doc() and \c apply_acl_to_match() for details.
700 *
701 * \param[in,out] doc XML document
702 */
703 void
704 pcmk__apply_acls(xmlDoc *doc)
705 {
706 xml_doc_private_t *docpriv = NULL;
707
708 pcmk__assert(doc != NULL);
709 docpriv = doc->_private;
710
711 if (!pcmk__xml_doc_all_flags_set(doc, pcmk__xf_acl_enabled)) {
712 return;
713 }
714
715 g_list_foreach(docpriv->acls, apply_acl_to_doc, doc);
716 }
717
718 /*!
719 * \internal
720 * \brief Fetch a user's ACLs from a source document and apply them to a target
721 *
722 * Unpack the given user's ACLs from the \c PCMK_XE_ACLS element in the source
723 * document to the \c acls list in the target document. Then set the target
724 * document's \c pcmk__xf_acl_enabled flag and apply the unpacked ACLs.
725 *
726 * \param[in] source XML document whose ACL definitions to use
727 * \param[in,out] target XML document to apply ACLs to
728 * \param[in] user User whose ACLs to apply
729 */
730 void
731 pcmk__enable_acls(xmlDoc *source, xmlDoc *target, const char *user)
732 {
733 if (target == NULL) {
734 return;
735 }
736 pcmk__unpack_acls(source, target->_private, user);
737 pcmk__xml_doc_set_flags(target, pcmk__xf_acl_enabled);
738 pcmk__apply_acls(target);
739 }
740
741 /*!
742 * \internal
743 * \brief Check whether a flag group allows the requested ACL access
744 *
745 * At most one ACL mode flag should be set in the flag group, but this function
746 * defines an order of precedence if multiple flags are set.
747 *
748 * \param[in] flags Group of <tt>enum pcmk__xml_flags</tt>
749 * \param[in] mode Requested access type (one of \c pcmk__xf_acl_read,
750 * \c pcmk__xf_acl_write, or \c pcmk__xf_acl_create)
751 *
752 * \return \c true if \p flags allows the access type in \p mode, or \c false
753 * otherwise
754 *
755 * \note \c pcmk__xf_acl_deny is an allowed value for \p mode, but it would make
756 * no sense. This function always returns \c false if \c pcmk__xf_acl_deny
757 * is set in \p flags.
758 */
759 static bool
760 is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode)
761 {
762 if (pcmk__is_set(flags, pcmk__xf_acl_deny)) {
763 // All access is denied
764 return false;
765 }
766
767 switch (mode) {
768 case pcmk__xf_acl_read:
769 // Write access provides read access
770 return pcmk__any_flags_set(flags,
771 pcmk__xf_acl_read|pcmk__xf_acl_write);
772
773 case pcmk__xf_acl_write:
774 return pcmk__is_set(flags, pcmk__xf_acl_write);
775
776 case pcmk__xf_acl_create:
777 /* Write access provides create access.
778 *
779 * @TODO Why does the \c pcmk__xf_created flag provide create
780 * access? This was introduced by commit e2ed85fe.
781 */
782 return pcmk__any_flags_set(flags,
783 pcmk__xf_acl_write|pcmk__xf_created);
784
785 default:
786 // Invalid mode
787 return false;
788 }
789 }
790
791 /*!
792 * \internal
793 * \brief Check whether an XML attribute's name is \c PCMK_XA_ID
794 *
795 * \param[in] attr Attribute to check
796 * \param[in] user_data Ignored
797 *
798 * \return \c true if the attribute's name is \c PCMK_XA_ID, or \c false
799 * otherwise
800 *
801 * \note This is compatible with \c pcmk__xe_foreach_const_attr().
802 */
803 static bool
804 attr_is_id(const xmlAttr *attr, void *user_data)
805 {
806 return pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none);
807 }
808
809 /*!
810 * \internal
811 * \brief Check whether an XML attribute's name is not \c PCMK_XA_ID
812 *
813 * \param[in] attr Attribute to check
814 * \param[in] user_data Ignored
815 *
816 * \return \c true if the attribute's name is not \c PCMK_XA_ID, or \c false
817 * otherwise
818 *
819 * \note This is compatible with \c pcmk__xe_remove_matching_attrs().
820 */
821 static bool
822 attr_is_not_id(const xmlAttr *attr, void *user_data)
823 {
824 return !attr_is_id(attr, user_data);
825 }
826
827 /*!
828 * \internal
829 * \brief Filter out nodes that are unreadable by the current ACL user
830 *
831 * Access or denial via ACLs is inherited, with more specific ACLs (those that
832 * match the node directly or match a more recent ancestor) taking precedence
833 * over less specific ones (those that match a less recent ancestor). Access is
834 * denied by default, if no ACL matches a node or any of its ancestors.
835 *
836 * For each node in the tree, check whether any ACL matched the node. If so,
837 * then use that ACL for the current node. If not, then the current node
838 * inherits read access or denial from its parent.
839 *
840 * Filter each child. Each child inherits read access or denial from the current
841 * node, unless an ACL matched the child directly.
842 *
843 * If the current node is readable, don't modify it. If it's unreadable but has
844 * at least one readable descendant, then remove all of its attributes other
845 * than \c PCMK_XA_ID. If it's unreadable and has no readable descendants,
846 * remove it.
847 *
848 * \param[in,out] xml XML tree (will be set to \c NULL if
849 * freed)
850 * \param[in] in_readable_context If \c true, the parent of \p xml is
851 * readable
852 *
853 * \note This function assumes that \c pcmk__apply_acls() has already been
854 * called for \p *xml->doc.
855 * \note This function is recursive.
856 */
857 static void
858 filter_unreadable_nodes(xmlNode **xml, bool in_readable_context)
859 {
|
(1) Event dereference: |
Dereferencing pointer "*xml". |
860 const xml_node_private_t *nodepriv = (*xml)->_private;
861 bool direct = false;
862 const char *how = NULL;
863 const char *name = (const char *) (*xml)->name;
864 const char *id = pcmk__s(pcmk__xe_id(*xml), "(unset)");
865 xmlNode *child = NULL;
866
867 /* If an ACL matched xml directly, update the context for xml and its
868 * descendants. Otherwise, keep the access that we inherited.
869 */
870 if (is_mode_allowed(nodepriv->flags, pcmk__xf_acl_read)) {
871 in_readable_context = true;
872 direct = true;
873
874 } else if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
875 in_readable_context = false;
876 direct = true;
877 }
878
879 if (direct) {
880 how = "directly";
881
882 } else if (*xml == xmlDocGetRootElement((*xml)->doc)) {
883 how = "by default";
884
885 } else {
886 how = "by inheritance";
887 }
888
889 pcmk__trace("ACLs %s read access to %s[@" PCMK_XA_ID "='%s'] %s",
890 (in_readable_context? "allow" : "deny"), name, id, how);
891
892 // Filter children recursively
893 child = pcmk__xml_first_child(*xml);
894 while (child != NULL) {
895 xmlNode *next = pcmk__xml_next(child);
896
897 filter_unreadable_nodes(&child, in_readable_context);
898 child = next;
899 }
900
901 if (in_readable_context) {
902 // This node is readable, either directly or by inheritance
903 return;
904 }
905
906 if ((*xml)->children != NULL) {
907 /* At least one descendant is readable, but this node is not.
908 *
909 * Remove all attributes except PCMK_XA_ID. Keep this node as a bare
910 * "scaffolding" element for structure, so that the path from the root
911 * to any readable descendant remains intact. Removing this node would
912 * also remove its readable descendants.
913 */
914 pcmk__xe_remove_matching_attrs(*xml, true, attr_is_not_id, NULL);
915 return;
916 }
917
918 // This node and all its descendants are unreadable, so remove it
919 // coverity[REVERSE_INULL : FALSE] Doesn't understand g_clear_pointer
920 g_clear_pointer(xml, pcmk__xml_free);
921 }
922
923 /*!
924 * \internal
925 * \brief Copy XML, filtering out portions that are unreadable based on ACLs
926 *
927 * Access or denial via ACLs is inherited, with more specific ACLs (those that
928 * match the node directly or match a more recent ancestor) taking precedence
929 * over less specific ones (those that match a less recent ancestor). Access is
930 * denied by default, if no ACL matches a node or any of its ancestors.
931 *
932 * If the user's ACLs grant read access to a node (either directly or through
933 * the most recent ancestor node matched by an ACL), then that node is kept
934 * intact.
935 *
936 * If the user's ACLs deny read access to a node (either directly, through the
937 * most recent ancestor node matched by an ACL, or by default), then:
938 * - If the current node has at least one readable descendant, then all of the
939 * current node's attributes are removed other than \c PCMK_XA_ID.
940 * - Otherwise, the current node is removed.
941 *
942 * \param[in] user User whose ACLs to use
943 * \param[in] acl_source XML document whose ACL definitions to use
944 * \param[in] xml XML to copy
945 *
946 * \return Newly allocated ACL-filtered copy of \p xml, or \c NULL if the entire
947 * document is filtered out
948 *
949 * \note The caller is responsible for freeing the return value using
950 * \c pcmk__xml_free().
951 */
952 xmlNode *
953 pcmk__acl_filtered_copy(const char *user, xmlDoc *acl_source, xmlNode *xml)
954 {
955 xmlNode *result = NULL;
956 xml_doc_private_t *docpriv = NULL;
957
958 pcmk__assert((acl_source != NULL) && (xml != NULL));
959
960 result = pcmk__xml_copy(NULL, xml);
961
962 if (!pcmk_acl_required(user)) {
963 // Return an unfiltered copy
964 return result;
965 }
966
967 pcmk__enable_acls(acl_source->doc, result->doc, user);
968
969 docpriv = result->doc->_private;
970 if (docpriv->acls == NULL) {
971 pcmk__trace("User '%s' without ACLs denied access to entire XML "
972 "document", user);
973 pcmk__xml_free(result);
974 return NULL;
975 }
976
977 pcmk__trace("Filtering XML copy using user '%s' ACLs", user);
978 filter_unreadable_nodes(&result, false);
979
|
CID (unavailable; MK=4b36ea9158d80f8db1136adae3497ebd) (#1 of 1): Dereference before null check (REVERSE_INULL): |
|
(2) Event check_after_deref: |
Null-checking "result" suggests that it may be null, but it has already been dereferenced on all paths leading to the check. |
| Also see events: |
[deref_ptr_in_call] |
980 if (result == NULL) {
981 // Entire document was freed, so don't free docpriv->acls here
982 pcmk__trace("ACLs deny user '%s' access to entire XML document", user);
983 return NULL;
984 }
985
986 g_clear_pointer(&docpriv->acls, pcmk__free_acls);
987 return result;
988 }
989
990 /*!
991 * \internal
992 * \brief Check whether creation of an XML element is implicitly allowed
993 *
994 * Check whether XML is a "scaffolding" element whose creation is implicitly
995 * allowed regardless of ACLs (that is, it is not in the ACL section and has
996 * no attributes other than \c PCMK_XA_ID).
997 *
998 * \param[in] xml XML element to check
999 *
1000 * \return \c true if XML element is implicitly allowed, or \c false otherwise
1001 */
1002 static bool
1003 implicitly_allowed(const xmlNode *xml)
1004 {
1005 if (!pcmk__xe_foreach_const_attr(xml, attr_is_id, NULL)) {
1006 return false;
1007 }
1008
1009 /* Creation is not implicitly allowed for a descendant of PCMK_XE_ACLS, but
1010 * it may be for PCMK_XE_ACLS itself. Start checking at xml->parent and walk
1011 * up the tree.
1012 */
1013 for (xml = xml->parent; xml != NULL; xml = xml->parent) {
1014 if (pcmk__xe_is(xml, PCMK_XE_ACLS)) {
1015 return false;
1016 }
1017 }
1018
1019 return true;
1020 }
1021
1022 /*!
1023 * \internal
1024 * \brief Check whether ACLs allow creation of an XML node
1025 *
1026 * "Scaffolding" elements (those that aren't in the ACLs section and don't have
1027 * any attributes other than \c PCMK_XA_ID) are always allowed.
1028 *
1029 * \param[in,out] xml XML node
1030 *
1031 * \return \c true \p xml is newly created and ACLs disallow its creation, or
1032 * \c false otherwise
1033 */
1034 static bool
1035 check_creation_disallowed(xmlNode *xml)
1036 {
1037 const char *type = (const char *) xml->name;
1038 const char *id = pcmk__s(pcmk__xe_id(xml), "<unset>");
1039 xml_node_private_t *nodepriv = xml->_private;
1040
1041 if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
1042 return false;
1043 }
1044
1045 if (implicitly_allowed(xml)) {
1046 pcmk__trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\" "
1047 "is implicitly allowed", type, id);
1048 return false;
1049 }
1050
1051 if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
1052 pcmk__trace("ACLs allow creation of <%s> with " PCMK_XA_ID "=\"%s\"",
1053 type, id);
1054 return false;
1055 }
1056
1057 pcmk__trace("ACLs disallow creation of <%s> with " PCMK_XA_ID "=\"%s\"",
1058 type, id);
1059 return true;
1060 }
1061
1062 /*!
1063 * \internal
1064 * \brief Remove XML nodes created in violation of ACLs
1065 *
1066 * Given an XML tree, free all nodes created in violation of ACLs, with the
1067 * exception of allowing "scaffolding" elements (those that aren't in the ACLs
1068 * section and don't have any attributes other than \c PCMK_XA_ID).
1069 *
1070 * \param[in,out] xml XML tree
1071 */
1072 void
1073 pcmk__check_creation_acls(xmlNode *xml)
1074 {
1075 pcmk__xml_tree_foreach_remove(xml, check_creation_disallowed);
1076 }
1077
1078 /*!
1079 * \brief Check whether or not an XML node is ACL-denied
1080 *
1081 * \param[in] xml node to check
1082 *
1083 * \return true if XML node exists and is ACL-denied, false otherwise
1084 */
1085 bool
1086 xml_acl_denied(const xmlNode *xml)
1087 {
1088 if (xml && xml->doc && xml->doc->_private){
1089 xml_doc_private_t *docpriv = xml->doc->_private;
1090
1091 return pcmk__is_set(docpriv->flags, pcmk__xf_acl_denied);
1092 }
1093 return false;
1094 }
1095
1096 void
1097 xml_acl_disable(xmlNode *xml)
1098 {
1099 if ((xml != NULL)
1100 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) {
1101
1102 xml_doc_private_t *docpriv = xml->doc->_private;
1103
1104 /* Catch anything that was created but shouldn't have been */
1105 pcmk__apply_acls(xml->doc);
1106
1107 /* Be sure not to free xml itself.
1108 *
1109 * @TODO Maybe we should free xml if it's newly created and the creation
1110 * is disallowed, but we would need a way to inform the caller. This is
1111 * public API.
1112 */
1113 xml = pcmk__xml_first_child(xml);
1114 while (xml != NULL) {
1115 xmlNode *next = pcmk__xml_next(xml);
1116
1117 pcmk__check_creation_acls(xml);
1118 xml = next;
1119 }
1120 pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
1121 }
1122 }
1123
1124 /*!
1125 * \internal
1126 * \brief Deny access to an XML tree's document based on ACLs
1127 *
1128 * \param[in,out] xml XML tree
1129 * \param[in] attr_name Name of attribute being accessed in \p xml (for
1130 * logging only)
1131 * \param[in] prefix Prefix describing ACL that denied access (for
1132 * logging only)
1133 * \param[in] user User accessing \p xml (for logging only)
1134 * \param[in] mode Access mode (for logging only)
1135 */
1136 #define check_acl_deny(xml, attr_name, prefix, user, mode) do { \
1137 xmlNode *tree = xml; \
1138 \
1139 pcmk__xml_doc_set_flags(tree->doc, pcmk__xf_acl_denied); \
1140 pcmk__if_tracing( \
1141 { \
1142 GString *xpath = pcmk__element_xpath(tree); \
1143 \
1144 if ((attr_name) != NULL) { \
1145 pcmk__g_strcat(xpath, "[@", attr_name, "]", NULL); \
1146 } \
1147 qb_log_from_external_source(__func__, __FILE__, \
1148 "%sACL denies user '%s' %s " \
1149 "access to %s", \
1150 LOG_TRACE, __LINE__, 0 , \
1151 prefix, user, \
1152 acl_mode_text(mode), \
1153 xpath->str); \
1154 g_string_free(xpath, TRUE); \
1155 }, \
1156 {} \
1157 ); \
1158 } while (0)
1159
1160 bool
1161 pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode)
1162 {
1163 xml_doc_private_t *docpriv = NULL;
1164
1165 pcmk__assert((xml != NULL) && (xml->doc->_private != NULL));
1166
1167 if (!pcmk__xml_doc_all_flags_set(xml->doc,
1168 pcmk__xf_tracking|pcmk__xf_acl_enabled)) {
1169 return true;
1170 }
1171
1172 docpriv = xml->doc->_private;
1173 if (docpriv->acls == NULL) {
1174 check_acl_deny(xml, attr_name, "Lack of ", docpriv->acl_user, mode);
1175 return false;
1176 }
1177
1178 /* Walk the tree upwards looking for xml_acl_* flags
1179 * - Creating an attribute requires write permissions for the node
1180 * - Creating a child requires write permissions for the parent
1181 */
1182
1183 if (attr_name != NULL) {
1184 xmlAttr *attr = xmlHasProp(xml, (const xmlChar *) attr_name);
1185
1186 if ((attr != NULL) && (mode == pcmk__xf_acl_create)) {
1187 mode = pcmk__xf_acl_write;
1188 }
1189 }
1190
1191 for (const xmlNode *parent = xml;
1192 (parent != NULL) && (parent->_private != NULL);
1193 parent = parent->parent) {
1194
1195 const xml_node_private_t *nodepriv = parent->_private;
1196
1197 if (is_mode_allowed(nodepriv->flags, mode)) {
1198 return true;
1199 }
1200
1201 if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
1202 const char *pfx = (parent != xml)? "Parent " : "";
1203
1204 check_acl_deny(xml, attr_name, pfx, docpriv->acl_user, mode);
1205 return false;
1206 }
1207 }
1208
1209 check_acl_deny(xml, attr_name, "Default ", docpriv->acl_user, mode);
1210 return false;
1211 }
1212
1213 /*!
1214 * \brief Check whether ACLs are required for a given user
1215 *
1216 * \param[in] User name to check
1217 *
1218 * \return true if the user requires ACLs, false otherwise
1219 */
1220 bool
1221 pcmk_acl_required(const char *user)
1222 {
1223 if (pcmk__str_empty(user)) {
1224 pcmk__trace("ACLs not required because no user set");
1225 return false;
1226
1227 } else if (pcmk__is_privileged(user)) {
1228 pcmk__trace("ACLs not required for privileged user %s", user);
1229 return false;
1230 }
1231 pcmk__trace("ACLs required for %s", user);
1232 return true;
1233 }
1234
1235 char *
1236 pcmk__uid2username(uid_t uid)
1237 {
1238 struct passwd *pwent = NULL;
1239
1240 errno = 0;
1241 pwent = getpwuid(uid);
1242
1243 if (pwent == NULL) {
1244 pcmk__err("Cannot get name from password database for user ID %lld: %s",
1245 (long long) uid,
1246 ((errno != 0)? strerror(errno) : "No matching entry found"));
1247 return NULL;
1248 }
1249
1250 return pcmk__str_copy(pwent->pw_name);
1251 }
1252
1253 /*!
1254 * \internal
1255 * \brief Set the ACL user field properly on an XML request
1256 *
1257 * Multiple user names are potentially involved in an XML request: the effective
1258 * user of the current process; the user name known from an IPC client
1259 * connection; and the user name obtained from the request itself, whether by
1260 * the current standard XML attribute name or an older legacy attribute name.
1261 * This function chooses the appropriate one that should be used for ACLs, sets
1262 * it in the request (using the standard attribute name, and the legacy name if
1263 * given), and returns it.
1264 *
1265 * \param[in,out] request XML request to update
1266 * \param[in] field Alternate name for ACL user name XML attribute
1267 * \param[in] peer_user User name as known from IPC connection
1268 *
1269 * \return ACL user name actually used
1270 */
1271 const char *
1272 pcmk__update_acl_user(xmlNode *request, const char *field,
1273 const char *peer_user)
1274 {
1275 static const char *effective_user = NULL;
1276 const char *requested_user = NULL;
1277 const char *user = NULL;
1278
1279 if (effective_user == NULL) {
1280 effective_user = pcmk__uid2username(geteuid());
1281 if (effective_user == NULL) {
1282 effective_user = pcmk__str_copy("#unprivileged");
1283 pcmk__err("Unable to determine effective user, assuming "
1284 "unprivileged for ACLs");
1285 }
1286 }
1287
1288 requested_user = pcmk__xe_get(request, PCMK__XA_ACL_TARGET);
1289 if (requested_user == NULL) {
1290 /* Currently, different XML attribute names are used for the ACL user in
1291 * different contexts (PCMK__XA_ATTR_USER, PCMK__XA_CIB_USER, etc.).
1292 * The caller may specify that name as the field argument.
1293 *
1294 * @TODO Standardize on PCMK__XA_ACL_TARGET and eventually drop the
1295 * others once rolling upgrades from versions older than that are no
1296 * longer supported.
1297 */
1298 requested_user = pcmk__xe_get(request, field);
1299 }
1300
1301 if (!pcmk__is_privileged(effective_user)) {
1302 /* We're not running as a privileged user, set or overwrite any existing
1303 * value for PCMK__XA_ACL_TARGET
1304 */
1305 user = effective_user;
1306
1307 } else if (peer_user == NULL && requested_user == NULL) {
1308 /* No user known or requested, use 'effective_user' and make sure one is
1309 * set for the request
1310 */
1311 user = effective_user;
1312
1313 } else if (peer_user == NULL) {
1314 /* No user known, trusting 'requested_user' */
1315 user = requested_user;
1316
1317 } else if (!pcmk__is_privileged(peer_user)) {
1318 /* The peer is not a privileged user, set or overwrite any existing
1319 * value for PCMK__XA_ACL_TARGET
1320 */
1321 user = peer_user;
1322
1323 } else if (requested_user == NULL) {
1324 /* Even if we're privileged, make sure there is always a value set */
1325 user = peer_user;
1326
1327 } else {
1328 /* Legal delegation to 'requested_user' */
1329 user = requested_user;
1330 }
1331
1332 // This requires pointer comparison, not string comparison
1333 if (user != pcmk__xe_get(request, PCMK__XA_ACL_TARGET)) {
1334 pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user);
1335 }
1336
1337 if ((field != NULL) && (user != pcmk__xe_get(request, field))) {
1338 pcmk__xe_set(request, field, user);
1339 }
1340
1341 return requested_user;
1342 }
1343
1344 // Deprecated functions kept only for backward API compatibility
1345 // LCOV_EXCL_START
1346
1347 #include <crm/common/acl_compat.h>
1348 #include <crm/common/xml_compat.h>
1349
1350 bool
1351 xml_acl_enabled(const xmlNode *xml)
1352 {
1353 if (xml && xml->doc && xml->doc->_private){
1354 xml_doc_private_t *docpriv = xml->doc->_private;
1355
1356 return pcmk__is_set(docpriv->flags, pcmk__xf_acl_enabled);
1357 }
1358 return false;
1359 }
1360
1361 bool
1362 xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
1363 xmlNode **result)
1364 {
1365 if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL)
1366 || !pcmk_acl_required(user)) {
1367
1368 return false;
1369 }
1370
1371 *result = pcmk__acl_filtered_copy(user, acl_source->doc, xml);
1372 return true;
1373 }
1374
1375 // LCOV_EXCL_STOP
1376 // End deprecated API
1377