diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index a35c5769ad..e8114c5279 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,400 +1,407 @@ /* * 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 (from acl.c) +/* 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); + +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) */ +/* 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/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 09a2767517..3b449468c2 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -1,313 +1,339 @@ /* * 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 # include # include # include /* 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); /*! * \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); +/* internal XML-related utilities */ + +enum xml_private_flags { + pcmk__xf_none = 0x0000, + pcmk__xf_dirty = 0x0001, + pcmk__xf_deleted = 0x0002, + pcmk__xf_created = 0x0004, + pcmk__xf_modified = 0x0008, + + pcmk__xf_tracking = 0x0010, + pcmk__xf_processed = 0x0020, + pcmk__xf_skip = 0x0040, + pcmk__xf_moved = 0x0080, + + pcmk__xf_acl_enabled = 0x0100, + pcmk__xf_acl_read = 0x0200, + pcmk__xf_acl_write = 0x0400, + pcmk__xf_acl_deny = 0x0800, + + pcmk__xf_acl_create = 0x1000, + pcmk__xf_acl_denied = 0x2000, + pcmk__xf_lazy = 0x4000, +}; + +void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); + #endif // PCMK__XML_INTERNAL__H diff --git a/lib/common/acl.c b/lib/common/acl.c index 7117a2979f..5b213554a8 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -1,1151 +1,783 @@ /* * 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 "crmcommon_private.h" -#include - #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); } } } } } } 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) : "") /*! * \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 = (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 = 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_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; - 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__unpack_acl(target, target, cred); - pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled); - pcmk__apply_acl(target); - ret = pcmk__eval_acl_as_namespaces_2(target); /* XXX may need "switch" */ - - if (ret > 0) { - *acl_evaled_doc = target->doc; - } else { - xmlFreeNode(target); - } - return pcmk_rc_ok; -} - -/* 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) -{ -#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 = 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 = 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 -1; -#endif } \ No newline at end of file diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index c1dc32a781..a6cc426ccb 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -1,314 +1,275 @@ /* * 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 -/* - * XML and ACLs - */ - -enum xml_private_flags { - pcmk__xf_none = 0x0000, - pcmk__xf_dirty = 0x0001, - pcmk__xf_deleted = 0x0002, - pcmk__xf_created = 0x0004, - pcmk__xf_modified = 0x0008, - - pcmk__xf_tracking = 0x0010, - pcmk__xf_processed = 0x0020, - pcmk__xf_skip = 0x0040, - pcmk__xf_moved = 0x0080, - - pcmk__xf_acl_enabled = 0x0100, - pcmk__xf_acl_read = 0x0200, - pcmk__xf_acl_write = 0x0400, - pcmk__xf_acl_deny = 0x0800, - - pcmk__xf_acl_create = 0x1000, - pcmk__xf_acl_denied = 0x2000, - pcmk__xf_lazy = 0x4000, -}; - /* 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 -void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); - 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 -bool pcmk__check_acl(xmlNode *xml, const char *name, - enum xml_private_flags mode); - -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/Makefile.am b/lib/pacemaker/Makefile.am index b02a412a2a..18a52dbd92 100644 --- a/lib/pacemaker/Makefile.am +++ b/lib/pacemaker/Makefile.am @@ -1,60 +1,61 @@ # # 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. # include $(top_srcdir)/mk/common.mk AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) noinst_HEADERS = libpacemaker_private.h ## libraries lib_LTLIBRARIES = libpacemaker.la ## SOURCES libpacemaker_la_LDFLAGS = -version-info 4:0:3 libpacemaker_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libpacemaker_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libpacemaker_la_LIBADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/common/libcrmcommon.la # -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version # Use += rather than backlashed continuation lines for parsing by bumplibs libpacemaker_la_SOURCES = +libpacemaker_la_SOURCES += pcmk_acl.c libpacemaker_la_SOURCES += pcmk_cluster_queries.c libpacemaker_la_SOURCES += pcmk_fence.c libpacemaker_la_SOURCES += pcmk_graph_consumer.c libpacemaker_la_SOURCES += pcmk_graph_logging.c libpacemaker_la_SOURCES += pcmk_graph_producer.c libpacemaker_la_SOURCES += pcmk_output.c libpacemaker_la_SOURCES += pcmk_output_utils.c libpacemaker_la_SOURCES += pcmk_resource.c libpacemaker_la_SOURCES += pcmk_sched_allocate.c libpacemaker_la_SOURCES += pcmk_sched_bundle.c libpacemaker_la_SOURCES += pcmk_sched_clone.c libpacemaker_la_SOURCES += pcmk_sched_colocation.c libpacemaker_la_SOURCES += pcmk_sched_constraints.c libpacemaker_la_SOURCES += pcmk_sched_fencing.c libpacemaker_la_SOURCES += pcmk_sched_group.c libpacemaker_la_SOURCES += pcmk_sched_location.c libpacemaker_la_SOURCES += pcmk_sched_messages.c libpacemaker_la_SOURCES += pcmk_sched_native.c libpacemaker_la_SOURCES += pcmk_sched_notif.c libpacemaker_la_SOURCES += pcmk_sched_ordering.c libpacemaker_la_SOURCES += pcmk_sched_promotable.c libpacemaker_la_SOURCES += pcmk_sched_remote.c libpacemaker_la_SOURCES += pcmk_sched_resource.c libpacemaker_la_SOURCES += pcmk_sched_tickets.c libpacemaker_la_SOURCES += pcmk_sched_transition.c libpacemaker_la_SOURCES += pcmk_sched_utilization.c libpacemaker_la_SOURCES += pcmk_sched_utils.c libpacemaker_la_SOURCES += pcmk_simulate.c diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c new file mode 100644 index 0000000000..d218101ca4 --- /dev/null +++ b/lib/pacemaker/pcmk_acl.c @@ -0,0 +1,389 @@ +/* + * 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 = 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_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; + 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; + } + + ret = pcmk__eval_acl_as_namespaces_2(target); /* XXX may need "switch" */ + + if (ret > 0) { + *acl_evaled_doc = target->doc; + } else { + xmlFreeNode(target); + } + return pcmk_rc_ok; +} + +/* 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) +{ +#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 = 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 = 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 -1; +#endif +} \ No newline at end of file