diff options
Diffstat (limited to 'daemons/based')
-rw-r--r-- | daemons/based/Makefile.am | 47 | ||||
-rw-r--r-- | daemons/based/based_callbacks.c | 1696 | ||||
-rw-r--r-- | daemons/based/based_common.c | 352 | ||||
-rw-r--r-- | daemons/based/based_io.c | 473 | ||||
-rw-r--r-- | daemons/based/based_messages.c | 427 | ||||
-rw-r--r-- | daemons/based/based_notify.c | 305 | ||||
-rw-r--r-- | daemons/based/based_remote.c | 680 | ||||
-rw-r--r-- | daemons/based/cib.pam | 6 | ||||
-rw-r--r-- | daemons/based/pacemaker-based.c | 442 | ||||
-rw-r--r-- | daemons/based/pacemaker-based.h | 150 |
10 files changed, 4578 insertions, 0 deletions
diff --git a/daemons/based/Makefile.am b/daemons/based/Makefile.am new file mode 100644 index 0000000..053d93c --- /dev/null +++ b/daemons/based/Makefile.am @@ -0,0 +1,47 @@ +# +# Copyright 2004-2021 the Pacemaker project contributors +# +# The version control history for this file may have further details. +# +# This source code is licensed under the GNU General Public License version 2 +# or later (GPLv2+) WITHOUT ANY WARRANTY. +# + +include $(top_srcdir)/mk/common.mk + +EXTRA_DIST = cib.pam + +halibdir = $(CRM_DAEMON_DIR) + +COMMONLIBS = $(top_builddir)/lib/common/libcrmcommon.la \ + $(top_builddir)/lib/cib/libcib.la + +halib_PROGRAMS = pacemaker-based + +noinst_HEADERS = pacemaker-based.h + +pacemaker_based_CFLAGS = $(CFLAGS_HARDENED_EXE) +pacemaker_based_LDFLAGS = $(LDFLAGS_HARDENED_EXE) + +pacemaker_based_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ + $(COMMONLIBS) $(CLUSTERLIBS) + +pacemaker_based_SOURCES = pacemaker-based.c \ + based_callbacks.c \ + based_common.c \ + based_io.c \ + based_messages.c \ + based_notify.c \ + based_remote.c + +clean-generic: + rm -f *.log *.debug *.xml *~ + +if BUILD_LEGACY_LINKS +install-exec-hook: + $(MKDIR_P) -- $(DESTDIR)$(CRM_DAEMON_DIR) + cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib && $(LN_S) pacemaker-based cib + +uninstall-hook: + cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f cib +endif diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c new file mode 100644 index 0000000..3726caa --- /dev/null +++ b/daemons/based/based_callbacks.c @@ -0,0 +1,1696 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <sys/param.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +#include <stdlib.h> +#include <stdint.h> // uint32_t, uint64_t, UINT64_C() +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> // PRIu64 + +#include <crm/crm.h> +#include <crm/cib.h> +#include <crm/msg_xml.h> +#include <crm/cluster/internal.h> + +#include <crm/common/xml.h> +#include <crm/common/remote_internal.h> + +#include <pacemaker-based.h> + +#define EXIT_ESCALATION_MS 10000 +#define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->uname) + +static unsigned long cib_local_bcast_num = 0; + +typedef struct cib_local_notify_s { + xmlNode *notify_src; + char *client_id; + gboolean from_peer; + gboolean sync_reply; +} cib_local_notify_t; + +int next_client_id = 0; + +gboolean legacy_mode = FALSE; + +qb_ipcs_service_t *ipcs_ro = NULL; +qb_ipcs_service_t *ipcs_rw = NULL; +qb_ipcs_service_t *ipcs_shm = NULL; + +static void cib_process_request(xmlNode *request, gboolean privileged, + const pcmk__client_t *cib_client); + +static int cib_process_command(xmlNode *request, xmlNode **reply, + xmlNode **cib_diff, gboolean privileged); + +static gboolean cib_common_callback(qb_ipcs_connection_t *c, void *data, + size_t size, gboolean privileged); + +gboolean +cib_legacy_mode(void) +{ + return legacy_mode; +} + +static int32_t +cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) +{ + if (cib_shutdown_flag) { + crm_info("Ignoring new IPC client [%d] during shutdown", + pcmk__client_pid(c)); + return -EPERM; + } + + if (pcmk__new_client(c, uid, gid) == NULL) { + return -EIO; + } + return 0; +} + +static int32_t +cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size) +{ + pcmk__client_t *client = pcmk__find_client(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) +{ + pcmk__client_t *client = pcmk__find_client(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) +{ + pcmk__client_t *client = pcmk__find_client(c); + + if (client == NULL) { + return 0; + } + crm_trace("Connection %p", c); + pcmk__free_client(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 = NULL, + .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 = NULL, + .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, + pcmk__client_t *cib_client, gboolean privileged) +{ + const char *op = crm_element_value(op_request, F_CIB_OPERATION); + + if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { + if (flags & crm_ipc_client_response) { + xmlNode *ack = create_xml_node(NULL, __func__); + + crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); + crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); + pcmk__ipc_send_xml(cib_client, id, ack, flags); + cib_client->request_id = 0; + free_xml(ack); + } + return; + + } else if (pcmk__str_eq(op, T_CIB_NOTIFY, pcmk__str_none)) { + /* Update the notify filters for this client */ + int on_off = 0; + crm_exit_t status = CRM_EX_OK; + uint64_t bit = UINT64_C(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 %s for client %s", + type, (on_off? "on" : "off"), pcmk__client_name(cib_client)); + + if (pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) { + bit = cib_notify_post; + + } else if (pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) { + bit = cib_notify_pre; + + } else if (pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) { + bit = cib_notify_confirm; + + } else if (pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) { + bit = cib_notify_diff; + + } else if (pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) { + bit = cib_notify_replace; + + } else { + status = CRM_EX_INVALID_PARAM; + } + + if (bit != 0) { + if (on_off) { + pcmk__set_client_flags(cib_client, bit); + } else { + pcmk__clear_client_flags(cib_client, bit); + } + } + + pcmk__ipc_send_ack(cib_client, id, flags, "ack", NULL, status); + return; + } + + cib_process_request(op_request, privileged, 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; + pcmk__client_t *cib_client = pcmk__find_client(c); + xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &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); + pcmk__ipc_send_ack(cib_client, id, flags, "nack", NULL, CRM_EX_PROTOCOL); + return 0; + + } else if(cib_client == NULL) { + crm_trace("Invalid client %p", c); + return 0; + } + + if (pcmk_is_set(call_options, cib_sync_call)) { + CRM_LOG_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 = pcmk__itoa(cib_client->pid); + } else { + cib_client->name = strdup(value); + if (crm_is_daemon_name(value)) { + pcmk__set_client_flags(cib_client, cib_is_daemon); + } + } + } + + /* Allow cluster daemons more leeway before being evicted */ + if (pcmk_is_set(cib_client->flags, cib_is_daemon)) { + const char *qmax = cib_config_lookup("cluster-ipc-limit"); + + if (pcmk__set_client_queue_max(cib_client, qmax)) { + crm_trace("IPC threshold for client %s[%u] is now %u", + pcmk__client_name(cib_client), cib_client->pid, + cib_client->queue_max); + } + } + + crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); + crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); + + CRM_LOG_ASSERT(cib_client->user != NULL); + pcmk__update_acl_user(op_request, F_CIB_USER, cib_client->user); + + 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; + +static gboolean +cib_digester_cb(gpointer data) +{ + if (based_is_primary) { + 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, "%" PRIu64, 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 == NULL) { + crm_debug("Ignoring ping reply with no " F_CIB_PING_ID); + return; + + } else { + long long seq_ll; + + if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) { + return; + } + seq = (uint64_t) seq_ll; + } + + 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 (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { + 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):"_", + remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_", + remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_", + digest, remote_cib); + + if(remote_cib && remote_cib->children) { + // Additional debug + xml_calculate_changes(the_cib, remote_cib); + + pcmk__output_set_log_level(logger_out, LOG_INFO); + pcmk__xml_show_changes(logger_out, remote_cib); + crm_trace("End of differences"); + } + + free_xml(remote_cib); + sync_our_cib(reply, FALSE); + } + } +} + +static void +do_local_notify(xmlNode * notify_src, const char *client_id, + gboolean sync_reply, gboolean from_peer) +{ + int rid = 0; + int call_id = 0; + pcmk__client_t *client_obj = NULL; + + CRM_ASSERT(notify_src && client_id); + + crm_element_value_int(notify_src, F_CIB_CALLID, &call_id); + + client_obj = pcmk__find_client_by_id(client_id); + if (client_obj == NULL) { + crm_debug("Could not send response %d: client %s not found", + call_id, client_id); + return; + } + + 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 client %s%s", + rid, pcmk__client_name(client_obj), + (from_peer? " (originator of delegated request)" : "")); + } else { + crm_trace("Sending response (call %d) to client %s%s", + call_id, pcmk__client_name(client_obj), + (from_peer? " (originator of delegated request)" : "")); + } + + } else { + crm_trace("Sending event %d to client %s%s", + call_id, pcmk__client_name(client_obj), + (from_peer? " (originator of delegated request)" : "")); + } + + switch (PCMK__CLIENT_TYPE(client_obj)) { + case pcmk__client_ipc: + { + int rc = pcmk__ipc_send_xml(client_obj, rid, notify_src, + (sync_reply? crm_ipc_flags_none + : crm_ipc_server_event)); + + if (rc != pcmk_rc_ok) { + crm_warn("%s reply to client %s failed: %s " CRM_XS " rc=%d", + (sync_reply? "Synchronous" : "Asynchronous"), + pcmk__client_name(client_obj), pcmk_rc_str(rc), + rc); + } + } + break; +#ifdef HAVE_GNUTLS_GNUTLS_H + case pcmk__client_tls: +#endif + case pcmk__client_tcp: + pcmk__remote_send_xml(client_obj->remote, notify_src); + break; + default: + crm_err("Unknown transport for client %s " + CRM_XS " flags=%#016" PRIx64, + pcmk__client_name(client_obj), client_obj->flags); + } +} + +static void +local_notify_destroy_callback(gpointer data) +{ + cib_local_notify_t *notify = data; + + free_xml(notify->notify_src); + free(notify->client_id); + free(notify); +} + +static void +check_local_notify(int bcast_id) +{ + cib_local_notify_t *notify = NULL; + + if (!local_notify_queue) { + return; + } + + notify = pcmk__intkey_table_lookup(local_notify_queue, bcast_id); + + if (notify) { + do_local_notify(notify->notify_src, notify->client_id, notify->sync_reply, + notify->from_peer); + pcmk__intkey_table_remove(local_notify_queue, bcast_id); + } +} + +static void +queue_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply, + gboolean from_peer) +{ + cib_local_notify_t *notify = calloc(1, sizeof(cib_local_notify_t)); + + notify->notify_src = notify_src; + notify->client_id = strdup(client_id); + notify->sync_reply = sync_reply; + notify->from_peer = from_peer; + + if (!local_notify_queue) { + local_notify_queue = pcmk__intkey_table(local_notify_destroy_callback); + } + pcmk__intkey_table_insert(local_notify_queue, cib_local_bcast_num, notify); + // cppcheck doesn't know notify will get freed when hash table is destroyed + // cppcheck-suppress memleak +} + +static void +parse_local_options_v1(const pcmk__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 client %s", + op, pcmk__client_name(cib_client)); + *local_notify = TRUE; + + } else if ((host == NULL) && based_is_primary) { + crm_trace("Processing %s op locally from client %s as primary", + op, pcmk__client_name(cib_client)); + *local_notify = TRUE; + + } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { + crm_trace("Processing locally addressed %s op from client %s", + op, pcmk__client_name(cib_client)); + *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 client %s", + op, pcmk__client_name(cib_client), + pcmk__s(host, "the primary instance")); + *needs_forward = TRUE; + *process = FALSE; + } +} + +static void +parse_local_options_v2(const pcmk__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)) { + if (pcmk__str_any_of(op, PCMK__CIB_REQUEST_PRIMARY, + PCMK__CIB_REQUEST_SECONDARY, NULL)) { + /* Always handle these locally */ + *process = TRUE; + *needs_reply = FALSE; + *local_notify = TRUE; + *needs_forward = FALSE; + return; + + } else { + /* Redirect all other updates via CPG */ + *needs_reply = TRUE; + *needs_forward = TRUE; + *process = FALSE; + crm_trace("%s op from %s needs to be forwarded to client %s", + op, pcmk__client_name(cib_client), + pcmk__s(host, "the primary instance")); + return; + } + } + + + *process = TRUE; + *needs_reply = FALSE; + *local_notify = TRUE; + *needs_forward = FALSE; + + if (stand_alone) { + crm_trace("Processing %s op from client %s (stand-alone)", + op, pcmk__client_name(cib_client)); + + } else if (host == NULL) { + crm_trace("Processing unaddressed %s op from client %s", + op, pcmk__client_name(cib_client)); + + } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { + crm_trace("Processing locally addressed %s op from client %s", + op, pcmk__client_name(cib_client)); + + } else { + crm_trace("%s op from %s needs to be forwarded to client %s", + op, pcmk__client_name(cib_client), host); + *needs_forward = TRUE; + *process = FALSE; + } +} + +static void +parse_local_options(const pcmk__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); + + gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); + + if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_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; + } + + op = crm_element_value(request, F_CIB_OPERATION); + crm_trace("Processing %s request sent by %s", op, originator); + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { + /* 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 && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { + 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 (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { + crm_trace("Processing %s request sent to us from %s", op, originator); + return TRUE; + + } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { + crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator); + *needs_reply = TRUE; + return TRUE; + + } else if ((host == NULL) && based_is_primary) { + crm_trace("Processing %s request sent to primary instance from %s", + op, originator); + return TRUE; + } + + delegated = crm_element_value(request, F_CIB_DELEGATED); + if (delegated != NULL) { + crm_trace("Ignoring message for primary instance"); + + } else if (host != NULL) { + /* this is for a specific instance and we're not it */ + crm_trace("Ignoring msg for instance on %s", host); + + } else if ((reply_to == NULL) && !based_is_primary) { + // This is for the primary instance, and we're not it + crm_trace("Ignoring reply for primary instance"); + + } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { + if (reply_to != NULL) { + crm_debug("Processing %s from %s", op, originator); + *needs_reply = FALSE; + + } else { + crm_debug("Processing %s reply from %s", op, originator); + } + 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); + + gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { + /* sync_our_cib() sets F_CIB_ISREPLY */ + if (reply_to) { + delegated = reply_to; + } + goto skip_is_reply; + + } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL, + pcmk__str_none)) { + // Nothing to do + + } else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { + process_ping_reply(request); + return FALSE; + + } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) { + /* Only the DC (node with the oldest software) should process + * this operation if F_CIB_SCHEMA_MAX is unset + * + * If the DC is happy it will then send out another + * PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual + * upgrade. + * + * Except this time F_CIB_SCHEMA_MAX will be set which puts a + * limit on how far newer nodes will go + */ + const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX); + const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC); + + crm_trace("Parsing %s operation%s for %s with max=%s and upgrade_rc=%s", + op, (is_reply? " reply" : ""), + (based_is_primary? "primary" : "secondary"), + (max? max : "none"), (upgrade_rc? upgrade_rc : "none")); + + if (upgrade_rc != NULL) { + // Our upgrade request was rejected by DC, notify clients of result + crm_xml_add(request, F_CIB_RC, upgrade_rc); + + } else if ((max == NULL) && based_is_primary) { + /* We are the DC, check if this upgrade is allowed */ + goto skip_is_reply; + + } else if(max) { + /* Ok, go ahead and upgrade to 'max' */ + goto skip_is_reply; + + } else { + // Ignore broadcast client requests when we're not DC + return FALSE; + } + + } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { + crm_info("Detected legacy %s global update from %s", op, originator); + send_sync_request(NULL); + legacy_mode = TRUE; + 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 (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { + /* 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; + + *local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei); + + host = crm_element_value(request, F_CIB_HOST); + if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { + 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, host); + return FALSE; + + } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { + *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, 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, OUR_NODENAME); + + 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 primary 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; + int format = 1; + + CRM_LOG_ASSERT(result_diff != NULL); + digest = crm_element_value(result_diff, XML_ATTR_DIGEST); + crm_element_value_int(result_diff, "format", &format); + + 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); + pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true); + crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH); + crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER); + + if (format == 1) { + 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; +} + +/*! + * \internal + * \brief Handle an IPC or CPG message containing a request + * + * \param[in,out] request Request XML + * \param[in] privileged Whether privileged commands may be run + * (see cib_server_ops[] definition) + * \param[in] cib_client IPC client that sent request (or NULL if CPG) + */ +static void +cib_process_request(xmlNode *request, gboolean privileged, + const pcmk__client_t *cib_client) +{ + int call_type = 0; + int call_options = 0; + + gboolean process = TRUE; // Whether to process request locally now + gboolean is_update = TRUE; // Whether request would modify CIB + gboolean needs_reply = TRUE; // Whether to build a reply + gboolean local_notify = FALSE; // Whether to notify (local) requester + gboolean needs_forward = FALSE; // Whether to forward request somewhere else + + 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 *call_id = crm_element_value(request, F_CIB_CALLID); + const char *client_id = crm_element_value(request, F_CIB_CLIENTID); + const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); + const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); + + crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); + + if ((host != NULL) && (*host == '\0')) { + host = NULL; + } + + if (host) { + target = host; + + } else if (call_options & cib_scope_local) { + target = "local host"; + + } else { + target = "primary"; + } + + if (cib_client == NULL) { + crm_trace("Processing peer %s operation from %s/%s on %s intended for %s (reply=%s)", + op, client_name, call_id, originator, target, reply_to); + } else { + crm_xml_add(request, F_ORIG, OUR_NODENAME); + crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_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 (cib_client != NULL) { + 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 (call_options & cib_discard_reply) { + /* If the request will modify the CIB, and we are in legacy mode, we + * need to build a reply so we can broadcast a diff, even if the + * requester doesn't want one. + */ + needs_reply = is_update && cib_legacy_mode(); + local_notify = FALSE; + } + + if (needs_forward) { + const char *section = crm_element_value(request, F_CIB_SECTION); + int log_level = LOG_INFO; + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_NOOP, pcmk__str_none)) { + log_level = LOG_DEBUG; + } + + do_crm_log(log_level, + "Forwarding %s operation for section %s to %s (origin=%s/%s/%s)", + op, + section ? section : "'all'", + pcmk__s(host, (cib_legacy_mode() ? "primary" : "all")), + originator ? originator : "local", + client_name, call_id); + + forward_request(request, 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; + time_t now = time(NULL); + int level = LOG_INFO; + const char *section = crm_element_value(request, F_CIB_SECTION); + + rc = cib_process_command(request, &op_reply, &result_diff, privileged); + + if (!is_update) { + level = LOG_TRACE; + + } else if (pcmk__xe_attr_is_true(request, F_CIB_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) { + 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", client_name, call_id, + 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 %lds to complete", op, (long)(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; + } + } + + if (is_update && !cib_legacy_mode()) { + crm_trace("Completed pre-sync update from %s/%s/%s%s", + originator ? originator : "local", client_name, call_id, + local_notify?" with local notification":""); + + } else if (!needs_reply || stand_alone) { + // This was a non-originating secondary update + crm_trace("Completed update as secondary"); + + } else if (cib_legacy_mode() && + rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) { + gboolean broadcast = FALSE; + + cib_local_bcast_num++; + crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num); + broadcast = send_peer_reply(request, result_diff, originator, TRUE); + + if (broadcast && client_id && local_notify && op_reply) { + + /* If we have been asked to sync the reply, + * and a bcast msg has gone out, we queue the local notify + * until we know the bcast message has been received */ + local_notify = FALSE; + crm_trace("Queuing local %ssync notification for %s", + (call_options & cib_sync_call) ? "" : "a-", client_id); + + queue_local_notify(op_reply, client_id, + pcmk_is_set(call_options, cib_sync_call), + (cib_client == NULL)); + op_reply = NULL; /* the reply is queued, so don't free here */ + } + + } else if (call_options & cib_discard_reply) { + crm_trace("Caller isn't interested in reply"); + + } else if (cib_client == NULL) { + 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", + (pcmk_is_set(call_options, cib_sync_call)? "" : "a"), + client_id); + if (process == FALSE) { + do_local_notify(request, client_id, + pcmk_is_set(call_options, cib_sync_call), + (cib_client == NULL)); + } else { + do_local_notify(op_reply, client_id, + pcmk_is_set(call_options, cib_sync_call), + (cib_client == NULL)); + } + } + + free_xml(op_reply); + free_xml(result_diff); + + return; +} + +static char * +calculate_section_digest(const char *xpath, xmlNode * xml_obj) +{ + xmlNode *xml_section = NULL; + + if (xml_obj == NULL) { + return NULL; + } + + xml_section = get_xpath_object(xpath, xml_obj, LOG_TRACE); + if (xml_section == NULL) { + return NULL; + } + return calculate_xml_versioned_digest(xml_section, FALSE, TRUE, CRM_FEATURE_SET); + +} + +// v1 and v2 patch formats +#define XPATH_CONFIG_CHANGE \ + "//" XML_CIB_TAG_CRMCONFIG " | " \ + "//" XML_DIFF_CHANGE \ + "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]" + +static bool +contains_config_change(xmlNode *diff) +{ + bool changed = false; + + if (diff) { + xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE); + + if (numXpathResults(xpathObj) > 0) { + changed = true; + } + freeXpathObject(xpathObj); + } + return changed; +} + +static 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; + + 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); + const char *client_id = crm_element_value(request, F_CIB_CLIENTID); + const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); + const char *origin = crm_element_value(request, F_ORIG); + + int rc = pcmk_ok; + int rc2 = pcmk_ok; + + gboolean send_r_notify = FALSE; + gboolean config_changed = FALSE; + gboolean manage_counters = TRUE; + + static mainloop_timer_t *digest_timer = NULL; + + char *current_nodes_digest = NULL; + char *current_alerts_digest = NULL; + char *current_status_digest = NULL; + uint32_t change_section = cib_change_section_nodes + |cib_change_section_alerts + |cib_change_section_status; + + 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); + } + + 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) { + rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, + section, request, input, FALSE, &config_changed, + current_cib, &result_cib, NULL, &output); + + CRM_CHECK(result_cib == NULL, free_xml(result_cib)); + goto done; + } + + /* Handle a valid write action */ + if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { + /* legacy code */ + manage_counters = FALSE; + cib__set_call_options(call_options, "call", 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")); + } + + ping_modified_since = TRUE; + if (pcmk_is_set(call_options, cib_inhibit_bcast)) { + crm_trace("Skipping update: inhibit broadcast"); + manage_counters = FALSE; + } + + if (!pcmk_is_set(call_options, cib_dryrun) + && pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { + // Copying large CIBs accounts for a huge percentage of our CIB usage + cib__set_call_options(call_options, "call", cib_zero_copy); + } else { + cib__clear_call_options(call_options, "call", cib_zero_copy); + } + +#define XPATH_CONFIG "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION +#define XPATH_NODES XPATH_CONFIG "/" XML_CIB_TAG_NODES +#define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS +#define XPATH_STATUS "//" XML_TAG_CIB "/" XML_CIB_TAG_STATUS + + // Calculate the hash value of the section before the change + if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) { + current_nodes_digest = calculate_section_digest(XPATH_NODES, + current_cib); + current_alerts_digest = calculate_section_digest(XPATH_ALERTS, + current_cib); + current_status_digest = calculate_section_digest(XPATH_STATUS, + current_cib); + crm_trace("current-digest %s:%s:%s", current_nodes_digest, + current_alerts_digest, current_status_digest); + } + + // 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) { + int format = 1; + + /* Legacy code + * If the diff is NULL at this point, it's because nothing changed + */ + if (*cib_diff != NULL) { + crm_element_value_int(*cib_diff, "format", &format); + } + + if (format == 1) { + config_changed = cib__config_changed_v1(NULL, NULL, cib_diff); + } + } + + /* Always write to disk for successful replace and upgrade ops. This also + * negates the need to detect ordering changes. + */ + if ((rc == pcmk_ok) + && pcmk__str_any_of(op, + PCMK__CIB_REQUEST_REPLACE, + PCMK__CIB_REQUEST_UPGRADE, + NULL)) { + config_changed = TRUE; + } + + if (rc == pcmk_ok && !pcmk_is_set(call_options, cib_dryrun)) { + crm_trace("Activating %s->%s%s%s", + crm_element_value(current_cib, XML_ATTR_NUMUPDATES), + crm_element_value(result_cib, XML_ATTR_NUMUPDATES), + (pcmk_is_set(call_options, cib_zero_copy)? " zero-copy" : ""), + (config_changed? " changed" : "")); + if (!pcmk_is_set(call_options, cib_zero_copy)) { + rc = activateCibXml(result_cib, config_changed, op); + crm_trace("Activated %s (%d)", + crm_element_value(current_cib, XML_ATTR_NUMUPDATES), rc); + } + + if ((rc == pcmk_ok) && contains_config_change(*cib_diff)) { + cib_read_config(config_hash, result_cib); + } + + if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) { + char *result_nodes_digest = NULL; + char *result_alerts_digest = NULL; + char *result_status_digest = NULL; + + /* Calculate the hash value of the changed section. */ + result_nodes_digest = calculate_section_digest(XPATH_NODES, + result_cib); + result_alerts_digest = calculate_section_digest(XPATH_ALERTS, + result_cib); + result_status_digest = calculate_section_digest(XPATH_STATUS, + result_cib); + crm_trace("result-digest %s:%s:%s", result_nodes_digest, + result_alerts_digest, result_status_digest); + + if (pcmk__str_eq(current_nodes_digest, result_nodes_digest, + pcmk__str_none)) { + change_section = + pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, + "CIB change section", + "change_section", change_section, + cib_change_section_nodes, "nodes"); + } + + if (pcmk__str_eq(current_alerts_digest, result_alerts_digest, + pcmk__str_none)) { + change_section = + pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, + "CIB change section", + "change_section", change_section, + cib_change_section_alerts, "alerts"); + } + + if (pcmk__str_eq(current_status_digest, result_status_digest, + pcmk__str_none)) { + change_section = + pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, + "CIB change section", + "change_section", change_section, + cib_change_section_status, "status"); + } + + if (change_section != cib_change_section_none) { + send_r_notify = TRUE; + } + + free(result_nodes_digest); + free(result_alerts_digest); + free(result_status_digest); + + } else if (pcmk__str_eq(PCMK__CIB_REQUEST_ERASE, op, pcmk__str_none)) { + send_r_notify = TRUE; + } + + mainloop_timer_stop(digest_timer); + mainloop_timer_start(digest_timer); + + } else if (rc == -pcmk_err_schema_validation) { + CRM_ASSERT(!pcmk_is_set(call_options, cib_zero_copy)); + + if (output != NULL) { + crm_log_xml_info(output, "cib:output"); + free_xml(output); + } + + output = result_cib; + + } else { + crm_trace("Not activating %d %d %s", rc, + pcmk_is_set(call_options, cib_dryrun), + crm_element_value(result_cib, XML_ATTR_NUMUPDATES)); + if (!pcmk_is_set(call_options, cib_zero_copy)) { + free_xml(result_cib); + } + } + + if ((call_options & (cib_inhibit_notify|cib_dryrun)) == 0) { + crm_trace("Sending notifications %d", + pcmk_is_set(call_options, cib_dryrun)); + cib_diff_notify(op, rc, call_id, client_id, client_name, origin, input, + *cib_diff); + } + + if (send_r_notify) { + cib_replace_notify(op, rc, call_id, client_id, client_name, origin, + the_cib, *cib_diff, change_section); + } + + pcmk__output_set_log_level(logger_out, LOG_TRACE); + logger_out->message(logger_out, "xml-patchset", *cib_diff); + + done: + if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) { + 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 (cib_op_modifies(call_type) == FALSE && output != current_cib) { + free_xml(output); + output = NULL; + } + + if (call_type >= 0) { + cib_op_cleanup(call_type, call_options, &input, &output); + } + + free(current_nodes_digest); + free(current_alerts_digest); + free(current_status_digest); + + crm_trace("done"); + return rc; +} + +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() + && pcmk__str_eq(originator, OUR_NODENAME, + pcmk__str_casei|pcmk__str_null_matches)) { + /* message is from ourselves */ + int bcast_id = 0; + + if (!(crm_element_value_int(msg, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) { + check_local_notify(bcast_id); + } + 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(msg, "Peer[inbound]"); */ + cib_process_request(msg, 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); + } +} + +static gboolean +cib_force_exit(gpointer data) +{ + crm_notice("Forcing exit!"); + terminate_cib(__func__, CRM_EX_ERROR); + return FALSE; +} + +static void +disconnect_remote_client(gpointer key, gpointer value, gpointer user_data) +{ + pcmk__client_t *a_client = value; + + crm_err("Can't disconnect client %s: Not implemented", + pcmk__client_name(a_client)); +} + +static void +initiate_exit(void) +{ + int active = 0; + xmlNode *leaving = NULL; + + active = crm_active_peers(); + if (active < 2) { + terminate_cib(__func__, 0); + 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, PCMK__CIB_REQUEST_SHUTDOWN); + + send_cluster_message(NULL, crm_msg_cib, leaving, TRUE); + free_xml(leaving); + + g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL); +} + +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 += pcmk__ipc_client_count(); + + crm_debug("Disconnecting %d remote clients", pcmk__ipc_client_count()); + pcmk__foreach_ipc_client(disconnect_remote_client, NULL); + crm_info("Disconnected %d clients", disconnects); + } + + qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE); + + if (pcmk__ipc_client_count() == 0) { + crm_info("All clients disconnected (%d)", srv_stats.active_connections); + initiate_exit(); + + } else { + crm_info("Waiting on %d clients to disconnect (%d)", + pcmk__ipc_client_count(), srv_stats.active_connections); + } +} + +extern int remote_fd; +extern int remote_tls_fd; + +/*! + * \internal + * \brief Close remote sockets, free the global CIB and quit + * + * \param[in] caller Name of calling function (for log message) + * \param[in] fast If -1, skip disconnect; if positive, exit that + */ +void +terminate_cib(const char *caller, int fast) +{ + crm_info("%s: Exiting%s...", caller, + (fast > 0)? " fast" : mainloop ? " from mainloop" : ""); + + if (remote_fd > 0) { + close(remote_fd); + remote_fd = 0; + } + if (remote_tls_fd > 0) { + close(remote_tls_fd); + remote_tls_fd = 0; + } + + uninitializeCib(); + + if (logger_out != NULL) { + logger_out->finish(logger_out, CRM_EX_OK, true, NULL); + pcmk__output_free(logger_out); + logger_out = NULL; + } + + if (fast > 0) { + /* Quit fast on error */ + pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); + crm_exit(fast); + + } else if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) { + /* Quit via returning from the main loop. If fast == -1, we skip the + * disconnect here, and it will be done when the main loop returns + * (this allows the peer status callback to avoid messing with the + * peer caches). + */ + if (fast == 0) { + crm_cluster_disconnect(crm_cluster); + } + g_main_loop_quit(mainloop); + + } else { + /* Quit via clean exit. Even the peer status callback can disconnect + * here, because we're not returning control to the caller. */ + crm_cluster_disconnect(crm_cluster); + pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); + crm_exit(CRM_EX_OK); + } +} diff --git a/daemons/based/based_common.c b/daemons/based/based_common.c new file mode 100644 index 0000000..7e68cf0 --- /dev/null +++ b/daemons/based/based_common.c @@ -0,0 +1,352 @@ +/* + * Copyright 2008-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <sys/param.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include <crm/crm.h> +#include <crm/cib.h> +#include <crm/msg_xml.h> +#include <crm/common/ipc.h> +#include <crm/cluster.h> + +#include <crm/common/xml.h> + +#include <pacemaker-based.h> + +gboolean stand_alone = FALSE; + +extern int cib_perform_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, + gboolean privileged); + +static xmlNode * +cib_prepare_common(xmlNode * root, const char *section) +{ + xmlNode *data = NULL; + + /* extract the CIB from the fragment */ + if (root == NULL) { + return NULL; + + } else if (pcmk__strcase_any_of(crm_element_name(root), XML_TAG_FRAGMENT, + F_CRM_DATA, F_CIB_CALLDATA, NULL)) { + data = first_named_child(root, XML_TAG_CIB); + + } else { + data = root; + } + + /* grab the section specified for the command */ + if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { + data = pcmk_find_cib_element(data, section); + } + + /* crm_log_xml_trace(root, "cib:input"); */ + return data; +} + +static int +cib_prepare_none(xmlNode * request, xmlNode ** data, const char **section) +{ + *data = NULL; + *section = crm_element_value(request, F_CIB_SECTION); + return pcmk_ok; +} + +static int +cib_prepare_data(xmlNode * request, xmlNode ** data, const char **section) +{ + xmlNode *input_fragment = get_message_xml(request, F_CIB_CALLDATA); + + *section = crm_element_value(request, F_CIB_SECTION); + *data = cib_prepare_common(input_fragment, *section); + /* crm_log_xml_debug(*data, "data"); */ + return pcmk_ok; +} + +static int +cib_prepare_sync(xmlNode * request, xmlNode ** data, const char **section) +{ + *data = NULL; + *section = crm_element_value(request, F_CIB_SECTION); + return pcmk_ok; +} + +static int +cib_prepare_diff(xmlNode * request, xmlNode ** data, const char **section) +{ + xmlNode *input_fragment = NULL; + + *data = NULL; + *section = NULL; + + if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { + input_fragment = get_message_xml(request, F_CIB_UPDATE_DIFF); + } else { + input_fragment = get_message_xml(request, F_CIB_CALLDATA); + } + + CRM_CHECK(input_fragment != NULL, crm_log_xml_warn(request, "no input")); + *data = cib_prepare_common(input_fragment, NULL); + return pcmk_ok; +} + +static int +cib_cleanup_query(int options, xmlNode ** data, xmlNode ** output) +{ + CRM_LOG_ASSERT(*data == NULL); + if ((options & cib_no_children) + || pcmk__str_eq(crm_element_name(*output), "xpath-query", pcmk__str_casei)) { + free_xml(*output); + } + return pcmk_ok; +} + +static int +cib_cleanup_data(int options, xmlNode ** data, xmlNode ** output) +{ + free_xml(*output); + *data = NULL; + return pcmk_ok; +} + +static int +cib_cleanup_output(int options, xmlNode ** data, xmlNode ** output) +{ + free_xml(*output); + return pcmk_ok; +} + +static int +cib_cleanup_none(int options, xmlNode ** data, xmlNode ** output) +{ + CRM_LOG_ASSERT(*data == NULL); + CRM_LOG_ASSERT(*output == NULL); + return pcmk_ok; +} + +static cib_operation_t cib_server_ops[] = { + // Booleans are modifies_cib, needs_privileges + { + NULL, FALSE, FALSE, + cib_prepare_none, cib_cleanup_none, cib_process_default + }, + { + PCMK__CIB_REQUEST_QUERY, FALSE, FALSE, + cib_prepare_none, cib_cleanup_query, cib_process_query + }, + { + PCMK__CIB_REQUEST_MODIFY, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_modify + }, + { + PCMK__CIB_REQUEST_APPLY_PATCH, TRUE, TRUE, + cib_prepare_diff, cib_cleanup_data, cib_server_process_diff + }, + { + PCMK__CIB_REQUEST_REPLACE, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_replace_svr + }, + { + PCMK__CIB_REQUEST_CREATE, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_create + }, + { + PCMK__CIB_REQUEST_DELETE, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_delete + }, + { + PCMK__CIB_REQUEST_SYNC_TO_ALL, FALSE, TRUE, + cib_prepare_sync, cib_cleanup_none, cib_process_sync + }, + { + PCMK__CIB_REQUEST_BUMP, TRUE, TRUE, + cib_prepare_none, cib_cleanup_output, cib_process_bump + }, + { + PCMK__CIB_REQUEST_ERASE, TRUE, TRUE, + cib_prepare_none, cib_cleanup_output, cib_process_erase + }, + { + PCMK__CIB_REQUEST_NOOP, FALSE, FALSE, + cib_prepare_none, cib_cleanup_none, cib_process_default + }, + { + PCMK__CIB_REQUEST_ABS_DELETE, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_delete_absolute + }, + { + PCMK__CIB_REQUEST_UPGRADE, TRUE, TRUE, + cib_prepare_none, cib_cleanup_output, cib_process_upgrade_server + }, + { + PCMK__CIB_REQUEST_SECONDARY, FALSE, TRUE, + cib_prepare_none, cib_cleanup_none, cib_process_readwrite + }, + { + PCMK__CIB_REQUEST_ALL_SECONDARY, FALSE, TRUE, + cib_prepare_none, cib_cleanup_none, cib_process_readwrite + }, + { + PCMK__CIB_REQUEST_SYNC_TO_ONE, FALSE, TRUE, + cib_prepare_sync, cib_cleanup_none, cib_process_sync_one + }, + { + PCMK__CIB_REQUEST_PRIMARY, TRUE, TRUE, + cib_prepare_data, cib_cleanup_data, cib_process_readwrite + }, + { + PCMK__CIB_REQUEST_IS_PRIMARY, FALSE, TRUE, + cib_prepare_none, cib_cleanup_none, cib_process_readwrite + }, + { + PCMK__CIB_REQUEST_SHUTDOWN, FALSE, TRUE, + cib_prepare_sync, cib_cleanup_none, cib_process_shutdown_req + }, + { + CRM_OP_PING, FALSE, FALSE, + cib_prepare_none, cib_cleanup_output, cib_process_ping + }, +}; + +int +cib_get_operation_id(const char *op, int *operation) +{ + static GHashTable *operation_hash = NULL; + + if (operation_hash == NULL) { + int lpc = 0; + int max_msg_types = PCMK__NELEM(cib_server_ops); + + operation_hash = pcmk__strkey_table(NULL, free); + for (lpc = 1; lpc < max_msg_types; lpc++) { + int *value = malloc(sizeof(int)); + + if(value) { + *value = lpc; + g_hash_table_insert(operation_hash, (gpointer) cib_server_ops[lpc].operation, value); + } + } + } + + if (op != NULL) { + int *value = g_hash_table_lookup(operation_hash, op); + + if (value) { + *operation = *value; + return pcmk_ok; + } + } + crm_err("Operation %s is not valid", op); + *operation = -1; + return -EINVAL; +} + +xmlNode * +cib_msg_copy(xmlNode * msg, gboolean with_data) +{ + int lpc = 0; + const char *field = NULL; + const char *value = NULL; + xmlNode *value_struct = NULL; + + static const char *field_list[] = { + F_XML_TAGNAME, + F_TYPE, + F_CIB_CLIENTID, + F_CIB_CALLOPTS, + F_CIB_CALLID, + F_CIB_OPERATION, + F_CIB_ISREPLY, + F_CIB_SECTION, + F_CIB_HOST, + F_CIB_RC, + F_CIB_DELEGATED, + F_CIB_OBJID, + F_CIB_OBJTYPE, + F_CIB_EXISTING, + F_CIB_SEENCOUNT, + F_CIB_TIMEOUT, + F_CIB_GLOBAL_UPDATE, + F_CIB_CLIENTNAME, + F_CIB_USER, + F_CIB_NOTIFY_TYPE, + F_CIB_NOTIFY_ACTIVATE + }; + + static const char *data_list[] = { + F_CIB_CALLDATA, + F_CIB_UPDATE, + F_CIB_UPDATE_RESULT + }; + + xmlNode *copy = create_xml_node(NULL, "copy"); + + CRM_ASSERT(copy != NULL); + + for (lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) { + field = field_list[lpc]; + value = crm_element_value(msg, field); + if (value != NULL) { + crm_xml_add(copy, field, value); + } + } + for (lpc = 0; with_data && lpc < PCMK__NELEM(data_list); lpc++) { + field = data_list[lpc]; + value_struct = get_message_xml(msg, field); + if (value_struct != NULL) { + add_message_xml(copy, field, value_struct); + } + } + + return copy; +} + +cib_op_t * +cib_op_func(int call_type) +{ + return &(cib_server_ops[call_type].fn); +} + +gboolean +cib_op_modifies(int call_type) +{ + return cib_server_ops[call_type].modifies_cib; +} + +int +cib_op_can_run(int call_type, int call_options, bool privileged) +{ + if (!privileged && cib_server_ops[call_type].needs_privileges) { + return -EACCES; + } + return pcmk_ok; +} + +int +cib_op_prepare(int call_type, xmlNode * request, xmlNode ** input, const char **section) +{ + crm_trace("Prepare %d", call_type); + return cib_server_ops[call_type].prepare(request, input, section); +} + +int +cib_op_cleanup(int call_type, int options, xmlNode ** input, xmlNode ** output) +{ + crm_trace("Cleanup %d", call_type); + return cib_server_ops[call_type].cleanup(options, input, output); +} diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c new file mode 100644 index 0000000..fc34f39 --- /dev/null +++ b/daemons/based/based_io.c @@ -0,0 +1,473 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <dirent.h> + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include <crm/crm.h> + +#include <crm/cib.h> +#include <crm/common/util.h> +#include <crm/msg_xml.h> +#include <crm/common/xml.h> +#include <crm/cib/internal.h> +#include <crm/cluster.h> + +#include <pacemaker-based.h> + +crm_trigger_t *cib_writer = NULL; + +int write_cib_contents(gpointer p); + +static void +cib_rename(const char *old) +{ + int new_fd; + char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root); + + umask(S_IWGRP | S_IWOTH | S_IROTH); + new_fd = mkstemp(new); + crm_err("Archiving unusable file %s as %s", old, new); + if ((new_fd < 0) || (rename(old, new) < 0)) { + crm_perror(LOG_ERR, "Couldn't rename %s as %s", old, new); + crm_err("Disabling disk writes and continuing"); + cib_writes_enabled = FALSE; + } + if (new_fd > 0) { + close(new_fd); + } + free(new); +} + +/* + * It is the callers responsibility to free the output of this function + */ + +static xmlNode * +retrieveCib(const char *filename, const char *sigfile) +{ + xmlNode *root = NULL; + + crm_info("Reading cluster configuration file %s (digest: %s)", + filename, sigfile); + switch (cib_file_read_and_verify(filename, sigfile, &root)) { + case -pcmk_err_cib_corrupt: + crm_warn("Continuing but %s will NOT be used.", filename); + break; + + case -pcmk_err_cib_modified: + /* Archive the original files so the contents are not lost */ + crm_warn("Continuing but %s will NOT be used.", filename); + cib_rename(filename); + cib_rename(sigfile); + break; + } + return root; +} + +/* + * for OSs without support for direntry->d_type, like Solaris + */ +#ifndef DT_UNKNOWN +# define DT_UNKNOWN 0 +# define DT_FIFO 1 +# define DT_CHR 2 +# define DT_DIR 4 +# define DT_BLK 6 +# define DT_REG 8 +# define DT_LNK 10 +# define DT_SOCK 12 +# define DT_WHT 14 +#endif /*DT_UNKNOWN*/ + +static int cib_archive_filter(const struct dirent * a) +{ + int rc = 0; + /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */ + struct stat s; + char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name); + + if(stat(a_path, &s) != 0) { + rc = errno; + crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_strerror(rc), rc); + rc = 0; + + } else if ((s.st_mode & S_IFREG) != S_IFREG) { + unsigned char dtype; +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + dtype = a->d_type; +#else + switch (s.st_mode & S_IFMT) { + case S_IFREG: dtype = DT_REG; break; + case S_IFDIR: dtype = DT_DIR; break; + case S_IFCHR: dtype = DT_CHR; break; + case S_IFBLK: dtype = DT_BLK; break; + case S_IFLNK: dtype = DT_LNK; break; + case S_IFIFO: dtype = DT_FIFO; break; + case S_IFSOCK: dtype = DT_SOCK; break; + default: dtype = DT_UNKNOWN; break; + } +#endif + crm_trace("%s - wrong type (%d)", a->d_name, dtype); + + } else if(strstr(a->d_name, "cib-") != a->d_name) { + crm_trace("%s - wrong prefix", a->d_name); + + } else if (pcmk__ends_with_ext(a->d_name, ".sig")) { + crm_trace("%s - wrong suffix", a->d_name); + + } else { + crm_debug("%s - candidate", a->d_name); + rc = 1; + } + + free(a_path); + return rc; +} + +static int cib_archive_sort(const struct dirent ** a, const struct dirent **b) +{ + /* Order by creation date - most recently created file first */ + int rc = 0; + struct stat buf; + + time_t a_age = 0; + time_t b_age = 0; + + char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name); + char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name); + + if(stat(a_path, &buf) == 0) { + a_age = buf.st_ctime; + } + if(stat(b_path, &buf) == 0) { + b_age = buf.st_ctime; + } + + free(a_path); + free(b_path); + + if(a_age > b_age) { + rc = 1; + } else if(a_age < b_age) { + rc = -1; + } + + crm_trace("%s (%lu) vs. %s (%lu) : %d", + a[0]->d_name, (unsigned long)a_age, + b[0]->d_name, (unsigned long)b_age, rc); + return rc; +} + +xmlNode * +readCibXmlFile(const char *dir, const char *file, gboolean discard_status) +{ + struct dirent **namelist = NULL; + + int lpc = 0; + char *sigfile = NULL; + char *sigfilepath = NULL; + char *filename = NULL; + const char *name = NULL; + const char *value = NULL; + const char *validation = NULL; + const char *use_valgrind = getenv("PCMK_valgrind_enabled"); + + xmlNode *root = NULL; + xmlNode *status = NULL; + + sigfile = crm_strdup_printf("%s.sig", file); + if (pcmk__daemon_can_write(dir, file) == FALSE + || pcmk__daemon_can_write(dir, sigfile) == FALSE) { + cib_status = -EACCES; + return NULL; + } + + filename = crm_strdup_printf("%s/%s", dir, file); + sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile); + free(sigfile); + + cib_status = pcmk_ok; + root = retrieveCib(filename, sigfilepath); + free(filename); + free(sigfilepath); + + if (root == NULL) { + crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root); + lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort); + if (lpc < 0) { + crm_perror(LOG_NOTICE, "scandir(%s) failed", cib_root); + } + } + + while (root == NULL && lpc > 1) { + crm_debug("Testing %d candidates", lpc); + + lpc--; + + filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name); + sigfile = crm_strdup_printf("%s.sig", filename); + + crm_info("Reading cluster configuration file %s (digest: %s)", + filename, sigfile); + if (cib_file_read_and_verify(filename, sigfile, &root) < 0) { + crm_warn("Continuing but %s will NOT be used.", filename); + } else { + crm_notice("Continuing with last valid configuration archive: %s", filename); + } + + free(namelist[lpc]); + free(filename); + free(sigfile); + } + free(namelist); + + if (root == NULL) { + root = createEmptyCib(0); + crm_warn("Continuing with an empty configuration."); + } + + if (cib_writes_enabled && use_valgrind && + (crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) { + + cib_writes_enabled = FALSE; + crm_err("*** Disabling disk writes to avoid confusing Valgrind ***"); + } + + status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE); + if (discard_status && status != NULL) { + /* strip out the status section if there is one */ + free_xml(status); + status = NULL; + } + if (status == NULL) { + create_xml_node(root, XML_CIB_TAG_STATUS); + } + + /* Do this before schema validation happens */ + + /* fill in some defaults */ + name = XML_ATTR_GENERATION_ADMIN; + value = crm_element_value(root, name); + if (value == NULL) { + crm_warn("No value for %s was specified in the configuration.", name); + crm_warn("The recommended course of action is to shutdown," + " run crm_verify and fix any errors it reports."); + crm_warn("We will default to zero and continue but may get" + " confused about which configuration to use if" + " multiple nodes are powered up at the same time."); + crm_xml_add_int(root, name, 0); + } + + name = XML_ATTR_GENERATION; + value = crm_element_value(root, name); + if (value == NULL) { + crm_xml_add_int(root, name, 0); + } + + name = XML_ATTR_NUMUPDATES; + value = crm_element_value(root, name); + if (value == NULL) { + crm_xml_add_int(root, name, 0); + } + + // Unset (DC should set appropriate value) + xml_remove_prop(root, XML_ATTR_DC_UUID); + + if (discard_status) { + crm_log_xml_trace(root, "[on-disk]"); + } + + validation = crm_element_value(root, XML_ATTR_VALIDATION); + if (validate_xml(root, NULL, TRUE) == FALSE) { + crm_err("CIB does not validate with %s", + pcmk__s(validation, "no schema specified")); + cib_status = -pcmk_err_schema_validation; + + } else if (validation == NULL) { + int version = 0; + + update_validation(&root, &version, 0, FALSE, FALSE); + if (version > 0) { + crm_notice("Enabling %s validation on" + " the existing (sane) configuration", get_schema_name(version)); + } else { + crm_err("CIB does not validate with any known schema"); + cib_status = -pcmk_err_schema_validation; + } + } + + return root; +} + +gboolean +uninitializeCib(void) +{ + xmlNode *tmp_cib = the_cib; + + if (tmp_cib == NULL) { + crm_debug("The CIB has already been deallocated."); + return FALSE; + } + + the_cib = NULL; + + crm_debug("Deallocating the CIB."); + + free_xml(tmp_cib); + + crm_debug("The CIB has been deallocated."); + + return TRUE; +} + +/* + * This method will free the old CIB pointer on success and the new one + * on failure. + */ +int +activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op) +{ + if (new_cib) { + xmlNode *saved_cib = the_cib; + + CRM_ASSERT(new_cib != saved_cib); + the_cib = new_cib; + free_xml(saved_cib); + if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) { + crm_debug("Triggering CIB write for %s op", op); + mainloop_set_trigger(cib_writer); + } + return pcmk_ok; + } + + crm_err("Ignoring invalid CIB"); + if (the_cib) { + crm_warn("Reverting to last known CIB"); + } else { + crm_crit("Could not write out new CIB and no saved version to revert to"); + } + return -ENODATA; +} + +static void +cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) +{ + const char *errmsg = "Could not write CIB to disk"; + + if ((exitcode != 0) && cib_writes_enabled) { + cib_writes_enabled = FALSE; + errmsg = "Disabling CIB disk writes after failure"; + } + + if ((signo == 0) && (exitcode == 0)) { + crm_trace("Disk write [%d] succeeded", (int) pid); + + } else if (signo == 0) { + crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode); + + } else { + crm_err("%s: process %d terminated with signal %d (%s)%s", + errmsg, (int) pid, signo, strsignal(signo), + (core? " and dumped core" : "")); + } + + mainloop_trigger_complete(cib_writer); +} + +int +write_cib_contents(gpointer p) +{ + int exit_rc = pcmk_ok; + xmlNode *cib_local = NULL; + + /* Make a copy of the CIB to write (possibly in a forked child) */ + if (p) { + /* Synchronous write out */ + cib_local = copy_xml(p); + + } else { + int pid = 0; + int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); + + /* Turn it off before the fork() to avoid: + * - 2 processes writing to the same shared mem + * - the child needing to disable it + * (which would close it from underneath the parent) + * This way, the shared mem files are already closed + */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + + pid = fork(); + if (pid < 0) { + crm_perror(LOG_ERR, "Disabling disk writes after fork failure"); + cib_writes_enabled = FALSE; + return FALSE; + } + + if (pid) { + /* Parent */ + mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete); + if (bb_state == QB_LOG_STATE_ENABLED) { + /* Re-enable now that it it safe */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); + } + + return -1; /* -1 means 'still work to do' */ + } + + /* Asynchronous write-out after a fork() */ + + /* In theory, we can scribble on the_cib here and not affect the parent, + * but let's be safe anyway. + */ + cib_local = copy_xml(the_cib); + } + + /* Write the CIB */ + exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml"); + + /* A nonzero exit code will cause further writes to be disabled */ + free_xml(cib_local); + if (p == NULL) { + crm_exit_t exit_code = CRM_EX_OK; + + switch (exit_rc) { + case pcmk_ok: + exit_code = CRM_EX_OK; + break; + case pcmk_err_cib_modified: + exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest + break; + case pcmk_err_cib_backup: // Existing CIB couldn't be backed up + case pcmk_err_cib_save: // New CIB couldn't be saved + exit_code = CRM_EX_CANTCREAT; + break; + default: + exit_code = CRM_EX_ERROR; + break; + } + + /* Use _exit() because exit() could affect the parent adversely */ + _exit(exit_code); + } + return exit_rc; +} diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c new file mode 100644 index 0000000..d46456c --- /dev/null +++ b/daemons/based/based_messages.c @@ -0,0 +1,427 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> + +#include <sys/param.h> +#include <sys/types.h> + +#include <crm/crm.h> +#include <crm/cib/internal.h> +#include <crm/msg_xml.h> + +#include <crm/common/xml.h> +#include <crm/common/ipc_internal.h> +#include <crm/common/xml_internal.h> +#include <crm/cluster/internal.h> + +#include <pacemaker-based.h> + +/* Maximum number of diffs to ignore while waiting for a resync */ +#define MAX_DIFF_RETRY 5 + +bool based_is_primary = false; + +xmlNode *the_cib = NULL; + +int +cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + const char *host = crm_element_value(req, F_ORIG); + + *answer = NULL; + + if (crm_element_value(req, F_CIB_ISREPLY) == NULL) { + crm_info("Peer %s is requesting to shut down", host); + return pcmk_ok; + } + + if (cib_shutdown_flag == FALSE) { + crm_err("Peer %s mistakenly thinks we wanted to shut down", host); + return -EINVAL; + } + + crm_info("Peer %s has acknowledged our shutdown request", host); + terminate_cib(__func__, 0); + return pcmk_ok; +} + +int +cib_process_default(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + int result = pcmk_ok; + + crm_trace("Processing \"%s\" event", op); + *answer = NULL; + + if (op == NULL) { + result = -EINVAL; + crm_err("No operation specified"); + + } else if (strcmp(PCMK__CIB_REQUEST_NOOP, op) != 0) { + result = -EPROTONOSUPPORT; + crm_err("Action [%s] is not supported by the CIB manager", op); + } + return result; +} + +int +cib_process_readwrite(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + int result = pcmk_ok; + + crm_trace("Processing \"%s\" event", op); + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) { + if (based_is_primary) { + result = pcmk_ok; + } else { + result = -EPERM; + } + return result; + } + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) { + if (!based_is_primary) { + crm_info("We are now in R/W mode"); + based_is_primary = true; + } else { + crm_debug("We are still in R/W mode"); + } + + } else if (based_is_primary) { + crm_info("We are now in R/O mode"); + based_is_primary = false; + } + + return result; +} + +/* Set to 1 when a sync is requested, incremented when a diff is ignored, + * reset to 0 when a sync is received + */ +static int sync_in_progress = 0; + +void +send_sync_request(const char *host) +{ + xmlNode *sync_me = create_xml_node(NULL, "sync-me"); + + crm_info("Requesting re-sync from %s", (host? host : "all peers")); + sync_in_progress = 1; + + crm_xml_add(sync_me, F_TYPE, "cib"); + crm_xml_add(sync_me, F_CIB_OPERATION, PCMK__CIB_REQUEST_SYNC_TO_ONE); + crm_xml_add(sync_me, F_CIB_DELEGATED, + stand_alone? "localhost" : crm_cluster->uname); + + send_cluster_message(host ? crm_get_peer(0, host) : NULL, crm_msg_cib, sync_me, FALSE); + free_xml(sync_me); +} + +int +cib_process_ping(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, + xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +{ + const char *host = crm_element_value(req, F_ORIG); + const char *seq = crm_element_value(req, F_CIB_PING_ID); + char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); + + crm_trace("Processing \"%s\" event %s from %s", op, seq, host); + *answer = create_xml_node(NULL, XML_CRM_TAG_PING); + + crm_xml_add(*answer, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); + crm_xml_add(*answer, XML_ATTR_DIGEST, digest); + crm_xml_add(*answer, F_CIB_PING_ID, seq); + + pcmk__if_tracing( + { + // Append additional detail so the receiver can log the differences + add_message_xml(*answer, F_CIB_CALLDATA, the_cib); + }, + { + // Always include at least the version details + const char *tag = TYPE(the_cib); + xmlNode *shallow = create_xml_node(NULL, tag); + + copy_in_properties(shallow, the_cib); + add_message_xml(*answer, F_CIB_CALLDATA, shallow); + free_xml(shallow); + } + ); + + crm_info("Reporting our current digest to %s: %s for %s.%s.%s", + host, digest, + crm_element_value(existing_cib, XML_ATTR_GENERATION_ADMIN), + crm_element_value(existing_cib, XML_ATTR_GENERATION), + crm_element_value(existing_cib, XML_ATTR_NUMUPDATES)); + + free(digest); + + return pcmk_ok; +} + +int +cib_process_sync(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, + xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +{ + return sync_our_cib(req, TRUE); +} + +int +cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, + xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +{ + int rc = pcmk_ok; + + *answer = NULL; + + if(crm_element_value(req, F_CIB_SCHEMA_MAX)) { + /* The originator of an upgrade request sends it to the DC, without + * F_CIB_SCHEMA_MAX. If an upgrade is needed, the DC re-broadcasts the + * request with F_CIB_SCHEMA_MAX, and each node performs the upgrade + * (and notifies its local clients) here. + */ + return cib_process_upgrade( + op, options, section, req, input, existing_cib, result_cib, answer); + + } else { + int new_version = 0; + int current_version = 0; + xmlNode *scratch = copy_xml(existing_cib); + const char *host = crm_element_value(req, F_ORIG); + const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION); + const char *client_id = crm_element_value(req, F_CIB_CLIENTID); + const char *call_opts = crm_element_value(req, F_CIB_CALLOPTS); + const char *call_id = crm_element_value(req, F_CIB_CALLID); + + crm_trace("Processing \"%s\" event", op); + if (value != NULL) { + current_version = get_schema_version(value); + } + + rc = update_validation(&scratch, &new_version, 0, TRUE, TRUE); + if (new_version > current_version) { + xmlNode *up = create_xml_node(NULL, __func__); + + rc = pcmk_ok; + crm_notice("Upgrade request from %s verified", host); + + crm_xml_add(up, F_TYPE, "cib"); + crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE); + crm_xml_add(up, F_CIB_SCHEMA_MAX, get_schema_name(new_version)); + crm_xml_add(up, F_CIB_DELEGATED, host); + crm_xml_add(up, F_CIB_CLIENTID, client_id); + crm_xml_add(up, F_CIB_CALLOPTS, call_opts); + crm_xml_add(up, F_CIB_CALLID, call_id); + + if (cib_legacy_mode() && based_is_primary) { + rc = cib_process_upgrade( + op, options, section, up, input, existing_cib, result_cib, answer); + + } else { + send_cluster_message(NULL, crm_msg_cib, up, FALSE); + } + + free_xml(up); + + } else if(rc == pcmk_ok) { + rc = -pcmk_err_schema_unchanged; + } + + if (rc != pcmk_ok) { + // Notify originating peer so it can notify its local clients + crm_node_t *origin = pcmk__search_cluster_node_cache(0, host); + + crm_info("Rejecting upgrade request from %s: %s " + CRM_XS " rc=%d peer=%s", host, pcmk_strerror(rc), rc, + (origin? origin->uname : "lost")); + + if (origin) { + xmlNode *up = create_xml_node(NULL, __func__); + + crm_xml_add(up, F_TYPE, "cib"); + crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE); + crm_xml_add(up, F_CIB_DELEGATED, host); + crm_xml_add(up, F_CIB_ISREPLY, host); + crm_xml_add(up, F_CIB_CLIENTID, client_id); + crm_xml_add(up, F_CIB_CALLOPTS, call_opts); + crm_xml_add(up, F_CIB_CALLID, call_id); + crm_xml_add_int(up, F_CIB_UPGRADE_RC, rc); + if (send_cluster_message(origin, crm_msg_cib, up, TRUE) + == FALSE) { + crm_warn("Could not send CIB upgrade result to %s", host); + } + free_xml(up); + } + } + free_xml(scratch); + } + return rc; +} + +int +cib_process_sync_one(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + return sync_our_cib(req, FALSE); +} + +int +cib_server_process_diff(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + int rc = pcmk_ok; + + if (sync_in_progress > MAX_DIFF_RETRY) { + /* Don't ignore diffs forever; the last request may have been lost. + * If the diff fails, we'll ask for another full resync. + */ + sync_in_progress = 0; + } + + // The primary instance should never ignore a diff + if (sync_in_progress && !based_is_primary) { + 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; + + cib_diff_version_details(input, + &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, + &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); + + sync_in_progress++; + crm_notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in progress)", + diff_del_admin_epoch, diff_del_epoch, diff_del_updates, + diff_add_admin_epoch, diff_add_epoch, diff_add_updates); + return -pcmk_err_diff_resync; + } + + rc = cib_process_diff(op, options, section, req, input, existing_cib, result_cib, answer); + crm_trace("result: %s (%d), %s", pcmk_strerror(rc), rc, + (based_is_primary? "primary": "secondary")); + + if ((rc == -pcmk_err_diff_resync) && !based_is_primary) { + free_xml(*result_cib); + *result_cib = NULL; + send_sync_request(NULL); + + } else if (rc == -pcmk_err_diff_resync) { + rc = -pcmk_err_diff_failed; + if (options & cib_force_diff) { + crm_warn("Not requesting full refresh in R/W mode"); + } + + } else if ((rc != pcmk_ok) && !based_is_primary && cib_legacy_mode()) { + crm_warn("Requesting full CIB refresh because update failed: %s" + CRM_XS " rc=%d", pcmk_strerror(rc), rc); + + pcmk__output_set_log_level(logger_out, LOG_INFO); + logger_out->message(logger_out, "xml-patchset", input); + free_xml(*result_cib); + *result_cib = NULL; + send_sync_request(NULL); + } + + return rc; +} + +int +cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + const char *tag = crm_element_name(input); + int rc = + cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer); + if (rc == pcmk_ok && pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) { + sync_in_progress = 0; + } + return rc; +} + +int +cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req, + xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, + xmlNode ** answer) +{ + return -EINVAL; +} + +int +sync_our_cib(xmlNode * request, gboolean all) +{ + int result = pcmk_ok; + char *digest = NULL; + const char *host = crm_element_value(request, F_ORIG); + const char *op = crm_element_value(request, F_CIB_OPERATION); + + xmlNode *replace_request = NULL; + + CRM_CHECK(the_cib != NULL, return -EINVAL); + + replace_request = cib_msg_copy(request, FALSE); + CRM_CHECK(replace_request != NULL, return -EINVAL); + + crm_debug("Syncing CIB to %s", all ? "all peers" : host); + if (all == FALSE && host == NULL) { + crm_log_xml_err(request, "bad sync"); + } + + /* remove the "all == FALSE" condition + * + * sync_from was failing, the local client wasn't being notified + * because it didn't know it was a reply + * setting this does not prevent the other nodes from applying it + * if all == TRUE + */ + if (host != NULL) { + crm_xml_add(replace_request, F_CIB_ISREPLY, host); + } + if (all) { + xml_remove_prop(replace_request, F_CIB_HOST); + } + + crm_xml_add(replace_request, F_CIB_OPERATION, PCMK__CIB_REQUEST_REPLACE); + crm_xml_add(replace_request, "original_" F_CIB_OPERATION, op); + pcmk__xe_set_bool_attr(replace_request, F_CIB_GLOBAL_UPDATE, true); + + crm_xml_add(replace_request, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); + digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); + crm_xml_add(replace_request, XML_ATTR_DIGEST, digest); + + add_message_xml(replace_request, F_CIB_CALLDATA, the_cib); + + if (send_cluster_message + (all ? NULL : crm_get_peer(0, host), crm_msg_cib, replace_request, FALSE) == FALSE) { + result = -ENOTCONN; + } + free_xml(replace_request); + free(digest); + return result; +} diff --git a/daemons/based/based_notify.c b/daemons/based/based_notify.c new file mode 100644 index 0000000..5881f6d --- /dev/null +++ b/daemons/based/based_notify.c @@ -0,0 +1,305 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <sys/param.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <inttypes.h> // PRIx64 + +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include <time.h> + +#include <crm/crm.h> +#include <crm/cib/internal.h> +#include <crm/msg_xml.h> + +#include <crm/common/xml.h> +#include <crm/common/remote_internal.h> +#include <pacemaker-based.h> + +struct cib_notification_s { + xmlNode *msg; + struct iovec *iov; + int32_t iov_size; +}; + +static void +cib_notify_send_one(gpointer key, gpointer value, gpointer user_data) +{ + const char *type = NULL; + gboolean do_send = FALSE; + int rc = pcmk_rc_ok; + + pcmk__client_t *client = value; + struct cib_notification_s *update = user_data; + + if (client->ipcs == NULL && client->remote == NULL) { + crm_warn("Skipping client with NULL channel"); + return; + } + + type = crm_element_value(update->msg, F_SUBTYPE); + CRM_LOG_ASSERT(type != NULL); + + if (pcmk_is_set(client->flags, cib_notify_diff) + && pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) { + + do_send = TRUE; + + } else if (pcmk_is_set(client->flags, cib_notify_replace) + && pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) { + do_send = TRUE; + + } else if (pcmk_is_set(client->flags, cib_notify_confirm) + && pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) { + do_send = TRUE; + + } else if (pcmk_is_set(client->flags, cib_notify_pre) + && pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) { + do_send = TRUE; + + } else if (pcmk_is_set(client->flags, cib_notify_post) + && pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) { + + do_send = TRUE; + } + + if (do_send) { + switch (PCMK__CLIENT_TYPE(client)) { + case pcmk__client_ipc: + rc = pcmk__ipc_send_iov(client, update->iov, + crm_ipc_server_event); + if (rc != pcmk_rc_ok) { + crm_warn("Could not notify client %s: %s " CRM_XS " id=%s", + pcmk__client_name(client), pcmk_rc_str(rc), + client->id); + } + break; +#ifdef HAVE_GNUTLS_GNUTLS_H + case pcmk__client_tls: +#endif + case pcmk__client_tcp: + crm_debug("Sent %s notification to client %s (id %s)", + type, pcmk__client_name(client), client->id); + pcmk__remote_send_xml(client->remote, update->msg); + break; + default: + crm_err("Unknown transport for client %s " + CRM_XS " flags=%#016" PRIx64, + pcmk__client_name(client), client->flags); + } + } +} + +static void +cib_notify_send(xmlNode * xml) +{ + struct iovec *iov; + struct cib_notification_s update; + + ssize_t bytes = 0; + int rc = pcmk__ipc_prepare_iov(0, xml, 0, &iov, &bytes); + + if (rc == pcmk_rc_ok) { + update.msg = xml; + update.iov = iov; + update.iov_size = bytes; + pcmk__foreach_ipc_client(cib_notify_send_one, &update); + + } else { + crm_notice("Could not notify clients: %s " CRM_XS " rc=%d", + pcmk_rc_str(rc), rc); + } + pcmk_free_ipc_event(iov); +} + +static void +attach_cib_generation(xmlNode *msg, const char *field, xmlNode *a_cib) +{ + xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE); + + if (a_cib != NULL) { + copy_in_properties(generation, a_cib); + } + add_message_xml(msg, field, generation); + free_xml(generation); +} + +void +cib_diff_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff) +{ + int add_updates = 0; + int add_epoch = 0; + int add_admin_epoch = 0; + + int del_updates = 0; + int del_epoch = 0; + int del_admin_epoch = 0; + + uint8_t log_level = LOG_TRACE; + + xmlNode *update_msg = NULL; + const char *type = NULL; + + if (diff == NULL) { + return; + } + + if (result != pcmk_ok) { + log_level = LOG_WARNING; + } + + cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates, + &del_admin_epoch, &del_epoch, &del_updates); + + if ((add_admin_epoch != del_admin_epoch) + || (add_epoch != del_epoch) + || (add_updates != del_updates)) { + + do_crm_log(log_level, + "Updated CIB generation %d.%d.%d to %d.%d.%d from client " + "%s%s%s (%s) (%s)", + del_admin_epoch, del_epoch, del_updates, + add_admin_epoch, add_epoch, add_updates, + client_name, + ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), + pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); + + } else if ((add_admin_epoch != 0) + || (add_epoch != 0) + || (add_updates != 0)) { + + do_crm_log(log_level, + "Local-only change to CIB generation %d.%d.%d from client " + "%s%s%s (%s) (%s)", + add_admin_epoch, add_epoch, add_updates, + client_name, + ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), + pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); + } + + update_msg = create_xml_node(NULL, "notify"); + + crm_xml_add(update_msg, F_TYPE, T_CIB_NOTIFY); + crm_xml_add(update_msg, F_SUBTYPE, T_CIB_DIFF_NOTIFY); + crm_xml_add(update_msg, F_CIB_OPERATION, op); + crm_xml_add(update_msg, F_CIB_CLIENTID, client_id); + crm_xml_add(update_msg, F_CIB_CALLID, call_id); + crm_xml_add(update_msg, F_ORIG, origin); + crm_xml_add_int(update_msg, F_CIB_RC, result); + + if (update != NULL) { + type = crm_element_name(update); + crm_trace("Setting type to update->name: %s", type); + } else { + type = crm_element_name(diff); + crm_trace("Setting type to new_obj->name: %s", type); + } + crm_xml_add(update_msg, F_CIB_OBJID, ID(diff)); + crm_xml_add(update_msg, F_CIB_OBJTYPE, type); + attach_cib_generation(update_msg, "cib_generation", the_cib); + + if (update != NULL) { + add_message_xml(update_msg, F_CIB_UPDATE, update); + } + add_message_xml(update_msg, F_CIB_UPDATE_RESULT, diff); + + cib_notify_send(update_msg); + free_xml(update_msg); +} + +void +cib_replace_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff, + uint32_t change_section) +{ + xmlNode *replace_msg = NULL; + + int add_updates = 0; + int add_epoch = 0; + int add_admin_epoch = 0; + + int del_updates = 0; + int del_epoch = 0; + int del_admin_epoch = 0; + + uint8_t log_level = LOG_INFO; + + if (diff == NULL) { + return; + } + + if (result != pcmk_ok) { + log_level = LOG_WARNING; + } + + cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates, + &del_admin_epoch, &del_epoch, &del_updates); + + if (del_updates < 0) { + crm_log_xml_debug(diff, "Bad replace diff"); + } + + if ((add_admin_epoch != del_admin_epoch) + || (add_epoch != del_epoch) + || (add_updates != del_updates)) { + + do_crm_log(log_level, + "Replaced CIB generation %d.%d.%d with %d.%d.%d from client " + "%s%s%s (%s) (%s)", + del_admin_epoch, del_epoch, del_updates, + add_admin_epoch, add_epoch, add_updates, + client_name, + ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), + pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); + + } else if ((add_admin_epoch != 0) + || (add_epoch != 0) + || (add_updates != 0)) { + + do_crm_log(log_level, + "Local-only replace of CIB generation %d.%d.%d from client " + "%s%s%s (%s) (%s)", + add_admin_epoch, add_epoch, add_updates, + client_name, + ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), + pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); + } + + replace_msg = create_xml_node(NULL, "notify-replace"); + + crm_xml_add(replace_msg, F_TYPE, T_CIB_NOTIFY); + crm_xml_add(replace_msg, F_SUBTYPE, T_CIB_REPLACE_NOTIFY); + crm_xml_add(replace_msg, F_CIB_OPERATION, op); + crm_xml_add(replace_msg, F_CIB_CLIENTID, client_id); + crm_xml_add(replace_msg, F_CIB_CALLID, call_id); + crm_xml_add(replace_msg, F_ORIG, origin); + crm_xml_add_int(replace_msg, F_CIB_RC, result); + crm_xml_add_ll(replace_msg, F_CIB_CHANGE_SECTION, + (long long) change_section); + attach_cib_generation(replace_msg, "cib-replace-generation", update); + + /* We can include update and diff if a replace callback needs them. Until + * then, avoid the overhead. + */ + + crm_log_xml_trace(replace_msg, "CIB replaced"); + + cib_notify_send(replace_msg); + free_xml(replace_msg); +} diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c new file mode 100644 index 0000000..38136d2 --- /dev/null +++ b/daemons/based/based_remote.c @@ -0,0 +1,680 @@ +/* + * Copyright 2004-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> +#include <crm/crm.h> + +#include <sys/param.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <inttypes.h> // PRIx64 +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <netinet/ip.h> + +#include <stdlib.h> +#include <errno.h> +#include <glib.h> + +#include <crm/msg_xml.h> +#include <crm/common/ipc.h> +#include <crm/common/ipc_internal.h> +#include <crm/common/xml.h> +#include <crm/common/remote_internal.h> +#include <crm/cib/internal.h> + +#include "pacemaker-based.h" + +/* #undef HAVE_PAM_PAM_APPL_H */ +/* #undef HAVE_GNUTLS_GNUTLS_H */ + +#ifdef HAVE_GNUTLS_GNUTLS_H +# include <gnutls/gnutls.h> +#endif + +#include <pwd.h> +#include <grp.h> +#if HAVE_SECURITY_PAM_APPL_H +# include <security/pam_appl.h> +# define HAVE_PAM 1 +#else +# if HAVE_PAM_PAM_APPL_H +# include <pam/pam_appl.h> +# define HAVE_PAM 1 +# endif +#endif + +extern int remote_tls_fd; +extern gboolean cib_shutdown_flag; + +int init_remote_listener(int port, gboolean encrypted); +void cib_remote_connection_destroy(gpointer user_data); + +#ifdef HAVE_GNUTLS_GNUTLS_H +gnutls_dh_params_t dh_params; +gnutls_anon_server_credentials_t anon_cred_s; +static void +debug_log(int level, const char *str) +{ + fputs(str, stderr); +} +#endif + +#define REMOTE_AUTH_TIMEOUT 10000 + +int num_clients; +int authenticate_user(const char *user, const char *passwd); +static int cib_remote_listen(gpointer data); +static int cib_remote_msg(gpointer data); + +static void +remote_connection_destroy(gpointer user_data) +{ + crm_info("No longer listening for remote connections"); + return; +} + +int +init_remote_listener(int port, gboolean encrypted) +{ + int rc; + int *ssock = NULL; + struct sockaddr_in saddr; + int optval; + + static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { + .dispatch = cib_remote_listen, + .destroy = remote_connection_destroy, + }; + + if (port <= 0) { + /* don't start it */ + return 0; + } + + if (encrypted) { +#ifndef HAVE_GNUTLS_GNUTLS_H + crm_warn("TLS support is not available"); + return 0; +#else + crm_notice("Starting TLS listener on port %d", port); + crm_gnutls_global_init(); + /* gnutls_global_set_log_level (10); */ + gnutls_global_set_log_function(debug_log); + if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) { + return -1; + } + gnutls_anon_allocate_server_credentials(&anon_cred_s); + gnutls_anon_set_server_dh_params(anon_cred_s, dh_params); +#endif + } else { + crm_warn("Starting plain-text listener on port %d", port); + } +#ifndef HAVE_PAM + crm_warn("PAM is _not_ enabled!"); +#endif + + /* create server socket */ + ssock = malloc(sizeof(int)); + if(ssock == NULL) { + crm_perror(LOG_ERR, "Listener socket allocation failed"); + return -1; + } + + *ssock = socket(AF_INET, SOCK_STREAM, 0); + if (*ssock == -1) { + crm_perror(LOG_ERR, "Listener socket creation failed"); + free(ssock); + return -1; + } + + /* reuse address */ + optval = 1; + rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + if (rc < 0) { + crm_perror(LOG_WARNING, + "Local address reuse not allowed on listener socket"); + } + + /* bind server socket */ + memset(&saddr, '\0', sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = INADDR_ANY; + saddr.sin_port = htons(port); + if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { + crm_perror(LOG_ERR, "Cannot bind to listener socket"); + close(*ssock); + free(ssock); + return -2; + } + if (listen(*ssock, 10) == -1) { + crm_perror(LOG_ERR, "Cannot listen on socket"); + close(*ssock); + free(ssock); + return -3; + } + + mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks); + crm_debug("Started listener on port %d", port); + + return *ssock; +} + +static int +check_group_membership(const char *usr, const char *grp) +{ + int index = 0; + struct passwd *pwd = NULL; + struct group *group = NULL; + + CRM_CHECK(usr != NULL, return FALSE); + CRM_CHECK(grp != NULL, return FALSE); + + pwd = getpwnam(usr); + if (pwd == NULL) { + crm_err("No user named '%s' exists!", usr); + return FALSE; + } + + group = getgrgid(pwd->pw_gid); + if (group != NULL && pcmk__str_eq(grp, group->gr_name, pcmk__str_none)) { + return TRUE; + } + + group = getgrnam(grp); + if (group == NULL) { + crm_err("No group named '%s' exists!", grp); + return FALSE; + } + + while (TRUE) { + char *member = group->gr_mem[index++]; + + if (member == NULL) { + break; + + } else if (pcmk__str_eq(usr, member, pcmk__str_none)) { + return TRUE; + } + }; + + return FALSE; +} + +static gboolean +cib_remote_auth(xmlNode * login) +{ + const char *user = NULL; + const char *pass = NULL; + const char *tmp = NULL; + + crm_log_xml_info(login, "Login: "); + if (login == NULL) { + return FALSE; + } + + tmp = crm_element_name(login); + if (!pcmk__str_eq(tmp, "cib_command", pcmk__str_casei)) { + crm_err("Wrong tag: %s", tmp); + return FALSE; + } + + tmp = crm_element_value(login, "op"); + if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) { + crm_err("Wrong operation: %s", tmp); + return FALSE; + } + + user = crm_element_value(login, "user"); + pass = crm_element_value(login, "password"); + + if (!user || !pass) { + crm_err("missing auth credentials"); + return FALSE; + } + + /* Non-root daemons can only validate the password of the + * user they're running as + */ + if (check_group_membership(user, CRM_DAEMON_GROUP) == FALSE) { + crm_err("User is not a member of the required group"); + return FALSE; + + } else if (authenticate_user(user, pass) == FALSE) { + crm_err("PAM auth failed"); + return FALSE; + } + + return TRUE; +} + +static gboolean +remote_auth_timeout_cb(gpointer data) +{ + pcmk__client_t *client = data; + + client->remote->auth_timeout = 0; + + if (pcmk_is_set(client->flags, pcmk__client_authenticated)) { + return FALSE; + } + + mainloop_del_fd(client->remote->source); + crm_err("Remote client authentication timed out"); + + return FALSE; +} + +static int +cib_remote_listen(gpointer data) +{ + int csock = 0; + unsigned laddr; + struct sockaddr_storage addr; + char ipstr[INET6_ADDRSTRLEN]; + int ssock = *(int *)data; + int rc; + + pcmk__client_t *new_client = NULL; + + static struct mainloop_fd_callbacks remote_client_fd_callbacks = { + .dispatch = cib_remote_msg, + .destroy = cib_remote_connection_destroy, + }; + + /* accept the connection */ + laddr = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + csock = accept(ssock, (struct sockaddr *)&addr, &laddr); + if (csock == -1) { + crm_perror(LOG_ERR, "Could not accept socket connection"); + return TRUE; + } + + pcmk__sockaddr2str(&addr, ipstr); + crm_debug("New %s connection from %s", + ((ssock == remote_tls_fd)? "secure" : "clear-text"), ipstr); + + rc = pcmk__set_nonblocking(csock); + if (rc != pcmk_rc_ok) { + crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d", + pcmk_rc_str(rc), rc); + close(csock); + return TRUE; + } + + num_clients++; + + new_client = pcmk__new_unauth_client(NULL); + new_client->remote = calloc(1, sizeof(pcmk__remote_t)); + + if (ssock == remote_tls_fd) { +#ifdef HAVE_GNUTLS_GNUTLS_H + pcmk__set_client_flags(new_client, pcmk__client_tls); + + /* create gnutls session for the server socket */ + new_client->remote->tls_session = pcmk__new_tls_session(csock, + GNUTLS_SERVER, + GNUTLS_CRD_ANON, + anon_cred_s); + if (new_client->remote->tls_session == NULL) { + close(csock); + return TRUE; + } +#endif + } else { + pcmk__set_client_flags(new_client, pcmk__client_tcp); + new_client->remote->tcp_socket = csock; + } + + // Require the client to authenticate within this time + new_client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT, + remote_auth_timeout_cb, + new_client); + crm_info("Remote CIB client pending authentication " + CRM_XS " %p id: %s", new_client, new_client->id); + + new_client->remote->source = + mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client, + &remote_client_fd_callbacks); + + return TRUE; +} + +void +cib_remote_connection_destroy(gpointer user_data) +{ + pcmk__client_t *client = user_data; + int csock = 0; + + if (client == NULL) { + return; + } + + crm_trace("Cleaning up after client %s disconnect", + pcmk__client_name(client)); + + num_clients--; + crm_trace("Num unfree'd clients: %d", num_clients); + + switch (PCMK__CLIENT_TYPE(client)) { + case pcmk__client_tcp: + csock = client->remote->tcp_socket; + break; +#ifdef HAVE_GNUTLS_GNUTLS_H + case pcmk__client_tls: + if (client->remote->tls_session) { + void *sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session); + + csock = GPOINTER_TO_INT(sock_ptr); + if (pcmk_is_set(client->flags, + pcmk__client_tls_handshake_complete)) { + gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_WR); + } + gnutls_deinit(*client->remote->tls_session); + gnutls_free(client->remote->tls_session); + client->remote->tls_session = NULL; + } + break; +#endif + default: + crm_warn("Unknown transport for client %s " + CRM_XS " flags=%#016" PRIx64, + pcmk__client_name(client), client->flags); + } + + if (csock > 0) { + close(csock); + } + + pcmk__free_client(client); + + crm_trace("Freed the cib client"); + + if (cib_shutdown_flag) { + cib_shutdown(0); + } + return; +} + +static void +cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) +{ + const char *value = NULL; + + value = crm_element_name(command); + if (!pcmk__str_eq(value, "cib_command", pcmk__str_casei)) { + crm_log_xml_trace(command, "Bad command: "); + return; + } + + if (client->name == NULL) { + value = crm_element_value(command, F_CLIENTNAME); + if (value == NULL) { + client->name = strdup(client->id); + } else { + client->name = strdup(value); + } + } + + /* unset dangerous options */ + xml_remove_prop(command, F_ORIG); + xml_remove_prop(command, F_CIB_HOST); + xml_remove_prop(command, F_CIB_GLOBAL_UPDATE); + + crm_xml_add(command, F_TYPE, T_CIB); + crm_xml_add(command, F_CIB_CLIENTID, client->id); + crm_xml_add(command, F_CIB_CLIENTNAME, client->name); + crm_xml_add(command, F_CIB_USER, client->user); + + if (crm_element_value(command, F_CIB_CALLID) == NULL) { + char *call_uuid = crm_generate_uuid(); + + /* fix the command */ + crm_xml_add(command, F_CIB_CALLID, call_uuid); + free(call_uuid); + } + + if (crm_element_value(command, F_CIB_CALLOPTS) == NULL) { + crm_xml_add_int(command, F_CIB_CALLOPTS, 0); + } + + crm_log_xml_trace(command, "Remote command: "); + cib_common_callback_worker(0, 0, command, client, TRUE); +} + +static int +cib_remote_msg(gpointer data) +{ + xmlNode *command = NULL; + pcmk__client_t *client = data; + int rc; + int timeout = 1000; + + if (pcmk_is_set(client->flags, pcmk__client_authenticated)) { + timeout = -1; + } + + crm_trace("Remote %s message received for client %s", + pcmk__client_type_str(PCMK__CLIENT_TYPE(client)), + pcmk__client_name(client)); + +#ifdef HAVE_GNUTLS_GNUTLS_H + if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls) + && !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) { + + int rc = pcmk__read_handshake_data(client); + + if (rc == EAGAIN) { + /* No more data is available at the moment. Just return for now; + * we'll get invoked again once the client sends more. + */ + return 0; + } else if (rc != pcmk_rc_ok) { + return -1; + } + + crm_debug("TLS handshake with remote CIB client completed"); + pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); + if (client->remote->auth_timeout) { + g_source_remove(client->remote->auth_timeout); + } + + // Require the client to authenticate within this time + client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT, + remote_auth_timeout_cb, + client); + return 0; + } +#endif + + rc = pcmk__read_remote_message(client->remote, timeout); + + /* must pass auth before we will process anything else */ + if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) { + xmlNode *reg; + const char *user = NULL; + + command = pcmk__remote_message_xml(client->remote); + if (cib_remote_auth(command) == FALSE) { + free_xml(command); + return -1; + } + + crm_notice("Remote CIB client connection accepted"); + pcmk__set_client_flags(client, pcmk__client_authenticated); + g_source_remove(client->remote->auth_timeout); + client->remote->auth_timeout = 0; + client->name = crm_element_value_copy(command, "name"); + + user = crm_element_value(command, "user"); + if (user) { + client->user = strdup(user); + } + + /* send ACK */ + reg = create_xml_node(NULL, "cib_result"); + crm_xml_add(reg, F_CIB_OPERATION, CRM_OP_REGISTER); + crm_xml_add(reg, F_CIB_CLIENTID, client->id); + pcmk__remote_send_xml(client->remote, reg); + free_xml(reg); + free_xml(command); + } + + command = pcmk__remote_message_xml(client->remote); + while (command) { + crm_trace("Remote client message received"); + cib_handle_remote_msg(client, command); + free_xml(command); + command = pcmk__remote_message_xml(client->remote); + } + + if (rc == ENOTCONN) { + crm_trace("Remote CIB client disconnected while reading from it"); + return -1; + } + + return 0; +} + +#ifdef HAVE_PAM +static int +construct_pam_passwd(int num_msg, const struct pam_message **msg, + struct pam_response **response, void *data) +{ + int count = 0; + struct pam_response *reply; + char *string = (char *)data; + + CRM_CHECK(data, return PAM_CONV_ERR); + CRM_CHECK(num_msg == 1, return PAM_CONV_ERR); /* We only want to handle one message */ + + reply = calloc(1, sizeof(struct pam_response)); + CRM_ASSERT(reply != NULL); + + for (count = 0; count < num_msg; ++count) { + switch (msg[count]->msg_style) { + case PAM_TEXT_INFO: + crm_info("PAM: %s", msg[count]->msg); + break; + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + reply[count].resp_retcode = 0; + reply[count].resp = string; /* We already made a copy */ + break; + case PAM_ERROR_MSG: + /* In theory we'd want to print this, but then + * we see the password prompt in the logs + */ + /* crm_err("PAM error: %s", msg[count]->msg); */ + break; + default: + crm_err("Unhandled conversation type: %d", msg[count]->msg_style); + goto bail; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + + bail: + for (count = 0; count < num_msg; ++count) { + if (reply[count].resp != NULL) { + switch (msg[count]->msg_style) { + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + /* Erase the data - it contained a password */ + while (*(reply[count].resp)) { + *(reply[count].resp)++ = '\0'; + } + free(reply[count].resp); + break; + } + reply[count].resp = NULL; + } + } + free(reply); + reply = NULL; + + return PAM_CONV_ERR; +} +#endif + +int +authenticate_user(const char *user, const char *passwd) +{ +#ifndef HAVE_PAM + gboolean pass = TRUE; +#else + int rc = 0; + gboolean pass = FALSE; + const void *p_user = NULL; + + struct pam_conv p_conv; + struct pam_handle *pam_h = NULL; + static const char *pam_name = NULL; + + if (pam_name == NULL) { + pam_name = getenv("CIB_pam_service"); + } + if (pam_name == NULL) { + pam_name = "login"; + } + + p_conv.conv = construct_pam_passwd; + p_conv.appdata_ptr = strdup(passwd); + + rc = pam_start(pam_name, user, &p_conv, &pam_h); + if (rc != PAM_SUCCESS) { + crm_err("Could not initialize PAM: %s (%d)", pam_strerror(pam_h, rc), rc); + goto bail; + } + + rc = pam_authenticate(pam_h, 0); + if (rc != PAM_SUCCESS) { + crm_err("Authentication failed for %s: %s (%d)", user, pam_strerror(pam_h, rc), rc); + goto bail; + } + + /* Make sure we authenticated the user we wanted to authenticate. + * Since we also run as non-root, it might be worth pre-checking + * the user has the same EID as us, since that the only user we + * can authenticate. + */ + rc = pam_get_item(pam_h, PAM_USER, &p_user); + if (rc != PAM_SUCCESS) { + crm_err("Internal PAM error: %s (%d)", pam_strerror(pam_h, rc), rc); + goto bail; + + } else if (p_user == NULL) { + crm_err("Unknown user authenticated."); + goto bail; + + } else if (!pcmk__str_eq(p_user, user, pcmk__str_casei)) { + crm_err("User mismatch: %s vs. %s.", (const char *)p_user, (const char *)user); + goto bail; + } + + rc = pam_acct_mgmt(pam_h, 0); + if (rc != PAM_SUCCESS) { + crm_err("Access denied: %s (%d)", pam_strerror(pam_h, rc), rc); + goto bail; + } + pass = TRUE; + + bail: + pam_end(pam_h, rc); +#endif + return pass; +} diff --git a/daemons/based/cib.pam b/daemons/based/cib.pam new file mode 100644 index 0000000..5d0f655 --- /dev/null +++ b/daemons/based/cib.pam @@ -0,0 +1,6 @@ +# login: auth account password session +# may require permission to read /etc/shadow +auth include common-auth +account include common-account +password include common-password +session include common-session diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c new file mode 100644 index 0000000..129997e --- /dev/null +++ b/daemons/based/pacemaker-based.c @@ -0,0 +1,442 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <stdlib.h> +#include <pwd.h> +#include <grp.h> +#include <bzlib.h> +#include <sys/types.h> + +#include <libxml/parser.h> + +#include <crm/crm.h> +#include <crm/cib/internal.h> +#include <crm/msg_xml.h> +#include <crm/cluster/internal.h> +#include <crm/common/cmdline_internal.h> +#include <crm/common/mainloop.h> +#include <crm/common/output_internal.h> +#include <crm/common/xml.h> + +#include <pacemaker-based.h> + +#define SUMMARY "daemon for managing the configuration of a Pacemaker cluster" + +extern int init_remote_listener(int port, gboolean encrypted); +gboolean cib_shutdown_flag = FALSE; +int cib_status = pcmk_ok; + +crm_cluster_t *crm_cluster = NULL; + +GMainLoop *mainloop = NULL; +gchar *cib_root = NULL; +static gboolean preserve_status = FALSE; + +gboolean cib_writes_enabled = TRUE; + +int remote_fd = 0; +int remote_tls_fd = 0; + +GHashTable *config_hash = NULL; +GHashTable *local_notify_queue = NULL; + +pcmk__output_t *logger_out = NULL; + +static void cib_init(void); +void cib_shutdown(int nsig); +static bool startCib(const char *filename); +extern int write_cib_contents(gpointer p); + +static crm_exit_t exit_code = CRM_EX_OK; + +static void +cib_enable_writes(int nsig) +{ + crm_info("(Re)enabling disk writes"); + cib_writes_enabled = TRUE; +} + +/*! + * \internal + * \brief Set up options, users, and groups for stand-alone mode + * + * \param[out] error GLib error object + * + * \return Standard Pacemaker return code + */ +static int +setup_stand_alone(GError **error) +{ + int rc = 0; + struct passwd *pwentry = NULL; + + preserve_status = TRUE; + cib_writes_enabled = FALSE; + + errno = 0; + pwentry = getpwnam(CRM_DAEMON_USER); + if (pwentry == NULL) { + exit_code = CRM_EX_FATAL; + if (errno != 0) { + g_set_error(error, PCMK__EXITC_ERROR, exit_code, + "Error getting password DB entry for %s: %s", + CRM_DAEMON_USER, strerror(errno)); + return errno; + } + g_set_error(error, PCMK__EXITC_ERROR, exit_code, + "Password DB entry for '%s' not found", CRM_DAEMON_USER); + return ENXIO; + } + + rc = setgid(pwentry->pw_gid); + if (rc < 0) { + exit_code = CRM_EX_FATAL; + g_set_error(error, PCMK__EXITC_ERROR, exit_code, + "Could not set group to %d: %s", + pwentry->pw_gid, strerror(errno)); + return errno; + } + + rc = initgroups(CRM_DAEMON_USER, pwentry->pw_gid); + if (rc < 0) { + exit_code = CRM_EX_FATAL; + g_set_error(error, PCMK__EXITC_ERROR, exit_code, + "Could not setup groups for user %d: %s", + pwentry->pw_uid, strerror(errno)); + return errno; + } + + rc = setuid(pwentry->pw_uid); + if (rc < 0) { + exit_code = CRM_EX_FATAL; + g_set_error(error, PCMK__EXITC_ERROR, exit_code, + "Could not set user to %d: %s", + pwentry->pw_uid, strerror(errno)); + return errno; + } + return pcmk_rc_ok; +} + +static GOptionEntry entries[] = { + { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone, + "(Advanced use only) Run in stand-alone mode", NULL }, + + { "disk-writes", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + &cib_writes_enabled, + "(Advanced use only) Enable disk writes (enabled by default unless in " + "stand-alone mode)", NULL }, + + { "cib-root", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &cib_root, + "(Advanced use only) Directory where the CIB XML file should be located " + "(default: " CRM_CONFIG_DIR ")", NULL }, + + { NULL } +}; + +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) +{ + GOptionContext *context = NULL; + + context = pcmk__build_arg_context(args, "text (default), xml", group, + "[metadata]"); + pcmk__add_main_args(context, entries); + return context; +} + +int +main(int argc, char **argv) +{ + int rc = pcmk_rc_ok; + crm_ipc_t *old_instance = NULL; + + pcmk__output_t *out = NULL; + + GError *error = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, "r"); + GOptionContext *context = build_arg_context(args, &output_group); + + crm_log_preinit(NULL, argc, argv); + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; + } + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error creating output format %s: %s", + args->output_ty, pcmk_rc_str(rc)); + goto done; + } + + if (args->version) { + out->version(out, false); + goto done; + } + + rc = pcmk__log_output_new(&logger_out); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error creating output format log: %s", pcmk_rc_str(rc)); + goto done; + } + pcmk__output_set_log_level(logger_out, LOG_TRACE); + + mainloop_add_signal(SIGTERM, cib_shutdown); + mainloop_add_signal(SIGPIPE, cib_enable_writes); + + cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); + + if ((g_strv_length(processed_args) >= 2) + && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { + cib_metadata(); + goto done; + } + + pcmk__cli_init_logging("pacemaker-based", args->verbosity); + crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); + crm_notice("Starting Pacemaker CIB manager"); + + old_instance = crm_ipc_new(PCMK__SERVER_BASED_RO, 0); + if (old_instance == NULL) { + /* crm_ipc_new() will have already logged an error message with + * crm_err() + */ + exit_code = CRM_EX_FATAL; + goto done; + } + + if (crm_ipc_connect(old_instance)) { + /* IPC end-point already up */ + crm_ipc_close(old_instance); + crm_ipc_destroy(old_instance); + crm_err("pacemaker-based is already active, aborting startup"); + goto done; + } else { + /* not up or not authentic, we'll proceed either way */ + crm_ipc_destroy(old_instance); + old_instance = NULL; + } + + if (stand_alone) { + rc = setup_stand_alone(&error); + if (rc != pcmk_rc_ok) { + goto done; + } + } + + if (cib_root == NULL) { + cib_root = g_strdup(CRM_CONFIG_DIR); + } else { + crm_notice("Using custom config location: %s", cib_root); + } + + if (!pcmk__daemon_can_write(cib_root, NULL)) { + exit_code = CRM_EX_FATAL; + crm_err("Terminating due to bad permissions on %s", cib_root); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Bad permissions on %s (see logs for details)", cib_root); + goto done; + } + + crm_peer_init(); + + // Read initial CIB, connect to cluster, and start IPC servers + cib_init(); + + // Run the main loop + mainloop = g_main_loop_new(NULL, FALSE); + crm_notice("Pacemaker CIB manager successfully started and accepting connections"); + g_main_loop_run(mainloop); + + /* If main loop returned, clean up and exit. We disconnect in case + * terminate_cib() was called with fast=-1. + */ + crm_cluster_disconnect(crm_cluster); + pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); + +done: + g_strfreev(processed_args); + pcmk__free_arg_context(context); + + crm_peer_destroy(); + + if (local_notify_queue != NULL) { + g_hash_table_destroy(local_notify_queue); + } + + if (config_hash != NULL) { + g_hash_table_destroy(config_hash); + } + pcmk__client_cleanup(); + pcmk_cluster_free(crm_cluster); + g_free(cib_root); + + pcmk__output_and_clear_error(&error, out); + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } + pcmk__unregister_formats(); + crm_exit(exit_code); +} + +#if SUPPORT_COROSYNC +static void +cib_cs_dispatch(cpg_handle_t handle, + const struct cpg_name *groupName, + uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) +{ + uint32_t kind = 0; + xmlNode *xml = NULL; + const char *from = NULL; + char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); + + if(data == NULL) { + return; + } + if (kind == crm_class_cluster) { + xml = string2xml(data); + if (xml == NULL) { + crm_err("Invalid XML: '%.120s'", data); + free(data); + return; + } + crm_xml_add(xml, F_ORIG, from); + /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ + cib_peer_callback(xml, NULL); + } + + free_xml(xml); + free(data); +} + +static void +cib_cs_destroy(gpointer user_data) +{ + if (cib_shutdown_flag) { + crm_info("Corosync disconnection complete"); + } else { + crm_crit("Lost connection to cluster layer, shutting down"); + terminate_cib(__func__, CRM_EX_DISCONNECT); + } +} +#endif + +static void +cib_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) +{ + switch (type) { + case crm_status_processes: + if (cib_legacy_mode() + && !pcmk_is_set(node->processes, crm_get_cluster_proc())) { + + uint32_t old = data? *(const uint32_t *)data : 0; + + if ((node->processes ^ old) & crm_proc_cpg) { + crm_info("Attempting to disable legacy mode after %s left the cluster", + node->uname); + legacy_mode = FALSE; + } + } + break; + + case crm_status_uname: + case crm_status_nstate: + if (cib_shutdown_flag && (crm_active_peers() < 2) + && (pcmk__ipc_client_count() == 0)) { + + crm_info("No more peers"); + terminate_cib(__func__, -1); + } + break; + } +} + +static void +cib_init(void) +{ + crm_cluster = pcmk_cluster_new(); + +#if SUPPORT_COROSYNC + if (is_corosync_cluster()) { + crm_cluster->destroy = cib_cs_destroy; + crm_cluster->cpg.cpg_deliver_fn = cib_cs_dispatch; + crm_cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership; + } +#endif // SUPPORT_COROSYNC + + config_hash = pcmk__strkey_table(free, free); + + if (startCib("cib.xml") == FALSE) { + crm_crit("Cannot start CIB... terminating"); + crm_exit(CRM_EX_NOINPUT); + } + + if (!stand_alone) { + crm_set_status_callback(&cib_peer_update_callback); + + if (!crm_cluster_connect(crm_cluster)) { + crm_crit("Cannot sign in to the cluster... terminating"); + crm_exit(CRM_EX_FATAL); + } + } + + pcmk__serve_based_ipc(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, + &ipc_rw_callbacks); + + if (stand_alone) { + based_is_primary = true; + } +} + +static bool +startCib(const char *filename) +{ + gboolean active = FALSE; + xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); + + if (activateCibXml(cib, TRUE, "start") == 0) { + int port = 0; + + active = TRUE; + + cib_read_config(config_hash, cib); + + pcmk__scan_port(crm_element_value(cib, "remote-tls-port"), &port); + if (port >= 0) { + remote_tls_fd = init_remote_listener(port, TRUE); + } + + pcmk__scan_port(crm_element_value(cib, "remote-clear-port"), &port); + if (port >= 0) { + remote_fd = init_remote_listener(port, FALSE); + } + } + return active; +} diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h new file mode 100644 index 0000000..05e49b3 --- /dev/null +++ b/daemons/based/pacemaker-based.h @@ -0,0 +1,150 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef PACEMAKER_BASED__H +# define PACEMAKER_BASED__H + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <glib.h> +#include <errno.h> +#include <fcntl.h> + +#include <crm/crm.h> +#include <crm/cib.h> +#include <crm/common/xml.h> +#include <crm/cluster.h> +#include <crm/common/ipc_internal.h> +#include <crm/common/mainloop.h> +#include <crm/cib/internal.h> + +#ifdef HAVE_GNUTLS_GNUTLS_H +# include <gnutls/gnutls.h> +#endif + +// CIB-specific client flags +enum cib_client_flags { + // Notifications + cib_notify_pre = (UINT64_C(1) << 0), + cib_notify_post = (UINT64_C(1) << 1), + cib_notify_replace = (UINT64_C(1) << 2), + cib_notify_confirm = (UINT64_C(1) << 3), + cib_notify_diff = (UINT64_C(1) << 4), + + // Whether client is another cluster daemon + cib_is_daemon = (UINT64_C(1) << 12), +}; + +typedef struct cib_operation_s { + const char *operation; + gboolean modifies_cib; + gboolean needs_privileges; + int (*prepare) (xmlNode *, xmlNode **, const char **); + int (*cleanup) (int, xmlNode **, xmlNode **); + int (*fn) (const char *, int, const char *, xmlNode *, + xmlNode *, xmlNode *, xmlNode **, xmlNode **); +} cib_operation_t; + +extern bool based_is_primary; +extern GHashTable *config_hash; +extern xmlNode *the_cib; +extern crm_trigger_t *cib_writer; +extern gboolean cib_writes_enabled; + +extern GMainLoop *mainloop; +extern crm_cluster_t *crm_cluster; +extern GHashTable *local_notify_queue; +extern gboolean legacy_mode; +extern gboolean stand_alone; +extern gboolean cib_shutdown_flag; +extern gchar *cib_root; +extern int cib_status; +extern pcmk__output_t *logger_out; + +extern struct qb_ipcs_service_handlers ipc_ro_callbacks; +extern struct qb_ipcs_service_handlers ipc_rw_callbacks; +extern qb_ipcs_service_t *ipcs_ro; +extern qb_ipcs_service_t *ipcs_rw; +extern qb_ipcs_service_t *ipcs_shm; + +void cib_peer_callback(xmlNode *msg, void *private_data); +void cib_common_callback_worker(uint32_t id, uint32_t flags, + xmlNode *op_request, pcmk__client_t *cib_client, + gboolean privileged); +void cib_shutdown(int nsig); +void terminate_cib(const char *caller, int fast); +gboolean cib_legacy_mode(void); + +gboolean uninitializeCib(void); +xmlNode *readCibXmlFile(const char *dir, const char *file, + gboolean discard_status); +int activateCibXml(xmlNode *doc, gboolean to_disk, const char *op); + +int cib_process_shutdown_req(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, + xmlNode *existing_cib, xmlNode **result_cib, + xmlNode **answer); +int cib_process_default(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_ping(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_readwrite(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_replace_svr(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_server_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_sync(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_sync_one(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_delete_absolute(const char *op, int options, + const char *section, xmlNode *req, + xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer); +int cib_process_upgrade_server(const char *op, int options, const char *section, + xmlNode *req, xmlNode *input, + xmlNode *existing_cib, xmlNode **result_cib, + xmlNode **answer); +void send_sync_request(const char *host); +int sync_our_cib(xmlNode *request, gboolean all); + +xmlNode *cib_msg_copy(xmlNode *msg, gboolean with_data); +int cib_get_operation_id(const char *op, int *operation); +cib_op_t *cib_op_func(int call_type); +gboolean cib_op_modifies(int call_type); +int cib_op_prepare(int call_type, xmlNode *request, xmlNode **input, + const char **section); +int cib_op_cleanup(int call_type, int options, xmlNode **input, + xmlNode **output); +int cib_op_can_run(int call_type, int call_options, bool privileged); +void cib_diff_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff); +void cib_replace_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff, + uint32_t change_section); + +static inline const char * +cib_config_lookup(const char *opt) +{ + return g_hash_table_lookup(config_hash, opt); +} + +#endif // PACEMAKER_BASED__H |