diff --git a/cib/callbacks.c b/cib/callbacks.c index 9fa75caf09..6a360d01fc 100644 --- a/cib/callbacks.c +++ b/cib/callbacks.c @@ -1,1617 +1,1629 @@ /* * 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" extern GMainLoop *mainloop; extern gboolean cib_shutdown_flag; extern gboolean stand_alone; extern const char *cib_root; typedef struct cib_local_notify_s { xmlNode *notify_src; char *client_id; gboolean from_peer; gboolean sync_reply; } cib_local_notify_t; qb_ipcs_service_t *ipcs_ro = NULL; qb_ipcs_service_t *ipcs_rw = NULL; qb_ipcs_service_t *ipcs_shm = NULL; extern crm_cluster_t crm_cluster; extern int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); extern void GHFunc_count_peers(gpointer key, gpointer value, gpointer user_data); gint cib_GCompareFunc(gconstpointer a, gconstpointer b); gboolean can_write(int flags); void send_cib_replace(const xmlNode * sync_request, const char *host); void cib_process_request(xmlNode * request, gboolean privileged, gboolean force_synchronous, gboolean from_peer, crm_client_t * cib_client); extern GHashTable *local_notify_queue; int next_client_id = 0; extern const char *cib_our_uname; extern unsigned long cib_num_ops, cib_num_local, cib_num_updates, cib_num_fail; extern unsigned long cib_bad_connects, cib_num_timeouts; extern int cib_status; int cib_process_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged); gboolean cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged); static gboolean cib_legacy_mode(void) { static gboolean init = TRUE; static gboolean legacy = FALSE; if(init) { init = FALSE; legacy = daemon_option_enabled("legacy", "cib"); if(legacy) { crm_notice("Enabled legacy mode"); } } return legacy; } static int32_t cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (cib_shutdown_flag) { crm_info("Ignoring new client [%d] during shutdown", crm_ipcs_client_pid(c)); return -EPERM; } if (crm_client_new(c, uid, gid) == NULL) { return -EIO; } return 0; } static void cib_ipc_created(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); } static int32_t cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size) { crm_client_t *client = crm_client_get(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, TRUE); } static int32_t cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size) { crm_client_t *client = crm_client_get(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, FALSE); } /* Error code means? */ static int32_t cib_ipc_closed(qb_ipcs_connection_t * c) { crm_client_t *client = crm_client_get(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); crm_client_destroy(client); return 0; } static void cib_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); cib_ipc_closed(c); if (cib_shutdown_flag) { cib_shutdown(0); } } struct qb_ipcs_service_handlers ipc_ro_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = cib_ipc_created, .msg_process = cib_ipc_dispatch_ro, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; struct qb_ipcs_service_handlers ipc_rw_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = cib_ipc_created, .msg_process = cib_ipc_dispatch_rw, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; void cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request, crm_client_t * cib_client, gboolean privileged) { const char *op = crm_element_value(op_request, F_CIB_OPERATION); if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) { if (flags & crm_ipc_client_response) { xmlNode *ack = create_xml_node(NULL, __FUNCTION__); crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); crm_ipcs_send(cib_client, id, ack, flags); cib_client->request_id = 0; free_xml(ack); } return; } else if (crm_str_eq(op, T_CIB_NOTIFY, TRUE)) { /* Update the notify filters for this client */ int on_off = 0; long long bit = 0; const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); crm_debug("Setting %s callbacks for %s (%s): %s", type, cib_client->name, cib_client->id, on_off ? "on" : "off"); if (safe_str_eq(type, T_CIB_POST_NOTIFY)) { bit = cib_notify_post; } else if (safe_str_eq(type, T_CIB_PRE_NOTIFY)) { bit = cib_notify_pre; } else if (safe_str_eq(type, T_CIB_UPDATE_CONFIRM)) { bit = cib_notify_confirm; } else if (safe_str_eq(type, T_CIB_DIFF_NOTIFY)) { bit = cib_notify_diff; } else if (safe_str_eq(type, T_CIB_REPLACE_NOTIFY)) { bit = cib_notify_replace; } if (on_off) { set_bit(cib_client->options, bit); } else { clear_bit(cib_client->options, bit); } if (flags & crm_ipc_client_response) { /* TODO - include rc */ crm_ipcs_send_ack(cib_client, id, flags, "ack", __FUNCTION__, __LINE__); } return; } cib_process_request(op_request, FALSE, privileged, FALSE, cib_client); } int32_t cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged) { uint32_t id = 0; uint32_t flags = 0; int call_options = 0; crm_client_t *cib_client = crm_client_get(c); xmlNode *op_request = crm_ipcs_recv(cib_client, data, size, &id, &flags); if (op_request) { crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); } if (op_request == NULL) { crm_trace("Invalid message from %p", c); crm_ipcs_send_ack(cib_client, id, flags, "nack", __FUNCTION__, __LINE__); return 0; } else if(cib_client == NULL) { crm_trace("Invalid client %p", c); return 0; } if (is_set(call_options, cib_sync_call)) { CRM_ASSERT(flags & crm_ipc_client_response); CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */ cib_client->request_id = id; /* Reply only to the last one */ } if (cib_client->name == NULL) { const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME); if (value == NULL) { cib_client->name = crm_itoa(cib_client->pid); } else { cib_client->name = strdup(value); } } crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); #if ENABLE_ACL determine_request_user(cib_client->user, op_request, F_CIB_USER); #endif crm_log_xml_trace(op_request, "Client[inbound]"); cib_common_callback_worker(id, flags, op_request, cib_client, privileged); free_xml(op_request); return 0; } static uint64_t ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = FALSE; int sync_our_cib(xmlNode * request, gboolean all); static gboolean cib_digester_cb(gpointer data) { if (cib_is_master) { char buffer[32]; xmlNode *ping = create_xml_node(NULL, "ping"); ping_seq++; free(ping_digest); ping_digest = NULL; ping_modified_since = FALSE; snprintf(buffer, 32, U64T, ping_seq); crm_trace("Requesting peer digests (%s)", buffer); crm_xml_add(ping, F_TYPE, "cib"); crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING); crm_xml_add(ping, F_CIB_PING_ID, buffer); crm_xml_add(ping, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); send_cluster_message(NULL, crm_msg_cib, ping, TRUE); free_xml(ping); } return FALSE; } static void process_ping_reply(xmlNode *reply) { uint64_t seq = 0; const char *host = crm_element_value(reply, F_ORIG); xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA); const char *seq_s = crm_element_value(pong, F_CIB_PING_ID); const char *digest = crm_element_value(pong, XML_ATTR_DIGEST); if (seq_s) { seq = crm_int_helper(seq_s, NULL); } if(digest == NULL) { crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host); } else if(seq != ping_seq) { crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host); } else if(ping_modified_since) { crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host); } else { const char *version = crm_element_value(pong, XML_ATTR_CRM_VERSION); if(ping_digest == NULL) { crm_trace("Calculating new digest"); ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version); } crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest); if(safe_str_eq(ping_digest, digest) == FALSE) { xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA); crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p", crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(the_cib, XML_ATTR_GENERATION), crm_element_value(the_cib, XML_ATTR_NUMUPDATES), ping_digest, host, remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"0", remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"0", remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"0", digest, remote_cib); if(remote_cib) { /* Additional debug */ xml_calculate_changes(the_cib, remote_cib, NULL); xml_log_changes(LOG_INFO, __FUNCTION__, remote_cib); free_xml(remote_cib); } crm_trace("End of differences"); sync_our_cib(reply, FALSE); } } } static void do_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { /* send callback to originating child */ crm_client_t *client_obj = NULL; int local_rc = pcmk_ok; if (client_id != NULL) { client_obj = crm_client_get_by_id(client_id); } if (client_obj == NULL) { local_rc = -ECONNRESET; crm_trace("No client to sent the response to. F_CIB_CLIENTID not set."); } else { int rid = 0; if (sync_reply) { if (client_obj->ipcs) { CRM_LOG_ASSERT(client_obj->request_id); rid = client_obj->request_id; client_obj->request_id = 0; crm_trace("Sending response %d to %s %s", rid, client_obj->name, from_peer ? "(originator of delegated request)" : ""); } else { crm_trace("Sending response to %s %s", client_obj->name, from_peer ? "(originator of delegated request)" : ""); } } else { crm_trace("Sending an event to %s %s", client_obj->name, from_peer ? "(originator of delegated request)" : ""); } switch (client_obj->kind) { case CRM_CLIENT_IPC: if (crm_ipcs_send(client_obj, rid, notify_src, sync_reply?crm_ipc_flags_none:crm_ipc_server_event) < 0) { local_rc = -ENOMSG; } break; #ifdef HAVE_GNUTLS_GNUTLS_H case CRM_CLIENT_TLS: #endif case CRM_CLIENT_TCP: crm_remote_send(client_obj->remote, notify_src); break; default: crm_err("Unknown transport %d for %s", client_obj->kind, client_obj->name); } } if (local_rc != pcmk_ok && client_obj != NULL) { crm_warn("%sSync reply to %s failed: %s", sync_reply ? "" : "A-", client_obj ? client_obj->name : "", pcmk_strerror(local_rc)); } } static void parse_local_options_v1(crm_client_t * cib_client, int call_type, int call_options, const char *host, const char *op, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { if (cib_op_modifies(call_type) && !(call_options & cib_inhibit_bcast)) { /* we need to send an update anyway */ *needs_reply = TRUE; } else { *needs_reply = FALSE; } if (host == NULL && (call_options & cib_scope_local)) { crm_trace("Processing locally scoped %s op from %s", op, cib_client->name); *local_notify = TRUE; } else if (host == NULL && cib_is_master) { crm_trace("Processing master %s op locally from %s", op, cib_client->name); *local_notify = TRUE; } else if (safe_str_eq(host, cib_our_uname)) { crm_trace("Processing locally addressed %s op from %s", op, cib_client->name); *local_notify = TRUE; } else if (stand_alone) { *needs_forward = FALSE; *local_notify = TRUE; *process = TRUE; } else { crm_trace("%s op from %s needs to be forwarded to %s", op, cib_client->name, host ? host : "the master instance"); *needs_forward = TRUE; *process = FALSE; } } static void parse_local_options_v2(crm_client_t * cib_client, int call_type, int call_options, const char *host, const char *op, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { if (cib_op_modifies(call_type) && safe_str_neq(op, CIB_OP_MASTER) && safe_str_neq(op, CIB_OP_SLAVE)) { /* we need to send an update anyway */ *needs_reply = TRUE; *needs_forward = TRUE; *process = FALSE; crm_trace("%s op from %s needs to be forwarded to %s", op, cib_client->name, host ? host : "the master instance"); return; } *process = TRUE; *needs_reply = FALSE; *local_notify = TRUE; *needs_forward = FALSE; if (stand_alone) { crm_trace("Processing %s op from %s (stand-alone)", op, cib_client->name); } else if (host == NULL) { crm_trace("Processing unaddressed %s op from %s", op, cib_client->name); } else if (safe_str_eq(host, cib_our_uname)) { crm_trace("Processing locally addressed %s op from %s", op, cib_client->name); } else { crm_trace("%s op from %s needs to be forwarded to %s", op, cib_client->name, host); *needs_forward = TRUE; *process = FALSE; } } static void parse_local_options(crm_client_t * cib_client, int call_type, int call_options, const char *host, const char *op, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { if(cib_legacy_mode()) { parse_local_options_v1(cib_client, call_type, call_options, host, op, local_notify, needs_reply, process, needs_forward); } else { parse_local_options_v2(cib_client, call_type, call_options, host, op, local_notify, needs_reply, process, needs_forward); } } static gboolean parse_peer_options_v1(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { const char *op = NULL; const char *host = NULL; const char *delegated = NULL; const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); const char *update = crm_element_value(request, F_CIB_GLOBAL_UPDATE); gboolean is_reply = safe_str_eq(reply_to, cib_our_uname); if (crm_is_true(update)) { *needs_reply = FALSE; if (is_reply) { *local_notify = TRUE; crm_trace("Processing global/peer update from %s" " that originated from us", originator); } else { crm_trace("Processing global/peer update from %s", originator); } return TRUE; } crm_trace("Processing %s request sent by %s", op, originator); op = crm_element_value(request, F_CIB_OPERATION); if (safe_str_eq(op, "cib_shutdown_req")) { /* Always process these */ *local_notify = FALSE; if (reply_to == NULL || is_reply) { *process = TRUE; } if (is_reply) { *needs_reply = FALSE; } return *process; } if (is_reply && safe_str_eq(op, CRM_OP_PING)) { process_ping_reply(request); return FALSE; } if (is_reply) { crm_trace("Forward reply sent from %s to local clients", originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } host = crm_element_value(request, F_CIB_HOST); if (host != NULL && safe_str_eq(host, cib_our_uname)) { crm_trace("Processing %s request sent to us from %s", op, originator); return TRUE; } else if(is_reply == FALSE && safe_str_eq(op, CRM_OP_PING)) { crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator); *needs_reply = TRUE; return TRUE; } else if (host == NULL && cib_is_master == TRUE) { crm_trace("Processing %s request sent to master instance from %s", op, originator); return TRUE; } delegated = crm_element_value(request, F_CIB_DELEGATED); if (delegated != NULL) { crm_trace("Ignoring msg for master instance"); } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring msg for instance on %s", crm_str(host)); } else if (reply_to == NULL && cib_is_master == FALSE) { /* this is for the master instance and we're not it */ crm_trace("Ignoring reply to %s", crm_str(reply_to)); } else if (safe_str_eq(op, "cib_shutdown_req")) { if (reply_to != NULL) { crm_debug("Processing %s from %s", op, host); *needs_reply = FALSE; } else { crm_debug("Processing %s reply from %s", op, host); } return TRUE; } else { crm_err("Nothing for us to do?"); crm_log_xml_err(request, "Peer[inbound]"); } return FALSE; } static gboolean parse_peer_options_v2(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { const char *host = NULL; const char *delegated = crm_element_value(request, F_CIB_DELEGATED); const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); const char *update = crm_element_value(request, F_CIB_GLOBAL_UPDATE); gboolean is_reply = safe_str_eq(reply_to, cib_our_uname); if(safe_str_eq(op, CIB_OP_REPLACE)) { /* sync_our_cib() sets F_CIB_ISREPLY */ delegated = reply_to; goto skip_is_reply; } else if(safe_str_eq(op, CIB_OP_SYNC)) { } else if (is_reply && safe_str_eq(op, CRM_OP_PING)) { process_ping_reply(request); return FALSE; } else if (crm_is_true(update)) { crm_trace("Ingoring legacy %s global update from %s", op, originator); return FALSE; } else if (is_reply && cib_op_modifies(call_type)) { crm_trace("Ignoring legacy %s reply sent from %s to local clients", op, originator); return FALSE; } else if (safe_str_eq(op, "cib_shutdown_req")) { /* Legacy handling */ crm_debug("Legacy handling of %s message from %s", op, originator); *local_notify = FALSE; if (reply_to == NULL) { *process = TRUE; } return *process; } if(is_reply) { crm_trace("Handling %s reply sent from %s to local clients", op, originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } skip_is_reply: *process = TRUE; *needs_reply = FALSE; if(safe_str_eq(delegated, cib_our_uname)) { *local_notify = TRUE; } else { *local_notify = FALSE; } host = crm_element_value(request, F_CIB_HOST); if (host != NULL && safe_str_eq(host, cib_our_uname)) { crm_trace("Processing %s request sent to us from %s", op, originator); *needs_reply = TRUE; return TRUE; } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring %s operation for instance on %s", op, crm_str(host)); return FALSE; } else if(is_reply == FALSE && safe_str_eq(op, CRM_OP_PING)) { *needs_reply = TRUE; } crm_trace("Processing %s request sent to everyone by %s/%s on %s %s", op, crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), originator, (*local_notify)?"(notify)":""); return TRUE; } static gboolean parse_peer_options(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { /* TODO: What happens when an update comes in after node A * requests the CIB from node B, but before it gets the reply (and * sends out the replace operation) */ if(cib_legacy_mode()) { return parse_peer_options_v1( call_type, request, local_notify, needs_reply, process, needs_forward); } else { return parse_peer_options_v2( call_type, request, local_notify, needs_reply, process, needs_forward); } } static void forward_request(xmlNode * request, crm_client_t * cib_client, int call_options) { const char *op = crm_element_value(request, F_CIB_OPERATION); const char *host = crm_element_value(request, F_CIB_HOST); crm_xml_add(request, F_CIB_DELEGATED, cib_our_uname); if (host != NULL) { crm_trace("Forwarding %s op to %s", op, host); send_cluster_message(crm_get_peer(0, host), crm_msg_cib, request, FALSE); } else { crm_trace("Forwarding %s op to master instance", op); send_cluster_message(NULL, crm_msg_cib, request, FALSE); } /* Return the request to its original state */ xml_remove_prop(request, F_CIB_DELEGATED); if (call_options & cib_discard_reply) { crm_trace("Client not interested in reply"); } } static gboolean send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast) { CRM_ASSERT(msg != NULL); if (broadcast) { /* this (successful) call modified the CIB _and_ the * change needs to be broadcast... * send via HA to other nodes */ int diff_add_updates = 0; int diff_add_epoch = 0; int diff_add_admin_epoch = 0; int diff_del_updates = 0; int diff_del_epoch = 0; int diff_del_admin_epoch = 0; const char *digest = NULL; CRM_LOG_ASSERT(result_diff != NULL); digest = crm_element_value(result_diff, XML_ATTR_DIGEST); cib_diff_version_details(result_diff, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s", diff_del_admin_epoch, diff_del_epoch, diff_del_updates, diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest); crm_xml_add(msg, F_CIB_ISREPLY, originator); crm_xml_add(msg, F_CIB_GLOBAL_UPDATE, XML_BOOLEAN_TRUE); crm_xml_add(msg, F_CIB_OPERATION, CIB_OP_APPLY_DIFF); CRM_ASSERT(digest != NULL); add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff); crm_log_xml_explicit(msg, "copy"); return send_cluster_message(NULL, crm_msg_cib, msg, TRUE); } else if (originator != NULL) { /* send reply via HA to originating node */ crm_trace("Sending request result to %s only", originator); crm_xml_add(msg, F_CIB_ISREPLY, originator); return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE); } return FALSE; } void cib_process_request(xmlNode * request, gboolean force_synchronous, gboolean privileged, gboolean unused, crm_client_t * cib_client) { int call_type = 0; int call_options = 0; gboolean process = TRUE; gboolean is_update = TRUE; gboolean from_peer = TRUE; gboolean needs_reply = TRUE; gboolean local_notify = FALSE; gboolean needs_forward = FALSE; gboolean global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE)); xmlNode *op_reply = NULL; xmlNode *result_diff = NULL; int rc = pcmk_ok; const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *host = crm_element_value(request, F_CIB_HOST); const char *target = NULL; const char *client_id = crm_element_value(request, F_CIB_CLIENTID); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); if (cib_client) { from_peer = FALSE; } cib_num_ops++; if (cib_num_ops == 0) { cib_num_fail = 0; cib_num_local = 0; cib_num_updates = 0; crm_info("Stats wrapped around"); } crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); if (force_synchronous) { call_options |= cib_sync_call; } if (host != NULL && strlen(host) == 0) { host = NULL; } if (host) { target = host; } else if (call_options & cib_scope_local) { target = "local host"; } else { target = "master"; } if (from_peer) { crm_trace("Processing peer %s operation from %s on %s intended for %s (reply=%s)", op, client_id, originator, target, reply_to); } else { crm_xml_add(request, F_ORIG, cib_our_uname); crm_trace("Processing local %s operation from %s intended for %s", op, client_id, target); } rc = cib_get_operation_id(op, &call_type); if (rc != pcmk_ok) { /* TODO: construct error reply? */ crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc)); return; } if (from_peer == FALSE) { parse_local_options(cib_client, call_type, call_options, host, op, &local_notify, &needs_reply, &process, &needs_forward); } else if (parse_peer_options(call_type, request, &local_notify, &needs_reply, &process, &needs_forward) == FALSE) { return; } is_update = cib_op_modifies(call_type); if (is_update) { cib_num_updates++; } if (call_options & cib_discard_reply) { needs_reply = is_update; local_notify = FALSE; } if (needs_forward) { const char *host = crm_element_value(request, F_CIB_HOST); const char *section = crm_element_value(request, F_CIB_SECTION); crm_info("Forwarding %s operation for section %s to %s (origin=%s/%s/%s)", op, section ? section : "'all'", host ? host : "master", originator ? originator : "local", crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID)); forward_request(request, cib_client, call_options); return; } if (cib_status != pcmk_ok) { const char *call = crm_element_value(request, F_CIB_CALLID); rc = cib_status; crm_err("Operation ignored, cluster configuration is invalid." " Please repair and restart: %s", pcmk_strerror(cib_status)); op_reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(op_reply, F_TYPE, T_CIB); crm_xml_add(op_reply, F_CIB_OPERATION, op); crm_xml_add(op_reply, F_CIB_CALLID, call); crm_xml_add(op_reply, F_CIB_CLIENTID, client_id); crm_xml_add_int(op_reply, F_CIB_CALLOPTS, call_options); crm_xml_add_int(op_reply, F_CIB_RC, rc); crm_trace("Attaching reply output"); add_message_xml(op_reply, F_CIB_CALLDATA, the_cib); crm_log_xml_explicit(op_reply, "cib:reply"); } else if (process) { time_t finished = 0; int now = time(NULL); int level = LOG_INFO; const char *section = crm_element_value(request, F_CIB_SECTION); cib_num_local++; rc = cib_process_command(request, &op_reply, &result_diff, privileged); if (global_update) { switch (rc) { case pcmk_ok: level = LOG_INFO; break; case -pcmk_err_old_data: case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: level = LOG_TRACE; break; default: level = LOG_ERR; } } else if (rc != pcmk_ok && is_update) { cib_num_fail++; level = LOG_WARNING; } do_crm_log(level, "Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)", op, section ? section : "'all'", pcmk_strerror(rc), rc, originator ? originator : "local", crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0"); finished = time(NULL); if (finished - now > 3) { crm_trace("%s operation took %ds to complete", op, finished - now); crm_write_blackbox(0, NULL); } if (op_reply == NULL && (needs_reply || local_notify)) { crm_err("Unexpected NULL reply to message"); crm_log_xml_err(request, "null reply"); needs_reply = FALSE; local_notify = FALSE; } } /* from now on we are the server */ if(is_update && cib_legacy_mode() == FALSE) { crm_trace("Completed pre-sync update from %s/%s/%s%s", originator ? originator : "local", crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), local_notify?" with local notification":""); } else if (needs_reply == FALSE || stand_alone) { /* nothing more to do... * this was a non-originating slave update */ crm_trace("Completed slave update"); } else if (call_options & cib_discard_reply) { crm_trace("Caller isn't interested in reply"); } else if (from_peer) { if (is_update == FALSE || result_diff == NULL) { crm_trace("Request not broadcast: R/O call"); } else if (call_options & cib_inhibit_bcast) { crm_trace("Request not broadcast: inhibited"); } else if (rc != pcmk_ok) { crm_trace("Request not broadcast: call failed: %s", pcmk_strerror(rc)); } else { crm_trace("Directing reply to %s", originator); } send_peer_reply(op_reply, result_diff, originator, FALSE); } if (local_notify && client_id) { crm_trace("Performing local %ssync notification for %s", (call_options & cib_sync_call) ? "" : "a-", client_id); if (process == FALSE) { do_local_notify(request, client_id, call_options & cib_sync_call, from_peer); } else { do_local_notify(op_reply, client_id, call_options & cib_sync_call, from_peer); } } free_xml(op_reply); free_xml(result_diff); return; } +static bool +acl_enabled(GHashTable * config_hash) +{ + bool rc = FALSE; + const char *value = NULL; + + value = cib_pref(config_hash, "enable-acl"); + rc = crm_is_true(value); + + crm_debug("CIB ACL is %s", rc ? "enabled" : "disabled"); + return rc; +} + int cib_process_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged) { xmlNode *input = NULL; xmlNode *output = NULL; xmlNode *result_cib = NULL; xmlNode *current_cib = NULL; #if ENABLE_ACL xmlNode *filtered_current_cib = NULL; #endif int call_type = 0; int call_options = 0; const char *op = NULL; const char *section = NULL; const char *call_id = crm_element_value(request, F_CIB_CALLID); int rc = pcmk_ok; int rc2 = pcmk_ok; gboolean send_r_notify = FALSE; gboolean global_update = FALSE; gboolean config_changed = FALSE; gboolean manage_counters = TRUE; static mainloop_timer_t *digest_timer = NULL; CRM_ASSERT(cib_status == pcmk_ok); if(digest_timer == NULL) { digest_timer = mainloop_timer_add("digester", 5000, FALSE, cib_digester_cb, NULL); } *reply = NULL; *cib_diff = NULL; current_cib = the_cib; /* Start processing the request... */ op = crm_element_value(request, F_CIB_OPERATION); crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); rc = cib_get_operation_id(op, &call_type); if (rc == pcmk_ok && privileged == FALSE) { rc = cib_op_can_run(call_type, call_options, privileged, global_update); } rc2 = cib_op_prepare(call_type, request, &input, §ion); if (rc == pcmk_ok) { rc = rc2; } if (rc != pcmk_ok) { crm_trace("Call setup failed: %s", pcmk_strerror(rc)); goto done; } else if (cib_op_modifies(call_type) == FALSE) { xmlNode *cib_ro = current_cib; #if ENABLE_ACL if (acl_enabled(config_hash)) { const char *user = crm_element_value(request, F_CIB_USER); if(xml_acl_filtered_copy(user, current_cib, &filtered_current_cib)) { if (filtered_current_cib == NULL) { crm_debug("Pre-filtered the entire cib"); rc = -EACCES; goto done; } cib_ro = filtered_current_cib; } } #endif rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, cib_ro, &result_cib, NULL, &output); CRM_CHECK(result_cib == NULL, free_xml(result_cib)); goto done; } /* Handle a valid write action */ global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE)); if (global_update) { /* legacy code */ manage_counters = FALSE; call_options |= cib_force_diff; crm_trace("Global update detected"); CRM_CHECK(call_type == 3 || call_type == 4, crm_err("Call type: %d", call_type); crm_log_xml_err(request, "bad op")); } if (rc == pcmk_ok) { ping_modified_since = TRUE; if (call_options & cib_inhibit_bcast) { /* skip */ crm_trace("Skipping update: inhibit broadcast"); manage_counters = FALSE; } if (is_not_set(call_options, cib_dryrun) && safe_str_eq(section, XML_CIB_TAG_STATUS)) { /* Copying large CIBs accounts for a huge percentage of our CIB usage */ call_options |= cib_zero_copy; } else { clear_bit(call_options, cib_zero_copy); } if(acl_enabled(config_hash)) { xml_acl_enable(current_cib); } /* result_cib must not be modified after cib_perform_op() returns */ rc = cib_perform_op(op, call_options, cib_op_func(call_type), FALSE, section, request, input, manage_counters, &config_changed, current_cib, &result_cib, cib_diff, &output); if (manage_counters == FALSE) { /* Legacy code * If the diff is NULL at this point, its because nothing changed */ config_changed = cib_config_changed(NULL, NULL, cib_diff); } /* Always write to disk for replace ops, * this also negates the need to detect ordering changes */ if (crm_str_eq(CIB_OP_REPLACE, op, TRUE)) { config_changed = TRUE; } } if (rc == pcmk_ok && is_not_set(call_options, cib_dryrun)) { if(is_not_set(call_options, cib_zero_copy)) { rc = activateCibXml(result_cib, config_changed, op); } if (rc == pcmk_ok && cib_internal_config_changed(*cib_diff)) { cib_read_config(config_hash, result_cib); } if (crm_str_eq(CIB_OP_REPLACE, op, TRUE)) { if (section == NULL) { send_r_notify = TRUE; } else if (safe_str_eq(section, XML_TAG_CIB)) { send_r_notify = TRUE; } else if (safe_str_eq(section, XML_CIB_TAG_NODES)) { send_r_notify = TRUE; } else if (safe_str_eq(section, XML_CIB_TAG_STATUS)) { send_r_notify = TRUE; } } else if (crm_str_eq(CIB_OP_ERASE, op, TRUE)) { send_r_notify = TRUE; } mainloop_timer_stop(digest_timer); mainloop_timer_start(digest_timer); } else if (rc == -pcmk_err_dtd_validation) { CRM_ASSERT(is_not_set(call_options, cib_zero_copy)); if (output != NULL) { crm_log_xml_info(output, "cib:output"); free_xml(output); } output = result_cib; #if ENABLE_ACL - { - xmlNode *filtered_result_cib = NULL; - - if (acl_enabled(config_hash) - && acl_filter_cib(request, current_cib, result_cib, - &filtered_result_cib)) { + if (acl_enabled(config_hash)) { + const char *user = crm_element_value(request, F_CIB_USER); - crm_debug("Filtered the result cib for output according to the ACLs"); - output = filtered_result_cib; - if (result_cib != NULL) { - free_xml(result_cib); + if(xml_acl_filtered_copy(user, current_cib, &filtered_current_cib)) { + if (filtered_current_cib == NULL) { + crm_debug("Pre-filtered the entire cib"); + rc = -EACCES; + goto done; } + free_xml(result_cib); + output = filtered_current_cib; } } #endif } else { CRM_ASSERT(is_not_set(call_options, cib_zero_copy)); free_xml(result_cib); } if ((call_options & cib_inhibit_notify) == 0) { const char *client = crm_element_value(request, F_CIB_CLIENTNAME); crm_trace("Sending notifications"); cib_diff_notify(call_options, client, call_id, op, input, rc, *cib_diff); } if (send_r_notify) { const char *origin = crm_element_value(request, F_ORIG); cib_replace_notify(origin, the_cib, rc, *cib_diff); } if (rc != pcmk_ok) { xml_log_patchset(LOG_DEBUG, "cib:diff", *cib_diff); } else { xml_log_patchset(LOG_INFO, "cib:diff", *cib_diff); } done: if ((call_options & cib_discard_reply) == 0) { const char *caller = crm_element_value(request, F_CIB_CLIENTID); *reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(*reply, F_TYPE, T_CIB); crm_xml_add(*reply, F_CIB_OPERATION, op); crm_xml_add(*reply, F_CIB_CALLID, call_id); crm_xml_add(*reply, F_CIB_CLIENTID, caller); crm_xml_add_int(*reply, F_CIB_CALLOPTS, call_options); crm_xml_add_int(*reply, F_CIB_RC, rc); if (output != NULL) { crm_trace("Attaching reply output"); add_message_xml(*reply, F_CIB_CALLDATA, output); } crm_log_xml_explicit(*reply, "cib:reply"); } crm_trace("cleanup"); #if ENABLE_ACL if (filtered_current_cib != NULL) { free_xml(filtered_current_cib); } #endif if (call_type >= 0) { cib_op_cleanup(call_type, call_options, &input, &output); } crm_trace("done"); return rc; } gint cib_GCompareFunc(gconstpointer a, gconstpointer b) { const xmlNode *a_msg = a; const xmlNode *b_msg = b; int msg_a_id = 0; int msg_b_id = 0; const char *value = NULL; value = crm_element_value_const(a_msg, F_CIB_CALLID); msg_a_id = crm_parse_int(value, NULL); value = crm_element_value_const(b_msg, F_CIB_CALLID); msg_b_id = crm_parse_int(value, NULL); if (msg_a_id == msg_b_id) { return 0; } else if (msg_a_id < msg_b_id) { return -1; } return 1; } #if SUPPORT_HEARTBEAT void cib_ha_peer_callback(HA_Message * msg, void *private_data) { xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__); cib_peer_callback(xml, private_data); free_xml(xml); } #endif void cib_peer_callback(xmlNode * msg, void *private_data) { const char *reason = NULL; const char *originator = crm_element_value(msg, F_ORIG); if (cib_legacy_mode() && (originator == NULL || crm_str_eq(originator, cib_our_uname, TRUE))) { /* message is from ourselves */ return; } else if (crm_peer_cache == NULL) { reason = "membership not established"; goto bail; } if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) { crm_xml_add(msg, F_CIB_CLIENTNAME, originator); } /* crm_log_xml_trace("Peer[inbound]", msg); */ cib_process_request(msg, FALSE, TRUE, TRUE, NULL); return; bail: if (reason) { const char *seq = crm_element_value(msg, F_SEQ); const char *op = crm_element_value(msg, F_CIB_OPERATION); crm_warn("Discarding %s message (%s) from %s: %s", op, seq, originator, reason); } } #if SUPPORT_HEARTBEAT extern oc_ev_t *cib_ev_token; static void *ccm_library = NULL; int (*ccm_api_callback_done) (void *cookie) = NULL; int (*ccm_api_handle_event) (const oc_ev_t * token) = NULL; void cib_client_status_callback(const char *node, const char *client, const char *status, void *private) { crm_node_t *peer = NULL; if (safe_str_eq(client, CRM_SYSTEM_CIB)) { crm_info("Status update: Client %s/%s now has status [%s]", node, client, status); if (safe_str_eq(status, JOINSTATUS)) { status = ONLINESTATUS; } else if (safe_str_eq(status, LEAVESTATUS)) { status = OFFLINESTATUS; } peer = crm_get_peer(0, node); crm_update_peer_proc(__FUNCTION__, peer, crm_proc_cib, status); } return; } int cib_ccm_dispatch(gpointer user_data) { int rc = 0; oc_ev_t *ccm_token = (oc_ev_t *) user_data; crm_trace("received callback"); if (ccm_api_handle_event == NULL) { ccm_api_handle_event = find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_handle_event", 1); } rc = (*ccm_api_handle_event) (ccm_token); if (0 == rc) { return 0; } crm_err("CCM connection appears to have failed: rc=%d.", rc); /* eventually it might be nice to recover and reconnect... but until then... */ crm_err("Exiting to recover from CCM connection failure"); return crm_exit(ENOTCONN); } int current_instance = 0; void cib_ccm_msg_callback(oc_ed_t event, void *cookie, size_t size, const void *data) { gboolean update_id = FALSE; const oc_ev_membership_t *membership = data; CRM_ASSERT(membership != NULL); crm_info("Processing CCM event=%s (id=%d)", ccm_event_name(event), membership->m_instance); if (current_instance > membership->m_instance) { crm_err("Membership instance ID went backwards! %d->%d", current_instance, membership->m_instance); CRM_ASSERT(current_instance <= membership->m_instance); } switch (event) { case OC_EV_MS_NEW_MEMBERSHIP: case OC_EV_MS_INVALID: update_id = TRUE; break; case OC_EV_MS_PRIMARY_RESTORED: update_id = TRUE; break; case OC_EV_MS_NOT_PRIMARY: crm_trace("Ignoring transitional CCM event: %s", ccm_event_name(event)); break; case OC_EV_MS_EVICTED: crm_err("Evicted from CCM: %s", ccm_event_name(event)); break; default: crm_err("Unknown CCM event: %d", event); } if (update_id) { unsigned int lpc = 0; CRM_CHECK(membership != NULL, return); current_instance = membership->m_instance; for (lpc = 0; lpc < membership->m_n_out; lpc++) { crm_update_ccm_node(membership, lpc + membership->m_out_idx, CRM_NODE_LOST, current_instance); } for (lpc = 0; lpc < membership->m_n_member; lpc++) { crm_update_ccm_node(membership, lpc + membership->m_memb_idx, CRM_NODE_ACTIVE, current_instance); } } if (ccm_api_callback_done == NULL) { ccm_api_callback_done = find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_callback_done", 1); } (*ccm_api_callback_done) (cookie); return; } #endif gboolean can_write(int flags) { return TRUE; } static gboolean cib_force_exit(gpointer data) { crm_notice("Forcing exit!"); terminate_cib(__FUNCTION__, TRUE); return FALSE; } static void disconnect_remote_client(gpointer key, gpointer value, gpointer user_data) { crm_client_t *a_client = value; crm_err("Disconnecting %s... Not implemented", crm_str(a_client->name)); } void cib_shutdown(int nsig) { struct qb_ipcs_stats srv_stats; if (cib_shutdown_flag == FALSE) { int disconnects = 0; qb_ipcs_connection_t *c = NULL; cib_shutdown_flag = TRUE; c = qb_ipcs_connection_first_get(ipcs_rw); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_rw, last); crm_debug("Disconnecting r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_ro); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_ro, last); crm_debug("Disconnecting r/o client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_shm); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_shm, last); crm_debug("Disconnecting non-blocking r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } disconnects += crm_hash_table_size(client_connections); crm_debug("Disconnecting %d remote clients", crm_hash_table_size(client_connections)); g_hash_table_foreach(client_connections, disconnect_remote_client, NULL); crm_info("Disconnected %d clients", disconnects); } qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE); if (crm_hash_table_size(client_connections) == 0) { crm_info("All clients disconnected (%d)", srv_stats.active_connections); initiate_exit(); } else { crm_info("Waiting on %d clients to disconnect (%d)", crm_hash_table_size(client_connections), srv_stats.active_connections); } } void initiate_exit(void) { int active = 0; xmlNode *leaving = NULL; active = crm_active_peers(); if (active < 2) { terminate_cib(__FUNCTION__, FALSE); return; } crm_info("Sending disconnect notification to %d peers...", active); leaving = create_xml_node(NULL, "exit-notification"); crm_xml_add(leaving, F_TYPE, "cib"); crm_xml_add(leaving, F_CIB_OPERATION, "cib_shutdown_req"); send_cluster_message(NULL, crm_msg_cib, leaving, TRUE); free_xml(leaving); g_timeout_add(crm_get_msec("5s"), cib_force_exit, NULL); } extern int remote_fd; extern int remote_tls_fd; void terminate_cib(const char *caller, gboolean fast) { if (remote_fd > 0) { close(remote_fd); remote_fd = 0; } if (remote_tls_fd > 0) { close(remote_tls_fd); remote_tls_fd = 0; } if (!fast) { crm_info("%s: Disconnecting from cluster infrastructure", caller); crm_cluster_disconnect(&crm_cluster); } uninitializeCib(); crm_info("%s: Exiting%s...", caller, fast ? " fast" : mainloop ? " from mainloop" : ""); if (fast == FALSE && mainloop != NULL && g_main_is_running(mainloop)) { g_main_quit(mainloop); } else { qb_ipcs_destroy(ipcs_ro); qb_ipcs_destroy(ipcs_rw); qb_ipcs_destroy(ipcs_shm); if (fast) { crm_exit(EINVAL); } else { crm_exit(pcmk_ok); } } } diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 48c5b7a815..5cba6fdd0e 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,196 +1,188 @@ /* * 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # define CIB_OP_SLAVE "cib_slave" # define CIB_OP_SLAVEALL "cib_slave_all" # define CIB_OP_MASTER "cib_master" # define CIB_OP_SYNC "cib_sync" # define CIB_OP_SYNC_ONE "cib_sync_one" # define CIB_OP_ISMASTER "cib_ismaster" # define CIB_OP_BUMP "cib_bump" # define CIB_OP_QUERY "cib_query" # define CIB_OP_CREATE "cib_create" # define CIB_OP_UPDATE "cib_update" # define CIB_OP_MODIFY "cib_modify" # define CIB_OP_DELETE "cib_delete" # define CIB_OP_ERASE "cib_erase" # define CIB_OP_REPLACE "cib_replace" # define CIB_OP_APPLY_DIFF "cib_apply_diff" # define CIB_OP_UPGRADE "cib_upgrade" # define CIB_OP_DELETE_ALT "cib_delete_alt" # define CIB_OP_NOTIFY "cib_notify" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_CALLBACK_TOKEN "cib_async_id" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" # define cib_channel_ro "cib_ro" # define cib_channel_rw "cib_rw" # define cib_channel_shm "cib_shm" void cib_add_digest(xmlNode * source, xmlNode * target); void fix_cib_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed); gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean startCib(const char *filename); int cib_compare_generation(xmlNode * left, xmlNode * right); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); void verify_cib_options(GHashTable * options); gboolean cib_internal_config_changed(xmlNode * diff); extern GHashTable *cib_op_callback_table; typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output); xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_native_register_notification(cib_t * cib, const char *callback, int enabled); gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)); -gboolean acl_enabled(GHashTable * config_hash); -gboolean acl_filter_cib(xmlNode * request, xmlNode * current_cib, xmlNode * orig_cib, - xmlNode ** filtered_cib); -gboolean acl_check_diff(xmlNode * request, xmlNode * current_cib, xmlNode * result_cib, - xmlNode * diff); - -bool cib_acl_check(GListPtr acls, xmlNode *xml, const char *name, const char *mode); - int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff); gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code); int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name); #endif diff --git a/lib/cib/Makefile.am b/lib/cib/Makefile.am index 13cd596c1d..34d1522acb 100644 --- a/lib/cib/Makefile.am +++ b/lib/cib/Makefile.am @@ -1,44 +1,40 @@ # # 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 # of the License, or (at your option) any later version. # # This program 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 program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # MAINTAINERCLEANFILES = Makefile.in INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl ## libraries lib_LTLIBRARIES = libcib.la ## SOURCES noinst_HEADERS = libcib_la_SOURCES = cib_ops.c cib_utils.c cib_client.c cib_native.c cib_attrs.c libcib_la_SOURCES += cib_file.c cib_remote.c -if ENABLE_ACL -libcib_la_SOURCES += cib_acl.c -endif - libcib_la_LDFLAGS = -version-info 3:1:0 -L$(top_builddir)/lib/pengine/.libs libcib_la_LIBADD = $(CRYPTOLIB) $(top_builddir)/lib/pengine/libpe_rules.la $(top_builddir)/lib/common/libcrmcommon.la libcib_la_CFLAGS = -I$(top_srcdir) clean-generic: rm -f *.log *.debug *.xml *~ install-exec-local: uninstall-local: diff --git a/lib/cib/cib_acl.c b/lib/cib/cib_acl.c deleted file mode 100644 index 10003451d8..0000000000 --- a/lib/cib/cib_acl.c +++ /dev/null @@ -1,812 +0,0 @@ -/* - * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include - -#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_privileged(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); - -gboolean -acl_enabled(GHashTable * config_hash) -{ - const char *value = NULL; - gboolean rc = FALSE; - - value = cib_pref(config_hash, "enable-acl"); - rc = crm_is_true(value); - - crm_debug("CIB ACL is %s", rc ? "enabled" : "disabled"); - return rc; -} - -/* 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_privileged(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; - xmlNode *diff_child = NULL; - int rc = FALSE; - - if (req_by_privileged(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); - - for (diff_child = __xml_first_child(orig_diff); diff_child; diff_child = __xml_next(diff_child)) { - const char *tag = crm_element_name(diff_child); - GListPtr parsed_acl = NULL; - xmlNode *diff_cib = 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; - } - - for (diff_cib = __xml_first_child(diff_child); diff_cib; diff_cib = __xml_next(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_privileged(xmlNode * request) -{ - const char *user = crm_element_value(request, F_CIB_USER); - - if (user == NULL || strcmp(user, "") == 0) { - 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 (is_privileged(user)) { - 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, NULL, "removed:top"); - if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { - free_xml(tmp1); - } - } - - added = create_xml_node(diff, "diff-added"); - - tmp1 = subtract_xml_object(added, new, old, FALSE, NULL, "added:top"); - if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { - free_xml(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) -{ - xmlNode *xml_acl = NULL; - - if (xml_acls == NULL) { - return FALSE; - } - - for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(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) -{ - CRM_CHECK(user != NULL && user[0] != '\0' && uid != NULL && uid[0] != '\0', return FALSE); - - if (crm_str_eq(user, uid, TRUE)) { - return TRUE; - } - - return FALSE; -} - -static gboolean -unpack_acl(xmlNode * xml_acls, xmlNode * xml_acl, GListPtr * acl) -{ - xmlNode *acl_child = NULL; - - for (acl_child = __xml_first_child(xml_acl); acl_child; acl_child = __xml_next(acl_child)) { - const char *tag = crm_element_name(acl_child); - - if (crm_str_eq(XML_ACL_TAG_ROLE_REF, tag, TRUE)) { - const char *ref_role = crm_element_value(acl_child, XML_ATTR_ID); - - if (ref_role) { - unpack_role_acl(xml_acls, ref_role, acl); - } - } else 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) -{ - xmlNode *xml_acl = NULL; - - for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(xml_acl)) { - if (crm_str_eq(XML_ACL_TAG_ROLE, (const char *)xml_acl->name, TRUE)) { - 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; - } - - acl_obj = calloc(1, 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_trace("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) { - 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) { - new_acl_obj = calloc(1, 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_trace("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) { - new_acl_obj = calloc(1, 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_trace - ("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) { - xmlNode *child = NULL; - - for (child = __xml_first_child(root); child; child = __xml_next(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 lpc = 0, max = 0; - int match_found = 0; - xmlXPathObjectPtr xpathObj = NULL; - - if (xpath == NULL) { - return 0; - } - - xpathObj = xpath_search(xml_obj, xpath); - max = numXpathResults(xpathObj); - - if (max <= 0) { - crm_debug("No match for %s in %s", xpath, xmlGetNodePath(xml_obj)); - } - - for (lpc = 0; lpc < max; lpc++) { - xmlNode *match = getXpathResult(xpathObj, lpc); - - if (match == NULL) { - continue; - } - - *objects = g_list_append(*objects, match); - match_found++; - } - - freeXpathObject(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 { - perm = calloc(1, sizeof(xml_perm_t)); - if (perm == NULL) { - return FALSE; - } - g_hash_table_insert(xml_perms, xml, perm); - } - - if (acl_obj->attribute == NULL) { - xmlNode *child = NULL; - - perm->mode = acl_obj->mode; - crm_trace("Permission for element: element_mode=%s, tag=%s, id=%s", - perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); - - for (child = __xml_first_child(xml); child; child = __xml_next(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(crm_str_hash, g_str_equal, g_hash_destroy_str, - g_hash_destroy_str); - } - - g_hash_table_insert(perm->attribute_perms, - strdup(acl_obj->attribute), strdup(acl_obj->mode)); - crm_trace("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; - xmlNode *child = NULL; - - if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer) & perm)) { - if (perm->mode != NULL) { - return FALSE; - } - } else { - perm = calloc(1, sizeof(xml_perm_t)); - if (perm == NULL) { - return FALSE; - } - g_hash_table_insert(xml_perms, xml, perm); - } - - perm->mode = mode; - crm_trace("Permission for child element: element_mode=%s, tag=%s, id=%s", - mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); - - for (child = __xml_first_child(xml); child; child = __xml_next(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); - } - - 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; - xmlNode *child = NULL; - - for (child = __xml_first_child(xml); child; child = __xml_next(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_trace("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_trace("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; - } - } - - if (xml) { - xmlAttrPtr xIter = xml->properties; - - while (xIter) { - const char *prop_name = (const char *)xIter->name; - gpointer mode = NULL; - - xIter = xIter->next; - 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_trace - ("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_trace - ("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_trace - ("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(xml); - crm_trace("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; - xmlNode *child = NULL; - - for (child = __xml_first_child(xml); child; child = __xml_next(child)) { - if (acl_check_diff_xml(child, xml_perms) == FALSE) { - return FALSE; - } - } - - g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer) & perm); - - if (xml) { - xmlAttrPtr xIter = NULL; - - for (xIter = xml->properties; xIter; xIter = xIter->next) { - const char *prop_name = (const char *)xIter->name; - 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 (crm_str_eq(prop_name, XML_DIFF_MARKER, TRUE) && xml_has_children(xml)) { - 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; -}