diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h index 78760c9b01..5d91a2580f 100644 --- a/include/crm/common/ipc.h +++ b/include/crm/common/ipc.h @@ -1,245 +1,240 @@ /* * Copyright 2004-2024 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 PCMK__CRM_COMMON_IPC__H #define PCMK__CRM_COMMON_IPC__H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief IPC interface to Pacemaker daemons * * \ingroup core */ /* * Message creation utilities * * These are used for both IPC messages and cluster layer messages. However, * since this is public API, they stay in this header for backward * compatibility. */ -// @COMPAT Make internal when we can break API backward compatibility -//! \deprecated Do not use -xmlNode *create_reply_adv(const xmlNode *request, xmlNode *xml_response_data, - const char *origin); - // @COMPAT Make internal when we can break API backward compatibility //! \deprecated Do not use #define create_request(task, xml_data, host_to, sys_to, sys_from, uuid_from) \ create_request_adv(task, xml_data, host_to, sys_to, sys_from, uuid_from, \ __func__) // @COMPAT Make internal when we can break API backward compatibility //! \deprecated Do not use xmlNode *create_request_adv(const char *task, xmlNode *xml_data, const char *host_to, const char *sys_to, const char *sys_from, const char *uuid_from, const char *origin); /* * The library supports two methods of creating IPC connections. The older code * allows connecting to any arbitrary IPC name. The newer code only allows * connecting to one of the Pacemaker daemons. * * As daemons are converted to use the new model, the old functions should be * considered deprecated for use with those daemons. Once all daemons are * converted, the old functions should be officially deprecated as public API * and eventually made internal API. */ /* * Pacemaker daemon IPC */ /* @COMPAT This is also used internally for cluster message types, but it's not * worth the hassle of redefining this public API just to change the name. */ //! Available IPC interfaces enum pcmk_ipc_server { pcmk_ipc_unknown, //!< Unknown or invalid pcmk_ipc_attrd, //!< Attribute manager pcmk_ipc_based, //!< CIB manager pcmk_ipc_controld, //!< Controller pcmk_ipc_execd, //!< Executor pcmk_ipc_fenced, //!< Fencer pcmk_ipc_pacemakerd, //!< Launcher pcmk_ipc_schedulerd, //!< Scheduler }; // NOTE: sbd (as of at least 1.5.2) uses this enum //! Possible event types that an IPC event callback can be called for enum pcmk_ipc_event { pcmk_ipc_event_connect, //!< Result of asynchronous connection attempt // NOTE: sbd (as of at least 1.5.2) uses this value pcmk_ipc_event_disconnect, //!< Termination of IPC connection // NOTE: sbd (as of at least 1.5.2) uses this value pcmk_ipc_event_reply, //!< Daemon's reply to client IPC request pcmk_ipc_event_notify, //!< Notification from daemon }; //! How IPC replies should be dispatched enum pcmk_ipc_dispatch { pcmk_ipc_dispatch_main, //!< Attach IPC to GMainLoop for dispatch pcmk_ipc_dispatch_poll, //!< Caller will poll and dispatch IPC pcmk_ipc_dispatch_sync, //!< Sending a command will wait for any reply }; // NOTE: sbd (as of at least 1.5.2) uses this //! Client connection to Pacemaker IPC typedef struct pcmk_ipc_api_s pcmk_ipc_api_t; /*! * \brief Callback function type for Pacemaker daemon IPC APIs * * \param[in,out] api IPC API connection * \param[in] event_type The type of event that occurred * \param[in] status Event status * \param[in,out] event_data Event-specific data * \param[in,out] user_data Caller data provided when callback was registered * * \note For connection and disconnection events, event_data may be NULL (for * local IPC) or the name of the connected node (for remote IPC, for * daemons that support that). For reply and notify events, event_data is * defined by the specific daemon API. */ typedef void (*pcmk_ipc_callback_t)(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data); // NOTE: sbd (as of at least 1.5.2) uses this int pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server); // NOTE: sbd (as of at least 1.5.2) uses this void pcmk_free_ipc_api(pcmk_ipc_api_t *api); // NOTE: sbd (as of at least 1.5.2) uses this int pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type); void pcmk_disconnect_ipc(pcmk_ipc_api_t *api); int pcmk_poll_ipc(const pcmk_ipc_api_t *api, int timeout_ms); void pcmk_dispatch_ipc(pcmk_ipc_api_t *api); // NOTE: sbd (as of at least 1.5.2) uses this void pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb, void *user_data); const char *pcmk_ipc_name(const pcmk_ipc_api_t *api, bool for_log); bool pcmk_ipc_is_connected(pcmk_ipc_api_t *api); int pcmk_ipc_purge_node(pcmk_ipc_api_t *api, const char *node_name, uint32_t nodeid); /* * Generic IPC API (to eventually be deprecated as public API and made internal) */ /* *INDENT-OFF* */ enum crm_ipc_flags { crm_ipc_flags_none = 0x00000000, crm_ipc_compressed = 0x00000001, /* Message has been compressed */ crm_ipc_proxied = 0x00000100, /* _ALL_ replies to proxied connections need to be sent as events */ crm_ipc_client_response = 0x00000200, /* A Response is expected in reply */ // These are options for Pacemaker's internal use only (pcmk__ipc_send_*()) crm_ipc_server_event = 0x00010000, /* Send an Event instead of a Response */ crm_ipc_server_free = 0x00020000, /* Free the iovec after sending */ crm_ipc_proxied_relay_response = 0x00040000, /* all replies to proxied connections are sent as events, this flag preserves whether the event should be treated as an actual event, or a response.*/ }; /* *INDENT-ON* */ typedef struct crm_ipc_s crm_ipc_t; crm_ipc_t *crm_ipc_new(const char *name, size_t max_size); bool crm_ipc_connect(crm_ipc_t * client); void crm_ipc_close(crm_ipc_t * client); void crm_ipc_destroy(crm_ipc_t * client); void pcmk_free_ipc_event(struct iovec *event); int crm_ipc_send(crm_ipc_t *client, const xmlNode *message, enum crm_ipc_flags flags, int32_t ms_timeout, xmlNode **reply); int crm_ipc_get_fd(crm_ipc_t * client); bool crm_ipc_connected(crm_ipc_t * client); int crm_ipc_ready(crm_ipc_t * client); long crm_ipc_read(crm_ipc_t * client); const char *crm_ipc_buffer(crm_ipc_t * client); uint32_t crm_ipc_buffer_flags(crm_ipc_t * client); const char *crm_ipc_name(crm_ipc_t * client); unsigned int crm_ipc_default_buffer_size(void); /*! * \brief Check the authenticity of the IPC socket peer process (legacy) * * If everything goes well, peer's authenticity is verified by the means * of comparing against provided referential UID and GID (either satisfies), * and the result of this check can be deduced from the return value. * As an exception, detected UID of 0 ("root") satisfies arbitrary * provided referential daemon's credentials. * * \param[in] sock IPC related, connected Unix socket to check peer of * \param[in] refuid referential UID to check against * \param[in] refgid referential GID to check against * \param[out] gotpid to optionally store obtained PID of the peer * (not available on FreeBSD, special value of 1 * used instead, and the caller is required to * special case this value respectively) * \param[out] gotuid to optionally store obtained UID of the peer * \param[out] gotgid to optionally store obtained GID of the peer * * \return 0 if IPC related socket's peer is not authentic given the * referential credentials (see above), 1 if it is, * negative value on error (generally expressing -errno unless * it was zero even on nonhappy path, -pcmk_err_generic is * returned then; no message is directly emitted) * * \note While this function is tolerant on what constitutes authorized * IPC daemon process (its effective user matches UID=0 or \p refuid, * or at least its group matches \p refgid), either or both (in case * of UID=0) mismatches on the expected credentials of such peer * process \e shall be investigated at the caller when value of 1 * gets returned there, since higher-than-expected privileges in * respect to the expected/intended credentials possibly violate * the least privilege principle and may pose an additional risk * (i.e. such accidental inconsistency shall be eventually fixed). */ int crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid, pid_t *gotpid, uid_t *gotuid, gid_t *gotgid); // @COMPAT Make internal when we can break API backward compatibility //! \deprecated Do not use xmlNode *create_hello_message(const char *uuid, const char *client_name, const char *major_version, const char *minor_version); #ifdef __cplusplus } #endif #endif diff --git a/include/crm/common/messages_internal.h b/include/crm/common/messages_internal.h index 648e207453..f80263bfc3 100644 --- a/include/crm/common/messages_internal.h +++ b/include/crm/common/messages_internal.h @@ -1,191 +1,194 @@ /* * Copyright 2018-2024 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 PCMK__CRM_COMMON_MESSAGES_INTERNAL__H #define PCMK__CRM_COMMON_MESSAGES_INTERNAL__H #include // uint32_t #include // xmlNode #include // pcmk__client_t #include // pcmk__action_result_t #include // pcmk__xml_copy() #ifdef __cplusplus extern "C" { #endif enum pcmk__request_flags { pcmk__request_none = UINT32_C(0), /* It would be nice if we could check for synchronous requests generically, * but each daemon uses its own call options, so the daemons are responsible * for setting this flag when appropriate. */ pcmk__request_sync = (UINT32_C(1) << 0), /* Whether reply must use original call options (the library code does not * use this, so it is for internal daemon use) */ pcmk__request_reuse_options = (UINT32_C(1) << 1), }; // Server request (whether from an IPC client or cluster peer) typedef struct { // If request is from an IPC client pcmk__client_t *ipc_client; // IPC client (NULL if not via IPC) uint32_t ipc_id; // IPC message ID uint32_t ipc_flags; // IPC message flags // If message is from a cluster peer const char *peer; // Peer name (NULL if not via cluster) // Common information regardless of origin xmlNode *xml; // Request XML int call_options; // Call options set on request uint32_t flags; // Flag group of pcmk__request_flags pcmk__action_result_t result; // Where to store operation result /* It would be nice if we could pull the IPC command from the XML * generically, but each daemon uses a different XML attribute for it, * so the daemon is responsible for populating this field. * * This must be a copy of the XML field, and not just a pointer into xml, * because handlers might modify the original XML. * * @TODO Create a per-daemon struct with IPC handlers, IPC endpoints, etc., * and the name of the XML attribute for IPC commands, then replace this * with a convenience function to copy the command. */ char *op; // IPC command name } pcmk__request_t; #define pcmk__set_request_flags(request, flags_to_set) do { \ (request)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Request", "message", (request)->flags, \ (flags_to_set), #flags_to_set); \ } while (0) // Type for mapping a server command to a handler typedef struct { const char *command; xmlNode *(*handler)(pcmk__request_t *request); } pcmk__server_command_t; /*! * \internal * \brief Create message XML (for IPC or the cluster layer) * * Create standard, generic XML that can be used as a message sent via IPC or * the cluster layer. Currently, not all IPC and cluster layer messaging uses * this, but it should (eventually, keeping backward compatibility in mind). * * \param[in] server Server whose protocol defines message semantics * \param[in] reply_to If NULL, create message as a request with a * generated message ID, otherwise create message * as a reply to this message ID * \param[in] sender_system Sender's subsystem (required; this is an * arbitrary string that may have meaning between * the sender and recipient) * \param[in] recipient_node If not NULL, add as message's recipient node * (NULL typically indicates a broadcast message) * \param[in] recipient_system If not NULL, add as message's recipient * subsystem (this is an arbitrary string that may * have meaning between the sender and recipient) * \param[in] task Add as message's task (required) * \param[in] data If not NULL, copy as message's data (callers * should not add attributes to the returned * message element, but instead pass any desired * information here, though this is not always * honored currently) * * \return Newly created message XML * \note The caller is responsible for freeing the return value using * \c pcmk__xml_free(). */ #define pcmk__new_message(server, reply_to, sender_system, \ recipient_node, recipient_system, task, data) \ pcmk__new_message_as(__func__, (server), (reply_to), \ (sender_system), (recipient_node), \ (recipient_system), (task), (data)) /*! * \internal * \brief Create a Pacemaker reply (for IPC or cluster layer) * * \param[in] original_request XML of request being replied to * \param[in] data If not NULL, copy as reply's data (callers * should not add attributes to the returned * message element, but instead pass any desired * information here, though this is not always * honored currently) * * \return Newly created reply XML * \note The caller is responsible for freeing the return value using * \c pcmk__xml_free(). */ #define pcmk__new_reply(original_request, data) \ - create_reply_adv((original_request), (data), __func__) + pcmk__new_reply_as(__func__, (original_request), (data)) xmlNode *pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server, const char *reply_to, const char *sender_system, const char *recipient_node, const char *recipient_system, const char *task, xmlNode *data); +xmlNode *pcmk__new_reply_as(const char *origin, const xmlNode *original_request, + xmlNode *data); + GHashTable *pcmk__register_handlers(const pcmk__server_command_t handlers[]); xmlNode *pcmk__process_request(pcmk__request_t *request, GHashTable *handlers); void pcmk__reset_request(pcmk__request_t *request); /*! * \internal * \brief Get a loggable description of a request's origin * * \param[in] request * * \return "peer" if request was via CPG, "client" if via IPC, or "originator" * if unknown */ static inline const char * pcmk__request_origin_type(const pcmk__request_t *request) { if ((request != NULL) && (request->ipc_client != NULL)) { return "client"; } else if ((request != NULL) && (request->peer != NULL)) { return "peer"; } else { return "originator"; } } /*! * \internal * \brief Get a loggable name for a request's origin * * \param[in] request * * \return Peer name if request was via CPG, client name if via IPC, or * "(unspecified)" if unknown */ static inline const char * pcmk__request_origin(const pcmk__request_t *request) { if ((request != NULL) && (request->ipc_client != NULL)) { return pcmk__client_name(request->ipc_client); } else if ((request != NULL) && (request->peer != NULL)) { return request->peer; } else { return "(unspecified)"; } } #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_MESSAGES_INTERNAL__H diff --git a/lib/common/messages.c b/lib/common/messages.c index 1d343ccba2..8415e79a77 100644 --- a/lib/common/messages.c +++ b/lib/common/messages.c @@ -1,276 +1,279 @@ /* * Copyright 2004-2024 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 // time() #include #include #include #include #include /*! * \internal * \brief Create message XML (for IPC or the cluster layer) * * Create standard, generic XML that can be used as a message sent via IPC or * the cluster layer. Currently, not all IPC and cluster layer messaging uses * this, but it should (eventually, keeping backward compatibility in mind). * * \param[in] origin Name of function that called this one (required) * \param[in] server Server whose protocol defines message semantics * \param[in] reply_to If NULL, create message as a request with a * generated message ID, otherwise create message * as a reply to this message ID * \param[in] sender_system Sender's subsystem (required; this is an * arbitrary string that may have meaning between * the sender and recipient) * \param[in] recipient_node If not NULL, add as message's recipient node * (NULL typically indicates a broadcast message) * \param[in] recipient_system If not NULL, add as message's recipient * subsystem (this is an arbitrary string that may * have meaning between the sender and recipient) * \param[in] task Add as message's task (required) * \param[in] data If not NULL, copy as message's data (callers * should not add attributes to the returned * message element, but instead pass any desired * information here, though this is not always * honored currently) * * \return Newly created message XML * * \note This function should usually not be called directly, but via the * pcmk__new_message() wrapper. * \note The caller is responsible for freeing the return value using * \c pcmk__xml_free(). */ xmlNode * pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server, const char *reply_to, const char *sender_system, const char *recipient_node, const char *recipient_system, const char *task, xmlNode *data) { static unsigned int message_counter = 0U; xmlNode *message = NULL; char *message_id = NULL; const char *subtype = PCMK__VALUE_RESPONSE; CRM_CHECK(!pcmk__str_empty(origin) && !pcmk__str_empty(sender_system) && !pcmk__str_empty(task), return NULL); if (reply_to == NULL) { subtype = PCMK__VALUE_REQUEST; message_id = crm_strdup_printf("%s-%s-%llu-%u", task, sender_system, (unsigned long long) time(NULL), message_counter++); reply_to = message_id; } message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE); pcmk__xe_set_props(message, PCMK_XA_ORIGIN, origin, PCMK__XA_T, pcmk__server_message_type(server), PCMK__XA_SUBT, subtype, PCMK_XA_VERSION, CRM_FEATURE_SET, PCMK_XA_REFERENCE, reply_to, PCMK__XA_CRM_SYS_FROM, sender_system, PCMK__XA_CRM_HOST_TO, recipient_node, PCMK__XA_CRM_SYS_TO, recipient_system, PCMK__XA_CRM_TASK, task, NULL); if (data != NULL) { xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML); pcmk__xml_copy(wrapper, data); } free(message_id); return message; } /*! * \brief Create a Pacemaker request (for IPC or cluster layer) * * \param[in] task What to set as the request's task * \param[in] msg_data What to add as the request's data contents * \param[in] host_to What to set as the request's destination host * \param[in] sys_to What to set as the request's destination system * \param[in] sys_from If not NULL, set as request's origin system * \param[in] uuid_from If not NULL, use in request's origin system * \param[in] origin Name of function that called this one * * \return XML of new request * * \note One of sys_from or uuid_from must be non-NULL * \note This function should not be called directly, but via the * create_request() wrapper. * \note The caller is responsible for freeing the return value using * \c pcmk__xml_free(). */ xmlNode * create_request_adv(const char *task, xmlNode *msg_data, const char *host_to, const char *sys_to, const char *sys_from, const char *uuid_from, const char *origin) { char *true_from = NULL; xmlNode *request = NULL; if (uuid_from != NULL) { true_from = crm_strdup_printf("%s_%s", uuid_from, (sys_from? sys_from : "none")); } else if (sys_from != NULL) { true_from = strdup(sys_from); } else { crm_err("Cannot create IPC request: No originating system specified"); } request = pcmk__new_message_as(origin, pcmk_ipc_controld, NULL, true_from, host_to, sys_to, task, msg_data); free(true_from); return request; } /*! + * \internal * \brief Create a Pacemaker reply (for IPC or cluster layer) * - * \param[in] original_request XML of request this is a reply to - * \param[in] xml_response_data XML to copy as data section of reply - * \param[in] origin Name of function that called this one + * \param[in] origin Name of function that called this one + * \param[in] original_request XML of request being replied to + * \param[in] data If not NULL, copy as reply's data (callers + * should not add attributes to the returned + * message element, but instead pass any desired + * information here, though this is not always + * honored currently) * - * \return XML of new reply + * \return Newly created reply XML * * \note This function should not be called directly, but via the * pcmk__new_reply() wrapper. * \note The caller is responsible for freeing the return value using * \c pcmk__xml_free(). */ xmlNode * -create_reply_adv(const xmlNode *original_request, xmlNode *xml_response_data, - const char *origin) +pcmk__new_reply_as(const char *origin, const xmlNode *original_request, + xmlNode *data) { const char *host_from = crm_element_value(original_request, PCMK__XA_SRC); const char *sys_from = crm_element_value(original_request, PCMK__XA_CRM_SYS_FROM); const char *sys_to = crm_element_value(original_request, PCMK__XA_CRM_SYS_TO); const char *type = crm_element_value(original_request, PCMK__XA_SUBT); const char *operation = crm_element_value(original_request, PCMK__XA_CRM_TASK); const char *crm_msg_reference = crm_element_value(original_request, PCMK_XA_REFERENCE); if (type == NULL) { - crm_err("Cannot create new_message, no message type in original message"); - CRM_ASSERT(type != NULL); + crm_warn("Cannot reply to invalid message: No message type specified"); return NULL; } if (strcmp(type, PCMK__VALUE_REQUEST) != 0) { /* Replies should only be generated for request messages, but it's possible * we expect replies to other messages right now so this can't be enforced. */ crm_trace("Creating a reply for a non-request original message"); } // Since this is a reply, we reverse the sender and recipient info return pcmk__new_message_as(origin, pcmk_ipc_controld, crm_msg_reference, - sys_to, host_from, sys_from, operation, - xml_response_data); + sys_to, host_from, sys_from, operation, data); } /*! * \internal * \brief Register handlers for server commands * * \param[in] handlers Array of handler functions for supported server commands * (the final entry must have a NULL command name, and if * it has a handler it will be used as the default handler * for unrecognized commands) * * \return Newly created hash table with commands and handlers * \note The caller is responsible for freeing the return value with * g_hash_table_destroy(). */ GHashTable * pcmk__register_handlers(const pcmk__server_command_t handlers[]) { GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal); if (handlers != NULL) { int i; for (i = 0; handlers[i].command != NULL; ++i) { g_hash_table_insert(commands, (gpointer) handlers[i].command, handlers[i].handler); } if (handlers[i].handler != NULL) { // g_str_hash() can't handle NULL, so use empty string for default g_hash_table_insert(commands, (gpointer) "", handlers[i].handler); } } return commands; } /*! * \internal * \brief Process an incoming request * * \param[in,out] request Request to process * \param[in] handlers Command table created by pcmk__register_handlers() * * \return XML to send as reply (or NULL if no reply is needed) */ xmlNode * pcmk__process_request(pcmk__request_t *request, GHashTable *handlers) { xmlNode *(*handler)(pcmk__request_t *request) = NULL; CRM_CHECK((request != NULL) && (request->op != NULL) && (handlers != NULL), return NULL); if (pcmk_is_set(request->flags, pcmk__request_sync) && (request->ipc_client != NULL)) { CRM_CHECK(request->ipc_client->request_id == request->ipc_id, return NULL); } handler = g_hash_table_lookup(handlers, request->op); if (handler == NULL) { handler = g_hash_table_lookup(handlers, ""); // Default handler if (handler == NULL) { crm_info("Ignoring %s request from %s %s with no handler", request->op, pcmk__request_origin_type(request), pcmk__request_origin(request)); return NULL; } } return (*handler)(request); } /*! * \internal * \brief Free memory used within a request (but not the request itself) * * \param[in,out] request Request to reset */ void pcmk__reset_request(pcmk__request_t *request) { free(request->op); request->op = NULL; pcmk__reset_result(&(request->result)); }