1    	/*
2    	 * Copyright 2004-2025 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(gpointer data, gpointer 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  	    if (pcmk__is_set(flags, mode)) {
768  	        // The access we requested is explicitly allowed
769  	        return true;
770  	    }
771  	
772  	    if ((mode == pcmk__xf_acl_read)
773  	        && pcmk__is_set(flags, pcmk__xf_acl_write)) {
774  	
775  	        // Write access provides read access
776  	        return true;
777  	    }
778  	
779  	    if ((mode == pcmk__xf_acl_create)
780  	        && pcmk__any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_created)) {
781  	
782  	        /* Write access provides create access.
783  	         *
784  	         * @TODO Why does the \c pcmk__xf_created flag provide create access?
785  	         * This was introduced by commit e2ed85fe.
786  	         */
787  	        return true;
788  	    }
789  	
790  	    return false;
791  	}
792  	
793  	/*!
794  	 * \internal
795  	 * \brief Check whether an XML attribute's name is not \c PCMK_XA_ID
796  	 *
797  	 * \param[in] attr       Attribute to check
798  	 * \param[in] user_data  Ignored
799  	 *
800  	 * \return \c true if the attribute's name is not \c PCMK_XA_ID, or \c false
801  	 *         otherwise
802  	 *
803  	 * \note This is compatible with \c pcmk__xe_remove_matching_attrs().
804  	 */
805  	static bool
806  	attr_is_not_id(xmlAttr *attr, void *user_data)
807  	{
808  	    return !pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none);
809  	}
810  	
811  	/*!
812  	 * \internal
813  	 * \brief Rid XML tree of all unreadable nodes and node properties
814  	 *
815  	 * \param[in,out] xml   Root XML node to be purged of attributes
816  	 *
817  	 * \return true if this node or any of its children are readable
818  	 *         if false is returned, xml will be freed
819  	 *
820  	 * \note This function is recursive
821  	 */
822  	static bool
823  	purge_xml_attributes(xmlNode *xml)
824  	{
825  	    xmlNode *child = NULL;
826  	    bool readable_children = false;
827  	    xml_node_private_t *nodepriv = xml->_private;
828  	
829  	    if (is_mode_allowed(nodepriv->flags, pcmk__xf_acl_read)) {
830  	        pcmk__trace("%s[@" PCMK_XA_ID "=%s] is readable", xml->name,
831  	                    pcmk__xe_id(xml));
832  	        return true;
833  	    }
834  	
835  	    pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL);
836  	
837  	    child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
838  	    while (child != NULL) {
839  	        xmlNode *tmp = child;
840  	
841  	        child = pcmk__xe_next(child, NULL);
842  	
843  	        if (purge_xml_attributes(tmp)) {
844  	            readable_children = true;
845  	        }
846  	    }
847  	
848  	    if (!readable_children) {
849  	        // Nothing readable under here, so purge completely
850  	        pcmk__xml_free(xml);
851  	    }
852  	    return readable_children;
853  	}
854  	
855  	/*!
856  	 * \brief Copy ACL-allowed portions of specified XML
857  	 *
858  	 * \param[in]  user        Username whose ACLs should be used
859  	 * \param[in]  acl_source  XML containing ACLs
860  	 * \param[in]  xml         XML to be copied
861  	 * \param[out] result      Copy of XML portions readable via ACLs
862  	 *
863  	 * \return \c true if \p acl_source and \p xml are non-<tt>NULL</tt> and ACLs
864  	 *         are required for \p user, or \c false otherwise
865  	 *
866  	 * \note If this returns true, caller should use \p result rather than \p xml
867  	 */
868  	bool
869  	xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
870  	                      xmlNode **result)
871  	{
872  	    xmlNode *target = NULL;
873  	    xml_doc_private_t *docpriv = NULL;
874  	
875  	    *result = NULL;
(1) Event path: Condition "acl_source == NULL", taking false branch.
(2) Event path: Condition "acl_source->doc == NULL", taking false branch.
(3) Event path: Condition "xml == NULL", taking false branch.
(4) Event path: Condition "!pcmk_acl_required(user)", taking false branch.
876  	    if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL)
877  	        || !pcmk_acl_required(user)) {
878  	
879  	        return false;
880  	    }
881  	
882  	    target = pcmk__xml_copy(NULL, xml);
883  	    docpriv = target->doc->_private;
884  	
885  	    pcmk__enable_acls(acl_source->doc, target->doc, user);
886  	
(5) Event path: Switch case default.
(6) Event path: Condition "trace_cs == NULL", taking true branch.
(7) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(8) Event path: Breaking from switch.
887  	    pcmk__trace("Filtering XML copy using user '%s' ACLs", user);
888  	
(9) Event path: Condition "iter != NULL", taking true branch.
(12) Event path: Condition "iter != NULL", taking false branch.
889  	    for (const GList *iter = docpriv->acls; iter != NULL; iter = iter->next) {
890  	        const xml_acl_t *acl = iter->data;
891  	        xmlXPathObject *xpath_obj = NULL;
892  	        int num_results = 0;
893  	
(10) Event path: Condition "acl->mode != pcmk__xf_acl_deny", taking true branch.
894  	        if ((acl->mode != pcmk__xf_acl_deny) || (acl->xpath == NULL)) {
(11) Event path: Continuing loop.
895  	            continue;
896  	        }
897  	
898  	        xpath_obj = pcmk__xpath_search(target->doc, acl->xpath);
899  	        num_results = pcmk__xpath_num_results(xpath_obj);
900  	
901  	        for (int i = 0; i < num_results; i++) {
902  	            xmlNode *match = pcmk__xpath_result(xpath_obj, i);
903  	
904  	            if (match == NULL) {
905  	                continue;
906  	            }
907  	
908  	            // @COMPAT See COMPAT comment in pcmk__apply_acls()
909  	            match = pcmk__xpath_match_element(match);
910  	            if (match == NULL) {
911  	                continue;
912  	            }
913  	
914  	            if (!purge_xml_attributes(match) && (match == target)) {
915  	                pcmk__trace("ACLs deny user '%s' access to entire XML document",
916  	                            user);
917  	                xmlXPathFreeObject(xpath_obj);
918  	                return true;
919  	            }
920  	        }
921  	        pcmk__trace("ACLs deny user '%s' access to %s (%d match%s)", user,
922  	                    acl->xpath, num_results,
923  	                    pcmk__plural_alt(num_results, "", "es"));
924  	        xmlXPathFreeObject(xpath_obj);
925  	    }
926  	
(13) Event path: Condition "!purge_xml_attributes(target)", taking false branch.
927  	    if (!purge_xml_attributes(target)) {
928  	        pcmk__trace("ACLs deny user '%s' access to entire XML document", user);
929  	        return true;
930  	    }
931  	
(14) Event path: Condition "docpriv->acls == NULL", taking false branch.
932  	    if (docpriv->acls == NULL) {
933  	        pcmk__trace("User '%s' without ACLs denied access to entire XML "
934  	                    "document", user);
935  	        pcmk__xml_free(target);
936  	        return true;
937  	    }
938  	
CID (unavailable; MK=f2ba8458b48d1447e509345f81a048a5) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(15) Event assign_union_field: The union field "in" of "_pp" is written.
(16) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
939  	    g_clear_pointer(&docpriv->acls, pcmk__free_acls);
940  	    *result = target;
941  	    return true;
942  	}
943  	
944  	/*!
945  	 * \internal
946  	 * \brief Check whether creation of an XML element is implicitly allowed
947  	 *
948  	 * Check whether XML is a "scaffolding" element whose creation is implicitly
949  	 * allowed regardless of ACLs (that is, it is not in the ACL section and has
950  	 * no attributes other than \c PCMK_XA_ID).
951  	 *
952  	 * \param[in] xml  XML element to check
953  	 *
954  	 * \return true if XML element is implicitly allowed, false otherwise
955  	 */
956  	static bool
957  	implicitly_allowed(const xmlNode *xml)
958  	{
959  	    GString *path = NULL;
960  	
961  	    for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
962  	        if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) {
963  	            return false;
964  	        }
965  	    }
966  	
967  	    path = pcmk__element_xpath(xml);
968  	    pcmk__assert(path != NULL);
969  	
970  	    if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) {
971  	        g_string_free(path, TRUE);
972  	        return false;
973  	    }
974  	
975  	    g_string_free(path, TRUE);
976  	    return true;
977  	}
978  	
979  	#define display_id(xml) pcmk__s(pcmk__xe_id(xml), "<unset>")
980  	
981  	/*!
982  	 * \internal
983  	 * \brief Drop XML nodes created in violation of ACLs
984  	 *
985  	 * Given an XML element, free all of its descendant nodes created in violation
986  	 * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
987  	 * that aren't in the ACL section and don't have any attributes other than
988  	 * \c PCMK_XA_ID).
989  	 *
990  	 * \param[in,out] xml        XML to check
991  	 * \param[in]     check_top  Whether to apply checks to argument itself
992  	 *                           (if true, xml might get freed)
993  	 *
994  	 * \note This function is recursive
995  	 */
996  	void
997  	pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
998  	{
999  	    xml_node_private_t *nodepriv = xml->_private;
1000 	
1001 	    if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
1002 	        if (implicitly_allowed(xml)) {
1003 	            pcmk__trace("Creation of <%s> scaffolding with "
1004 	                        PCMK_XA_ID "=\"%s\" is implicitly allowed",
1005 	                        xml->name, display_id(xml));
1006 	
1007 	        } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
1008 	            pcmk__trace("ACLs allow creation of <%s> with "
1009 	                        PCMK_XA_ID "=\"%s\"",
1010 	                        xml->name, display_id(xml));
1011 	
1012 	        } else if (check_top) {
1013 	            /* is_root=true should be impossible with check_top=true, but check
1014 	             * for sanity
1015 	             */
1016 	            bool is_root = (xmlDocGetRootElement(xml->doc) == xml);
1017 	            xml_doc_private_t *docpriv = xml->doc->_private;
1018 	
1019 	            pcmk__trace("ACLs disallow creation of %s<%s> with "
1020 	                        PCMK_XA_ID "=\"%s\"",
1021 	                        (is_root? "root element " : ""), xml->name,
1022 	                        display_id(xml));
1023 	
1024 	            // pcmk__xml_free() checks ACLs if enabled, which would fail
1025 	            pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
1026 	            pcmk__xml_free(xml);
1027 	
1028 	            if (!is_root) {
1029 	                // If root, the document was freed. Otherwise re-enable ACLs.
1030 	                pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled);
1031 	            }
1032 	            return;
1033 	
1034 	        } else {
1035 	            const bool is_root = (xml == xmlDocGetRootElement(xml->doc));
1036 	
1037 	            pcmk__notice("ACLs would disallow creation of %s<%s> with "
1038 	                         PCMK_XA_ID "=\"%s\"",
1039 	                         (is_root? "root element " : ""), xml->name,
1040 	                         display_id(xml));
1041 	        }
1042 	    }
1043 	
1044 	    for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
1045 	        xmlNode *child = cIter;
1046 	        cIter = pcmk__xml_next(cIter); /* In case it is free'd */
1047 	        pcmk__apply_creation_acl(child, true);
1048 	    }
1049 	}
1050 	
1051 	/*!
1052 	 * \brief Check whether or not an XML node is ACL-denied
1053 	 *
1054 	 * \param[in]  xml node to check
1055 	 *
1056 	 * \return true if XML node exists and is ACL-denied, false otherwise
1057 	 */
1058 	bool
1059 	xml_acl_denied(const xmlNode *xml)
1060 	{
1061 	    if (xml && xml->doc && xml->doc->_private){
1062 	        xml_doc_private_t *docpriv = xml->doc->_private;
1063 	
1064 	        return pcmk__is_set(docpriv->flags, pcmk__xf_acl_denied);
1065 	    }
1066 	    return false;
1067 	}
1068 	
1069 	void
1070 	xml_acl_disable(xmlNode *xml)
1071 	{
1072 	    if ((xml != NULL)
1073 	        && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) {
1074 	
1075 	        xml_doc_private_t *docpriv = xml->doc->_private;
1076 	
1077 	        /* Catch anything that was created but shouldn't have been */
1078 	        pcmk__apply_acls(xml->doc);
1079 	        pcmk__apply_creation_acl(xml, false);
1080 	        pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
1081 	    }
1082 	}
1083 	
1084 	/*!
1085 	 * \internal
1086 	 * \brief Deny access to an XML tree's document based on ACLs
1087 	 *
1088 	 * \param[in,out] xml        XML tree
1089 	 * \param[in]     attr_name  Name of attribute being accessed in \p xml (for
1090 	 *                           logging only)
1091 	 * \param[in]     prefix     Prefix describing ACL that denied access (for
1092 	 *                           logging only)
1093 	 * \param[in]     user       User accessing \p xml (for logging only)
1094 	 * \param[in]     mode       Access mode (for logging only)
1095 	 */
1096 	#define check_acl_deny(xml, attr_name, prefix, user, mode) do {             \
1097 	        xmlNode *tree = xml;                                                \
1098 	                                                                            \
1099 	        pcmk__xml_doc_set_flags(tree->doc, pcmk__xf_acl_denied);            \
1100 	        pcmk__if_tracing(                                                   \
1101 	            {                                                               \
1102 	                GString *xpath = pcmk__element_xpath(tree);                 \
1103 	                                                                            \
1104 	                if ((attr_name) != NULL) {                                  \
1105 	                    pcmk__g_strcat(xpath, "[@", attr_name, "]", NULL);      \
1106 	                }                                                           \
1107 	                qb_log_from_external_source(__func__, __FILE__,             \
1108 	                                            "%sACL denies user '%s' %s "    \
1109 	                                            "access to %s",                 \
1110 	                                            LOG_TRACE, __LINE__, 0 ,        \
1111 	                                            prefix, user,                   \
1112 	                                            acl_mode_text(mode),            \
1113 	                                            xpath->str);                    \
1114 	                g_string_free(xpath, TRUE);                                 \
1115 	            },                                                              \
1116 	            {}                                                              \
1117 	        );                                                                  \
1118 	    } while (0)
1119 	
1120 	bool
1121 	pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode)
1122 	{
1123 	    xml_doc_private_t *docpriv = NULL;
1124 	
1125 	    pcmk__assert((xml != NULL) && (xml->doc->_private != NULL));
1126 	
1127 	    if (!pcmk__xml_doc_all_flags_set(xml->doc,
1128 	                                     pcmk__xf_tracking|pcmk__xf_acl_enabled)) {
1129 	        return true;
1130 	    }
1131 	
1132 	    docpriv = xml->doc->_private;
1133 	    if (docpriv->acls == NULL) {
1134 	        check_acl_deny(xml, attr_name, "Lack of ", docpriv->acl_user, mode);
1135 	        return false;
1136 	    }
1137 	
1138 	    /* Walk the tree upwards looking for xml_acl_* flags
1139 	     * - Creating an attribute requires write permissions for the node
1140 	     * - Creating a child requires write permissions for the parent
1141 	     */
1142 	
1143 	    if (attr_name != NULL) {
1144 	        xmlAttr *attr = xmlHasProp(xml, (const xmlChar *) attr_name);
1145 	
1146 	        if ((attr != NULL) && (mode == pcmk__xf_acl_create)) {
1147 	            mode = pcmk__xf_acl_write;
1148 	        }
1149 	    }
1150 	
1151 	    for (const xmlNode *parent = xml;
1152 	         (parent != NULL) && (parent->_private != NULL);
1153 	         parent = parent->parent) {
1154 	
1155 	        const xml_node_private_t *nodepriv = parent->_private;
1156 	
1157 	        if (is_mode_allowed(nodepriv->flags, mode)) {
1158 	            return true;
1159 	        }
1160 	
1161 	        if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
1162 	            const char *pfx = (parent != xml)? "Parent " : "";
1163 	
1164 	            check_acl_deny(xml, attr_name, pfx, docpriv->acl_user, mode);
1165 	            return false;
1166 	        }
1167 	    }
1168 	
1169 	    check_acl_deny(xml, attr_name, "Default ", docpriv->acl_user, mode);
1170 	    return false;
1171 	}
1172 	
1173 	/*!
1174 	 * \brief Check whether ACLs are required for a given user
1175 	 *
1176 	 * \param[in]  User name to check
1177 	 *
1178 	 * \return true if the user requires ACLs, false otherwise
1179 	 */
1180 	bool
1181 	pcmk_acl_required(const char *user)
1182 	{
1183 	    if (pcmk__str_empty(user)) {
1184 	        pcmk__trace("ACLs not required because no user set");
1185 	        return false;
1186 	
1187 	    } else if (pcmk__is_privileged(user)) {
1188 	        pcmk__trace("ACLs not required for privileged user %s", user);
1189 	        return false;
1190 	    }
1191 	    pcmk__trace("ACLs required for %s", user);
1192 	    return true;
1193 	}
1194 	
1195 	char *
1196 	pcmk__uid2username(uid_t uid)
1197 	{
1198 	    struct passwd *pwent = NULL;
1199 	
1200 	    errno = 0;
1201 	    pwent = getpwuid(uid);
1202 	
1203 	    if (pwent == NULL) {
1204 	        pcmk__err("Cannot get name from password database for user ID %lld: %s",
1205 	                  (long long) uid,
1206 	                  ((errno != 0)? strerror(errno) : "No matching entry found"));
1207 	        return NULL;
1208 	    }
1209 	
1210 	    return pcmk__str_copy(pwent->pw_name);
1211 	}
1212 	
1213 	/*!
1214 	 * \internal
1215 	 * \brief Set the ACL user field properly on an XML request
1216 	 *
1217 	 * Multiple user names are potentially involved in an XML request: the effective
1218 	 * user of the current process; the user name known from an IPC client
1219 	 * connection; and the user name obtained from the request itself, whether by
1220 	 * the current standard XML attribute name or an older legacy attribute name.
1221 	 * This function chooses the appropriate one that should be used for ACLs, sets
1222 	 * it in the request (using the standard attribute name, and the legacy name if
1223 	 * given), and returns it.
1224 	 *
1225 	 * \param[in,out] request    XML request to update
1226 	 * \param[in]     field      Alternate name for ACL user name XML attribute
1227 	 * \param[in]     peer_user  User name as known from IPC connection
1228 	 *
1229 	 * \return ACL user name actually used
1230 	 */
1231 	const char *
1232 	pcmk__update_acl_user(xmlNode *request, const char *field,
1233 	                      const char *peer_user)
1234 	{
1235 	    static const char *effective_user = NULL;
1236 	    const char *requested_user = NULL;
1237 	    const char *user = NULL;
1238 	
1239 	    if (effective_user == NULL) {
1240 	        effective_user = pcmk__uid2username(geteuid());
1241 	        if (effective_user == NULL) {
1242 	            effective_user = pcmk__str_copy("#unprivileged");
1243 	            pcmk__err("Unable to determine effective user, assuming "
1244 	                      "unprivileged for ACLs");
1245 	        }
1246 	    }
1247 	
1248 	    requested_user = pcmk__xe_get(request, PCMK__XA_ACL_TARGET);
1249 	    if (requested_user == NULL) {
1250 	        /* Currently, different XML attribute names are used for the ACL user in
1251 	         * different contexts (PCMK__XA_ATTR_USER, PCMK__XA_CIB_USER, etc.).
1252 	         * The caller may specify that name as the field argument.
1253 	         *
1254 	         * @TODO Standardize on PCMK__XA_ACL_TARGET and eventually drop the
1255 	         * others once rolling upgrades from versions older than that are no
1256 	         * longer supported.
1257 	         */
1258 	        requested_user = pcmk__xe_get(request, field);
1259 	    }
1260 	
1261 	    if (!pcmk__is_privileged(effective_user)) {
1262 	        /* We're not running as a privileged user, set or overwrite any existing
1263 	         * value for PCMK__XA_ACL_TARGET
1264 	         */
1265 	        user = effective_user;
1266 	
1267 	    } else if (peer_user == NULL && requested_user == NULL) {
1268 	        /* No user known or requested, use 'effective_user' and make sure one is
1269 	         * set for the request
1270 	         */
1271 	        user = effective_user;
1272 	
1273 	    } else if (peer_user == NULL) {
1274 	        /* No user known, trusting 'requested_user' */
1275 	        user = requested_user;
1276 	
1277 	    } else if (!pcmk__is_privileged(peer_user)) {
1278 	        /* The peer is not a privileged user, set or overwrite any existing
1279 	         * value for PCMK__XA_ACL_TARGET
1280 	         */
1281 	        user = peer_user;
1282 	
1283 	    } else if (requested_user == NULL) {
1284 	        /* Even if we're privileged, make sure there is always a value set */
1285 	        user = peer_user;
1286 	
1287 	    } else {
1288 	        /* Legal delegation to 'requested_user' */
1289 	        user = requested_user;
1290 	    }
1291 	
1292 	    // This requires pointer comparison, not string comparison
1293 	    if (user != pcmk__xe_get(request, PCMK__XA_ACL_TARGET)) {
1294 	        pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user);
1295 	    }
1296 	
1297 	    if ((field != NULL) && (user != pcmk__xe_get(request, field))) {
1298 	        pcmk__xe_set(request, field, user);
1299 	    }
1300 	
1301 	    return requested_user;
1302 	}
1303 	
1304 	// Deprecated functions kept only for backward API compatibility
1305 	// LCOV_EXCL_START
1306 	
1307 	#include <crm/common/acl_compat.h>
1308 	#include <crm/common/xml_compat.h>
1309 	
1310 	bool
1311 	xml_acl_enabled(const xmlNode *xml)
1312 	{
1313 	    if (xml && xml->doc && xml->doc->_private){
1314 	        xml_doc_private_t *docpriv = xml->doc->_private;
1315 	
1316 	        return pcmk__is_set(docpriv->flags, pcmk__xf_acl_enabled);
1317 	    }
1318 	    return false;
1319 	}
1320 	
1321 	// LCOV_EXCL_STOP
1322 	// End deprecated API
1323