diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h index e7b2437b09..a67e385f0c 100644 --- a/include/crm/common/xml.h +++ b/include/crm/common/xml.h @@ -1,341 +1,346 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef CRM_COMMON_XML__H #define CRM_COMMON_XML__H #include #include #include #include #include #include #include #include #include #include typedef xmlNode crm_data_t; /* Encryption costs a LOT, don't do it unless we're hitting message limits * * For now, use 256k as the lower size, which means we can have 4 big data fields * before we hit heartbeat's message limit * * The previous limit was 10k, compressing 184 of 1071 messages accounted for 23% * of the total CPU used by the cib */ #define CRM_BZ2_BLOCKS 4 #define CRM_BZ2_WORK 20 #define CRM_BZ2_THRESHOLD 128 * 1024 #define XML_PARANOIA_CHECKS 0 extern gboolean add_message_xml( xmlNode *msg, const char *field, xmlNode *xml); extern xmlNode *get_message_xml(xmlNode *msg, const char *field); extern GHashTable *xml2list(xmlNode *parent); #if CRM_DEPRECATED_SINCE_2_0_3 extern GHashTable *xml2list_202(xmlNode *parent); #endif extern void hash2nvpair(gpointer key, gpointer value, gpointer user_data); extern void hash2field(gpointer key, gpointer value, gpointer user_data); extern void hash2metafield(gpointer key, gpointer value, gpointer user_data); extern void hash2smartfield(gpointer key, gpointer value, gpointer user_data); /* * Replacement function for xmlCopyPropList which at the very least, * doesnt work the way *I* would expect it to. * * Copy all the attributes/properties from src into target. * * Not recursive, does not return anything. * */ extern void copy_in_properties(xmlNode *target, xmlNode *src); extern void expand_plus_plus(xmlNode* target, const char *name, const char *value); extern void fix_plus_plus_recursive(xmlNode* target); void free_xml_from_parent(xmlNode *parent, xmlNode *a_node); #define zap_xml_from_parent(parent, xml_obj) free_xml_from_parent(parent, xml_obj); xml_obj = NULL /* * Create a node named "name" as a child of "parent" * If parent is NULL, creates an unconnected node. * * Returns the created node * */ extern xmlNode *create_xml_node(xmlNode *parent, const char *name); /* * Make a copy of name and value and use the copied memory to create * an attribute for node. * * If node, name or value are NULL, nothing is done. * * If name or value are an empty string, nothing is done. * * Returns FALSE on failure and TRUE on success. * */ extern const char *crm_xml_add( xmlNode *node, const char *name, const char *value); extern const char *crm_xml_replace( xmlNode *node, const char *name, const char *value); extern const char *crm_xml_add_int( xmlNode* node, const char *name, int value); /* * Unlink the node and set its doc pointer to NULL so free_xml() * will act appropriately */ extern void unlink_xml_node(xmlNode *node); /* * */ extern void purge_diff_markers(xmlNode *a_node); /* * Returns a deep copy of src_node * */ extern xmlNode *copy_xml(xmlNode *src_node); /* * Add a copy of xml_node to new_parent */ extern xmlNode *add_node_copy( xmlNode *new_parent, xmlNode *xml_node); extern int add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child); /* * XML I/O Functions * * Whitespace between tags is discarded. */ extern xmlNode *filename2xml(const char *filename); extern xmlNode *stdin2xml(void); extern xmlNode *string2xml(const char *input); extern int write_xml_file( xmlNode *xml_node, const char *filename, gboolean compress); extern char *dump_xml_formatted(xmlNode *msg); extern char *dump_xml_unformatted(xmlNode *msg); /* * Diff related Functions */ extern xmlNode *diff_xml_object( xmlNode *left, xmlNode *right, gboolean suppress); +extern xmlNode *subtract_xml_object( + xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, const char *marker); + +extern gboolean can_prune_leaf(xmlNode *xml_node); + extern void print_xml_diff(FILE *where, xmlNode *diff); extern void log_xml_diff(unsigned int log_level, xmlNode *diff, const char *function); extern gboolean apply_xml_diff( xmlNode *old, xmlNode *diff, xmlNode **new); /* * Searching & Modifying */ extern xmlNode *find_xml_node( xmlNode *cib, const char * node_path, gboolean must_find); extern xmlNode *find_entity( xmlNode *parent, const char *node_name, const char *id); extern void xml_remove_prop(xmlNode *obj, const char *name); extern gboolean replace_xml_child( xmlNode *parent, xmlNode *child, xmlNode *update, gboolean delete_only); extern gboolean update_xml_child(xmlNode *child, xmlNode *to_update); extern int find_xml_children( xmlNode **children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches); extern int crm_element_value_int(xmlNode *data, const char *name, int *dest); extern char *crm_element_value_copy(xmlNode *data, const char *name); extern const char *crm_element_value_const(const xmlNode *data, const char *name); extern xmlNode *get_xpath_object(const char *xpath, xmlNode *xml_obj, int error_level); extern xmlNode *get_xpath_object_relative(const char *xpath, xmlNode *xml_obj, int error_level); #define crm_element_name(xml) (xml)?(const char *)(xml)->name:NULL extern const char *crm_element_value(xmlNode *data, const char *name); extern void xml_validate(const xmlNode *root); extern gboolean xml_has_children(const xmlNode *root); /* For ABI compatability with version < 1.1.4 */ extern char *calculate_xml_digest(xmlNode *local_cib, gboolean sort, gboolean do_filter); extern char *calculate_on_disk_digest(xmlNode *local_cib); extern char *calculate_operation_digest(xmlNode *local_cib, const char *version); extern char *calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version); extern gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs); extern gboolean validate_xml_verbose(xmlNode *xml_blob); extern int update_validation(xmlNode **xml_blob, int *best, gboolean transform, gboolean to_logs); extern int get_schema_version(const char *name); extern const char *get_schema_name(int version); extern void crm_xml_cleanup(void); #if XML_PARANOIA_CHECKS # define crm_validate_data(obj) xml_validate(obj) #else # define crm_validate_data(obj) CRM_LOG_ASSERT(obj != NULL) #endif static inline xmlNode *__xml_first_child(xmlNode *parent) { xmlNode *child = NULL; if(parent) { child = parent->children; while(child && child->type != XML_ELEMENT_NODE) { child = child->next; } } return child; } static inline xmlNode *__xml_next(xmlNode *child) { if(child) { child = child->next; while(child && child->type != XML_ELEMENT_NODE) { child = child->next; } } return child; } /* These two child iterator macros are no longer to be used * They exist for compatability reasons and will be removed in a * future release */ # define xml_child_iter(parent, child, code) do { \ if(parent != NULL) { \ xmlNode *child = NULL; \ xmlNode *__crm_xml_iter = parent->children; \ while(__crm_xml_iter != NULL) { \ child = __crm_xml_iter; \ __crm_xml_iter = __crm_xml_iter->next; \ if(child->type == XML_ELEMENT_NODE) { \ code; \ } \ } \ } \ } while(0) # define xml_child_iter_filter(parent, child, filter, code) do { \ if(parent != NULL) { \ xmlNode *child = NULL; \ xmlNode *__crm_xml_iter = parent->children; \ while(__crm_xml_iter != NULL) { \ child = __crm_xml_iter; \ __crm_xml_iter = __crm_xml_iter->next; \ if(child->type == XML_ELEMENT_NODE) { \ if(filter == NULL \ || crm_str_eq(filter, (const char *)child->name, TRUE)) { \ code; \ } \ } \ } \ } \ } while(0) # define xml_prop_iter(parent, prop_name, prop_value, code) do { \ if(parent != NULL) { \ xmlAttrPtr prop_iter = parent->properties; \ const char *prop_name = NULL; \ const char *prop_value = NULL; \ while(prop_iter != NULL) { \ prop_name = (const char *)prop_iter->name; \ prop_value = crm_element_value(parent, prop_name); \ prop_iter = prop_iter->next; \ if(prop_name) { \ code; \ } \ } \ } \ } while(0) # define xml_prop_name_iter(parent, prop_name, code) do { \ if(parent != NULL) { \ xmlAttrPtr prop_iter = parent->properties; \ const char *prop_name = NULL; \ while(prop_iter != NULL) { \ prop_name = (const char *)prop_iter->name; \ prop_iter = prop_iter->next; \ if(prop_name) { \ code; \ } \ } \ } \ } while(0) # define free_xml(a_node) do { \ if((a_node) != NULL) { \ xmlNode *a_doc_top = NULL; \ xmlDoc *a_doc = (a_node)->doc; \ if (a_doc != NULL) { \ a_doc_top = xmlDocGetRootElement(a_doc); \ } \ if(a_doc != NULL && a_doc_top == (a_node)) { \ xmlFreeDoc(a_doc); \ \ } else { \ /* make sure the node is unlinked first */ \ xmlUnlinkNode(a_node); \ xmlFreeNode(a_node); \ } \ } \ } while(0) extern xmlNode *first_named_child(xmlNode *parent, const char *name); extern xmlNode *convert_ipc_message(IPC_Message *msg, const char *field); extern xmlNode *convert_ha_message(xmlNode *parent, HA_Message *msg, const char *field); extern HA_Message *convert_xml_message(xmlNode *msg); extern xmlNode *sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive); extern xmlXPathObjectPtr xpath_search(xmlNode *xml_top, const char *path); extern gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs); extern xmlNode *expand_idref(xmlNode *input, xmlNode *top); extern xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index); #endif diff --git a/lib/cib/cib_acl.c b/lib/cib/cib_acl.c index 71711a172c..b25553154b 100644 --- a/lib/cib/cib_acl.c +++ b/lib/cib/cib_acl.c @@ -1,736 +1,782 @@ /* * Copyright (C) 2009 Yan Gao * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include +#include typedef struct acl_obj_s { const char *mode; const char *tag; const char *ref; const char *xpath; const char *attribute; } acl_obj_t; typedef struct xml_perm_s { const char *mode; GHashTable *attribute_perms; } xml_perm_t; static gboolean req_by_superuser(xmlNode *request); +static xmlNode *diff_xml_object_orig(xmlNode *old, xmlNode *new, gboolean suppress, xmlNode *new_diff); static gboolean unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl); static gboolean user_match(const char *user, const char *uid); static gboolean unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl); static gboolean unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl); static gboolean acl_append(xmlNode *acl_child, GListPtr *acl); static void free_acl(GListPtr acl); static gboolean parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl); static gboolean gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms); static int search_xml_children(GListPtr *children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches); static int search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath); static gboolean update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms); static gboolean update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms); static void free_xml_perm(gpointer xml_perm); static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms); static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms); /* rc = TRUE if orig_cib has been filtered*/ /* That means *filtered_cib rather than orig_cib should be exploited afterwards*/ gboolean acl_filter_cib(xmlNode *request, xmlNode *current_cib, xmlNode *orig_cib, xmlNode **filtered_cib) { const char *user = NULL; xmlNode *xml_acls = NULL; xmlNode *tmp_cib = NULL; GListPtr user_acl = NULL; GHashTable *xml_perms = NULL; *filtered_cib = NULL; if (req_by_superuser(request)) { return FALSE; } if (orig_cib == NULL) { return FALSE; } if (current_cib == NULL) { return TRUE; } xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); if (xml_acls == NULL) { crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); return TRUE; } user = crm_element_value(request, F_CIB_USER); unpack_user_acl(xml_acls, user, &user_acl); tmp_cib = copy_xml(orig_cib); gen_xml_perms(tmp_cib, user_acl, &xml_perms); if (acl_filter_xml(tmp_cib, xml_perms)) { crm_warn("User '%s' doesn't have the permission for the whole CIB", user); tmp_cib = NULL; } g_hash_table_destroy(xml_perms); free_acl(user_acl); *filtered_cib = tmp_cib; return TRUE; } /* rc = TRUE if the request passes the ACL check */ /* rc = FALSE if the permission is denied */ gboolean acl_check_diff(xmlNode *request, xmlNode *current_cib, xmlNode *result_cib, xmlNode *diff) { const char *user = NULL; xmlNode *xml_acls = NULL; GListPtr user_acl = NULL; + xmlNode *orig_diff = NULL; int rc = FALSE; if (req_by_superuser(request)) { return TRUE; } if (diff == NULL) { return TRUE; } if (current_cib == NULL) { return FALSE; } xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); if (xml_acls == NULL) { crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); return FALSE; } user = crm_element_value(request, F_CIB_USER); unpack_user_acl(xml_acls, user, &user_acl); + orig_diff = diff_xml_object_orig(current_cib, result_cib, FALSE, diff); + xml_child_iter( - diff, diff_child, + orig_diff, diff_child, const char *tag = crm_element_name(diff_child); GListPtr parsed_acl = NULL; crm_debug("Preparing ACL checking on '%s'", tag); if (crm_str_eq(tag, XML_TAG_DIFF_REMOVED, TRUE)) { crm_debug("Parsing any xpaths under the ACL according to the current CIB"); parse_acl_xpath(current_cib, user_acl, &parsed_acl); } else if (crm_str_eq(tag, XML_TAG_DIFF_ADDED, TRUE)) { crm_debug("Parsing any xpaths under the ACL according to the result CIB"); parse_acl_xpath(result_cib, user_acl, &parsed_acl); } else { continue; } xml_child_iter( diff_child, diff_cib, GHashTable *xml_perms = NULL; gen_xml_perms(diff_cib, parsed_acl, &xml_perms); rc = acl_check_diff_xml(diff_cib, xml_perms); g_hash_table_destroy(xml_perms); if (rc == FALSE) { crm_warn("User '%s' doesn't have enough permission to modify the CIB objects", user); goto done; } ); free_acl(parsed_acl); ); done: + free_xml(orig_diff); free_acl(user_acl); return rc; } static gboolean req_by_superuser(xmlNode *request) { const char *user = crm_element_value(request, F_CIB_USER); if (user == NULL) { crm_debug("Request without an explicit client user: op=%s, origin=%s, client=%s", crm_element_value(request, F_CIB_OPERATION), crm_element_value(request, F_ORIG)?crm_element_value(request, F_ORIG):"local", crm_element_value(request, F_CIB_CLIENTNAME)); return TRUE; } if (crm_str_eq(user, CRM_DAEMON_USER, TRUE) || crm_str_eq(user, "root", TRUE)) { return TRUE; } return FALSE; } +/* Borrowed from lib/common/xml.c: diff_xml_object() */ +/* But if a new format of diff ("new_diff") exists, we could reuse its "diff-removed" part */ +/* So it would be more time-saving than generating the diff from start */ +static xmlNode * +diff_xml_object_orig(xmlNode *old, xmlNode *new, gboolean suppress, xmlNode *new_diff) +{ + xmlNode *tmp1 = NULL; + xmlNode *diff = create_xml_node(NULL, "diff"); + xmlNode *removed = NULL; + xmlNode *added = NULL; + + crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); + + if (new_diff && (tmp1 = find_xml_node(new_diff, "diff-removed", FALSE))) { + removed = add_node_copy(diff, tmp1); + + } else { + removed = create_xml_node(diff, "diff-removed"); + + tmp1 = subtract_xml_object(removed, old, new, FALSE, "removed:top"); + if(suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { + free_xml_from_parent(removed, tmp1); + } + } + + added = create_xml_node(diff, "diff-added"); + + tmp1 = subtract_xml_object(added, new, old, FALSE, "added:top"); + if(suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { + free_xml_from_parent(added, tmp1); + } + + if(added->children == NULL && removed->children == NULL) { + free_xml(diff); + diff = NULL; + } + + return diff; +} + static gboolean unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl) { if (xml_acls == NULL) { return FALSE; } xml_child_iter( xml_acls, xml_acl, const char *tag = crm_element_name(xml_acl); const char *id = crm_element_value(xml_acl, XML_ATTR_ID); if (crm_str_eq(tag, XML_ACL_TAG_USER, TRUE)) { if (user_match(user, id)) { crm_debug("Unpacking ACL of user: '%s'", id); unpack_acl(xml_acls, xml_acl, user_acl); return TRUE; } } ); return FALSE; } static gboolean user_match(const char *user, const char *uid) { struct passwd *pwent = NULL; int user_uid_num = -1; int uid_num = -1; if (crm_str_eq(user, uid, TRUE)) { return TRUE; } pwent = getpwnam(user); if (pwent == NULL) { crm_warn("No user named '%s' exists!", user); return FALSE; } user_uid_num = pwent->pw_uid; uid_num = crm_int_helper(uid, NULL); if(errno == 0 && uid_num == user_uid_num) { return TRUE; } return FALSE; } static gboolean unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl) { const char *role = crm_element_value(xml_acl, XML_ACL_ATTR_ROLE_REF); if (role) { unpack_role_acl(xml_acls, role, acl); } xml_child_iter( xml_acl, acl_child, const char *tag = crm_element_name(acl_child); if (crm_str_eq(XML_ACL_TAG_READ, tag, TRUE) || crm_str_eq(XML_ACL_TAG_WRITE, tag, TRUE) || crm_str_eq(XML_ACL_TAG_DENY, tag, TRUE)) acl_append(acl_child, acl); ); return TRUE; } static gboolean unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl) { xml_child_iter_filter( xml_acls, xml_acl, XML_ACL_TAG_ROLE, const char *role_id = crm_element_value(xml_acl, XML_ATTR_ID); if (role_id && crm_str_eq(role, role_id, TRUE)) { crm_debug("Unpacking ACL of the referenced role: '%s'", role); unpack_acl(xml_acls, xml_acl, acl); return TRUE; } ); return FALSE; } static gboolean acl_append(xmlNode *acl_child, GListPtr *acl) { acl_obj_t *acl_obj = NULL; const char *tag = crm_element_value(acl_child, XML_ACL_ATTR_TAG); const char *ref = crm_element_value(acl_child, XML_ACL_ATTR_REF); const char *xpath = crm_element_value(acl_child, XML_ACL_ATTR_XPATH); if (tag == NULL && ref == NULL && xpath == NULL) { return FALSE; } crm_malloc0(acl_obj, sizeof(acl_obj_t)); if (acl_obj == NULL) { return FALSE; } acl_obj->mode = crm_element_name(acl_child); acl_obj->tag = tag; acl_obj->ref = ref; acl_obj->xpath = xpath; acl_obj->attribute = crm_element_value(acl_child, XML_ACL_ATTR_ATTRIBUTE); *acl = g_list_append(*acl, acl_obj); crm_debug_3("ACL object appended: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); return TRUE; } static void free_acl(GListPtr acl) { GListPtr iterator = acl; while(iterator != NULL) { crm_free(iterator->data); iterator = iterator->next; } if(acl != NULL) { g_list_free(acl); } } static gboolean parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl) { GListPtr acl_iterator = acl; acl_obj_t *new_acl_obj = NULL; *parsed_acl = NULL; while (acl_iterator != NULL) { acl_obj_t *acl_obj = acl_iterator->data; if (acl_obj->tag || acl_obj->ref) { crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); if (new_acl_obj == NULL) { return FALSE; } memcpy(new_acl_obj, acl_obj, sizeof(acl_obj_t)); *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); crm_debug_3("Copied ACL object: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", new_acl_obj->mode, new_acl_obj->tag, new_acl_obj->ref, new_acl_obj->xpath, new_acl_obj->attribute); } else if (acl_obj->xpath) { GListPtr children = NULL; GListPtr children_iterator = NULL; search_xpath_objects(&children, xml, acl_obj->xpath); children_iterator = children; while (children_iterator != NULL) { crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); if (new_acl_obj == NULL) { return FALSE; } new_acl_obj->mode = acl_obj->mode; new_acl_obj->tag = crm_element_name((xmlNode*)children_iterator->data); new_acl_obj->ref = crm_element_value(children_iterator->data, XML_ATTR_ID); new_acl_obj->attribute = acl_obj->attribute; *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); crm_debug_3("Parsed the ACL object with xpath '%s' to: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->xpath, new_acl_obj->mode, new_acl_obj->tag, new_acl_obj->ref, new_acl_obj->xpath, new_acl_obj->attribute); children_iterator = children_iterator->next; } g_list_free(children); } acl_iterator = acl_iterator->next; } return TRUE; } static gboolean gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms) { GListPtr acl_iterator = acl; if (*xml_perms == NULL) { *xml_perms = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_xml_perm); } while (acl_iterator != NULL) { acl_obj_t *acl_obj = acl_iterator->data; GListPtr children = NULL; GListPtr children_iterator = NULL; crm_debug("Generating permissions with ACL: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); if (acl_obj->tag || acl_obj->ref) { search_xml_children(&children, xml, acl_obj->tag, XML_ATTR_ID, acl_obj->ref, TRUE); } else if (acl_obj->xpath) { /* Never be here for a modification operation */ /* Already parse_acl_xpath() previously */ search_xpath_objects(&children, xml, acl_obj->xpath); } children_iterator = children; while (children_iterator != NULL) { update_xml_perms(children_iterator->data, acl_obj, *xml_perms); children_iterator = children_iterator->next; } g_list_free(children); acl_iterator = acl_iterator->next; } return TRUE; } /* Borrowed from lib/common/xml.c: find_xml_children() */ /* But adding the original xmlNode pointers into a GList */ static int search_xml_children(GListPtr *children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches) { int match_found = 0; CRM_CHECK(root != NULL, return FALSE); CRM_CHECK(children != NULL, return FALSE); if(tag != NULL && safe_str_neq(tag, crm_element_name(root))) { } else if(value != NULL && safe_str_neq(value, crm_element_value(root, field))) { } else { *children = g_list_append(*children, root); match_found = 1; } if(search_matches || match_found == 0) { xml_child_iter( root, child, match_found += search_xml_children( children, child, tag, field, value, search_matches); ); } return match_found; } static int search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath) { int match_found = 0; xmlXPathObjectPtr xpathObj = NULL; if(xpath == NULL) { return 0; } xpathObj = xpath_search(xml_obj, xpath); if(xpathObj == NULL || xpathObj->nodesetval == NULL || xpathObj->nodesetval->nodeNr < 1) { crm_debug("No match for %s in %s", xpath, xmlGetNodePath(xml_obj)); } else if(xpathObj->nodesetval->nodeNr > 0) { int lpc = 0, max = xpathObj->nodesetval->nodeNr; for(lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); if (match == NULL) { continue; } *objects = g_list_append(*objects, match); match_found++; } } if(xpathObj) { xmlXPathFreeObject(xpathObj); } return match_found; } static gboolean update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms) { xml_perm_t *perm = NULL; if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { if (perm->mode != NULL) { return FALSE; } } else { crm_malloc0(perm, sizeof(xml_perm_t)); if (perm == NULL) { return FALSE; } g_hash_table_insert(xml_perms, xml, perm); } if (acl_obj->attribute == NULL) { perm->mode = acl_obj->mode; crm_debug_3("Permission for element: element_mode=%s, tag=%s, id=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); xml_child_iter( xml, child, update_xml_children_perms(child, perm->mode, xml_perms); ); } else { if (perm->attribute_perms == NULL || (g_hash_table_lookup_extended(perm->attribute_perms, acl_obj->attribute, NULL, NULL) == FALSE)) { if (perm->attribute_perms == NULL) { perm->attribute_perms = g_hash_table_new_full( g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); } g_hash_table_insert(perm->attribute_perms, crm_strdup(acl_obj->attribute), crm_strdup(acl_obj->mode)); crm_debug_3("Permission for attribute: attribute_mode=%s, tag=%s, id=%s attribute=%s", acl_obj->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), acl_obj->attribute); } } return TRUE; } static gboolean update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms) { xml_perm_t *perm = NULL; if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { if (perm->mode != NULL) { return FALSE; } } else { crm_malloc0(perm, sizeof(xml_perm_t)); if (perm == NULL) { return FALSE; } g_hash_table_insert(xml_perms, xml, perm); } perm->mode = mode; crm_debug_4("Permission for child element: element_mode=%s, tag=%s, id=%s", mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); xml_child_iter( xml, child, update_xml_children_perms(child, mode, xml_perms); ); return TRUE; } static void free_xml_perm(gpointer xml_perm) { xml_perm_t *perm = xml_perm; if (perm == NULL) { return; } if (perm->attribute_perms != NULL) { g_hash_table_destroy(perm->attribute_perms); } crm_free(perm); } #define can_read(mode) (crm_str_eq(mode, XML_ACL_TAG_READ, TRUE) \ || crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE)) #define can_write(mode) crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE) /* rc = TRUE if the xml is filtered out*/ static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms) { int children_counter = 0; xml_perm_t *perm = NULL; int allow_counter = 0; xml_child_iter( xml, child, if (acl_filter_xml(child, xml_perms) == FALSE) { children_counter++; } ); g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); if (perm == NULL) { crm_debug_4("No ACL defined to read the element: tag=%s, id=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); goto end_filter; } if (perm->attribute_perms == NULL) { if (can_read(perm->mode)) { return FALSE; } else { crm_debug_4("No enough permission to read the element: element_mode=%s, tag=%s, id=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); goto end_filter; } } xml_prop_iter(xml, prop_name, prop_value, gpointer mode = NULL; if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { if (can_read(mode)) { allow_counter++; } else { xml_remove_prop(xml, prop_name); crm_debug_4("Filtered out the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); } } else { if (can_read(perm->mode)) { allow_counter++; } else if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE) == FALSE) { xml_remove_prop(xml, prop_name); crm_debug_4("Filtered out the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); } } ); if (allow_counter) { return FALSE; } if (can_read(perm->mode)) { return FALSE; } end_filter: if (children_counter) { crm_debug_4("Don't filter out the element (tag=%s, id=%s) because user can read its children", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); return FALSE; } free_xml_from_parent(NULL, xml); crm_debug_4("Filtered out the element: tag=%s, id=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); return TRUE; } static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms) { xml_perm_t *perm = NULL; xml_child_iter( xml, child, if (acl_check_diff_xml(child, xml_perms) == FALSE) { return FALSE; } ); g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); xml_prop_iter(xml, prop_name, prop_value, gpointer mode = NULL; if (crm_str_eq(crm_element_name(xml), XML_TAG_CIB, TRUE)) { if (crm_str_eq(prop_name, XML_ATTR_GENERATION, TRUE) || crm_str_eq(prop_name, XML_ATTR_NUMUPDATES, TRUE) || crm_str_eq(prop_name, XML_ATTR_GENERATION_ADMIN, TRUE)) { continue; } } if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE)) { continue; } if (perm == NULL) { crm_warn("No ACL defined to modify the element: tag=%s, id=%s, attribute=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } if (perm->attribute_perms == NULL) { if (can_write(perm->mode)) { return TRUE; } else { crm_warn("No enough permission to modify the element: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } } if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { if (can_write(mode) == FALSE) { crm_warn("No enough permission to modify the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } } else if (can_write(perm->mode) == FALSE) { crm_warn("No enough permission to modify the element and the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } ); return TRUE; }