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 <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/parser.h>
20   	#include <libxml/tree.h>
21   	#include <libxml/xpath.h>
22   	#include <libxslt/transform.h>
23   	#include <libxslt/variables.h>
24   	#include <libxslt/xsltutils.h>
25   	
26   	#include <crm/crm.h>
27   	#include <crm/common/xml.h>
28   	#include <crm/common/internal.h>
29   	
30   	#include <pacemaker-internal.h>
31   	
32   	#define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
33   	#define ACL_NS_Q_PREFIX  "pcmk-access-"
34   	#define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX   "writable"
35   	#define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX   "readable"
36   	#define ACL_NS_Q_DENIED   (const xmlChar *) ACL_NS_Q_PREFIX   "denied"
37   	
38   	static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
39   	static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
40   	static const xmlChar *NS_DENIED =   (const xmlChar *) ACL_NS_PREFIX "denied";
41   	
42   	/*!
43   	 * \brief This function takes a node and marks it with the namespace
44   	 *        given in the ns parameter.
45   	 *
46   	 * \param[in,out] i_node
47   	 * \param[in] ns
48   	 * \param[in,out] ret
49   	 * \param[in,out] ns_recycle_writable
50   	 * \param[in,out] ns_recycle_readable
51   	 * \param[in,out] ns_recycle_denied
52   	 */
53   	static void
54   	pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
55   	                                   xmlNs **ns_recycle_writable,
56   	                                   xmlNs **ns_recycle_readable,
57   	                                   xmlNs **ns_recycle_denied)
58   	{
59   	    if (ns == NS_WRITABLE)
60   	    {
61   	        if (*ns_recycle_writable == NULL)
62   	        {
63   	            *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
64   	                                           NS_WRITABLE, ACL_NS_Q_WRITABLE);
65   	        }
66   	        xmlSetNs(i_node, *ns_recycle_writable);
67   	        *ret = pcmk_rc_ok;
68   	    }
69   	    else if (ns == NS_READABLE)
70   	    {
71   	        if (*ns_recycle_readable == NULL)
72   	        {
73   	            *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
74   	                                           NS_READABLE, ACL_NS_Q_READABLE);
75   	        }
76   	        xmlSetNs(i_node, *ns_recycle_readable);
77   	        *ret = pcmk_rc_ok;
78   	    }
79   	    else if (ns == NS_DENIED)
80   	    {
81   	        if (*ns_recycle_denied == NULL)
82   	        {
83   	            *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
84   	                                         NS_DENIED, ACL_NS_Q_DENIED);
85   	        };
86   	        xmlSetNs(i_node, *ns_recycle_denied);
87   	        *ret = pcmk_rc_ok;
88   	    }
89   	}
90   	
91   	/*!
92   	 * \brief Annotate a given XML element or property and its siblings with
93   	 *        XML namespaces to indicate ACL permissions
94   	 *
95   	 * \param[in,out] xml_modify  XML to annotate
96   	 *
97   	 * \return  A standard Pacemaker return code
98   	 *          Namely:
99   	 *          - pcmk_rc_ok upon success,
100  	 *          - pcmk_rc_already if ACLs were not applicable,
101  	 *          - pcmk_rc_schema_validation if the validation schema version
102  	 *              is unsupported (see note), or
103  	 *          - EINVAL or ENOMEM as appropriate;
104  	 *
105  	 * \note This function is recursive
106  	 */
107  	static int
108  	annotate_with_siblings(xmlNode *xml_modify)
109  	{
110  	
111  	    static xmlNs *ns_recycle_writable = NULL,
112  	                 *ns_recycle_readable = NULL,
113  	                 *ns_recycle_denied = NULL;
114  	    static const xmlDoc *prev_doc = NULL;
115  	
116  	    xmlNode *i_node = NULL;
117  	    const xmlChar *ns;
118  	    int ret = EINVAL; // nodes have not been processed yet
119  	
120  	    if (prev_doc == NULL || prev_doc != xml_modify->doc) {
121  	        prev_doc = xml_modify->doc;
122  	        ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
123  	    }
124  	
125  	    for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
126  	        switch (i_node->type) {
127  	            case XML_ELEMENT_NODE:
128  	                pcmk__xml_doc_set_flags(i_node->doc, pcmk__xf_tracking);
129  	
130  	                if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
131  	                    ns = NS_DENIED;
132  	                } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
133  	                    ns = NS_READABLE;
134  	                } else {
135  	                    ns = NS_WRITABLE;
136  	                }
137  	                pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
138  	                                                   &ns_recycle_writable,
139  	                                                   &ns_recycle_readable,
140  	                                                   &ns_recycle_denied);
141  	                // @TODO Could replace recursion with iteration to save stack
142  	                if (i_node->properties != NULL) {
143  	                    /* This is not entirely clear, but relies on the very same
144  	                     * class-hierarchy emulation that libxml2 has firmly baked
145  	                     * in its API/ABI
146  	                     */
147  	                    ret |= annotate_with_siblings((xmlNodePtr)
148  	                                                  i_node->properties);
149  	                }
150  	                if (i_node->children != NULL) {
151  	                    ret |= annotate_with_siblings(i_node->children);
152  	                }
153  	                break;
154  	
155  	            case XML_ATTRIBUTE_NODE:
156  	                // We can utilize that parent has already been assigned the ns
157  	                if (!pcmk__check_acl(i_node->parent,
158  	                                     (const char *) i_node->name,
159  	                                     pcmk__xf_acl_read)) {
160  	                    ns = NS_DENIED;
161  	                } else if (!pcmk__check_acl(i_node,
162  	                                       (const char *) i_node->name,
163  	                                       pcmk__xf_acl_write)) {
164  	                    ns = NS_READABLE;
165  	                } else {
166  	                    ns = NS_WRITABLE;
167  	                }
168  	                pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
169  	                                                   &ns_recycle_writable,
170  	                                                   &ns_recycle_readable,
171  	                                                   &ns_recycle_denied);
172  	                break;
173  	
174  	            case XML_COMMENT_NODE:
175  	                // We can utilize that parent has already been assigned the ns
176  	                if (!pcmk__check_acl(i_node->parent,
177  	                                     (const char *) i_node->name,
178  	                                     pcmk__xf_acl_read)) {
179  	                    ns = NS_DENIED;
180  	                } else if (!pcmk__check_acl(i_node->parent,
181  	                                            (const char *) i_node->name,
182  	                                            pcmk__xf_acl_write)) {
183  	                    ns = NS_READABLE;
184  	                } else {
185  	                    ns = NS_WRITABLE;
186  	                }
187  	                pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
188  	                                                   &ns_recycle_writable,
189  	                                                   &ns_recycle_readable,
190  	                                                   &ns_recycle_denied);
191  	                break;
192  	
193  	            default:
194  	                break;
195  	        }
196  	    }
197  	
198  	    return ret;
199  	}
200  	
201  	int
202  	pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
203  	                               xmlDoc **acl_evaled_doc)
204  	{
205  	    int ret;
206  	    xmlNode *target, *comment;
207  	    const char *validation;
208  	
209  	    CRM_CHECK(cred != NULL, return EINVAL);
210  	    CRM_CHECK(cib_doc != NULL, return EINVAL);
211  	    CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
212  	
213  	    /* avoid trivial accidental XML injection */
214  	    if (strpbrk(cred, "<>&") != NULL) {
215  	        return EINVAL;
216  	    }
217  	
218  	    if (!pcmk_acl_required(cred)) {
219  	        /* nothing to evaluate */
220  	        return pcmk_rc_already;
221  	    }
222  	
223  	    validation = pcmk__xe_get(xmlDocGetRootElement(cib_doc),
224  	                              PCMK_XA_VALIDATE_WITH);
225  	
226  	    if (pcmk__cmp_schemas_by_name(PCMK__COMPAT_ACL_2_MIN_INCL,
227  	                                  validation) > 0) {
228  	        return pcmk_rc_schema_validation;
229  	    }
230  	
231  	    target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
232  	    if (target == NULL) {
233  	        return EINVAL;
234  	    }
235  	
236  	    pcmk__enable_acls(target->doc, target->doc, cred);
237  	
238  	    ret = annotate_with_siblings(target);
239  	
240  	    if (ret == pcmk_rc_ok) {
241  	        char *content = pcmk__assert_asprintf("ACLs as evaluated for user %s",
242  	                                              cred);
243  	
244  	        comment = pcmk__xc_create(target->doc, content);
245  	        xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
246  	        *acl_evaled_doc = target->doc;
247  	        free(content);
248  	
249  	    } else {
250  	        pcmk__xml_free(target);
251  	    }
252  	    return ret;
253  	}
254  	
255  	int
256  	pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
257  	                        xmlChar **doc_txt_ptr)
258  	{
259  	    xmlDoc *xslt_doc;
260  	    xsltStylesheet *xslt;
261  	    xsltTransformContext *xslt_ctxt;
262  	    xmlDoc *res;
263  	    char *sfile;
264  	    static const char *params_namespace[] = {
265  	        "accessrendercfg:c-writable",           ACL_NS_Q_PREFIX "writable:",
266  	        "accessrendercfg:c-readable",           ACL_NS_Q_PREFIX "readable:",
267  	        "accessrendercfg:c-denied",             ACL_NS_Q_PREFIX "denied:",
268  	        "accessrendercfg:c-reset",              "",
269  	        "accessrender:extra-spacing",           "no",
270  	        "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
271  	        NULL
272  	    }, *params_useansi[] = {
273  	        /* start with hard-coded defaults, then adapt per the template ones */
274  	        "accessrendercfg:c-writable",           "\x1b[32m",
275  	        "accessrendercfg:c-readable",           "\x1b[34m",
276  	        "accessrendercfg:c-denied",             "\x1b[31m",
277  	        "accessrendercfg:c-reset",              "\x1b[0m",
278  	        "accessrender:extra-spacing",           "no",
279  	        "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
280  	        NULL
281  	    }, *params_noansi[] = {
282  	        "accessrendercfg:c-writable",           "vvv---[ WRITABLE ]---vvv",
283  	        "accessrendercfg:c-readable",           "vvv---[ READABLE ]---vvv",
284  	        "accessrendercfg:c-denied",             "vvv---[ ~DENIED~ ]---vvv",
285  	        "accessrendercfg:c-reset",              "",
286  	        "accessrender:extra-spacing",           "yes",
287  	        "accessrender:self-reproducing-prefix", "",
288  	        NULL
289  	    };
290  	    const char **params;
291  	    int rc = pcmk_rc_ok;
292  	    xmlParserCtxtPtr parser_ctxt;
293  	
294  	    /* unfortunately, the input (coming from CIB originally) was parsed with
295  	       blanks ignored, and since the output is a conversion of XML to text
296  	       format (we would be covered otherwise thanks to implicit
297  	       pretty-printing), we need to dump the tree to string output first,
298  	       only to subsequently reparse it -- this time with blanks honoured */
299  	    xmlChar *annotated_dump;
300  	    int dump_size;
301  	
(1) Event path: Condition "!(how != pcmk__acl_render_none)", taking false branch.
302  	    pcmk__assert(how != pcmk__acl_render_none);
303  	
304  	    // Color is the default render mode for terminals; text is default otherwise
(2) Event path: Condition "how == pcmk__acl_render_default", taking true branch.
305  	    if (how == pcmk__acl_render_default) {
(3) Event path: Condition "isatty(1)", taking true branch.
306  	        if (isatty(STDOUT_FILENO)) {
307  	            how = pcmk__acl_render_color;
(4) Event path: Falling through to end of if statement.
308  	        } else {
309  	            how = pcmk__acl_render_text;
310  	        }
311  	    }
312  	
313  	    xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
314  	
315  	    /* res does not need private data: it's temporary and used only with libxslt
316  	     * functions
317  	     */
318  	    res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
319  	                     XML_PARSE_NONET);
(5) Event path: Condition "!(res != NULL)", taking false branch.
320  	    pcmk__assert(res != NULL);
321  	    xmlFree(annotated_dump);
322  	    pcmk__xml_free_doc(annotated_doc);
323  	    annotated_doc = res;
324  	
325  	    sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
326  	                                    "access-render-2");
327  	    parser_ctxt = xmlNewParserCtxt();
328  	
(6) Event path: Condition "!(sfile != NULL)", taking false branch.
329  	    pcmk__assert(sfile != NULL);
(7) Event path: Condition "parser_ctxt == NULL", taking false branch.
330  	    pcmk__mem_assert(parser_ctxt);
331  	
332  	    xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
333  	
334  	    xslt = xsltParseStylesheetDoc(xslt_doc);  /* acquires xslt_doc! */
(8) Event path: Condition "xslt == NULL", taking false branch.
335  	    if (xslt == NULL) {
336  	        pcmk__crit("Problem in parsing %s", sfile);
337  	        rc = EINVAL;
338  	        goto done;
339  	    }
340  	    xmlFreeParserCtxt(parser_ctxt);
341  	
342  	    xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
(9) Event path: Condition "xslt_ctxt == NULL", taking false branch.
343  	    pcmk__mem_assert(xslt_ctxt);
344  	
(10) Event path: Switch case default.
345  	    switch (how) {
346  	        case pcmk__acl_render_namespace:
347  	            params = params_namespace;
348  	            break;
349  	        case pcmk__acl_render_text:
350  	            params = params_noansi;
351  	            break;
352  	        default:
353  	            /* pcmk__acl_render_color is the only remaining option.
354  	             * The compiler complains about params possibly uninitialized if we
355  	             * don't use default here.
356  	             */
357  	            params = params_useansi;
(11) Event path: Breaking from switch.
358  	            break;
359  	    }
360  	
361  	    xsltQuoteUserParams(xslt_ctxt, params);
362  	
363  	    res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
364  	                                  NULL, NULL, xslt_ctxt);
365  	
CID (unavailable; MK=7452edc28a66685428deff614f9c48b0) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(12) Event assign_union_field: The union field "in" of "_pp" is written.
(13) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
366  	    g_clear_pointer(&annotated_doc, pcmk__xml_free_doc);
367  	    xsltFreeTransformContext(xslt_ctxt);
368  	    xslt_ctxt = NULL;
369  	
370  	    if (how == pcmk__acl_render_color && params != params_useansi) {
371  	        char **param_i = (char **) params;
372  	        do {
373  	            free(*param_i);
374  	        } while (*param_i++ != NULL);
375  	        free(params);
376  	    }
377  	
378  	    if (res == NULL) {
379  	        rc = EINVAL;
380  	    } else {
381  	        int doc_txt_len;
382  	        int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
383  	
384  	        pcmk__xml_free_doc(res);
385  	        if (temp != 0) {
386  	            rc = EINVAL;
387  	        }
388  	    }
389  	
390  	done:
391  	    if (xslt != NULL) {
392  	        xsltFreeStylesheet(xslt);
393  	    }
394  	    free(sfile);
395  	    return rc;
396  	}
397