Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4149395
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
75 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment