diff --git a/daemons/execd/execd_messages.c b/daemons/execd/execd_messages.c index c9012bcb40..9f9229e77e 100644 --- a/daemons/execd/execd_messages.c +++ b/daemons/execd/execd_messages.c @@ -1,639 +1,654 @@ /* * Copyright 2012-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include // ENOMEM #include // bool #include // NULL, size_t #include // int32_t, uint32_t #include // free #include // gid_t, uid_t #include // g_byte_array_free, FALSE #include // xmlNode #include // qb_ipcs_connection_t, qb_ipcs_service_handlers #include // QB_XS #include // CRM_SYSTEM_LRMD #include // pcmk__process_request, pcmk__xml_free #include // crm_ipc_flags #include // pcmk__client_s, pcmk__find_client #include // pcmk_rc_e, pcmk_rc_str #include // PCMK__XA_LRMD_*, pcmk__xe_is #include "pacemaker-execd.h" // client_disconnect_cleanup static GHashTable *execd_handlers = NULL; static int lrmd_call_id = 0; static xmlNode * handle_ipc_fwd_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; xmlNode *reply = NULL; #ifdef PCMK__COMPILE_REMOTE bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } - ipc_proxy_forward_client(request->ipc_client, request->xml); + rc = ipc_proxy_forward_client(request->ipc_client, request->xml); #else rc = EPROTONOSUPPORT; #endif + if (rc == pcmk_rc_ok) { + /* Coverity gets confused by the #ifdef above and thinks this block + * is unreachable due to rc always being EPROTONOSUPPORT. + */ + // coverity[dead_error_line] + pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); + } else { + pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, + pcmk_rc_str(rc)); + } + pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); /* Create a generic reply since forwarding doesn't create a more specific one */ reply = execd_create_reply(pcmk_rc2legacy(rc), call_id); return reply; } static xmlNode * handle_register_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; xmlNode *reply = NULL; pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_signon(request->ipc_client, request->xml, call_id, &reply); if (rc != pcmk_rc_ok) { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); return NULL; } pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); return reply; } static xmlNode * handle_alert_exec_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_alert_exec(request->ipc_client, request->xml); if (rc == pcmk_rc_ok) { pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); } else { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); } /* Create a generic reply since executing an alert doesn't create a * more specific one. */ reply = execd_create_reply(pcmk_rc2legacy(rc), call_id); return reply; } static xmlNode * handle_check_request(pcmk__request_t *request) { bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *wrapper = NULL; xmlNode *data = NULL; const char *timeout = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } wrapper = pcmk__xe_first_child(request->xml, PCMK__XE_LRMD_CALLDATA, NULL, NULL); data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); if (data == NULL) { pcmk__set_result(&request->result, CRM_EX_SOFTWARE, PCMK_EXEC_INVALID, NULL); return NULL; } timeout = pcmk__xe_get(data, PCMK__XA_LRMD_WATCHDOG); /* FIXME: This just exits on certain conditions, which seems like a pretty * extreme reaction for a daemon to take. */ pcmk__valid_fencing_watchdog_timeout(timeout); pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); return NULL; } static xmlNode * handle_get_recurring_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_get_recurring(request->xml, call_id, &reply); if (rc == pcmk_rc_ok) { pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); } else { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); } return reply; } static xmlNode * handle_poke_request(pcmk__request_t *request) { int call_id = 0; xmlNode *reply = NULL; pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); + pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); + /* Create a generic reply since this doesn't create a more specific one */ reply = execd_create_reply(pcmk_ok, call_id); return reply; } static xmlNode * handle_rsc_cancel_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_rsc_cancel(request->ipc_client, request->xml); if (rc == pcmk_rc_ok) { pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); } else { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); } /* Create a generic reply since canceling a resource doesn't create a * more specific one. */ reply = execd_create_reply(pcmk_rc2legacy(rc), call_id); return reply; } static xmlNode * handle_rsc_exec_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_rsc_exec(request->ipc_client, request->xml); if (rc == pcmk_rc_ok) { pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); /* This looks redundant, but it's unfortunately necessary. The first * argument is set as the PCMK__XA_LRMD_RC attribute in the response. * On the other side of the connection, lrmd_send_command will read * this and use it as its return value, which passes back up to the * public API function lrmd_api_exec. */ reply = execd_create_reply(call_id, call_id); } else { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); reply = execd_create_reply(pcmk_rc2legacy(rc), call_id); } return reply; } static xmlNode * handle_rsc_info_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); /* This returns ENODEV if the resource isn't in the cache which will be * logged as an error. However, this isn't fatal to the client - it may - * querying to see if the resource exists before deciding to register it. + * be querying to see if the resource exists before deciding to register it. + * Thus, we'll ignore an ENODEV to prevent a warning message from being + * logged. */ rc = execd_process_get_rsc_info(request->xml, call_id, &reply); - if (rc == pcmk_rc_ok) { + if ((rc == pcmk_rc_ok) || (rc == ENODEV)) { pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); } else { pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR, pcmk_rc_str(rc)); } return reply; } static xmlNode * handle_rsc_reg_request(pcmk__request_t *request) { int call_id = 0; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); execd_process_rsc_register(request->ipc_client, request->ipc_id, request->xml); /* Create a generic reply since registering a resource doesn't create * a more specific one. */ reply = execd_create_reply(pcmk_ok, call_id); pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); return reply; } static xmlNode * handle_rsc_unreg_request(pcmk__request_t *request) { int call_id = 0; int rc = pcmk_rc_ok; bool allowed = pcmk__is_set(request->ipc_client->flags, pcmk__client_privileged); xmlNode *reply = NULL; if (!allowed) { pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV, PCMK_EXEC_ERROR, NULL); crm_warn("Rejecting IPC request '%s' from unprivileged client %s", request->op, pcmk__client_name(request->ipc_client)); return NULL; } pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id); rc = execd_process_rsc_unregister(request->ipc_client, request->xml); /* Create a generic reply since unregistering a resource doesn't create * a more specific one. */ reply = execd_create_reply(pcmk_rc2legacy(rc), call_id); pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); return reply; } static bool requires_notify(const char *command, int rc) { if (pcmk__str_eq(command, LRMD_OP_RSC_UNREG, pcmk__str_none)) { /* Don't notify about failed unregisters */ return (rc == pcmk_ok) || (rc == -EINPROGRESS); } else { return pcmk__str_any_of(command, LRMD_OP_POKE, LRMD_OP_RSC_REG, NULL); } } static xmlNode * handle_unknown_request(pcmk__request_t *request) { pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, PCMK__XE_NACK, NULL, CRM_EX_PROTOCOL); pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID, "Unknown IPC request type '%s' (bug?)", pcmk__s(request->op, "")); return NULL; } static void execd_register_handlers(void) { pcmk__server_command_t handlers[] = { { CRM_OP_IPC_FWD, handle_ipc_fwd_request }, { CRM_OP_REGISTER, handle_register_request }, { LRMD_OP_ALERT_EXEC, handle_alert_exec_request }, { LRMD_OP_CHECK, handle_check_request }, { LRMD_OP_GET_RECURRING, handle_get_recurring_request }, { LRMD_OP_POKE, handle_poke_request }, { LRMD_OP_RSC_CANCEL, handle_rsc_cancel_request }, { LRMD_OP_RSC_EXEC, handle_rsc_exec_request }, { LRMD_OP_RSC_INFO, handle_rsc_info_request }, { LRMD_OP_RSC_REG, handle_rsc_reg_request }, { LRMD_OP_RSC_UNREG, handle_rsc_unreg_request }, { NULL, handle_unknown_request }, }; execd_handlers = pcmk__register_handlers(handlers); } void execd_unregister_handlers(void) { if (execd_handlers != NULL) { g_hash_table_destroy(execd_handlers); execd_handlers = NULL; } } static int32_t lrmd_ipc_accept(qb_ipcs_connection_t *qbc, uid_t uid, gid_t gid) { crm_trace("Connection %p", qbc); if (pcmk__new_client(qbc, uid, gid) == NULL) { return -ENOMEM; } return 0; } static void lrmd_ipc_created(qb_ipcs_connection_t *qbc) { pcmk__client_t *new_client = pcmk__find_client(qbc); crm_trace("Connection %p", qbc); pcmk__assert(new_client != NULL); /* Now that the connection is offically established, alert * the other clients a new connection exists. */ notify_of_new_client(new_client); } static int32_t lrmd_ipc_dispatch(qb_ipcs_connection_t *qbc, void *data, size_t size) { int rc = pcmk_rc_ok; uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *client = pcmk__find_client(qbc); xmlNode *msg = NULL; CRM_CHECK(client != NULL, crm_err("Invalid client"); return FALSE); CRM_CHECK(client->id != NULL, crm_err("Invalid client: %p", client); return FALSE); rc = pcmk__ipc_msg_append(&client->buffer, data); if (rc == pcmk_rc_ipc_more) { /* We haven't read the complete message yet, so just return. */ return 0; } else if (rc == pcmk_rc_ok) { /* We've read the complete message and there's already a header on * the front. Pass it off for processing. */ msg = pcmk__client_data2xml(client, &id, &flags); g_byte_array_free(client->buffer, TRUE); client->buffer = NULL; } else { /* Some sort of error occurred reassembling the message. All we can * do is clean up, log an error and return. */ crm_err("Error when reading IPC message: %s", pcmk_rc_str(rc)); if (client->buffer != NULL) { g_byte_array_free(client->buffer, TRUE); client->buffer = NULL; } return 0; } CRM_CHECK(flags & crm_ipc_client_response, crm_err("Invalid client request: %p", client); return FALSE); if (!msg) { return 0; } execd_process_message(client, id, flags, msg); pcmk__xml_free(msg); return 0; } static int32_t lrmd_ipc_closed(qb_ipcs_connection_t *qbc) { pcmk__client_t *client = pcmk__find_client(qbc); if (client == NULL) { return 0; } crm_trace("Connection %p", qbc); client_disconnect_cleanup(client->id); #ifdef PCMK__COMPILE_REMOTE ipc_proxy_remove_provider(client); #endif lrmd_client_destroy(client); return 0; } static void lrmd_ipc_destroy(qb_ipcs_connection_t *qbc) { lrmd_ipc_closed(qbc); crm_trace("Connection %p", qbc); } struct qb_ipcs_service_handlers lrmd_ipc_callbacks = { .connection_accept = lrmd_ipc_accept, .connection_created = lrmd_ipc_created, .msg_process = lrmd_ipc_dispatch, .connection_closed = lrmd_ipc_closed, .connection_destroyed = lrmd_ipc_destroy }; static bool invalid_msg(xmlNode *msg) { const char *to = pcmk__xe_get(msg, PCMK__XA_T); /* IPC proxy messages do not get a t="" attribute set on them. */ bool invalid = !pcmk__str_eq(to, CRM_SYSTEM_LRMD, pcmk__str_none) && !pcmk__xe_is(msg, PCMK__XE_LRMD_IPC_PROXY); if (invalid) { crm_info("Ignoring invalid IPC message: to '%s' not " CRM_SYSTEM_LRMD, pcmk__s(to, "")); crm_log_xml_info(msg, "[Invalid]"); } return invalid; } void execd_process_message(pcmk__client_t *c, uint32_t id, uint32_t flags, xmlNode *msg) { int rc = pcmk_rc_ok; if (execd_handlers == NULL) { execd_register_handlers(); } if (!c->name) { c->name = pcmk__xe_get_copy(msg, PCMK__XA_LRMD_CLIENTNAME); } lrmd_call_id++; if (lrmd_call_id < 1) { lrmd_call_id = 1; } pcmk__xe_set(msg, PCMK__XA_LRMD_CLIENTID, c->id); pcmk__xe_set(msg, PCMK__XA_LRMD_CLIENTNAME, c->name); pcmk__xe_set_int(msg, PCMK__XA_LRMD_CALLID, lrmd_call_id); if (invalid_msg(msg)) { pcmk__ipc_send_ack(c, id, flags, PCMK__XE_NACK, NULL, CRM_EX_PROTOCOL); } else { char *log_msg = NULL; const char *reason = NULL; const char *status_s = NULL; xmlNode *reply = NULL; pcmk__request_t request = { .ipc_client = c, .ipc_id = id, .ipc_flags = flags, .peer = NULL, .xml = msg, .call_options = 0, .result = PCMK__UNKNOWN_RESULT, }; request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_LRMD_OP); CRM_CHECK(request.op != NULL, return); crm_trace("Processing %s operation from %s", request.op, c->id); reply = pcmk__process_request(&request, execd_handlers); if (reply != NULL) { int reply_rc = pcmk_ok; rc = lrmd_server_send_reply(c, id, reply); if (rc != pcmk_rc_ok) { crm_warn("Reply to client %s failed: %s " QB_XS " rc=%d", pcmk__client_name(c), pcmk_rc_str(rc), rc); } pcmk__xe_get_int(reply, PCMK__XA_LRMD_RC, &reply_rc); if (requires_notify(request.op, reply_rc)) { execd_send_generic_notify(reply_rc, request.xml); } pcmk__xml_free(reply); } reason = request.result.exit_reason; status_s = pcmk_exec_status_str(request.result.execution_status); log_msg = pcmk__assert_asprintf("Processed %s request from %s %s: " "%s%s%s%s", request.op, pcmk__request_origin_type(&request), pcmk__request_origin(&request), status_s, ((reason == NULL)? "" : " ("), ((reason == NULL)? "" : reason), ((reason == NULL)? "" : ")")); if (!pcmk__result_ok(&request.result)) { crm_warn("%s", log_msg); } else { crm_debug("%s", log_msg); } free(log_msg); pcmk__reset_request(&request); } } diff --git a/daemons/execd/pacemaker-execd.h b/daemons/execd/pacemaker-execd.h index e0ee477db8..4e92f99ebe 100644 --- a/daemons/execd/pacemaker-execd.h +++ b/daemons/execd/pacemaker-execd.h @@ -1,121 +1,121 @@ /* * Copyright 2012-2025 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_EXECD__H # define PACEMAKER_EXECD__H # include # include # include # include # include extern GHashTable *rsc_list; extern time_t start_time; extern struct qb_ipcs_service_handlers lrmd_ipc_callbacks; typedef struct lrmd_rsc_s { char *rsc_id; char *class; char *provider; char *type; int call_opts; /* NEVER dereference this pointer, * It simply exists as a switch to let us know * when the currently active operation has completed */ void *active; /* Operations in this list * have not been executed yet. */ GList *pending_ops; /* Operations in this list are recurring operations * that have been handed off from the pending ops list. */ GList *recurring_ops; /* If this resource is a fence device, probes are handled internally by the * executor, and this value indicates the result that should currently be * returned for probes. It should be one of: * PCMK_EXEC_DONE (to indicate "running"), * PCMK_EXEC_NO_FENCE_DEVICE ("not running"), or * PCMK_EXEC_NOT_CONNECTED ("unknown because fencer connection was lost"). */ pcmk__action_result_t fence_probe_result; crm_trigger_t *work; } lrmd_rsc_t; // in remoted_tls.c int lrmd_init_remote_tls_server(void); void execd_stop_tls_server(void); int lrmd_server_send_reply(pcmk__client_t *client, uint32_t id, xmlNode *reply); int lrmd_server_send_notify(pcmk__client_t *client, xmlNode *msg); void notify_of_new_client(pcmk__client_t *new_client); void execd_free_rsc(gpointer data); void handle_shutdown_ack(void); void handle_shutdown_nack(void); void lrmd_client_destroy(pcmk__client_t *client); void client_disconnect_cleanup(const char *client_id); /*! * \internal * \brief Don't worry about freeing this connection. It is * taken care of after mainloop exits by the main() function. */ stonith_t *execd_get_fencer_connection(void); void execd_fencer_connection_failed(void); #ifdef PCMK__COMPILE_REMOTE void ipc_proxy_init(void); void ipc_proxy_cleanup(void); void ipc_proxy_add_provider(pcmk__client_t *client); void ipc_proxy_remove_provider(pcmk__client_t *client); -void ipc_proxy_forward_client(pcmk__client_t *client, xmlNode *xml); +int ipc_proxy_forward_client(pcmk__client_t *client, xmlNode *xml); pcmk__client_t *ipc_proxy_get_provider(void); int ipc_proxy_shutdown_req(pcmk__client_t *ipc_proxy); void remoted_spawn_pidone(int argc, char **argv); void remoted_request_cib_schema_files(void); #endif void execd_unregister_handlers(void); void lrmd_drain_alerts(GMainLoop *mloop); void execd_process_message(pcmk__client_t *c, uint32_t id, uint32_t flags, xmlNode *msg); xmlNode *execd_create_reply_as(const char *origin, int rc, int call_id); void execd_send_generic_notify(int rc, xmlNode *request); #define execd_create_reply(rc, call_id) \ execd_create_reply_as(__func__, (rc), (call_id)) int execd_process_alert_exec(pcmk__client_t *client, xmlNode *request); int execd_process_get_recurring(xmlNode *request, int call_id, xmlNode **reply); int execd_process_get_rsc_info(xmlNode *request, int call_id, xmlNode **reply); int execd_process_rsc_cancel(pcmk__client_t *client, xmlNode *request); int execd_process_rsc_exec(pcmk__client_t *client, xmlNode *request); void execd_process_rsc_register(pcmk__client_t *client, uint32_t id, xmlNode *request); int execd_process_rsc_unregister(pcmk__client_t *client, xmlNode *request); int execd_process_signon(pcmk__client_t *client, xmlNode *request, int call_id, xmlNode **reply); #endif // PACEMAKER_EXECD__H diff --git a/daemons/execd/remoted_proxy.c b/daemons/execd/remoted_proxy.c index 290dad8686..4e74a5ae51 100644 --- a/daemons/execd/remoted_proxy.c +++ b/daemons/execd/remoted_proxy.c @@ -1,511 +1,513 @@ /* * Copyright 2012-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include "pacemaker-execd.h" #include #include #include #include #include #include #include #include static qb_ipcs_service_t *cib_ro = NULL; static qb_ipcs_service_t *cib_rw = NULL; static qb_ipcs_service_t *cib_shm = NULL; static qb_ipcs_service_t *attrd_ipcs = NULL; static qb_ipcs_service_t *crmd_ipcs = NULL; static qb_ipcs_service_t *fencer_ipcs = NULL; static qb_ipcs_service_t *pacemakerd_ipcs = NULL; // An IPC provider is a cluster node controller connecting as a client static GList *ipc_providers = NULL; /* ipc clients == things like cibadmin, crm_resource, connecting locally * * @TODO This should be unnecessary (pcmk__foreach_ipc_client() should be * sufficient) */ static GHashTable *ipc_clients = NULL; /*! * \internal * \brief Get an IPC proxy provider * * \return Pointer to a provider if one exists, NULL otherwise * * \note Grab the first provider, which is the most recent connection. That way, * if we haven't yet timed out an old, failed connection, we don't try to * use it. */ pcmk__client_t * ipc_proxy_get_provider(void) { return ipc_providers? (pcmk__client_t *) (ipc_providers->data) : NULL; } /*! * \internal * \brief Accept a client connection on a proxy IPC server * * \param[in] c Client's IPC connection * \param[in] uid Client's user ID * \param[in] gid Client's group ID * \param[in] ipc_channel Name of IPC server to proxy * * \return pcmk_ok on success, -errno on error */ static int32_t ipc_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid, const char *ipc_channel) { pcmk__client_t *client; pcmk__client_t *ipc_proxy = ipc_proxy_get_provider(); xmlNode *msg; if (ipc_proxy == NULL) { crm_warn("Cannot proxy IPC connection from uid %d gid %d to %s " "because not connected to cluster", uid, gid, ipc_channel); return -EREMOTEIO; } /* This new client is a local IPC client on a Pacemaker Remote controlled * node, needing to access cluster node IPC services. */ client = pcmk__new_client(c, uid, gid); if (client == NULL) { return -ENOMEM; } /* This ipc client is bound to a single ipc provider. If the * provider goes away, this client is disconnected */ client->userdata = pcmk__str_copy(ipc_proxy->id); client->name = pcmk__assert_asprintf("proxy-%s-%d-%.8s", ipc_channel, client->pid, client->id); /* Allow remote executor to distinguish between proxied local clients and * actual executor API clients */ pcmk__set_client_flags(client, pcmk__client_to_proxy); g_hash_table_insert(ipc_clients, client->id, client); msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_NEW); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SERVER, ipc_channel); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, client->id); lrmd_server_send_notify(ipc_proxy, msg); pcmk__xml_free(msg); crm_debug("Accepted IPC proxy connection (session ID %s) " "from uid %d gid %d on channel %s", client->id, uid, gid, ipc_channel); return 0; } static int32_t crmd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { return ipc_proxy_accept(c, uid, gid, CRM_SYSTEM_CRMD); } static int32_t attrd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { return ipc_proxy_accept(c, uid, gid, PCMK__VALUE_ATTRD); } static int32_t fencer_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { return ipc_proxy_accept(c, uid, gid, "stonith-ng"); } static int32_t pacemakerd_proxy_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { return -EREMOTEIO; } static int32_t cib_proxy_accept_rw(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RW); } static int32_t cib_proxy_accept_ro(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RO); } -void +int ipc_proxy_forward_client(pcmk__client_t *ipc_proxy, xmlNode *xml) { const char *session = pcmk__xe_get(xml, PCMK__XA_LRMD_IPC_SESSION); const char *msg_type = pcmk__xe_get(xml, PCMK__XA_LRMD_IPC_OP); xmlNode *wrapper = pcmk__xe_first_child(xml, PCMK__XE_LRMD_IPC_MSG, NULL, NULL); xmlNode *msg = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); pcmk__client_t *ipc_client; int rc = pcmk_rc_ok; if (pcmk__str_eq(msg_type, LRMD_IPC_OP_SHUTDOWN_ACK, pcmk__str_casei)) { handle_shutdown_ack(); - return; + return rc; } if (pcmk__str_eq(msg_type, LRMD_IPC_OP_SHUTDOWN_NACK, pcmk__str_casei)) { handle_shutdown_nack(); - return; + return rc; } ipc_client = pcmk__find_client_by_id(session); if (ipc_client == NULL) { xmlNode *msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_DESTROY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, session); lrmd_server_send_notify(ipc_proxy, msg); pcmk__xml_free(msg); - return; + return rc; } /* This is an event or response from the ipc provider * going to the local ipc client. * * Looking at the chain of events. * * -----remote node----------------|---- cluster node ------ * ipc_client <--1--> this code * <--2--> pacemaker-controld:remote_proxy_cb/remote_proxy_relay_event() * <--3--> ipc server * * This function is receiving a msg from connection 2 * and forwarding it to connection 1. */ if (pcmk__str_eq(msg_type, LRMD_IPC_OP_EVENT, pcmk__str_casei)) { crm_trace("Sending event to %s", ipc_client->id); rc = pcmk__ipc_send_xml(ipc_client, 0, msg, crm_ipc_server_event); } else if (pcmk__str_eq(msg_type, LRMD_IPC_OP_RESPONSE, pcmk__str_casei)) { int msg_id = 0; pcmk__xe_get_int(xml, PCMK__XA_LRMD_IPC_MSG_ID, &msg_id); crm_trace("Sending response to %d - %s", ipc_client->request_id, ipc_client->id); rc = pcmk__ipc_send_xml(ipc_client, msg_id, msg, crm_ipc_flags_none); CRM_LOG_ASSERT(msg_id == ipc_client->request_id); ipc_client->request_id = 0; } else if (pcmk__str_eq(msg_type, LRMD_IPC_OP_DESTROY, pcmk__str_casei)) { qb_ipcs_disconnect(ipc_client->ipcs); } else { crm_err("Unknown ipc proxy msg type %s" , msg_type); } if (rc != pcmk_rc_ok) { crm_warn("Could not proxy IPC to client %s: %s " QB_XS " rc=%d", ipc_client->id, pcmk_rc_str(rc), rc); } + + return rc; } static int32_t ipc_proxy_dispatch(qb_ipcs_connection_t * c, void *data, size_t size) { int rc = pcmk_rc_ok; uint32_t id = 0; uint32_t flags = 0; pcmk__client_t *client = pcmk__find_client(c); pcmk__client_t *ipc_proxy = pcmk__find_client_by_id(client->userdata); xmlNode *wrapper = NULL; xmlNode *request = NULL; xmlNode *msg = NULL; if (!ipc_proxy) { qb_ipcs_disconnect(client->ipcs); return 0; } /* This is a request from the local ipc client going * to the ipc provider. * * Looking at the chain of events. * * -----remote node----------------|---- cluster node ------ * ipc_client <--1--> this code * <--2--> pacemaker-controld:remote_proxy_dispatch_internal() * <--3--> ipc server * * This function is receiving a request from connection * 1 and forwarding it to connection 2. */ rc = pcmk__ipc_msg_append(&client->buffer, data); if (rc == pcmk_rc_ipc_more) { /* We haven't read the complete message yet, so just return. */ return 0; } else if (rc == pcmk_rc_ok) { /* We've read the complete message and there's already a header on * the front. Pass it off for processing. */ request = pcmk__client_data2xml(client, &id, &flags); g_byte_array_free(client->buffer, TRUE); client->buffer = NULL; } else { /* Some sort of error occurred reassembling the message. All we can * do is clean up, log an error and return. */ crm_err("Error when reading IPC message: %s", pcmk_rc_str(rc)); if (client->buffer != NULL) { g_byte_array_free(client->buffer, TRUE); client->buffer = NULL; } return 0; } if (!request) { return 0; } CRM_CHECK(client != NULL, crm_err("Invalid client"); pcmk__xml_free(request); return FALSE); CRM_CHECK(client->id != NULL, crm_err("Invalid client: %p", client); pcmk__xml_free(request); return FALSE); /* This ensures that synced request/responses happen over the event channel * in the controller, allowing the controller to process the messages async. */ pcmk__set_ipc_flags(flags, pcmk__client_name(client), crm_ipc_proxied); client->request_id = id; msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_REQUEST); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, client->id); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_CLIENT, pcmk__client_name(client)); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_USER, client->user); pcmk__xe_set_int(msg, PCMK__XA_LRMD_IPC_MSG_ID, id); // @TODO Use different setter for uint32_t pcmk__xe_set_int(msg, PCMK__XA_LRMD_IPC_MSG_FLAGS, flags); wrapper = pcmk__xe_create(msg, PCMK__XE_LRMD_IPC_MSG); pcmk__xml_copy(wrapper, request); lrmd_server_send_notify(ipc_proxy, msg); pcmk__xml_free(request); pcmk__xml_free(msg); return 0; } /*! * \internal * \brief Notify a proxy provider that we wish to shut down * * \param[in,out] ipc_proxy IPC client connection to proxy provider * * \return 0 on success, -1 on error */ int ipc_proxy_shutdown_req(pcmk__client_t *ipc_proxy) { xmlNode *msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY); int rc; pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_SHUTDOWN_REQ); /* We don't really have a session, but the controller needs this attribute * to recognize this as proxy communication. */ pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, "0"); rc = (lrmd_server_send_notify(ipc_proxy, msg) != pcmk_rc_ok)? -1 : 0; pcmk__xml_free(msg); return rc; } static int32_t ipc_proxy_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); pcmk__client_t *ipc_proxy; if (client == NULL) { return 0; } ipc_proxy = pcmk__find_client_by_id(client->userdata); crm_trace("Connection %p", c); if (ipc_proxy) { xmlNode *msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_DESTROY); pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, client->id); lrmd_server_send_notify(ipc_proxy, msg); pcmk__xml_free(msg); } g_hash_table_remove(ipc_clients, client->id); free(client->userdata); client->userdata = NULL; pcmk__free_client(client); return 0; } static void ipc_proxy_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); ipc_proxy_closed(c); } static struct qb_ipcs_service_handlers crmd_proxy_callbacks = { .connection_accept = crmd_proxy_accept, .connection_created = NULL, .msg_process = ipc_proxy_dispatch, .connection_closed = ipc_proxy_closed, .connection_destroyed = ipc_proxy_destroy }; static struct qb_ipcs_service_handlers attrd_proxy_callbacks = { .connection_accept = attrd_proxy_accept, .connection_created = NULL, .msg_process = ipc_proxy_dispatch, .connection_closed = ipc_proxy_closed, .connection_destroyed = ipc_proxy_destroy }; static struct qb_ipcs_service_handlers fencer_proxy_callbacks = { .connection_accept = fencer_proxy_accept, .connection_created = NULL, .msg_process = ipc_proxy_dispatch, .connection_closed = ipc_proxy_closed, .connection_destroyed = ipc_proxy_destroy }; static struct qb_ipcs_service_handlers pacemakerd_proxy_callbacks = { .connection_accept = pacemakerd_proxy_accept, .connection_created = NULL, .msg_process = NULL, .connection_closed = NULL, .connection_destroyed = NULL }; static struct qb_ipcs_service_handlers cib_proxy_callbacks_ro = { .connection_accept = cib_proxy_accept_ro, .connection_created = NULL, .msg_process = ipc_proxy_dispatch, .connection_closed = ipc_proxy_closed, .connection_destroyed = ipc_proxy_destroy }; static struct qb_ipcs_service_handlers cib_proxy_callbacks_rw = { .connection_accept = cib_proxy_accept_rw, .connection_created = NULL, .msg_process = ipc_proxy_dispatch, .connection_closed = ipc_proxy_closed, .connection_destroyed = ipc_proxy_destroy }; void ipc_proxy_add_provider(pcmk__client_t *ipc_proxy) { // Prepending ensures the most recent connection is always first ipc_providers = g_list_prepend(ipc_providers, ipc_proxy); } void ipc_proxy_remove_provider(pcmk__client_t *ipc_proxy) { GHashTableIter iter; pcmk__client_t *ipc_client = NULL; char *key = NULL; GList *remove_these = NULL; GList *gIter = NULL; ipc_providers = g_list_remove(ipc_providers, ipc_proxy); g_hash_table_iter_init(&iter, ipc_clients); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & ipc_client)) { const char *proxy_id = ipc_client->userdata; if (pcmk__str_eq(proxy_id, ipc_proxy->id, pcmk__str_casei)) { crm_info("ipc proxy connection for client %s pid %d destroyed because cluster node disconnected.", ipc_client->id, ipc_client->pid); /* we can't remove during the iteration, so copy items * to a list we can destroy later */ remove_these = g_list_append(remove_these, ipc_client); } } for (gIter = remove_these; gIter != NULL; gIter = gIter->next) { ipc_client = gIter->data; // Disconnection callback will free the client here qb_ipcs_disconnect(ipc_client->ipcs); } /* just frees the list, not the elements in the list */ g_list_free(remove_these); } void ipc_proxy_init(void) { ipc_clients = pcmk__strkey_table(NULL, NULL); pcmk__serve_based_ipc(&cib_ro, &cib_rw, &cib_shm, &cib_proxy_callbacks_ro, &cib_proxy_callbacks_rw); pcmk__serve_attrd_ipc(&attrd_ipcs, &attrd_proxy_callbacks); pcmk__serve_fenced_ipc(&fencer_ipcs, &fencer_proxy_callbacks); pcmk__serve_pacemakerd_ipc(&pacemakerd_ipcs, &pacemakerd_proxy_callbacks); crmd_ipcs = pcmk__serve_controld_ipc(&crmd_proxy_callbacks); if (crmd_ipcs == NULL) { crm_err("Failed to create controller: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled"); crm_exit(CRM_EX_FATAL); } } void ipc_proxy_cleanup(void) { if (ipc_providers) { g_list_free(ipc_providers); ipc_providers = NULL; } if (ipc_clients) { g_hash_table_destroy(ipc_clients); ipc_clients = NULL; } pcmk__stop_based_ipc(cib_ro, cib_rw, cib_shm); qb_ipcs_destroy(attrd_ipcs); qb_ipcs_destroy(fencer_ipcs); qb_ipcs_destroy(pacemakerd_ipcs); qb_ipcs_destroy(crmd_ipcs); cib_ro = NULL; cib_rw = NULL; cib_shm = NULL; }