diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index e8114c5279..ad0e9a7f37 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,407 +1,405 @@ /* * Copyright 2015-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_INTERNAL__H #define CRM_COMMON_INTERNAL__H #include // getpid() #include // bool #include // uint8_t, uint64_t #include // strcmp() #include // open() #include // uid_t, gid_t, pid_t #include // guint, GList, GHashTable #include // xmlNode #include // crm_strdup_printf() #include // do_crm_log_unlikely(), etc. #include // mainloop_io_t, struct ipc_client_callbacks #include #include #include /* This says whether the current application is a Pacemaker daemon or not, * and is used to change default logging settings such as whether to log to * stderr, etc., as well as a few other details such as whether blackbox signal * handling is enabled. * * It is set when logging is initialized, and does not need to be set directly. */ extern bool pcmk__is_daemon; // Number of elements in a statically defined array #define PCMK__NELEM(a) ((int) (sizeof(a)/sizeof(a[0])) ) /* internal ACL-related utilities */ char *pcmk__uid2username(uid_t uid); const char *pcmk__update_acl_user(xmlNode *request, const char *field, const char *peer_user); static inline bool pcmk__is_privileged(const char *user) { return user && (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")); } -void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user); +void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user); bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode); -void pcmk__apply_acl(xmlNode *xml); - #if SUPPORT_CIBSECRETS /* internal CIB utilities (from cib_secrets.c) */ int pcmk__substitute_secrets(const char *rsc_id, GHashTable *params); #endif /* internal digest-related utilities (from digest.c) */ bool pcmk__verify_digest(xmlNode *input, const char *expected); /* internal I/O utilities (from io.c) */ int pcmk__real_path(const char *path, char **resolved_path); char *pcmk__series_filename(const char *directory, const char *series, int sequence, bool bzip); int pcmk__read_series_sequence(const char *directory, const char *series, unsigned int *seq); void pcmk__write_series_sequence(const char *directory, const char *series, unsigned int sequence, int max); int pcmk__chown_series_sequence(const char *directory, const char *series, uid_t uid, gid_t gid); int pcmk__build_path(const char *path_c, mode_t mode); char *pcmk__full_path(const char *filename, const char *dirname); bool pcmk__daemon_can_write(const char *dir, const char *file); void pcmk__sync_directory(const char *name); int pcmk__file_contents(const char *filename, char **contents); int pcmk__write_sync(int fd, const char *contents); int pcmk__set_nonblocking(int fd); const char *pcmk__get_tmpdir(void); void pcmk__close_fds_in_child(bool); /*! * \internal * \brief Open /dev/null to consume next available file descriptor * * Open /dev/null, disregarding the result. This is intended when daemonizing to * be able to null stdin, stdout, and stderr. * * \param[in] flags O_RDONLY (stdin) or O_WRONLY (stdout and stderr) */ static inline void pcmk__open_devnull(int flags) { // Static analysis clutter // cppcheck-suppress leakReturnValNotUsed (void) open("/dev/null", flags); } /* internal main loop utilities (from mainloop.c) */ int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, struct ipc_client_callbacks *callbacks, mainloop_io_t **source); guint pcmk__mainloop_timer_get_period(mainloop_timer_t *timer); /* internal messaging utilities (from messages.c) */ const char *pcmk__message_name(const char *name); /* internal name/value utilities (from nvpair.c) */ int pcmk__scan_nvpair(const char *input, char **name, char **value); char *pcmk__format_nvpair(const char *name, const char *value, const char *units); char *pcmk__format_named_time(const char *name, time_t epoch_time); /*! * \internal * \brief Add a boolean attribute to an XML node. * * \param[in,out] node XML node to add attributes to * \param[in] name XML attribute to create * \param[in] value Value to give to the attribute */ void pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value); /*! * \internal * \brief Extract a boolean attribute's value from an XML element * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * * \return True if the given \p name is an attribute on \p node and has * the value "true", False in all other cases */ bool pcmk__xe_attr_is_true(xmlNodePtr node, const char *name); /*! * \internal * \brief Extract a boolean attribute's value from an XML element, with * error checking * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * \param[out] value Destination for the value of the attribute * * \return EINVAL if \p name or \p value are NULL, ENODATA if \p node is * NULL or the attribute does not exist, pcmk_rc_unknown_format * if the attribute is not a boolean, and pcmk_rc_ok otherwise. * * \note \p value only has any meaning if the return value is pcmk_rc_ok. */ int pcmk__xe_get_bool_attr(xmlNodePtr node, const char *name, bool *value); /* internal procfs utilities (from procfs.c) */ pid_t pcmk__procfs_pid_of(const char *name); unsigned int pcmk__procfs_num_cores(void); /* internal XML schema functions (from xml.c) */ void crm_schema_init(void); void crm_schema_cleanup(void); /* internal functions related to process IDs (from pid.c) */ /*! * \internal * \brief Check whether process exists (by PID and optionally executable path) * * \param[in] pid PID of process to check * \param[in] daemon If not NULL, path component to match with procfs entry * * \return Standard Pacemaker return code * \note Particular return codes of interest include pcmk_rc_ok for alive, * ESRCH for process is not alive (verified by kill and/or executable path * match), EACCES for caller unable or not allowed to check. A result of * "alive" is less reliable when \p daemon is not provided or procfs is * not available, since there is no guarantee that the PID has not been * recycled for another process. * \note This function cannot be used to verify \e authenticity of the process. */ int pcmk__pid_active(pid_t pid, const char *daemon); int pcmk__read_pidfile(const char *filename, pid_t *pid); int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid); int pcmk__lock_pidfile(const char *filename, const char *name); /* internal functions related to resource operations (from operations.c) */ // printf-style format to create operation ID from resource, action, interval #define PCMK__OP_FMT "%s_%s_%u" char *pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms); char *pcmk__notify_key(const char *rsc_id, const char *notify_type, const char *op_type); char *pcmk__transition_key(int transition_id, int action_id, int target_rc, const char *node); void pcmk__filter_op_for_digest(xmlNode *param_set); // bitwise arithmetic utilities /*! * \internal * \brief Set specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be set * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__set_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group | flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags 0x%.8llx (%s) for %s set by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } /*! * \internal * \brief Clear specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be cleared * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__clear_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group & ~flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags 0x%.8llx (%s) for %s cleared by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } // miscellaneous utilities (from utils.c) void pcmk__daemonize(const char *name, const char *pidfile); void pcmk__panic(const char *origin); pid_t pcmk__locate_sbd(void); void pcmk__sleep_ms(unsigned int ms); extern int pcmk__score_red; extern int pcmk__score_green; extern int pcmk__score_yellow; /*! * \internal * \brief Resize a dynamically allocated memory block * * \param[in] ptr Memory block to resize (or NULL to allocate new memory) * \param[in] size New size of memory block in bytes (must be > 0) * * \return Pointer to resized memory block * * \note This asserts on error, so the result is guaranteed to be non-NULL * (which is the main advantage of this over directly using realloc()). */ static inline void * pcmk__realloc(void *ptr, size_t size) { void *new_ptr; // realloc(p, 0) can replace free(p) but this wrapper can't CRM_ASSERT(size > 0); new_ptr = realloc(ptr, size); if (new_ptr == NULL) { free(ptr); abort(); } return new_ptr; } static inline char * pcmk__getpid_s(void) { return crm_strdup_printf("%lu", (unsigned long) getpid()); } // More efficient than g_list_length(list) == 1 static inline bool pcmk__list_of_1(GList *list) { return list && (list->next == NULL); } // More efficient than g_list_length(list) > 1 static inline bool pcmk__list_of_multiple(GList *list) { return list && (list->next != NULL); } /* convenience functions for failure-related node attributes */ #define PCMK__FAIL_COUNT_PREFIX "fail-count" #define PCMK__LAST_FAILURE_PREFIX "last-failure" /*! * \internal * \brief Generate a failure-related node attribute name for a resource * * \param[in] prefix Start of attribute name * \param[in] rsc_id Resource name * \param[in] op Operation name * \param[in] interval_ms Operation interval * * \return Newly allocated string with attribute name * * \note Failure attributes are named like PREFIX-RSC#OP_INTERVAL (for example, * "fail-count-myrsc#monitor_30000"). The '#' is used because it is not * a valid character in a resource ID, to reliably distinguish where the * operation name begins. The '_' is used simply to be more comparable to * action labels like "myrsc_monitor_30000". */ static inline char * pcmk__fail_attr_name(const char *prefix, const char *rsc_id, const char *op, guint interval_ms) { CRM_CHECK(prefix && rsc_id && op, return NULL); return crm_strdup_printf("%s-%s#%s_%u", prefix, rsc_id, op, interval_ms); } static inline char * pcmk__failcount_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__FAIL_COUNT_PREFIX, rsc_id, op, interval_ms); } static inline char * pcmk__lastfailure_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__LAST_FAILURE_PREFIX, rsc_id, op, interval_ms); } // internal resource agent functions (from agents.c) int pcmk__effective_rc(int rc); #endif /* CRM_COMMON_INTERNAL__H */ diff --git a/lib/common/acl.c b/lib/common/acl.c index 5b213554a8..928f3ae747 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1,783 +1,797 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #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; } /* */ 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); } } } } } } +/*! + * \internal + * \brief Copy source to target and set xf_acl_enabled flag in target + * + * \param[in] acl_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 set + */ +void +pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user) +{ + pcmk__unpack_acl(acl_source, target, user); + pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled); + pcmk__apply_acl(target); +} + 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); + pcmk__enable_acl(acl_source, target, user); 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) : "") /*! * \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; } \ No newline at end of file diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index a6cc426ccb..fb1f64fd3c 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -1,275 +1,281 @@ /* * Copyright 2018-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 CRMCOMMON_PRIVATE__H # define CRMCOMMON_PRIVATE__H /* This header is for the sole use of libcrmcommon, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #include // uint8_t, uint32_t #include // bool #include // size_t #include // GList #include // xmlNode, xmlAttr #include // struct qb_ipc_response_header // Decent chunk size for processing large amounts of data #define PCMK__BUFFER_SIZE 4096 /* When deleting portions of an XML tree, we keep a record so we can know later * (e.g. when checking differences) that something was deleted. */ typedef struct pcmk__deleted_xml_s { char *path; int position; } pcmk__deleted_xml_t; typedef struct xml_private_s { long check; uint32_t flags; char *user; GList *acls; GList *deleted_objs; // List of pcmk__deleted_xml_t } xml_private_t; #define pcmk__set_xml_flags(xml_priv, flags_to_set) do { \ (xml_priv)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_xml_flags(xml_priv, flags_to_clear) do { \ (xml_priv)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) G_GNUC_INTERNAL void pcmk__xml2text(xmlNode *data, int options, char **buffer, int *offset, int *max, int depth); G_GNUC_INTERNAL void pcmk__buffer_add_char(char **buffer, int *offset, int *max, char c); G_GNUC_INTERNAL bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy); G_GNUC_INTERNAL int pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer, int offset, size_t buffer_size); G_GNUC_INTERNAL void pcmk__mark_xml_created(xmlNode *xml); G_GNUC_INTERNAL int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set); G_GNUC_INTERNAL xmlNode *pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact); G_GNUC_INTERNAL void pcmk__xe_log(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, int options); G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff); G_GNUC_INTERNAL xmlNode *pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact); G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update); G_GNUC_INTERNAL void pcmk__free_acls(GList *acls); +G_GNUC_INTERNAL +void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user); + +G_GNUC_INTERNAL +void pcmk__apply_acl(xmlNode *xml); + G_GNUC_INTERNAL void pcmk__apply_creation_acl(xmlNode *xml, bool check_top); G_GNUC_INTERNAL void pcmk__mark_xml_attr_dirty(xmlAttr *a); G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name); static inline const char * pcmk__xml_attr_value(const xmlAttr *attr) { return ((attr == NULL) || (attr->children == NULL))? NULL : (const char *) attr->children->content; } /* * IPC */ #define PCMK__IPC_VERSION 1 #define PCMK__CONTROLD_API_MAJOR "1" #define PCMK__CONTROLD_API_MINOR "0" // IPC behavior that varies by daemon typedef struct pcmk__ipc_methods_s { /*! * \internal * \brief Allocate any private data needed by daemon IPC * * \param[in] api IPC API connection * * \return Standard Pacemaker return code */ int (*new_data)(pcmk_ipc_api_t *api); /*! * \internal * \brief Free any private data used by daemon IPC * * \param[in] api_data Data allocated by new_data() method */ void (*free_data)(void *api_data); /*! * \internal * \brief Perform daemon-specific handling after successful connection * * Some daemons require clients to register before sending any other * commands. The controller requires a CRM_OP_HELLO (with no reply), and * the CIB manager, executor, and fencer require a CRM_OP_REGISTER (with a * reply). Ideally this would be consistent across all daemons, but for now * this allows each to do its own authorization. * * \param[in] api IPC API connection * * \return Standard Pacemaker return code */ int (*post_connect)(pcmk_ipc_api_t *api); /*! * \internal * \brief Check whether an IPC request results in a reply * * \param[in] api IPC API connection * \param[in] request IPC request XML * * \return true if request would result in an IPC reply, false otherwise */ bool (*reply_expected)(pcmk_ipc_api_t *api, xmlNode *request); /*! * \internal * \brief Perform daemon-specific handling of an IPC message * * \param[in] api IPC API connection * \param[in] msg Message read from IPC connection */ void (*dispatch)(pcmk_ipc_api_t *api, xmlNode *msg); /*! * \internal * \brief Perform daemon-specific handling of an IPC disconnect * * \param[in] api IPC API connection */ void (*post_disconnect)(pcmk_ipc_api_t *api); } pcmk__ipc_methods_t; // Implementation of pcmk_ipc_api_t struct pcmk_ipc_api_s { enum pcmk_ipc_server server; // Daemon this IPC API instance is for enum pcmk_ipc_dispatch dispatch_type; // How replies should be dispatched size_t ipc_size_max; // maximum IPC buffer size crm_ipc_t *ipc; // IPC connection mainloop_io_t *mainloop_io; // If using mainloop, I/O source for IPC bool free_on_disconnect; // Whether disconnect should free object pcmk_ipc_callback_t cb; // Caller-registered callback (if any) void *user_data; // Caller-registered data (if any) void *api_data; // For daemon-specific use pcmk__ipc_methods_t *cmds; // Behavior that varies by daemon }; typedef struct pcmk__ipc_header_s { struct qb_ipc_response_header qb; uint32_t size_uncompressed; uint32_t size_compressed; uint32_t flags; uint8_t version; } pcmk__ipc_header_t; G_GNUC_INTERNAL int pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request); G_GNUC_INTERNAL void pcmk__call_ipc_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data); G_GNUC_INTERNAL unsigned int pcmk__ipc_buffer_size(unsigned int max); G_GNUC_INTERNAL bool pcmk__valid_ipc_header(const pcmk__ipc_header_t *header); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__controld_api_methods(void); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__pacemakerd_api_methods(void); G_GNUC_INTERNAL pcmk__ipc_methods_t *pcmk__schedulerd_api_methods(void); /* * Logging */ /*! * \brief Check the authenticity of the IPC socket peer process * * If everything goes well, peer's authenticity is verified by the means * of comparing against provided referential UID and GID (either satisfies), * and the result of this check can be deduced from the return value. * As an exception, detected UID of 0 ("root") satisfies arbitrary * provided referential daemon's credentials. * * \param[in] qb_ipc libqb client connection if available * \param[in] sock IPC related, connected Unix socket to check peer of * \param[in] refuid referential UID to check against * \param[in] refgid referential GID to check against * \param[out] gotpid to optionally store obtained PID of the peer * (not available on FreeBSD, special value of 1 * used instead, and the caller is required to * special case this value respectively) * \param[out] gotuid to optionally store obtained UID of the peer * \param[out] gotgid to optionally store obtained GID of the peer * * \return Standard Pacemaker return code * ie: 0 if it the connection is authentic * pcmk_rc_ipc_unauthorized if the connection is not authentic, * standard errors. * * \note While this function is tolerant on what constitutes authorized * IPC daemon process (its effective user matches UID=0 or \p refuid, * or at least its group matches \p refgid), either or both (in case * of UID=0) mismatches on the expected credentials of such peer * process \e shall be investigated at the caller when value of 1 * gets returned there, since higher-than-expected privileges in * respect to the expected/intended credentials possibly violate * the least privilege principle and may pose an additional risk * (i.e. such accidental inconsistency shall be eventually fixed). */ int pcmk__crm_ipc_is_authentic_process(qb_ipcc_connection_t *qb_ipc, int sock, uid_t refuid, gid_t refgid, pid_t *gotpid, uid_t *gotuid, gid_t *gotgid); #endif // CRMCOMMON_PRIVATE__H diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c index e88d7805c3..916a176590 100644 --- a/lib/pacemaker/pcmk_acl.c +++ b/lib/pacemaker/pcmk_acl.c @@ -1,326 +1,328 @@ /* * 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 #include #include #include #include #include #include #include #include #include #if HAVE_LIBXSLT # include # include # include #endif #include #include #include #include #include #include #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 = (const xmlChar *) ACL_NS_PREFIX "writable"; static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable"; static const xmlChar *NS_DENIED = (const 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 = EINVAL; // nodes have not been processed yet 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_doc_flag(i_node, pcmk__xf_tracking); if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) { ns = NS_DENIED; } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) { ns = NS_READABLE; } else { ns = 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); } xmlSetNs(i_node, ns_recycle_writable); ret = pcmk_rc_ok; } else if (ns == NS_READABLE) { if (ns_recycle_readable == NULL) { ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_READABLE, ACL_NS_Q_READABLE); } xmlSetNs(i_node, ns_recycle_readable); ret = pcmk_rc_ok; } else if (ns == NS_DENIED) { if (ns_recycle_denied == NULL) { ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_DENIED, ACL_NS_Q_DENIED); }; xmlSetNs(i_node, ns_recycle_denied); ret = pcmk_rc_ok; } /* 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 */ if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_read)) { ns = NS_DENIED; } else if (!pcmk__check_acl(i_node, (const char *) i_node->name, pcmk__xf_acl_write)) { ns = NS_READABLE; } else { ns = 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); } xmlSetNs(i_node, ns_recycle_writable); ret = pcmk_rc_ok; } else if (ns == NS_READABLE) { if (ns_recycle_readable == NULL) { ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_READABLE, ACL_NS_Q_READABLE); } xmlSetNs(i_node, ns_recycle_readable); ret = pcmk_rc_ok; } else if (ns == NS_DENIED) { if (ns_recycle_denied == NULL) { ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_DENIED, ACL_NS_Q_DENIED); } xmlSetNs(i_node, ns_recycle_denied); ret = pcmk_rc_ok; } break; default: break; } } return ret; } int pcmk__acl_evaled_as_namespaces(const char *cred, xmlDoc *cib_doc, xmlDoc **acl_evaled_doc) { int ret, version; xmlNode *target, *comment; const char *validation; CRM_CHECK(cred != NULL, return EINVAL); CRM_CHECK(cib_doc != NULL, return EINVAL); CRM_CHECK(acl_evaled_doc != NULL, return EINVAL); /* avoid trivial accidental XML injection */ if (strpbrk(cred, "<>&") != NULL) { return EINVAL; } if (!pcmk_acl_required(cred)) { /* nothing to evaluate */ return pcmk_rc_already; } /* 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) { return pcmk_rc_schema_validation; } target = copy_xml(xmlDocGetRootElement(cib_doc)); if (target == NULL) { return EINVAL; } + pcmk__enable_acl(target, target, cred); + ret = pcmk__eval_acl_as_namespaces_2(target); /* XXX may need "switch" */ if (ret > 0) { char* credentials = crm_strdup_printf("%s", cred); comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials); free(credentials); if (comment == NULL) { xmlFreeNode(target); return EINVAL; } xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment); *acl_evaled_doc = target->doc; } else { xmlFreeNode(target); } return pcmk_rc_ok; } int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, 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 EINVAL; } free(sfile); sfile = NULL; xmlFreeParserCtxt(parser_ctxt); xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc); CRM_ASSERT(xslt_ctxt != NULL); if (how == pcmk__acl_render_ns_simple) { params = params_ns_simple; } else if (how == pcmk__acl_render_text) { params = params_noansi; } else { params = 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 = EINVAL; } else { 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 EINVAL; #endif } \ No newline at end of file