Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index d149b53d77..2a00a67e4d 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,352 +1,337 @@
/*
* Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef CRM_COMMON_XML__H
# define CRM_COMMON_XML__H
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Wrappers for and extensions to libxml2
* \ingroup core
*/
# include <stdio.h>
# include <sys/types.h>
# include <unistd.h>
# include <stdlib.h>
# include <errno.h>
# include <fcntl.h>
# include <libxml/tree.h>
# include <libxml/xpath.h>
# include <crm/crm.h>
# include <crm/common/nvpair.h>
/* Define compression parameters for IPC messages
*
* Compression costs a LOT, so we don't want to do it unless we're hitting
* message limits. Currently, we use 128KB as the threshold, because higher
* values don't play well with the heartbeat stack. With an earlier limit of
* 10KB, compressing 184 of 1071 messages accounted for 23% of the total CPU
* used by the cib.
*/
# define CRM_BZ2_BLOCKS 4
# define CRM_BZ2_WORK 20
# define CRM_BZ2_THRESHOLD 128 * 1024
# define XML_PARANOIA_CHECKS 0
typedef const xmlChar *pcmkXmlStr;
gboolean add_message_xml(xmlNode * msg, const char *field, xmlNode * xml);
xmlNode *get_message_xml(xmlNode * msg, const char *field);
xmlDoc *getDocPtr(xmlNode * node);
/*
* Replacement function for xmlCopyPropList which at the very least,
* doesn't work the way *I* would expect it to.
*
* Copy all the attributes/properties from src into target.
*
* Not recursive, does not return anything.
*
*/
void copy_in_properties(xmlNode * target, xmlNode * src);
void expand_plus_plus(xmlNode * target, const char *name, const char *value);
void fix_plus_plus_recursive(xmlNode * target);
/*
* Create a node named "name" as a child of "parent"
* If parent is NULL, creates an unconnected node.
*
* Returns the created node
*
*/
xmlNode *create_xml_node(xmlNode * parent, const char *name);
/*
* Create a node named "name" as a child of "parent", giving it the provided
* text content.
* If parent is NULL, creates an unconnected node.
*
* Returns the created node
*
*/
xmlNode *pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content);
/*
* Create a new HTML node named "element_name" as a child of "parent", giving it the
* provided text content. Optionally, apply a CSS #id and #class.
*
* Returns the created node.
*/
xmlNode *pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
const char *class_name, const char *text);
/*
*
*/
void purge_diff_markers(xmlNode * a_node);
/*
* Returns a deep copy of src_node
*
*/
xmlNode *copy_xml(xmlNode * src_node);
/*
* Add a copy of xml_node to new_parent
*/
xmlNode *add_node_copy(xmlNode * new_parent, xmlNode * xml_node);
int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child);
/*
* XML I/O Functions
*
* Whitespace between tags is discarded.
*/
xmlNode *filename2xml(const char *filename);
xmlNode *stdin2xml(void);
xmlNode *string2xml(const char *input);
int write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress);
int write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress);
char *dump_xml_formatted(xmlNode * msg);
/* Also dump the text node with xml_log_option_text enabled */
char *dump_xml_formatted_with_text(xmlNode * msg);
char *dump_xml_unformatted(xmlNode * msg);
/*
* Diff related Functions
*/
xmlNode *diff_xml_object(xmlNode * left, xmlNode * right, gboolean suppress);
xmlNode *subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean full, gboolean * changed, const char *marker);
gboolean can_prune_leaf(xmlNode * xml_node);
/*
* Searching & Modifying
*/
xmlNode *find_xml_node(xmlNode * cib, const char *node_path, gboolean must_find);
void xml_remove_prop(xmlNode * obj, const char *name);
gboolean replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update,
gboolean delete_only);
gboolean update_xml_child(xmlNode * child, xmlNode * to_update);
int find_xml_children(xmlNode ** children, xmlNode * root,
const char *tag, const char *field, const char *value,
gboolean search_matches);
xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
xmlNode *get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level);
static inline const char *
crm_element_name(const xmlNode *xml)
{
return xml? (const char *)(xml->name) : NULL;
}
static inline const char *
crm_map_element_name(const xmlNode *xml)
{
const char *name = crm_element_name(xml);
if (strcmp(name, "master") == 0) {
return "clone";
} else {
return name;
}
}
gboolean xml_has_children(const xmlNode * root);
char *calculate_on_disk_digest(xmlNode * local_cib);
char *calculate_operation_digest(xmlNode * local_cib, const char *version);
char *calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
const char *version);
/* schema-related functions (from schemas.c) */
gboolean validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs);
gboolean validate_xml_verbose(xmlNode * xml_blob);
/*!
* \brief Update CIB XML to most recent schema version
*
* "Update" means either actively employ XSLT-based transformation(s)
* (if intermediate product to transform valid per its declared schema version,
* transformation available, proceeded successfully with a result valid per
* expectated newer schema version), or just try to bump the marked validating
* schema until all gradually rising schema versions attested or the first
* such attempt subsequently fails to validate. Which of the two styles will
* be used depends on \p transform parameter (positive/negative, respectively).
*
* \param[in,out] xml_blob XML tree representing CIB, may be swapped with
* an "updated" one
* \param[out] best The highest configuration version (per its index
* in the global schemas table) it was possible to
* reach during the update steps while ensuring
* the validity of the result; if no validation
* success was observed against possibly multiple
* schemas, the value is less or equal the result
* of \c get_schema_version applied on the input
* \p xml_blob value (unless that function maps it
* to -1, then 0 would be used instead)
* \param[in] max When \p transform is positive, this allows to
* set upper boundary schema (per its index in the
* global schemas table) beyond which it's forbidden
* to update by the means of XSLT transformation
* \param[in] transform Whether to employ XSLT-based transformation so
* as to allow overcoming possible incompatibilities
* between major schema versions (see above)
* \param[in] to_logs If true, output notable progress info to
* internal log streams; if false, to stderr
*
* \return \c pcmk_ok if no non-recoverable error encountered (up to
* caller to evaluate if the update satisfies the requirements
* per returned \p best value), negative value carrying the reason
* otherwise
*/
int update_validation(xmlNode **xml_blob, int *best, int max,
gboolean transform, gboolean to_logs);
int get_schema_version(const char *name);
const char *get_schema_name(int version);
const char *xml_latest_schema(void);
gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
/*!
* \brief Initialize the CRM XML subsystem
*
* This method sets global XML settings and loads pacemaker schemas into the cache.
*/
void crm_xml_init(void);
void crm_xml_cleanup(void);
void pcmk_free_xml_subtree(xmlNode *xml);
void free_xml(xmlNode * child);
xmlNode *first_named_child(const xmlNode *parent, const char *name);
xmlNode *crm_next_same_xml(const xmlNode *sibling);
xmlNode *sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive);
xmlXPathObjectPtr xpath_search(xmlNode * xml_top, const char *path);
void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
void (*helper)(xmlNode*, void*), void *user_data);
xmlNode *expand_idref(xmlNode * input, xmlNode * top);
void freeXpathObject(xmlXPathObjectPtr xpathObj);
xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
void dedupXpathResults(xmlXPathObjectPtr xpathObj);
static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
{
if(xpathObj == NULL || xpathObj->nodesetval == NULL) {
return 0;
}
return xpathObj->nodesetval->nodeNr;
}
bool xml_tracking_changes(xmlNode * xml);
bool xml_document_dirty(xmlNode *xml);
void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_accept_changes(xmlNode * xml);
void xml_log_changes(uint8_t level, const char *function, xmlNode *xml);
void xml_log_patchset(uint8_t level, const char *function, xmlNode *xml);
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3]);
xmlNode *xml_create_patchset(
int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
-/* exclusive (should any combination make sense -> explicitly enumerated),
- with intentional symbolic constant duality (easier to adapt to growth) */
enum pcmk_acl_cred_type {
-#define PCMK_ACL_CRED_UNSET PCMK_ACL_CRED_UNSET
PCMK_ACL_CRED_UNSET = 0,
-#define PCMK_ACL_CRED_USER PCMK_ACL_CRED_USER
PCMK_ACL_CRED_USER,
/* XXX no proper support for groups yet */
};
-/* need to be ORable */
-enum pcmk_acl_verdict {
- PCMK_ACL_VERDICT_WRITABLE = 1 << 0,
- PCMK_ACL_VERDICT_READABLE = 1 << 1,
- PCMK_ACL_VERDICT_DENIED = 1 << 2,
-};
-
/*!
* \brief Mark CIB with namespace-encoded result of ACLs eval'd per credential
*
* \param[in] cred_type credential type that \p cred represents
* \param[in] cred credential whose ACL perspective to switch to
* \param[in] cib_doc XML document representing CIB
* \param[out] acl_evaled_doc XML document representing CIB, with said
* namespace-based annotations throughout
*
* \return 0 if ACLs were not applicable, >0 if it was and all went fine
* (this is the only case when it's safe to touch \p acl_evaled_doc
* afterwards, the result is #PCMK_ACL_VERDICT_WRITABLE,
* #PCMK_ACL_VERDICT_READABLE and #PCMK_ACL_VERDICT_DENIED bits
* ORed respectively), -2 on run-time unrecognized \p cred_type,
* -3 on unsupported validation schema version (see below),
* or -1 on any other/generic issue
*
* \note Only supported schemas are those following acls-2.0.rng, that is,
- * those validated with pacemaker-2.0.rng and newer (artificially caped
- * at 4.0, not including it (the future cannot be predicted,
- * compatibility needs to be confirmed, then, hopefully leading to
- * this comment being updated).
+ * those validated with pacemaker-2.0.rng and newer.
*/
-int pcmk_acl_evaled_as_namespaces(enum pcmk_acl_cred_type cred_type,
- const char *cred, xmlDoc *cib_doc,
+int pcmk_acl_evaled_as_namespaces(const char *cred, xmlDoc *cib_doc,
xmlDoc **acl_evaled_doc);
void save_xml_to_file(xmlNode * xml, const char *desc, const char *filename);
char *xml_get_path(xmlNode *xml);
char * crm_xml_escape(const char *text);
void crm_xml_sanitize_id(char *id);
void crm_xml_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3);
/*!
* \brief xmlNode destructor which can be used in glib collections
*/
void crm_destroy_xml(gpointer data);
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include <crm/common/xml_compat.h>
#endif
#ifdef __cplusplus
}
#endif
#endif
diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index 933e75a9cc..262681e47d 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -1,345 +1,344 @@
/*
* Copyright 2017-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__XML_INTERNAL__H
# define PCMK__XML_INTERNAL__H
/*
* Internal-only wrappers for and extensions to libxml2 (libxslt)
*/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <crm/crm.h> /* transitively imports qblog.h */
/*!
* \brief Base for directing lib{xml2,xslt} log into standard libqb backend
*
* This macro implements the core of what can be needed for directing
* libxml2 or libxslt error messaging into standard, preconfigured
* libqb-backed log stream.
*
* It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
* emits a single message by chunks (location is emitted separatedly from
* the message itself), so we have to take the effort to combine these
* chunks back to single message. Whether to do this or not is driven
* with \p dechunk toggle.
*
* The form of a macro was chosen for implicit deriving of __FILE__, etc.
* and also because static dechunking buffer should be differentiated per
* library (here we assume different functions referring to this macro
* will not ever be using both at once), preferably also per-library
* context of use to avoid clashes altogether.
*
* Note that we cannot use qb_logt, because callsite data have to be known
* at the moment of compilation, which it is not always the case -- xml_log
* (and unfortunately there's no clear explanation of the fail to compile).
*
* Also note that there's no explicit guard against said libraries producing
* never-newline-terminated chunks (which would just keep consuming memory),
* as it's quite improbable. Termination of the program in between the
* same-message chunks will raise a flag with valgrind and the likes, though.
*
* And lastly, regarding how dechunking combines with other non-message
* parameters -- for \p priority, most important running specification
* wins (possibly elevated to LOG_ERR in case of nonconformance with the
* newline-termination "protocol"), \p dechunk is expected to always be
* on once it was at the start, and the rest (\p postemit and \p prefix)
* are picked directly from the last chunk entry finalizing the message
* (also reasonable to always have it the same with all related entries).
*
* \param[in] priority Syslog priority for the message to be logged
* \param[in] dechunk Whether to dechunk new-line terminated message
* \param[in] postemit Code to be executed once message is sent out
* \param[in] prefix How to prefix the message or NULL for raw passing
* \param[in] fmt Format string as with printf-like functions
* \param[in] ap Variable argument list to supplement \p fmt format string
*/
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
do { \
if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
(priority), __LINE__, 0, (ap)); \
(void) (postemit); \
} else { \
int CXLB_len = 0; \
char *CXLB_buf = NULL; \
static int CXLB_buffer_len = 0; \
static char *CXLB_buffer = NULL; \
static uint8_t CXLB_priority = 0; \
\
CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
\
if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
if (CXLB_len < 0) { \
CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
} else if (CXLB_len > 0 /* && (dechunk) */ \
&& CXLB_buf[CXLB_len - 1] == '\n') { \
CXLB_buf[CXLB_len - 1] = '\0'; \
} \
if (CXLB_buffer) { \
qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
CXLB_priority, __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buffer, CXLB_buf); \
free(CXLB_buffer); \
} else { \
qb_log_from_external_source(__func__, __FILE__, "%s%s", \
(priority), __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buf); \
} \
if (CXLB_len < 0) { \
CXLB_buf = NULL; /* restore temporary override */ \
} \
CXLB_buffer = NULL; \
CXLB_buffer_len = 0; \
(void) (postemit); \
\
} else if (CXLB_buffer == NULL) { \
CXLB_buffer_len = CXLB_len; \
CXLB_buffer = CXLB_buf; \
CXLB_buf = NULL; \
CXLB_priority = (priority); /* remember as a running severest */ \
\
} else { \
CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
CXLB_buffer_len += CXLB_len; \
CXLB_buffer[CXLB_buffer_len] = '\0'; \
CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
} \
free(CXLB_buf); \
} \
} while (0)
/* XML search strings for guest, remote and pacemaker_remote nodes */
/* search string to find CIB resources entries for cluster nodes */
#define PCMK__XP_MEMBER_NODE_CONFIG \
"//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \
"/" XML_CIB_TAG_NODE "[not(@type) or @type='member']"
/* search string to find CIB resources entries for guest nodes */
#define PCMK__XP_GUEST_NODE_CONFIG \
"//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
"//" XML_TAG_META_SETS "//" XML_CIB_TAG_NVPAIR \
"[@name='" XML_RSC_ATTR_REMOTE_NODE "']"
/* search string to find CIB resources entries for remote nodes */
#define PCMK__XP_REMOTE_NODE_CONFIG \
"//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
"[@type='remote'][@provider='pacemaker']"
/* search string to find CIB node status entries for pacemaker_remote nodes */
#define PCMK__XP_REMOTE_NODE_STATUS \
"//" XML_TAG_CIB "//" XML_CIB_TAG_STATUS "//" XML_CIB_TAG_STATE \
"[@" XML_NODE_IS_REMOTE "='true']"
enum pcmk__xml_artefact_ns {
pcmk__xml_artefact_ns_legacy_rng = 1,
pcmk__xml_artefact_ns_legacy_xslt,
pcmk__xml_artefact_ns_base_rng,
pcmk__xml_artefact_ns_base_xslt,
};
void pcmk__strip_xml_text(xmlNode *xml);
const char *pcmk__xe_add_last_written(xmlNode *xe);
xmlNode *pcmk__xe_match(xmlNode *parent, const char *node_name,
const char *attr_n, const char *attr_v);
void pcmk__xe_remove_matching_attrs(xmlNode *element,
bool (*match)(xmlAttrPtr, void *),
void *user_data);
/*!
* \internal
* \brief Get the root directory to scan XML artefacts of given kind for
*
* \param[in] ns governs the hierarchy nesting against the inherent root dir
*
* \return root directory to scan XML artefacts of given kind for
*/
char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
/*!
* \internal
* \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
*
* \param[in] ns denotes path forming details (parent dir, suffix)
* \param[in] filespec symbolic file specification to be combined with
* #artefact_ns to form the final path
* \return unwrapped path to particular XML artifact (RNG/XSLT)
*/
char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
const char *filespec);
enum pcmk__acl_render_how {
pcmk__acl_render_ns_simple = 1,
pcmk__acl_render_text,
pcmk__acl_render_color,
};
/*!
* \internal
* \brief Serialize-render already pcmk_acl_evaled_as_namespaces annotated XML
*
* This function is vitally coupled with externalized material:
* - access-render-2.xsl
*
* In fact, it's just a wrapper for a graceful conducting of such
* transformation, in particular, it cares about converting values of some
* configuration parameters directly in said stylesheet since the desired
* ANSI colors at the output are not expressible directly (alternative approach
* to this preprocessing: eventual postprocessing, which is less handy here).
*
* \param[in] annotated_doc pcmk_acl_evaled_as_namespaces annotated XML
* \param[in] how render kind, see #pcmk__acl_render_how enumeration
* \param[out] doc_txt_ptr where to put the final outcome string
- * \param[out] doc_txt_len length of the output string \p doc_txt_ptr
* \return 0 or -1, see \c xsltSaveResultToString
*
* \note Currently, the function did not receive enough of testing regarding
* leak of resources, hence it is not recommended for anything other
* than short-lived processes at this time.
*/
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how,
- xmlChar **doc_txt_ptr, int *doc_txt_len);
+ xmlChar **doc_txt_ptr);
/*!
* \internal
* \brief Return first non-text child node of an XML node
*
* \param[in] parent XML node to check
*
* \return First non-text child node of \p parent (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_first_child(const xmlNode *parent)
{
xmlNode *child = (parent? parent->children : NULL);
while (child && (child->type == XML_TEXT_NODE)) {
child = child->next;
}
return child;
}
/*!
* \internal
* \brief Return next non-text sibling node of an XML node
*
* \param[in] child XML node to check
*
* \return Next non-text sibling of \p child (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_next(const xmlNode *child)
{
xmlNode *next = (child? child->next : NULL);
while (next && (next->type == XML_TEXT_NODE)) {
next = next->next;
}
return next;
}
/*!
* \internal
* \brief Return first non-text child element of an XML node
*
* \param[in] parent XML node to check
*
* \return First child element of \p parent (or NULL if none)
*/
static inline xmlNode *
pcmk__xe_first_child(const xmlNode *parent)
{
xmlNode *child = (parent? parent->children : NULL);
while (child && (child->type != XML_ELEMENT_NODE)) {
child = child->next;
}
return child;
}
/*!
* \internal
* \brief Return next non-text sibling element of an XML element
*
* \param[in] child XML element to check
*
* \return Next sibling element of \p child (or NULL if none)
*/
static inline xmlNode *
pcmk__xe_next(const xmlNode *child)
{
xmlNode *next = child? child->next : NULL;
while (next && (next->type != XML_ELEMENT_NODE)) {
next = next->next;
}
return next;
}
/*!
* \internal
* \brief Like pcmk__xe_set_props, but takes a va_list instead of
* arguments directly.
*/
void
pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
/*!
* \internal
* \brief Add a NULL-terminated list of name/value pairs to the given
* XML node as properties.
*
* \param[in,out] node XML node to add properties to
* \param[in] ... NULL-terminated list of name/value pairs
*
* \note A NULL name terminates the arguments; a NULL value will be skipped.
*/
void
pcmk__xe_set_props(xmlNodePtr node, ...)
G_GNUC_NULL_TERMINATED;
/*!
* \internal
* \brief Get first attribute of an XML element
*
* \param[in] xe XML element to check
*
* \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
*/
static inline xmlAttr *
pcmk__xe_first_attr(const xmlNode *xe)
{
return (xe == NULL)? NULL : xe->properties;
}
/*!
* \internal
* \brief Extract the ID attribute from an XML element
*
* \param[in] xpath String to search
* \param[in] node Node to get the ID for
*
* \return ID attribute of \p node in xpath string \p xpath
*/
char *
pcmk__xpath_node_id(const char *xpath, const char *node);
#endif // PCMK__XML_INTERNAL__H
diff --git a/include/crm/compatibility.h b/include/crm/compatibility.h
index a07399b99a..567c43966b 100644
--- a/include/crm/compatibility.h
+++ b/include/crm/compatibility.h
@@ -1,255 +1,254 @@
/*
* Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#ifndef CRM_COMPATIBILITY__H
# define CRM_COMPATIBILITY__H
#ifdef __cplusplus
extern "C" {
#endif
/* This file allows external code that uses Pacemaker libraries to transition
* more easily from old APIs to current ones. Any code that compiled with an
* earlier API but not with the current API can include this file and have a
* good chance of compiling again.
*
* Everything here is deprecated and will be removed at the next major Pacemaker
* release (i.e. 3.0), so it should only be used during a transitionary period
* while the external code is being updated to the current API.
*/
#include <crm/msg_xml.h>
#include <crm/pengine/pe_types.h> // enum pe_obj_types
/* Heartbeat-specific definitions. Support for heartbeat has been removed
* entirely, so any code branches relying on these should be deleted.
*/
#define ACTIVESTATUS "active"
#define DEADSTATUS "dead"
#define PINGSTATUS "ping"
#define JOINSTATUS "join"
#define LEAVESTATUS "leave"
#define NORMALNODE "normal"
#define CRM_NODE_EVICTED "evicted"
#define CRM_LEGACY_CONFIG_DIR "/var/lib/heartbeat/crm"
#define HA_VARLIBHBDIR "/var/lib/heartbeat"
#define pcmk_cluster_heartbeat 0x0004
/* Corosync-version-1-specific definitions */
/* Support for corosync version 1 has been removed entirely, so any code
* branches relying on these should be deleted.
*/
#define PCMK_SERVICE_ID 9
#define CRM_SERVICE PCMK_SERVICE_ID
#define XML_ATTR_EXPECTED_VOTES "expected-quorum-votes"
#define crm_class_members 1
#define crm_class_notify 2
#define crm_class_nodeid 3
#define crm_class_rmpeer 4
#define crm_class_quorum 5
#define pcmk_cluster_classic_ais 0x0010
#define pcmk_cluster_cman 0x0040
#define ais_fd_sync -1
// These are always true now
#define CS_USES_LIBQB 1
#define HAVE_CMAP 1
#define SUPPORT_CS_QUORUM 1
#define SUPPORT_AIS 1
#define AIS_COROSYNC 1
// These are always false now
#define HAVE_CONFDB 0
#define SUPPORT_CMAN 0
#define SUPPORT_PLUGIN 0
#define SUPPORT_STONITH_CONFIG 0
#define is_classic_ais_cluster() 0
#define is_cman_cluster() 0
// These have newer names
#define is_openais_cluster() is_corosync_cluster()
#if SUPPORT_COROSYNC
#define SUPPORT_CS
#endif
/* Isolation-specific definitions. Support for the resource isolation feature
* has been removed * entirely, so any code branches relying on these should be
* deleted.
*/
#define XML_RSC_ATTR_ISOLATION_INSTANCE "isolation-instance"
#define XML_RSC_ATTR_ISOLATION_WRAPPER "isolation-wrapper"
#define XML_RSC_ATTR_ISOLATION_HOST "isolation-host"
#define XML_RSC_ATTR_ISOLATION "isolation"
/* Schema-related definitions */
// This has been renamed
#define CRM_DTD_DIRECTORY CRM_SCHEMA_DIRECTORY
/* Exit-code-related definitions */
#define DAEMON_RESPAWN_STOP CRM_EX_FATAL
#define pcmk_err_panic CRM_EX_PANIC
// Deprecated symbols that were removed
#define APPNAME_LEN 256
#define CRM_NODE_ACTIVE CRM_NODE_MEMBER
#define CRM_OP_DIE "die_no_respawn"
#define CRM_OP_RETRIVE_CIB "retrieve_cib"
#define CRM_OP_HBEAT "dc_beat"
#define CRM_OP_ABORT "abort"
#define CRM_OP_DEBUG_UP "debug_inc"
#define CRM_OP_DEBUG_DOWN "debug_dec"
#define CRM_OP_EVENTCC "event_cc"
#define CRM_OP_TEABORT "te_abort"
#define CRM_OP_TEABORTED "te_abort_confirmed"
#define CRM_OP_TE_HALT "te_halt"
#define CRM_OP_TECOMPLETE "te_complete"
#define CRM_OP_TETIMEOUT "te_timeout"
#define CRM_OP_TRANSITION "transition"
#define CRM_OP_NODES_PROBED "probe_nodes_complete"
#define DOT_ALL_FSA_INPUTS 1
#define DOT_FSA_ACTIONS 1
#define F_LRMD_CANCEL_CALLID "lrmd_cancel_callid"
#define F_LRMD_RSC_METADATA "lrmd_rsc_metadata_res"
#define F_LRMD_IPC_PROXY_NODE "lrmd_ipc_proxy_node"
#define INSTANCE(x) crm_element_value(x, XML_CIB_ATTR_INSTANCE)
#define LOG_DEBUG_2 LOG_TRACE
#define LOG_DEBUG_3 LOG_TRACE
#define LOG_DEBUG_4 LOG_TRACE
#define LOG_DEBUG_5 LOG_TRACE
#define LOG_DEBUG_6 LOG_TRACE
#define LRMD_OP_RSC_CHK_REG "lrmd_rsc_check_register"
#define MAX_IPC_FAIL 5
#define NAME(x) crm_element_value(x, XML_NVPAIR_ATTR_NAME)
#define MSG_LOG 1
#define PE_OBJ_T_NATIVE "native"
#define PE_OBJ_T_GROUP "group"
#define PE_OBJ_T_INCARNATION "clone"
#define PE_OBJ_T_MASTER "master"
#define SERVICE_SCRIPT "/sbin/service"
#define SOCKET_LEN 1024
#define TSTAMP(x) crm_element_value(x, XML_ATTR_TSTAMP)
#define XML_ATTR_TAGNAME F_XML_TAGNAME
#define XML_ATTR_FILTER_TYPE "type-filter"
#define XML_ATTR_FILTER_ID "id-filter"
#define XML_ATTR_FILTER_PRIORITY "priority-filter"
#define XML_ATTR_DC "is_dc"
#define XML_MSG_TAG "crm_message"
#define XML_MSG_TAG_DATA "msg_data"
#define XML_FAIL_TAG_RESOURCE "failed_resource"
#define XML_FAILRES_ATTR_RESID "resource_id"
#define XML_FAILRES_ATTR_REASON "reason"
#define XML_FAILRES_ATTR_RESSTATUS "resource_status"
#define XML_ATTR_RESULT "result"
#define XML_ATTR_SECTION "section"
#define XML_CIB_TAG_DOMAIN "domain"
#define XML_CIB_TAG_CONSTRAINT "constraint"
#define XML_RSC_ATTR_STATE "clone-state"
#define XML_RSC_ATTR_PRIORITY "priority"
#define XML_OP_ATTR_DEPENDENT "dependent-on"
#define XML_LRM_TAG_AGENTS "lrm_agents"
#define XML_LRM_TAG_AGENT "lrm_agent"
#define XML_LRM_TAG_ATTRIBUTES "attributes"
#define XML_CIB_ATTR_HEALTH "health"
#define XML_CIB_ATTR_WEIGHT "weight"
#define XML_CIB_ATTR_CLEAR "clear_on"
#define XML_CIB_ATTR_STONITH "stonith"
#define XML_CIB_ATTR_STANDBY "standby"
#define XML_RULE_ATTR_SCORE_MANGLED "score-attribute-mangled"
#define XML_RULE_ATTR_RESULT "result"
#define XML_NODE_ATTR_STATE "state"
#define XML_ATTR_LRM_PROBE "lrm-is-probe"
#define XML_ATTR_TE_ALLOWFAIL "op_allow_fail"
#define VALUE(x) crm_element_value(x, XML_NVPAIR_ATTR_VALUE)
#define action_wrapper_s pe_action_wrapper_s
#define add_cib_op_callback(cib, id, flag, data, fn) do { \
cib->cmds->register_callback(cib, id, 120, flag, data, #fn, fn); \
} while(0)
#define cib_default_options = cib_none
#define crm_remote_baremetal 0x0004
#define crm_remote_container 0x0002
#define crm_element_value_const crm_element_value
#define crm_element_value_const_int crm_element_value_int
#define n_object_classes 3
#define no_quorum_policy_e pe_quorum_policy
#define node_s pe_node_s
#define node_shared_s pe_node_shared_s
#define pe_action_failure_is_fatal 0x00020
#define pe_rsc_munging 0x00000800ULL
#define pe_rsc_try_reload 0x00001000ULL
#define pe_rsc_shutdown 0x00020000ULL
#define pe_rsc_migrating 0x00400000ULL
#define pe_rsc_unexpectedly_running 0x02000000ULL
#define pe_rsc_have_unfencing 0x80000000ULL
#define resource_s pe_resource_s
#define ticket_s pe_ticket_s
#define node_score_infinity 1000000
/* Clone terminology definitions */
// These can no longer be used in a switch together
#define pe_master pe_clone
static inline enum pe_obj_types
get_resource_type(const char *name)
{
if (safe_str_eq(name, XML_CIB_TAG_RESOURCE)) {
return pe_native;
} else if (safe_str_eq(name, XML_CIB_TAG_GROUP)) {
return pe_group;
} else if (safe_str_eq(name, XML_CIB_TAG_INCARNATION)
|| safe_str_eq(name, PCMK_XE_PROMOTABLE_LEGACY)) {
return pe_clone;
} else if (safe_str_eq(name, XML_CIB_TAG_CONTAINER)) {
return pe_container;
}
return pe_unknown;
}
static inline const char *
get_resource_typename(enum pe_obj_types type)
{
switch (type) {
case pe_native:
return XML_CIB_TAG_RESOURCE;
case pe_group:
return XML_CIB_TAG_GROUP;
case pe_clone:
return XML_CIB_TAG_INCARNATION;
case pe_container:
return XML_CIB_TAG_CONTAINER;
case pe_unknown:
return "unknown";
}
return "<unknown>";
}
/*
* Version compatibility tracking incl. open-ended intervals for occasional
* bumps (to avoid hard to follow open-coding throughout). Grouped by context.
*/
/* Schema version vs. evaluate-as-namespace-annotations-per-credentials */
#define PCMK_COMPAT_ACL_2_MIN_INCL "pacemaker-2.0"
-#define PCMK_COMPAT_ACL_2_MAX_EXCL "pacemaker-4.0" /* bump if remains OK */
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/acl.c b/lib/common/acl.c
index af06d9af5d..458b934183 100644
--- a/lib/common/acl.c
+++ b/lib/common/acl.c
@@ -1,1152 +1,1149 @@
/*
* Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#if HAVE_LIBXSLT
# include <libxslt/transform.h>
# include <libxslt/variables.h>
# include <libxslt/xsltutils.h>
#endif
-#include <crm/compatibility.h> /* PCMK_COMPAT_ACL_2_* */
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/common/xml_internal.h>
#include "crmcommon_private.h"
#define MAX_XPATH_LEN 4096
typedef struct xml_acl_s {
enum xml_private_flags mode;
char *xpath;
} xml_acl_t;
static void
free_acl(void *data)
{
if (data) {
xml_acl_t *acl = data;
free(acl->xpath);
free(acl);
}
}
void
pcmk__free_acls(GList *acls)
{
g_list_free_full(acls, free_acl);
}
static GList *
create_acl(xmlNode *xml, GList *acls, enum xml_private_flags mode)
{
xml_acl_t *acl = NULL;
const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
if (tag == NULL) {
// @COMPAT rolling upgrades <=1.1.11
tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
}
if (ref == NULL) {
// @COMPAT rolling upgrades <=1.1.11
ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
}
if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
// Schema should prevent this, but to be safe ...
crm_trace("Ignoring ACL <%s> element without selection criteria",
crm_element_name(xml));
return NULL;
}
acl = calloc(1, sizeof (xml_acl_t));
CRM_ASSERT(acl != NULL);
acl->mode = mode;
if (xpath) {
acl->xpath = strdup(xpath);
CRM_ASSERT(acl->xpath != NULL);
crm_trace("Unpacked ACL <%s> element using xpath: %s",
crm_element_name(xml), acl->xpath);
} else {
int offset = 0;
char buffer[MAX_XPATH_LEN];
if (tag) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"//%s", tag);
} else {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"//*");
}
if (ref || attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"[");
}
if (ref) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"@id='%s'", ref);
}
// NOTE: schema currently does not allow this
if (ref && attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
" and ");
}
if (attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"@%s", attr);
}
if (ref || attr) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"]");
}
CRM_LOG_ASSERT(offset > 0);
acl->xpath = strdup(buffer);
CRM_ASSERT(acl->xpath != NULL);
crm_trace("Unpacked ACL <%s> element as xpath: %s",
crm_element_name(xml), acl->xpath);
}
return g_list_append(acls, acl);
}
/*!
* \internal
* \brief Unpack a user, group, or role subtree of the ACLs section
*
* \param[in] acl_top XML of entire ACLs section
* \param[in] acl_entry XML of ACL element being unpacked
* \param[in,out] acls List of ACLs unpacked so far
*
* \return New head of (possibly modified) acls
*/
static GList *
parse_acl_entry(xmlNode *acl_top, xmlNode *acl_entry, GList *acls)
{
xmlNode *child = NULL;
for (child = pcmk__xe_first_child(acl_entry); child;
child = pcmk__xe_next(child)) {
const char *tag = crm_element_name(child);
const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
CRM_ASSERT(kind != NULL);
crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
tag = kind;
} else {
crm_trace("Unpacking ACL <%s> element", tag);
}
if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
|| strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
const char *ref_role = crm_element_value(child, XML_ATTR_ID);
if (ref_role) {
xmlNode *role = NULL;
for (role = pcmk__xe_first_child(acl_top); role;
role = pcmk__xe_next(role)) {
if (!strcmp(XML_ACL_TAG_ROLE, (const char *) role->name)) {
const char *role_id = crm_element_value(role,
XML_ATTR_ID);
if (role_id && strcmp(ref_role, role_id) == 0) {
crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
role_id, crm_element_name(acl_entry));
acls = parse_acl_entry(acl_top, role, acls);
break;
}
}
}
}
} else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
acls = create_acl(child, acls, pcmk__xf_acl_read);
} else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
acls = create_acl(child, acls, pcmk__xf_acl_write);
} else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
acls = create_acl(child, acls, pcmk__xf_acl_deny);
} else {
crm_warn("Ignoring unknown ACL %s '%s'",
(kind? "kind" : "element"), tag);
}
}
return acls;
}
/*
<acls>
<acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
<acl_role id="auto-l33t-haxor">
<acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
</acl_role>
<acl_target id="niceguy">
<role id="observer"/>
</acl_target>
<acl_role id="observer">
<acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
<acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
<acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
</acl_role>
<acl_target id="badidea"><role id="auto-badidea"/></acl_target>
<acl_role id="auto-badidea">
<acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
<acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
</acl_role>
</acls>
*/
static const char *
acl_to_text(enum xml_private_flags flags)
{
if (pcmk_is_set(flags, pcmk__xf_acl_deny)) {
return "deny";
} else if (pcmk_any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_acl_create)) {
return "read/write";
} else if (pcmk_is_set(flags, pcmk__xf_acl_read)) {
return "read";
}
return "none";
}
void
pcmk__apply_acl(xmlNode *xml)
{
GList *aIter = NULL;
xml_private_t *p = xml->doc->_private;
xmlXPathObjectPtr xpathObj = NULL;
if (!xml_acl_enabled(xml)) {
crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
p->user);
return;
}
for (aIter = p->acls; aIter != NULL; aIter = aIter->next) {
int max = 0, lpc = 0;
xml_acl_t *acl = aIter->data;
xpathObj = xpath_search(xml, acl->xpath);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
char *path = xml_get_path(match);
p = match->_private;
crm_trace("Applying %s ACL to %s matched by %s",
acl_to_text(acl->mode), path, acl->xpath);
pcmk__set_xml_flags(p, acl->mode);
free(path);
}
crm_trace("Applied %s ACL %s (%d match%s)",
acl_to_text(acl->mode), acl->xpath, max,
((max == 1)? "" : "es"));
freeXpathObject(xpathObj);
}
}
/*!
* \internal
* \brief Unpack ACLs for a given user
*
* \param[in] source XML with ACL definitions
* \param[in,out] target XML that ACLs will be applied to
* \param[in] user Username whose ACLs need to be unpacked
*/
void
pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
{
xml_private_t *p = NULL;
if ((target == NULL) || (target->doc == NULL)
|| (target->doc->_private == NULL)) {
return;
}
p = target->doc->_private;
if (!pcmk_acl_required(user)) {
crm_trace("Not unpacking ACLs because not required for user '%s'",
user);
} else if (p->acls == NULL) {
xmlNode *acls = get_xpath_object("//" XML_CIB_TAG_ACLS,
source, LOG_NEVER);
free(p->user);
p->user = strdup(user);
if (acls) {
xmlNode *child = NULL;
for (child = pcmk__xe_first_child(acls); child;
child = pcmk__xe_next(child)) {
const char *tag = crm_element_name(child);
if (!strcmp(tag, XML_ACL_TAG_USER)
|| !strcmp(tag, XML_ACL_TAG_USERv1)) {
const char *id = crm_element_value(child, XML_ATTR_ID);
if (id && strcmp(id, user) == 0) {
crm_debug("Unpacking ACLs for user '%s'", id);
p->acls = parse_acl_entry(acls, child, p->acls);
}
}
}
}
}
}
static inline bool
test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
{
if (pcmk_is_set(allowed, pcmk__xf_acl_deny)) {
return false;
} else if (pcmk_all_flags_set(allowed, requested)) {
return true;
} else if (pcmk_is_set(requested, pcmk__xf_acl_read)
&& pcmk_is_set(allowed, pcmk__xf_acl_write)) {
return true;
} else if (pcmk_is_set(requested, pcmk__xf_acl_create)
&& pcmk_any_flags_set(allowed, pcmk__xf_acl_write|pcmk__xf_created)) {
return true;
}
return false;
}
static bool
purge_xml_attributes(xmlNode *xml)
{
xmlNode *child = NULL;
xmlAttr *xIter = NULL;
bool readable_children = false;
xml_private_t *p = xml->_private;
if (test_acl_mode(p->flags, pcmk__xf_acl_read)) {
crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
return true;
}
xIter = xml->properties;
while (xIter != NULL) {
xmlAttr *tmp = xIter;
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
xmlUnsetProp(xml, tmp->name);
}
child = pcmk__xml_first_child(xml);
while ( child != NULL ) {
xmlNode *tmp = child;
child = pcmk__xml_next(child);
readable_children |= purge_xml_attributes(tmp);
}
if (!readable_children) {
free_xml(xml); /* Nothing readable under here, purge completely */
}
return readable_children;
}
/*!
* \internal
* \brief Copy ACL-allowed portions of specified XML
*
* \param[in] user Username whose ACLs should be used
* \param[in] acl_source XML containing ACLs
* \param[in] xml XML to be copied
* \param[out] result Copy of XML portions readable via ACLs
*
* \return true if xml exists and ACLs are required for user, false otherwise
* \note If this returns true, caller should use \p result rather than \p xml
*/
bool
xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
xmlNode **result)
{
GList *aIter = NULL;
xmlNode *target = NULL;
xml_private_t *doc = NULL;
*result = NULL;
if ((xml == NULL) || !pcmk_acl_required(user)) {
crm_trace("Not filtering XML because ACLs not required for user '%s'",
user);
return false;
}
crm_trace("Filtering XML copy using user '%s' ACLs", user);
target = copy_xml(xml);
if (target == NULL) {
return true;
}
pcmk__unpack_acl(acl_source, target, user);
pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled);
pcmk__apply_acl(target);
doc = target->doc->_private;
for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
int max = 0;
xml_acl_t *acl = aIter->data;
if (acl->mode != pcmk__xf_acl_deny) {
/* Nothing to do */
} else if (acl->xpath) {
int lpc = 0;
xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
if (!purge_xml_attributes(match) && (match == target)) {
crm_trace("ACLs deny user '%s' access to entire XML document",
user);
freeXpathObject(xpathObj);
return true;
}
}
crm_trace("ACLs deny user '%s' access to %s (%d %s)",
user, acl->xpath, max,
pcmk__plural_alt(max, "match", "matches"));
freeXpathObject(xpathObj);
}
}
if (!purge_xml_attributes(target)) {
crm_trace("ACLs deny user '%s' access to entire XML document", user);
return true;
}
if (doc->acls) {
g_list_free_full(doc->acls, free_acl);
doc->acls = NULL;
} else {
crm_trace("User '%s' without ACLs denied access to entire XML document",
user);
free_xml(target);
target = NULL;
}
if (target) {
*result = target;
}
return true;
}
/*!
* \internal
* \brief Check whether creation of an XML element is implicitly allowed
*
* Check whether XML is a "scaffolding" element whose creation is implicitly
* allowed regardless of ACLs (that is, it is not in the ACL section and has
* no attributes other than "id").
*
* \param[in] xml XML element to check
*
* \return true if XML element is implicitly allowed, false otherwise
*/
static bool
implicitly_allowed(xmlNode *xml)
{
char *path = NULL;
for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
if (strcmp((const char *) prop->name, XML_ATTR_ID) != 0) {
return false;
}
}
path = xml_get_path(xml);
if (strstr(path, "/" XML_CIB_TAG_ACLS "/") != NULL) {
free(path);
return false;
}
free(path);
return true;
}
#define display_id(xml) (ID(xml)? ID(xml) : "<unset>")
/*!
* \internal
* \brief Drop XML nodes created in violation of ACLs
*
* Given an XML element, free all of its descendent nodes created in violation
* of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
* that aren't in the ACL section and don't have any attributes other than
* "id").
*
* \param[in,out] xml XML to check
* \param[in] check_top Whether to apply checks to argument itself
* (if true, xml might get freed)
*/
void
pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
{
xml_private_t *p = xml->_private;
if (pcmk_is_set(p->flags, pcmk__xf_created)) {
if (implicitly_allowed(xml)) {
crm_trace("Creation of <%s> scaffolding with id=\"%s\""
" is implicitly allowed",
crm_element_name(xml), display_id(xml));
} else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
crm_trace("ACLs allow creation of <%s> with id=\"%s\"",
crm_element_name(xml), display_id(xml));
} else if (check_top) {
crm_trace("ACLs disallow creation of <%s> with id=\"%s\"",
crm_element_name(xml), display_id(xml));
pcmk_free_xml_subtree(xml);
return;
} else {
crm_notice("ACLs would disallow creation of %s<%s> with id=\"%s\" ",
((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
crm_element_name(xml), display_id(xml));
}
}
for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
xmlNode *child = cIter;
cIter = pcmk__xml_next(cIter); /* In case it is free'd */
pcmk__apply_creation_acl(child, true);
}
}
bool
xml_acl_denied(xmlNode *xml)
{
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return pcmk_is_set(p->flags, pcmk__xf_acl_denied);
}
return false;
}
void
xml_acl_disable(xmlNode *xml)
{
if (xml_acl_enabled(xml)) {
xml_private_t *p = xml->doc->_private;
/* Catch anything that was created but shouldn't have been */
pcmk__apply_acl(xml);
pcmk__apply_creation_acl(xml, false);
pcmk__clear_xml_flags(p, pcmk__xf_acl_enabled);
}
}
bool
xml_acl_enabled(xmlNode *xml)
{
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return pcmk_is_set(p->flags, pcmk__xf_acl_enabled);
}
return false;
}
bool
pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
{
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
CRM_ASSERT(xml->doc->_private);
if (pcmk__tracking_xml_changes(xml, false) && xml_acl_enabled(xml)) {
int offset = 0;
xmlNode *parent = xml;
char buffer[MAX_XPATH_LEN];
xml_private_t *docp = xml->doc->_private;
offset = pcmk__element_xpath(NULL, xml, buffer, offset,
sizeof(buffer));
if (name) {
offset += snprintf(buffer + offset, MAX_XPATH_LEN - offset,
"[@%s]", name);
}
CRM_LOG_ASSERT(offset > 0);
if (docp->acls == NULL) {
crm_trace("User '%s' without ACLs denied %s access to %s",
docp->user, acl_to_text(mode), buffer);
pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
/* Walk the tree upwards looking for xml_acl_* flags
* - Creating an attribute requires write permissions for the node
* - Creating a child requires write permissions for the parent
*/
if (name) {
xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
if (attr && mode == pcmk__xf_acl_create) {
mode = pcmk__xf_acl_write;
}
}
while (parent && parent->_private) {
xml_private_t *p = parent->_private;
if (test_acl_mode(p->flags, mode)) {
return true;
} else if (pcmk_is_set(p->flags, pcmk__xf_acl_deny)) {
crm_trace("%sACL denies user '%s' %s access to %s",
(parent != xml) ? "Parent " : "", docp->user,
acl_to_text(mode), buffer);
pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
parent = parent->parent;
}
crm_trace("Default ACL denies user '%s' %s access to %s",
docp->user, acl_to_text(mode), buffer);
pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
return true;
}
/*!
* \brief Check whether ACLs are required for a given user
*
* \param[in] User name to check
*
* \return true if the user requires ACLs, false otherwise
*/
bool
pcmk_acl_required(const char *user)
{
if (pcmk__str_empty(user)) {
crm_trace("ACLs not required because no user set");
return false;
} else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
crm_trace("ACLs not required for privileged user %s", user);
return false;
}
crm_trace("ACLs required for %s", user);
return true;
}
char *
pcmk__uid2username(uid_t uid)
{
struct passwd *pwent = getpwuid(uid);
if (pwent == NULL) {
crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
return NULL;
}
return strdup(pwent->pw_name);
}
/*!
* \internal
* \brief Set the ACL user field properly on an XML request
*
* Multiple user names are potentially involved in an XML request: the effective
* user of the current process; the user name known from an IPC client
* connection; and the user name obtained from the request itself, whether by
* the current standard XML attribute name or an older legacy attribute name.
* This function chooses the appropriate one that should be used for ACLs, sets
* it in the request (using the standard attribute name, and the legacy name if
* given), and returns it.
*
* \param[in,out] request XML request to update
* \param[in] field Alternate name for ACL user name XML attribute
* \param[in] peer_user User name as known from IPC connection
*
* \return ACL user name actually used
*/
const char *
pcmk__update_acl_user(xmlNode *request, const char *field,
const char *peer_user)
{
static const char *effective_user = NULL;
const char *requested_user = NULL;
const char *user = NULL;
if (effective_user == NULL) {
effective_user = pcmk__uid2username(geteuid());
if (effective_user == NULL) {
effective_user = strdup("#unprivileged");
CRM_CHECK(effective_user != NULL, return NULL);
crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
}
}
requested_user = crm_element_value(request, XML_ACL_TAG_USER);
if (requested_user == NULL) {
/* @COMPAT rolling upgrades <=1.1.11
*
* field is checked for backward compatibility with older versions that
* did not use XML_ACL_TAG_USER.
*/
requested_user = crm_element_value(request, field);
}
if (!pcmk__is_privileged(effective_user)) {
/* We're not running as a privileged user, set or overwrite any existing
* value for $XML_ACL_TAG_USER
*/
user = effective_user;
} else if (peer_user == NULL && requested_user == NULL) {
/* No user known or requested, use 'effective_user' and make sure one is
* set for the request
*/
user = effective_user;
} else if (peer_user == NULL) {
/* No user known, trusting 'requested_user' */
user = requested_user;
} else if (!pcmk__is_privileged(peer_user)) {
/* The peer is not a privileged user, set or overwrite any existing
* value for $XML_ACL_TAG_USER
*/
user = peer_user;
} else if (requested_user == NULL) {
/* Even if we're privileged, make sure there is always a value set */
user = peer_user;
} else {
/* Legal delegation to 'requested_user' */
user = requested_user;
}
// This requires pointer comparison, not string comparison
if (user != crm_element_value(request, XML_ACL_TAG_USER)) {
crm_xml_add(request, XML_ACL_TAG_USER, user);
}
if (field != NULL && user != crm_element_value(request, field)) {
crm_xml_add(request, field, user);
}
return requested_user;
}
#define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
#define ACL_NS_Q_PREFIX "pcmk-access-"
#define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX "writable"
#define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX "readable"
#define ACL_NS_Q_DENIED (const xmlChar *) ACL_NS_Q_PREFIX "denied"
static const xmlChar *NS_WRITABLE = (xmlChar *) ACL_NS_PREFIX "writable";
static const xmlChar *NS_READABLE = (xmlChar *) ACL_NS_PREFIX "readable";
static const xmlChar *NS_DENIED = (xmlChar *) ACL_NS_PREFIX "denied";
static int
pcmk__eval_acl_as_namespaces_2(xmlNode *xml_modify)
{
static xmlNs *ns_recycle_writable = NULL,
*ns_recycle_readable = NULL,
*ns_recycle_denied = NULL;
static const xmlDoc *prev_doc = NULL;
xmlNode *i_node = NULL;
const xmlChar *ns;
int ret = 0;
if (prev_doc == NULL || prev_doc != xml_modify->doc) {
prev_doc = xml_modify->doc;
ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
}
for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
switch (i_node->type) {
case XML_ELEMENT_NODE:
pcmk__set_xml_flag(i_node, xpf_tracking);
ns = !pcmk__check_acl(i_node, NULL, xpf_acl_read)
? NS_DENIED
: !pcmk__check_acl(i_node, NULL, xpf_acl_write)
? NS_READABLE
: NS_WRITABLE;
if (ns == NS_WRITABLE) {
if (ns_recycle_writable == NULL) {
ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_WRITABLE, ACL_NS_Q_WRITABLE);
ret |= PCMK_ACL_VERDICT_WRITABLE;
}
xmlSetNs(i_node, ns_recycle_writable);
} else if (ns == NS_READABLE) {
if (ns_recycle_readable == NULL) {
ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_READABLE, ACL_NS_Q_READABLE);
ret |= PCMK_ACL_VERDICT_READABLE;
}
xmlSetNs(i_node, ns_recycle_readable);
} else if (ns == NS_DENIED) {
if (ns_recycle_denied == NULL) {
ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_DENIED, ACL_NS_Q_DENIED);
ret |= PCMK_ACL_VERDICT_DENIED;
};
xmlSetNs(i_node, ns_recycle_denied);
}
/* XXX recursion can be turned into plain iteration to save stack */
if (i_node->properties != NULL) {
/* this is not entirely clear, but relies on the very same
class-hierarchy emulation that libxml2 has firmly baked in
its API/ABI */
ret |= pcmk__eval_acl_as_namespaces_2((xmlNodePtr) i_node->properties);
}
if (i_node->children != NULL) {
ret |= pcmk__eval_acl_as_namespaces_2(i_node->children);
}
break;
case XML_ATTRIBUTE_NODE:
/* we can utilize that parent has already been assigned the ns */
ns = !pcmk__check_acl(i_node->parent,
(const char *) i_node->name,
xpf_acl_read)
? NS_DENIED
: !pcmk__check_acl(i_node,
(const char *) i_node->name,
xpf_acl_write)
? NS_READABLE
: NS_WRITABLE;
if (ns == NS_WRITABLE) {
if (ns_recycle_writable == NULL) {
ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_WRITABLE, ACL_NS_Q_WRITABLE);
ret |= PCMK_ACL_VERDICT_WRITABLE;
}
xmlSetNs(i_node, ns_recycle_writable);
} else if (ns == NS_READABLE) {
if (ns_recycle_readable == NULL) {
ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_READABLE, ACL_NS_Q_READABLE);
ret |= PCMK_ACL_VERDICT_READABLE;
}
xmlSetNs(i_node, ns_recycle_readable);
} else if (ns == NS_DENIED) {
if (ns_recycle_denied == NULL) {
ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_DENIED, ACL_NS_Q_DENIED);
ret |= PCMK_ACL_VERDICT_DENIED;
}
xmlSetNs(i_node, ns_recycle_denied);
}
break;
default:
break;
}
}
return ret;
}
int
-pcmk_acl_evaled_as_namespaces(enum pcmk_acl_cred_type cred_type,
- const char *cred, xmlDoc *cib_doc,
+pcmk_acl_evaled_as_namespaces(const char *cred, xmlDoc *cib_doc,
xmlDoc **acl_evaled_doc)
{
- /* XXX requires prior crm_xml_init */
-
int ret, version;
xmlNode *target, *comment;
char comment_buf[256] = "access as evaluated for user ";
const char *validation;
- CRM_CHECK(cred != NULL, return -1);
- CRM_CHECK(cib_doc != NULL, return -1);
- CRM_CHECK(acl_evaled_doc != NULL, return -1);
- /* XXX no proper support for groups yet */
- CRM_CHECK(cred_type == PCMK_ACL_CRED_USER, return -2);
+ CRM_CHECK(cred != NULL, return EINVAL);
+ CRM_CHECK(cib_doc != NULL, return EINVAL);
+ CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
if (!pcmk_acl_required(cred)) {
/* nothing to evaluate */
return 0;
}
/* XXX see the comment for this function, pacemaker-4.0 may need
updating respectively in the future */
validation = crm_element_value(xmlDocGetRootElement(cib_doc),
XML_ATTR_VALIDATION);
version = get_schema_version(validation);
- if (get_schema_version(PCMK_COMPAT_ACL_2_MIN_INCL) > version
- || (-1 != get_schema_version(PCMK_COMPAT_ACL_2_MAX_EXCL)
- && get_schema_version(PCMK_COMPAT_ACL_2_MAX_EXCL)
- <= version)) {
+ if (get_schema_version(PCMK_COMPAT_ACL_2_MIN_INCL) > version) {
return -3;
}
target = copy_xml(xmlDocGetRootElement(cib_doc));
if (target == NULL) {
return -1;
}
pcmk__unpack_acl(target, target, cred);
pcmk__set_xml_flag(target, xpf_acl_enabled);
pcmk__apply_acl(target);
ret = pcmk__eval_acl_as_namespaces_2(target); /* XXX may need "switch" */
if (ret > 0) {
/* avoid trivial accidental XML injection */
if (strpbrk(cred, "<>&") == NULL) {
snprintf(comment_buf + strlen(comment_buf),
sizeof(comment_buf) - strlen(comment_buf), "%s", cred);
comment = xmlNewDocComment(target->doc, (pcmkXmlStr) comment_buf);
if (comment == NULL) {
xmlFreeNode(target);
return -1;
}
xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
}
*acl_evaled_doc = target->doc;
} else {
xmlFreeNode(target);
}
return ret;
}
/* this is used to dynamically adapt to user-modified stylesheet */
static const char **
parse_params(xmlDoc *doc, const char **fallback)
{
xmlXPathContext *xpath_ctxt;
xmlXPathObject *xpath_obj;
const char **ret = NULL;
size_t ret_cnt = 0, ret_iter = 0;
if (doc == NULL) {
return fallback;
}
xpath_ctxt = xmlXPathNewContext(doc);
CRM_ASSERT(xpath_ctxt != NULL);
if (xmlXPathRegisterNs(xpath_ctxt, (pcmkXmlStr) "xsl",
(pcmkXmlStr) "http://www.w3.org/1999/XSL/Transform") != 0) {
return fallback;
}
while (*fallback != NULL) {
char xpath_query[1024];
const char *key = *fallback++;
const char *value = *fallback++;
CRM_ASSERT(value != NULL);
if (ret_iter + 1 >= ret_cnt) {
ret_cnt = ret_cnt ? ret_cnt : 1;
ret_cnt *= 2;
ret_cnt += 1;
ret = realloc(ret, ret_cnt * sizeof(*ret));
CRM_ASSERT(ret != NULL);
}
key = strdup(key);
CRM_ASSERT(key != NULL);
ret[ret_iter++] = key;
snprintf(xpath_query, sizeof(xpath_query),
"substring("
"/xsl:stylesheet/xsl:param[@name = '%s']/xsl:value-of/@select,"
"2,"
"string-length(/xsl:stylesheet/xsl:param[@name = '%s']/xsl:value-of/@select) - 2"
")",
key, key);
xpath_obj = xmlXPathEvalExpression((pcmkXmlStr) xpath_query, xpath_ctxt);
if (xpath_obj != NULL && xpath_obj->type == XPATH_STRING
&& *xpath_obj->stringval != '\0') {
/* XXX convert first! */
char *origval = strdup((const char *) xpath_obj->stringval);
size_t reminder = strlen(origval) + 1;
xmlXPathFreeObject(xpath_obj);
value = origval;
/* reconcile "\x1b" (3 chars) -> '\x1b' (single char) */
while ((origval = strstr(origval, "\\x1b")) != NULL) {
origval[0] = '\x1b';
memmove(origval + 1, origval + (sizeof("\\x1b") - 1),
(reminder -= (sizeof("\\x1b") - 1)));
}
} else {
value = strdup(value);
}
CRM_ASSERT(value != NULL);
ret[ret_iter++] = value;
}
ret[ret_iter] = NULL;
return ret;
}
int
pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
- xmlChar **doc_txt_ptr, int *doc_txt_len)
+ xmlChar **doc_txt_ptr)
{
#if HAVE_LIBXSLT
xmlDoc *xslt_doc;
xsltStylesheet *xslt;
xsltTransformContext *xslt_ctxt;
xmlDoc *res;
char *sfile;
static const char *params_ns_simple[] = {
"accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
"accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
"accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_useansi[] = {
/* start with hard-coded defaults, then adapt per the template ones */
"accessrendercfg:c-writable", "\x1b[32m",
"accessrendercfg:c-readable", "\x1b[34m",
"accessrendercfg:c-denied", "\x1b[31m",
"accessrendercfg:c-reset", "\x1b[0m",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_noansi[] = {
"accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
"accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
"accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "yes",
"accessrender:self-reproducing-prefix", "",
NULL
};
const char **params;
int ret;
xmlParserCtxtPtr parser_ctxt;
/* unfortunately, the input (coming from CIB originally) was parsed with
blanks ignored, and since the output is a conversion of XML to text
format (we would be covered otherwise thanks to implicit
pretty-printing), we need to dump the tree to string output first,
only to subsequently reparse it -- this time with blanks honoured */
xmlChar *annotated_dump;
int dump_size;
xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
XML_PARSE_NONET);
CRM_ASSERT(res != NULL);
xmlFree(annotated_dump);
xmlFreeDoc(annotated_doc);
annotated_doc = res;
sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
"access-render-2");
parser_ctxt = xmlNewParserCtxt();
CRM_ASSERT(sfile != NULL);
CRM_ASSERT(parser_ctxt != NULL);
xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
if (xslt == NULL) {
crm_crit("Problem in parsing %s", sfile);
return -1;
}
free(sfile);
sfile = NULL;
xmlFreeParserCtxt(parser_ctxt);
xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
CRM_ASSERT(xslt_ctxt != NULL);
params = (how == pcmk__acl_render_ns_simple)
? params_ns_simple
: (how == pcmk__acl_render_text)
? params_noansi
: parse_params(xslt_doc, params_useansi);
xsltQuoteUserParams(xslt_ctxt, params);
res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
NULL, NULL, xslt_ctxt);
xmlFreeDoc(annotated_doc);
annotated_doc = NULL;
xsltFreeTransformContext(xslt_ctxt);
xslt_ctxt = NULL;
if (how == pcmk__acl_render_color && params != params_useansi) {
char **param_i = (char **) params;
do {
free(*param_i);
} while (*param_i++ != NULL);
free(params);
}
if (res == NULL) {
- ret = -1;
+ ret = EINVAL;
} else {
- ret = xsltSaveResultToString(doc_txt_ptr, doc_txt_len, res, xslt);
+ int doc_txt_len;
+ int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
xmlFreeDoc(res);
+ if (temp == 0) {
+ ret = pcmk_rc_ok;
+ } else {
+ ret = EINVAL;
+ }
}
xsltFreeStylesheet(xslt);
return ret;
#else
return -1;
#endif
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 4, 6:13 AM (6 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1854724
Default Alt Text
(75 KB)

Event Timeline