diff --git a/daemons/execd/execd_messages.c b/daemons/execd/execd_messages.c index c668fbefe8..b3c9d5d130 100644 --- a/daemons/execd/execd_messages.c +++ b/daemons/execd/execd_messages.c @@ -1,161 +1,270 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include "pacemaker-execd.h" extern int lrmd_call_id; +static GHashTable *execd_handlers = NULL; + +static void +execd_register_handlers(void) +{ + pcmk__server_command_t handlers[] = { + { NULL, NULL }, + }; + + execd_handlers = pcmk__register_handlers(handlers); +} + static int32_t lrmd_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (pcmk__new_client(c, uid, gid) == NULL) { return -ENOMEM; } return 0; } static void lrmd_ipc_created(qb_ipcs_connection_t * c) { pcmk__client_t *new_client = pcmk__find_client(c); crm_trace("Connection %p", c); 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 *c = pcmk__find_client(qbc); xmlNode *msg = NULL; CRM_CHECK(c != NULL, crm_err("Invalid client"); return FALSE); CRM_CHECK(c->id != NULL, crm_err("Invalid client: %p", c); return FALSE); rc = pcmk__ipc_msg_append(&c->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(c, &id, &flags); g_byte_array_free(c->buffer, TRUE); c->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 (c->buffer != NULL) { g_byte_array_free(c->buffer, TRUE); c->buffer = NULL; } return 0; } CRM_CHECK(flags & crm_ipc_client_response, crm_err("Invalid client request: %p", c); return FALSE); if (!msg) { return 0; } /* @TODO functionize some of this to reduce duplication with * lrmd_remote_client_msg() */ if (!c->name) { const char *value = crm_element_value(msg, PCMK__XA_LRMD_CLIENTNAME); if (value == NULL) { c->name = pcmk__itoa(pcmk__client_pid(qbc)); } else { c->name = pcmk__str_copy(value); } } lrmd_call_id++; if (lrmd_call_id < 1) { lrmd_call_id = 1; } crm_xml_add(msg, PCMK__XA_LRMD_CLIENTID, c->id); crm_xml_add(msg, PCMK__XA_LRMD_CLIENTNAME, c->name); crm_xml_add_int(msg, PCMK__XA_LRMD_CALLID, lrmd_call_id); - process_lrmd_message(c, id, msg); + lrmd_process_message(c, id, flags, msg); pcmk__xml_free(msg); return 0; } static int32_t lrmd_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); 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 * c) { lrmd_ipc_closed(c); crm_trace("Connection %p", c); } 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 = crm_element_value(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 +lrmd_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 (invalid_msg(msg)) { + pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, + CRM_EX_INDETERMINATE); + } else { + char *log_msg = NULL; + const char *reason = 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 = crm_element_value_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); + + /* FIXME: THIS IS TEMPORARY + * + * If the above returns NULL (which could be because something bad happened, + * or because a message doesn't send a reply, but is most likely because not + * all messages have been implemented yet), try falling back to the older + * code. This means we don't have to implement everything before testing. + * Memory cleanup isn't important here. This will be removed. + */ + if (reply == NULL) { + crm_trace("Falling back to old function"); + process_lrmd_message(c, id, request.xml); + return; + } + + if (reply != NULL) { + 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__xml_free(reply); + } + + reason = request.result.exit_reason; + + log_msg = crm_strdup_printf("Processed %s request from %s %s: %s%s%s%s", + request.op, pcmk__request_origin_type(&request), + pcmk__request_origin(&request), + pcmk_exec_status_str(request.result.execution_status), + (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 12325a850e..b570b7bc85 100644 --- a/daemons/execd/pacemaker-execd.h +++ b/daemons/execd/pacemaker-execd.h @@ -1,109 +1,112 @@ /* * 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 process_lrmd_message(pcmk__client_t *client, uint32_t id, xmlNode *request); void 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); /*! * \brief Don't worry about freeing this connection. It is * taken care of after mainloop exits by the main() function. */ stonith_t *get_stonith_connection(void); /*! * \brief This is a callback that tells the lrmd * the current stonith connection has gone away. This allows * us to timeout any pending stonith commands */ void stonith_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); 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 int process_lrmd_alert_exec(pcmk__client_t *client, uint32_t id, xmlNode *request); void lrmd_drain_alerts(GMainLoop *mloop); +void lrmd_process_message(pcmk__client_t *c, uint32_t id, uint32_t flags, + xmlNode *msg); + #endif // PACEMAKER_EXECD__H diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c index 13c867f3e6..9166be4e1a 100644 --- a/daemons/execd/remoted_tls.c +++ b/daemons/execd/remoted_tls.c @@ -1,439 +1,439 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include "pacemaker-execd.h" #include #define LRMD_REMOTE_AUTH_TIMEOUT 10000 static pcmk__tls_t *tls = NULL; static int ssock = -1; extern int lrmd_call_id; /*! * \internal * \brief Read (more) TLS handshake data from client * * \param[in,out] client IPC client doing handshake * * \return 0 on success or more data needed, -1 on error */ static int remoted__read_handshake_data(pcmk__client_t *client) { 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; } if (client->remote->auth_timeout) { g_source_remove(client->remote->auth_timeout); } client->remote->auth_timeout = 0; pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); crm_notice("Remote client connection accepted"); /* Now that the handshake is done, see if any client TLS certificate is * close to its expiration date and log if so. If a TLS certificate is not * in use, this function will just return so we don't need to check for the * session type here. */ pcmk__tls_check_cert_expiration(client->remote->tls_session); /* Only a client with access to the TLS key can connect, so we can treat * it as privileged. */ pcmk__set_client_flags(client, pcmk__client_privileged); // Alert other clients of the new connection notify_of_new_client(client); return 0; } static int lrmd_remote_client_msg(gpointer data) { int id = 0; int rc = pcmk_rc_ok; xmlNode *request = NULL; pcmk__client_t *client = data; if (!pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) { return remoted__read_handshake_data(client); } rc = pcmk__remote_ready(client->remote, 0); switch (rc) { case pcmk_rc_ok: break; case ETIME: /* No message available to read */ return 0; default: /* Error */ crm_info("Error polling remote client: %s", pcmk_rc_str(rc)); return -1; } rc = pcmk__read_available_remote_data(client->remote); switch (rc) { case pcmk_rc_ok: break; case EAGAIN: /* We haven't read the whole message yet */ return 0; default: /* Error */ crm_info("Error reading from remote client: %s", pcmk_rc_str(rc)); return -1; } request = pcmk__remote_message_xml(client->remote); if (request == NULL) { return 0; } crm_element_value_int(request, PCMK__XA_LRMD_REMOTE_MSG_ID, &id); crm_trace("Processing remote client request %d", id); if (!client->name) { client->name = crm_element_value_copy(request, PCMK__XA_LRMD_CLIENTNAME); } lrmd_call_id++; if (lrmd_call_id < 1) { lrmd_call_id = 1; } crm_xml_add(request, PCMK__XA_LRMD_CLIENTID, client->id); crm_xml_add(request, PCMK__XA_LRMD_CLIENTNAME, client->name); crm_xml_add_int(request, PCMK__XA_LRMD_CALLID, lrmd_call_id); - process_lrmd_message(client, id, request); + lrmd_process_message(client, id, client->flags, request); pcmk__xml_free(request); return 0; } static void lrmd_remote_client_destroy(gpointer user_data) { pcmk__client_t *client = user_data; if (client == NULL) { return; } crm_notice("Cleaning up after remote client %s disconnected", pcmk__client_name(client)); ipc_proxy_remove_provider(client); /* if this is the last remote connection, stop recurring * operations */ if (pcmk__ipc_client_count() == 1) { client_disconnect_cleanup(NULL); } if (client->remote->tls_session) { int csock = pcmk__tls_get_client_sock(client->remote); gnutls_bye(client->remote->tls_session, GNUTLS_SHUT_RDWR); gnutls_deinit(client->remote->tls_session); client->remote->tls_session = NULL; close(csock); } lrmd_client_destroy(client); return; } static gboolean lrmd_auth_timeout_cb(gpointer data) { pcmk__client_t *client = data; client->remote->auth_timeout = 0; if (pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) { return FALSE; } mainloop_del_fd(client->remote->source); client->remote->source = NULL; crm_err("Remote client authentication timed out"); return FALSE; } // Dispatch callback for remote server socket static int lrmd_remote_listen(gpointer data) { int csock = -1; gnutls_session_t session = NULL; pcmk__client_t *new_client = NULL; // For client socket static struct mainloop_fd_callbacks lrmd_remote_fd_cb = { .dispatch = lrmd_remote_client_msg, .destroy = lrmd_remote_client_destroy, }; CRM_CHECK(ssock >= 0, return TRUE); if (pcmk__accept_remote_connection(ssock, &csock) != pcmk_rc_ok) { return TRUE; } session = pcmk__new_tls_session(tls, csock); if (session == NULL) { close(csock); return TRUE; } new_client = pcmk__new_unauth_client(NULL); new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t)); pcmk__set_client_flags(new_client, pcmk__client_tls); new_client->remote->tls_session = session; // Require the client to authenticate within this time new_client->remote->auth_timeout = pcmk__create_timer(LRMD_REMOTE_AUTH_TIMEOUT, lrmd_auth_timeout_cb, new_client); crm_info("Remote client pending authentication " QB_XS " %p id: %s", new_client, new_client->id); new_client->remote->source = mainloop_add_fd("pacemaker-remote-client", G_PRIORITY_DEFAULT, csock, new_client, &lrmd_remote_fd_cb); return TRUE; } static void tls_server_dropped(gpointer user_data) { crm_notice("TLS server session ended"); return; } // \return 0 on success, -1 on error (gnutls_psk_server_credentials_function) static int lrmd_tls_server_key_cb(gnutls_session_t session, const char *username, gnutls_datum_t * key) { return (lrmd__init_remote_key(key) == pcmk_rc_ok)? 0 : -1; } static int bind_and_listen(struct addrinfo *addr) { int optval; int fd; int rc; char buffer[INET6_ADDRSTRLEN] = { 0, }; pcmk__sockaddr2str(addr->ai_addr, buffer); crm_trace("Attempting to bind to address %s", buffer); fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (fd < 0) { rc = errno; crm_err("Listener socket creation failed: %", pcmk_rc_str(rc)); return -rc; } /* reuse address */ optval = 1; rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if (rc < 0) { rc = errno; crm_err("Local address reuse not allowed on %s: %s", buffer, pcmk_rc_str(rc)); close(fd); return -rc; } if (addr->ai_family == AF_INET6) { optval = 0; rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)); if (rc < 0) { rc = errno; crm_err("Couldn't disable IPV6-only on %s: %s", buffer, pcmk_rc_str(rc)); close(fd); return -rc; } } if (bind(fd, addr->ai_addr, addr->ai_addrlen) != 0) { rc = errno; crm_err("Cannot bind to %s: %s", buffer, pcmk_rc_str(rc)); close(fd); return -rc; } if (listen(fd, 10) == -1) { rc = errno; crm_err("Cannot listen on %s: %s", buffer, pcmk_rc_str(rc)); close(fd); return -rc; } return fd; } static int get_address_info(const char *bind_name, int port, struct addrinfo **res) { int rc = pcmk_rc_ok; char *port_s = pcmk__itoa(port); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; // IPv6 or IPv4 hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; rc = getaddrinfo(bind_name, port_s, &hints, res); rc = pcmk__gaierror2rc(rc); if (rc != pcmk_rc_ok) { crm_err("Unable to get IP address(es) for %s: %s", (bind_name? bind_name : "local node"), pcmk_rc_str(rc)); } free(port_s); return rc; } int lrmd_init_remote_tls_server(void) { int rc = pcmk_rc_ok; int filter; int port = crm_default_remote_port(); struct addrinfo *res = NULL, *iter; const char *bind_name = pcmk__env_option(PCMK__ENV_REMOTE_ADDRESS); bool use_cert = pcmk__x509_enabled(); static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { .dispatch = lrmd_remote_listen, .destroy = tls_server_dropped, }; CRM_CHECK(ssock == -1, return ssock); crm_debug("Starting TLS listener on %s port %d", (bind_name? bind_name : "all addresses on"), port); rc = pcmk__init_tls(&tls, true, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK); if (rc != pcmk_rc_ok) { return -1; } if (!use_cert) { gnutls_datum_t psk_key = { NULL, 0 }; pcmk__tls_add_psk_callback(tls, lrmd_tls_server_key_cb); /* The key callback won't get called until the first client connection * attempt. Do it once here, so we can warn the user at start-up if we can't * read the key. We don't error out, though, because it's fine if the key is * going to be added later. */ if (lrmd__init_remote_key(&psk_key) != pcmk_rc_ok) { crm_warn("A cluster connection will not be possible until the key is available"); } gnutls_free(psk_key.data); } if (get_address_info(bind_name, port, &res) != pcmk_rc_ok) { return -1; } /* Currently we listen on only one address from the resulting list (the * first IPv6 address we can bind to if possible, otherwise the first IPv4 * address we can bind to). When bind_name is NULL, this should be the * respective wildcard address. * * @TODO If there is demand for specifying more than one address, allow * bind_name to be a space-separated list, call getaddrinfo() for each, * and create a socket for each result (set IPV6_V6ONLY on IPv6 sockets * since IPv4 listeners will have their own sockets). */ iter = res; filter = AF_INET6; while (iter) { if (iter->ai_family == filter) { ssock = bind_and_listen(iter); } if (ssock >= 0) { break; } iter = iter->ai_next; if (iter == NULL && filter == AF_INET6) { iter = res; filter = AF_INET; } } if (ssock >= 0) { mainloop_add_fd("pacemaker-remote-server", G_PRIORITY_DEFAULT, ssock, NULL, &remote_listen_fd_callbacks); crm_debug("Started TLS listener on %s port %d", (bind_name? bind_name : "all addresses on"), port); } freeaddrinfo(res); return ssock; } void execd_stop_tls_server(void) { if (tls != NULL) { pcmk__free_tls(tls); tls = NULL; } if (ssock >= 0) { close(ssock); ssock = -1; } }