diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h index e255c172c4..02eac4f106 100644 --- a/include/crm/common/ipc.h +++ b/include/crm/common/ipc.h @@ -1,233 +1,218 @@ /* * 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_request_adv(const char *task, xmlNode *xml_data, - const char *host_to, const char *sys_to, - const char *sender_system, 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/lib/common/messages.c b/lib/common/messages.c index 328d2b74c3..242ae2796f 100644 --- a/lib/common/messages.c +++ b/lib/common/messages.c @@ -1,276 +1,250 @@ /* * 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] sender_system Sender's subsystem (required; this is an - * arbitrary string that may have meaning between - * the sender and recipient) - * \param[in] origin Name of function that called this one - * - * \return XML of new request - * - * \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 *sender_system, const char *origin) -{ - return pcmk__new_message_as(origin, pcmk_ipc_controld, NULL, sender_system, - host_to, sys_to, task, msg_data); -} - /*! * \internal * \brief Create a Pacemaker reply (for IPC or cluster layer) * * \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 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 * pcmk__new_reply_as(const char *origin, const xmlNode *original_request, xmlNode *data) { const char *message_type = crm_element_value(original_request, PCMK__XA_T); 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); enum pcmk_ipc_server server = pcmk__parse_server(message_type); if (server == pcmk_ipc_unknown) { /* @COMPAT Not all requests currently specify a message type, so use a * default that preserves past behavior. * * @TODO Ensure all requests specify a message type, drop this check * after we no longer support rolling upgrades or Pacemaker Remote * connections involving versions before that. */ server = pcmk_ipc_controld; } if (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, server, crm_msg_reference, 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)); }