1 /*
2  * Copyright 2004-2021 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 <stdio.h>
13 #include <sys/types.h>
14 #include <pwd.h>
15 #include <string.h>
16 #include <stdlib.h>
17 #include <stdarg.h>
18 
19 #include <libxml/tree.h>
20 
21 #include <crm/crm.h>
22 #include <crm/msg_xml.h>
23 #include <crm/common/xml.h>
24 #include <crm/common/xml_internal.h>
25 #include "crmcommon_private.h"
26 
27 #define MAX_XPATH_LEN	4096
28 
29 typedef struct xml_acl_s {
30         enum xml_private_flags mode;
31         char *xpath;
32 } xml_acl_t;
33 
34 static void
free_acl(void * data)35 free_acl(void *data)
36 {
37     if (data) {
38         xml_acl_t *acl = data;
39 
40         free(acl->xpath);
41         free(acl);
42     }
43 }
44 
45 void
pcmk__free_acls(GList * acls)46 pcmk__free_acls(GList *acls)
47 {
48     g_list_free_full(acls, free_acl);
49 }
50 
51 static GList *
create_acl(xmlNode * xml,GList * acls,enum xml_private_flags mode)52 create_acl(xmlNode *xml, GList *acls, enum xml_private_flags mode)
53 {
54     xml_acl_t *acl = NULL;
55 
56     const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
57     const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
58     const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
59     const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
60 
61     if (tag == NULL) {
62         // @COMPAT rolling upgrades <=1.1.11
63         tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
64     }
65     if (ref == NULL) {
66         // @COMPAT rolling upgrades <=1.1.11
67         ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
68     }
69 
70     if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
71         // Schema should prevent this, but to be safe ...
72         crm_trace("Ignoring ACL <%s> element without selection criteria",
73                   crm_element_name(xml));
74         return NULL;
75     }
76 
77     acl = calloc(1, sizeof (xml_acl_t));
78     CRM_ASSERT(acl != NULL);
79 
80     acl->mode = mode;
81     if (xpath) {
82         acl->xpath = strdup(xpath);
83         CRM_ASSERT(acl->xpath != NULL);
84         crm_trace("Unpacked ACL <%s> element using xpath: %s",
85                   crm_element_name(xml), acl->xpath);
86 
87     } else {
88         int offset = 0;
89         char buffer[MAX_XPATH_LEN];
90 
91         if (tag) {
92             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
93                                "//%s", tag);
94         } else {
95             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
96                                "//*");
97         }
98 
99         if (ref || attr) {
100             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
101                                "[");
102         }
103 
104         if (ref) {
105             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
106                                "@id='%s'", ref);
107         }
108 
109         // NOTE: schema currently does not allow this
110         if (ref && attr) {
111             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
112                                " and ");
113         }
114 
115         if (attr) {
116             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
117                                "@%s", attr);
118         }
119 
120         if (ref || attr) {
121             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
122                                "]");
123         }
124 
125         CRM_LOG_ASSERT(offset > 0);
126         acl->xpath = strdup(buffer);
127         CRM_ASSERT(acl->xpath != NULL);
128 
129         crm_trace("Unpacked ACL <%s> element as xpath: %s",
130                   crm_element_name(xml), acl->xpath);
131     }
132 
133     return g_list_append(acls, acl);
134 }
135 
136 /*!
137  * \internal
138  * \brief Unpack a user, group, or role subtree of the ACLs section
139  *
140  * \param[in]     acl_top    XML of entire ACLs section
141  * \param[in]     acl_entry  XML of ACL element being unpacked
142  * \param[in,out] acls       List of ACLs unpacked so far
143  *
144  * \return New head of (possibly modified) acls
145  */
146 static GList *
parse_acl_entry(xmlNode * acl_top,xmlNode * acl_entry,GList * acls)147 parse_acl_entry(xmlNode *acl_top, xmlNode *acl_entry, GList *acls)
148 {
149     xmlNode *child = NULL;
150 
151     for (child = pcmk__xe_first_child(acl_entry); child;
152          child = pcmk__xe_next(child)) {
153         const char *tag = crm_element_name(child);
154         const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
155 
156         if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
157             CRM_ASSERT(kind != NULL);
158             crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
159             tag = kind;
160         } else {
161             crm_trace("Unpacking ACL <%s> element", tag);
162         }
163 
164         if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
165                    || strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
166             const char *ref_role = crm_element_value(child, XML_ATTR_ID);
167 
168             if (ref_role) {
169                 xmlNode *role = NULL;
170 
171                 for (role = pcmk__xe_first_child(acl_top); role;
172                      role = pcmk__xe_next(role)) {
173                     if (!strcmp(XML_ACL_TAG_ROLE, (const char *) role->name)) {
174                         const char *role_id = crm_element_value(role,
175                                                                 XML_ATTR_ID);
176 
177                         if (role_id && strcmp(ref_role, role_id) == 0) {
178                             crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
179                                       role_id, crm_element_name(acl_entry));
180                             acls = parse_acl_entry(acl_top, role, acls);
181                             break;
182                         }
183                     }
184                 }
185             }
186 
187         } else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
188             acls = create_acl(child, acls, xpf_acl_read);
189 
190         } else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
191             acls = create_acl(child, acls, xpf_acl_write);
192 
193         } else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
194             acls = create_acl(child, acls, xpf_acl_deny);
195 
196         } else {
197             crm_warn("Ignoring unknown ACL %s '%s'",
198                      (kind? "kind" : "element"), tag);
199         }
200     }
201 
202     return acls;
203 }
204 
205 /*
206     <acls>
207       <acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
208       <acl_role id="auto-l33t-haxor">
209         <acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
210       </acl_role>
211       <acl_target id="niceguy">
212         <role id="observer"/>
213       </acl_target>
214       <acl_role id="observer">
215         <acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
216         <acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
217         <acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
218       </acl_role>
219       <acl_target id="badidea"><role id="auto-badidea"/></acl_target>
220       <acl_role id="auto-badidea">
221         <acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
222         <acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
223       </acl_role>
224     </acls>
225 */
226 
227 static const char *
acl_to_text(enum xml_private_flags flags)228 acl_to_text(enum xml_private_flags flags)
229 {
230     if (pcmk_is_set(flags, xpf_acl_deny)) {
231         return "deny";
232 
233     } else if (pcmk_any_flags_set(flags, xpf_acl_write|xpf_acl_create)) {
234         return "read/write";
235 
236     } else if (pcmk_is_set(flags, xpf_acl_read)) {
237         return "read";
238     }
239     return "none";
240 }
241 
242 void
pcmk__apply_acl(xmlNode * xml)243 pcmk__apply_acl(xmlNode *xml)
244 {
245     GList *aIter = NULL;
246     xml_private_t *p = xml->doc->_private;
247     xmlXPathObjectPtr xpathObj = NULL;
248 
249     if (!xml_acl_enabled(xml)) {
250         crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
251                   p->user);
252         return;
253     }
254 
255     for (aIter = p->acls; aIter != NULL; aIter = aIter->next) {
256         int max = 0, lpc = 0;
257         xml_acl_t *acl = aIter->data;
258 
259         xpathObj = xpath_search(xml, acl->xpath);
260         max = numXpathResults(xpathObj);
261 
262         for (lpc = 0; lpc < max; lpc++) {
263             xmlNode *match = getXpathResult(xpathObj, lpc);
264             char *path = xml_get_path(match);
265 
266             p = match->_private;
267             crm_trace("Applying %s ACL to %s matched by %s",
268                       acl_to_text(acl->mode), path, acl->xpath);
269             pcmk__set_xml_flags(p, acl->mode);
270             free(path);
271         }
272         crm_trace("Applied %s ACL %s (%d match%s)",
273                   acl_to_text(acl->mode), acl->xpath, max,
274                   ((max == 1)? "" : "es"));
275         freeXpathObject(xpathObj);
276     }
277 }
278 
279 /*!
280  * \internal
281  * \brief Unpack ACLs for a given user
282  *
283  * \param[in]     source  XML with ACL definitions
284  * \param[in,out] target  XML that ACLs will be applied to
285  * \param[in]     user    Username whose ACLs need to be unpacked
286  */
287 void
pcmk__unpack_acl(xmlNode * source,xmlNode * target,const char * user)288 pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
289 {
290     xml_private_t *p = NULL;
291 
292     if ((target == NULL) || (target->doc == NULL)
293         || (target->doc->_private == NULL)) {
294         return;
295     }
296 
297     p = target->doc->_private;
298     if (!pcmk_acl_required(user)) {
299         crm_trace("Not unpacking ACLs because not required for user '%s'",
300                   user);
301 
302     } else if (p->acls == NULL) {
303         xmlNode *acls = get_xpath_object("//" XML_CIB_TAG_ACLS,
304                                          source, LOG_NEVER);
305 
306         free(p->user);
307         p->user = strdup(user);
308 
309         if (acls) {
310             xmlNode *child = NULL;
311 
312             for (child = pcmk__xe_first_child(acls); child;
313                  child = pcmk__xe_next(child)) {
314                 const char *tag = crm_element_name(child);
315 
316                 if (!strcmp(tag, XML_ACL_TAG_USER)
317                     || !strcmp(tag, XML_ACL_TAG_USERv1)) {
318                     const char *id = crm_element_value(child, XML_ATTR_ID);
319 
320                     if (id && strcmp(id, user) == 0) {
321                         crm_debug("Unpacking ACLs for user '%s'", id);
322                         p->acls = parse_acl_entry(acls, child, p->acls);
323                     }
324                 }
325             }
326         }
327     }
328 }
329 
330 static inline bool
test_acl_mode(enum xml_private_flags allowed,enum xml_private_flags requested)331 test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
332 {
333     if (pcmk_is_set(allowed, xpf_acl_deny)) {
334         return false;
335 
336     } else if (pcmk_all_flags_set(allowed, requested)) {
337         return true;
338 
339     } else if (pcmk_is_set(requested, xpf_acl_read)
340                && pcmk_is_set(allowed, xpf_acl_write)) {
341         return true;
342 
343     } else if (pcmk_is_set(requested, xpf_acl_create)
344                && pcmk_any_flags_set(allowed, xpf_acl_write|xpf_created)) {
345         return true;
346     }
347     return false;
348 }
349 
350 static bool
purge_xml_attributes(xmlNode * xml)351 purge_xml_attributes(xmlNode *xml)
352 {
353     xmlNode *child = NULL;
354     xmlAttr *xIter = NULL;
355     bool readable_children = false;
356     xml_private_t *p = xml->_private;
357 
358     if (test_acl_mode(p->flags, xpf_acl_read)) {
359         crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
360         return true;
361     }
362 
363     xIter = xml->properties;
364     while (xIter != NULL) {
365         xmlAttr *tmp = xIter;
366         const char *prop_name = (const char *)xIter->name;
367 
368         xIter = xIter->next;
369         if (strcmp(prop_name, XML_ATTR_ID) == 0) {
370             continue;
371         }
372 
373         xmlUnsetProp(xml, tmp->name);
374     }
375 
376     child = pcmk__xml_first_child(xml);
377     while ( child != NULL ) {
378         xmlNode *tmp = child;
379 
380         child = pcmk__xml_next(child);
381         readable_children |= purge_xml_attributes(tmp);
382     }
383 
384     if (!readable_children) {
385         free_xml(xml); /* Nothing readable under here, purge completely */
386     }
387     return readable_children;
388 }
389 
390 /*!
391  * \internal
392  * \brief Copy ACL-allowed portions of specified XML
393  *
394  * \param[in]  user        Username whose ACLs should be used
395  * \param[in]  acl_source  XML containing ACLs
396  * \param[in]  xml         XML to be copied
397  * \param[out] result      Copy of XML portions readable via ACLs
398  *
399  * \return true if xml exists and ACLs are required for user, false otherwise
400  * \note If this returns true, caller should use \p result rather than \p xml
401  */
402 bool
xml_acl_filtered_copy(const char * user,xmlNode * acl_source,xmlNode * xml,xmlNode ** result)403 xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
404                       xmlNode **result)
405 {
406     GList *aIter = NULL;
407     xmlNode *target = NULL;
408     xml_private_t *doc = NULL;
409 
410     *result = NULL;
411     if ((xml == NULL) || !pcmk_acl_required(user)) {
412         crm_trace("Not filtering XML because ACLs not required for user '%s'",
413                   user);
414         return false;
415     }
416 
417     crm_trace("Filtering XML copy using user '%s' ACLs", user);
418     target = copy_xml(xml);
419     if (target == NULL) {
420         return true;
421     }
422 
423     pcmk__unpack_acl(acl_source, target, user);
424     pcmk__set_xml_doc_flag(target, xpf_acl_enabled);
425     pcmk__apply_acl(target);
426 
427     doc = target->doc->_private;
428     for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
429         int max = 0;
430         xml_acl_t *acl = aIter->data;
431 
432         if (acl->mode != xpf_acl_deny) {
433             /* Nothing to do */
434 
435         } else if (acl->xpath) {
436             int lpc = 0;
437             xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
438 
439             max = numXpathResults(xpathObj);
440             for(lpc = 0; lpc < max; lpc++) {
441                 xmlNode *match = getXpathResult(xpathObj, lpc);
442 
443                 if (!purge_xml_attributes(match) && (match == target)) {
444                     crm_trace("ACLs deny user '%s' access to entire XML document",
445                               user);
446                     freeXpathObject(xpathObj);
447                     return true;
448                 }
449             }
450             crm_trace("ACLs deny user '%s' access to %s (%d %s)",
451                       user, acl->xpath, max,
452                       pcmk__plural_alt(max, "match", "matches"));
453             freeXpathObject(xpathObj);
454         }
455     }
456 
457     if (!purge_xml_attributes(target)) {
458         crm_trace("ACLs deny user '%s' access to entire XML document", user);
459         return true;
460     }
461 
462     if (doc->acls) {
463         g_list_free_full(doc->acls, free_acl);
464         doc->acls = NULL;
465 
466     } else {
467         crm_trace("User '%s' without ACLs denied access to entire XML document",
468                   user);
469         free_xml(target);
470         target = NULL;
471     }
472 
473     if (target) {
474         *result = target;
475     }
476 
477     return true;
478 }
479 
480 /*!
481  * \internal
482  * \brief Check whether creation of an XML element is implicitly allowed
483  *
484  * Check whether XML is a "scaffolding" element whose creation is implicitly
485  * allowed regardless of ACLs (that is, it is not in the ACL section and has
486  * no attributes other than "id").
487  *
488  * \param[in] xml  XML element to check
489  *
490  * \return true if XML element is implicitly allowed, false otherwise
491  */
492 static bool
implicitly_allowed(xmlNode * xml)493 implicitly_allowed(xmlNode *xml)
494 {
495     char *path = NULL;
496 
497     for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
498         if (strcmp((const char *) prop->name, XML_ATTR_ID) != 0) {
499             return false;
500         }
501     }
502 
503     path = xml_get_path(xml);
504     if (strstr(path, "/" XML_CIB_TAG_ACLS "/") != NULL) {
505         free(path);
506         return false;
507     }
508     free(path);
509 
510     return true;
511 }
512 
513 #define display_id(xml) (ID(xml)? ID(xml) : "<unset>")
514 
515 /*!
516  * \internal
517  * \brief Drop XML nodes created in violation of ACLs
518  *
519  * Given an XML element, free all of its descendent nodes created in violation
520  * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
521  * that aren't in the ACL section and don't have any attributes other than
522  * "id").
523  *
524  * \param[in,out] xml        XML to check
525  * \param[in]     check_top  Whether to apply checks to argument itself
526  *                           (if true, xml might get freed)
527  */
528 void
pcmk__apply_creation_acl(xmlNode * xml,bool check_top)529 pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
530 {
531     xml_private_t *p = xml->_private;
532 
533     if (pcmk_is_set(p->flags, xpf_created)) {
534         if (implicitly_allowed(xml)) {
535             crm_trace("Creation of <%s> scaffolding with id=\"%s\""
536                       " is implicitly allowed",
537                       crm_element_name(xml), display_id(xml));
538 
539         } else if (pcmk__check_acl(xml, NULL, xpf_acl_write)) {
540             crm_trace("ACLs allow creation of <%s> with id=\"%s\"",
541                       crm_element_name(xml), display_id(xml));
542 
543         } else if (check_top) {
544             crm_trace("ACLs disallow creation of <%s> with id=\"%s\"",
545                       crm_element_name(xml), display_id(xml));
546             pcmk_free_xml_subtree(xml);
547             return;
548 
549         } else {
550             crm_notice("ACLs would disallow creation of %s<%s> with id=\"%s\" ",
551                        ((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
552                        crm_element_name(xml), display_id(xml));
553         }
554     }
555 
556     for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
557         xmlNode *child = cIter;
558         cIter = pcmk__xml_next(cIter); /* In case it is free'd */
559         pcmk__apply_creation_acl(child, true);
560     }
561 }
562 
563 bool
xml_acl_denied(xmlNode * xml)564 xml_acl_denied(xmlNode *xml)
565 {
566     if (xml && xml->doc && xml->doc->_private){
567         xml_private_t *p = xml->doc->_private;
568 
569         return pcmk_is_set(p->flags, xpf_acl_denied);
570     }
571     return false;
572 }
573 
574 void
xml_acl_disable(xmlNode * xml)575 xml_acl_disable(xmlNode *xml)
576 {
577     if (xml_acl_enabled(xml)) {
578         xml_private_t *p = xml->doc->_private;
579 
580         /* Catch anything that was created but shouldn't have been */
581         pcmk__apply_acl(xml);
582         pcmk__apply_creation_acl(xml, false);
583         pcmk__clear_xml_flags(p, xpf_acl_enabled);
584     }
585 }
586 
587 bool
xml_acl_enabled(xmlNode * xml)588 xml_acl_enabled(xmlNode *xml)
589 {
590     if (xml && xml->doc && xml->doc->_private){
591         xml_private_t *p = xml->doc->_private;
592 
593         return pcmk_is_set(p->flags, xpf_acl_enabled);
594     }
595     return false;
596 }
597 
598 bool
pcmk__check_acl(xmlNode * xml,const char * name,enum xml_private_flags mode)599 pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
600 {
601     CRM_ASSERT(xml);
602     CRM_ASSERT(xml->doc);
603     CRM_ASSERT(xml->doc->_private);
604 
605     if (pcmk__tracking_xml_changes(xml, false) && xml_acl_enabled(xml)) {
606         int offset = 0;
607         xmlNode *parent = xml;
608         char buffer[MAX_XPATH_LEN];
609         xml_private_t *docp = xml->doc->_private;
610 
611         offset = pcmk__element_xpath(NULL, xml, buffer, offset,
612                                      sizeof(buffer));
613         if (name) {
614             offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
615                                "[@%s]", name);
616         }
617         CRM_LOG_ASSERT(offset > 0);
618 
619         if (docp->acls == NULL) {
620             crm_trace("User '%s' without ACLs denied %s access to %s",
621                       docp->user, acl_to_text(mode), buffer);
622             pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
623             return false;
624         }
625 
626         /* Walk the tree upwards looking for xml_acl_* flags
627          * - Creating an attribute requires write permissions for the node
628          * - Creating a child requires write permissions for the parent
629          */
630 
631         if (name) {
632             xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
633 
634             if (attr && mode == xpf_acl_create) {
635                 mode = xpf_acl_write;
636             }
637         }
638 
639         while (parent && parent->_private) {
640             xml_private_t *p = parent->_private;
641             if (test_acl_mode(p->flags, mode)) {
642                 return true;
643 
644             } else if (pcmk_is_set(p->flags, xpf_acl_deny)) {
645                 crm_trace("%sACL denies user '%s' %s access to %s",
646                           (parent != xml) ? "Parent " : "", docp->user,
647                           acl_to_text(mode), buffer);
648                 pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
649                 return false;
650             }
651             parent = parent->parent;
652         }
653 
654         crm_trace("Default ACL denies user '%s' %s access to %s",
655                   docp->user, acl_to_text(mode), buffer);
656         pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
657         return false;
658     }
659 
660     return true;
661 }
662 
663 /*!
664  * \brief Check whether ACLs are required for a given user
665  *
666  * \param[in]  User name to check
667  *
668  * \return true if the user requires ACLs, false otherwise
669  */
670 bool
pcmk_acl_required(const char * user)671 pcmk_acl_required(const char *user)
672 {
673     if (pcmk__str_empty(user)) {
674         crm_trace("ACLs not required because no user set");
675         return false;
676 
677     } else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
678         crm_trace("ACLs not required for privileged user %s", user);
679         return false;
680     }
681     crm_trace("ACLs required for %s", user);
682     return true;
683 }
684 
685 char *
pcmk__uid2username(uid_t uid)686 pcmk__uid2username(uid_t uid)
687 {
688     struct passwd *pwent = getpwuid(uid);
689 
690     if (pwent == NULL) {
691         crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
692         return NULL;
693     }
694     return strdup(pwent->pw_name);
695 }
696 
697 /*!
698  * \internal
699  * \brief Set the ACL user field properly on an XML request
700  *
701  * Multiple user names are potentially involved in an XML request: the effective
702  * user of the current process; the user name known from an IPC client
703  * connection; and the user name obtained from the request itself, whether by
704  * the current standard XML attribute name or an older legacy attribute name.
705  * This function chooses the appropriate one that should be used for ACLs, sets
706  * it in the request (using the standard attribute name, and the legacy name if
707  * given), and returns it.
708  *
709  * \param[in,out] request    XML request to update
710  * \param[in]     field      Alternate name for ACL user name XML attribute
711  * \param[in]     peer_user  User name as known from IPC connection
712  *
713  * \return ACL user name actually used
714  */
715 const char *
pcmk__update_acl_user(xmlNode * request,const char * field,const char * peer_user)716 pcmk__update_acl_user(xmlNode *request, const char *field,
717                       const char *peer_user)
718 {
719     static const char *effective_user = NULL;
720     const char *requested_user = NULL;
721     const char *user = NULL;
722 
723     if (effective_user == NULL) {
724         effective_user = pcmk__uid2username(geteuid());
725         if (effective_user == NULL) {
726             effective_user = strdup("#unprivileged");
727             CRM_CHECK(effective_user != NULL, return NULL);
728             crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
729         }
730     }
731 
732     requested_user = crm_element_value(request, XML_ACL_TAG_USER);
733     if (requested_user == NULL) {
734         /* @COMPAT rolling upgrades <=1.1.11
735          *
736          * field is checked for backward compatibility with older versions that
737          * did not use XML_ACL_TAG_USER.
738          */
739         requested_user = crm_element_value(request, field);
740     }
741 
742     if (!pcmk__is_privileged(effective_user)) {
743         /* We're not running as a privileged user, set or overwrite any existing
744          * value for $XML_ACL_TAG_USER
745          */
746         user = effective_user;
747 
748     } else if (peer_user == NULL && requested_user == NULL) {
749         /* No user known or requested, use 'effective_user' and make sure one is
750          * set for the request
751          */
752         user = effective_user;
753 
754     } else if (peer_user == NULL) {
755         /* No user known, trusting 'requested_user' */
756         user = requested_user;
757 
758     } else if (!pcmk__is_privileged(peer_user)) {
759         /* The peer is not a privileged user, set or overwrite any existing
760          * value for $XML_ACL_TAG_USER
761          */
762         user = peer_user;
763 
764     } else if (requested_user == NULL) {
765         /* Even if we're privileged, make sure there is always a value set */
766         user = peer_user;
767 
768     } else {
769         /* Legal delegation to 'requested_user' */
770         user = requested_user;
771     }
772 
773     // This requires pointer comparison, not string comparison
774     if (user != crm_element_value(request, XML_ACL_TAG_USER)) {
775         crm_xml_add(request, XML_ACL_TAG_USER, user);
776     }
777 
778     if (field != NULL && user != crm_element_value(request, field)) {
779         crm_xml_add(request, field, user);
780     }
781 
782     return requested_user;
783 }
784