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