1    	/*
2    	 * Copyright 2004-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdio.h>
13   	#include <time.h>                       // time()
14   	#include <sys/types.h>
15   	
16   	#include <glib.h>
17   	#include <libxml/tree.h>
18   	
19   	#include <crm/common/xml.h>
20   	
21   	/*!
22   	 * \internal
23   	 * \brief Create message XML (for IPC or the cluster layer)
24   	 *
25   	 * Create standard, generic XML that can be used as a message sent via IPC or
26   	 * the cluster layer. Currently, not all IPC and cluster layer messaging uses
27   	 * this, but it should (eventually, keeping backward compatibility in mind).
28   	 *
29   	 * \param[in] origin            Name of function that called this one (required)
30   	 * \param[in] server            Server whose protocol defines message semantics
31   	 * \param[in] reply_to          If NULL, create message as a request with a
32   	 *                              generated message ID, otherwise create message
33   	 *                              as a reply to this message ID
34   	 * \param[in] sender_system     Sender's subsystem (required; this is an
35   	 *                              arbitrary string that may have meaning between
36   	 *                              the sender and recipient)
37   	 * \param[in] recipient_node    If not NULL, add as message's recipient node
38   	 *                              (NULL typically indicates a broadcast message)
39   	 * \param[in] recipient_system  If not NULL, add as message's recipient
40   	 *                              subsystem (this is an arbitrary string that may
41   	 *                              have meaning between the sender and recipient)
42   	 * \param[in] task              Add as message's task (required)
43   	 * \param[in] data              If not NULL, copy as message's data (callers
44   	 *                              should not add attributes to the returned
45   	 *                              message element, but instead pass any desired
46   	 *                              information here, though this is not always
47   	 *                              honored currently)
48   	 *
49   	 * \return Newly created message XML
50   	 *
51   	 * \note This function should usually not be called directly, but via the
52   	 *       pcmk__new_message() wrapper.
53   	 * \note The caller is responsible for freeing the return value using
54   	 *       \c pcmk__xml_free().
55   	 */
56   	xmlNode *
57   	pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server,
58   	                     const char *reply_to, const char *sender_system,
59   	                     const char *recipient_node, const char *recipient_system,
60   	                     const char *task, xmlNode *data)
61   	{
62   	    static unsigned int message_counter = 0U;
63   	
64   	    xmlNode *message = NULL;
65   	    char *message_id = NULL;
66   	    const char *subtype = PCMK__VALUE_RESPONSE;
67   	
68   	    CRM_CHECK(!pcmk__str_empty(origin)
69   	              && !pcmk__str_empty(sender_system)
70   	              && !pcmk__str_empty(task),
71   	              return NULL);
72   	
73   	    if (reply_to == NULL) {
74   	        subtype = PCMK__VALUE_REQUEST;
75   	        message_id = pcmk__assert_asprintf("%s-%s-%llu-%u", task, sender_system,
76   	                                           (unsigned long long) time(NULL),
77   	                                           message_counter++);
78   	        reply_to = message_id;
79   	    }
80   	
81   	    message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE);
82   	    pcmk__xe_set_props(message,
83   	                       PCMK_XA_ORIGIN, origin,
84   	                       PCMK__XA_T, pcmk__server_message_type(server),
85   	                       PCMK__XA_SUBT, subtype,
86   	                       PCMK_XA_VERSION, CRM_FEATURE_SET,
87   	                       PCMK_XA_REFERENCE, reply_to,
88   	                       PCMK__XA_CRM_SYS_FROM, sender_system,
89   	                       PCMK__XA_CRM_HOST_TO, recipient_node,
90   	                       PCMK__XA_CRM_SYS_TO, recipient_system,
91   	                       PCMK__XA_CRM_TASK, task,
92   	                       NULL);
93   	    if (data != NULL) {
94   	        xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML);
95   	
96   	        pcmk__xml_copy(wrapper, data);
97   	    }
98   	    free(message_id);
99   	    return message;
100  	}
101  	
102  	/*!
103  	 * \internal
104  	 * \brief Create a Pacemaker reply (for IPC or cluster layer)
105  	 *
106  	 * \param[in] origin            Name of function that called this one
107  	 * \param[in] original_request  XML of request being replied to
108  	 * \param[in] data              If not NULL, copy as reply's data (callers
109  	 *                              should not add attributes to the returned
110  	 *                              message element, but instead pass any desired
111  	 *                              information here, though this is not always
112  	 *                              honored currently)
113  	 *
114  	 * \return Newly created reply XML
115  	 *
116  	 * \note This function should not be called directly, but via the
117  	 *       pcmk__new_reply() wrapper.
118  	 * \note The caller is responsible for freeing the return value using
119  	 *       \c pcmk__xml_free().
120  	 */
121  	xmlNode *
122  	pcmk__new_reply_as(const char *origin, const xmlNode *original_request,
123  	                   xmlNode *data)
124  	{
125  	    const char *message_type = pcmk__xe_get(original_request, PCMK__XA_T);
126  	    const char *host_from = pcmk__xe_get(original_request, PCMK__XA_SRC);
127  	    const char *sys_from = pcmk__xe_get(original_request,
128  	                                        PCMK__XA_CRM_SYS_FROM);
129  	    const char *sys_to = pcmk__xe_get(original_request, PCMK__XA_CRM_SYS_TO);
130  	    const char *type = pcmk__xe_get(original_request, PCMK__XA_SUBT);
131  	    const char *operation = pcmk__xe_get(original_request, PCMK__XA_CRM_TASK);
132  	    const char *crm_msg_reference = pcmk__xe_get(original_request,
133  	                                                 PCMK_XA_REFERENCE);
134  	    enum pcmk_ipc_server server = pcmk__parse_server(message_type);
135  	
136  	    if (server == pcmk_ipc_unknown) {
137  	        /* @COMPAT Not all requests currently specify a message type, so use a
138  	         * default that preserves past behavior.
139  	         *
140  	         * @TODO Ensure all requests specify a message type, drop this check
141  	         * after we no longer support rolling upgrades or Pacemaker Remote
142  	         * connections involving versions before that.
143  	         */
144  	        server = pcmk_ipc_controld;
145  	    }
146  	
147  	    if (type == NULL) {
148  	        pcmk__warn("Cannot reply to invalid message: No message type "
149  	                   "specified");
150  	        return NULL;
151  	    }
152  	
153  	    if (strcmp(type, PCMK__VALUE_REQUEST) != 0) {
154  	        /* Replies should only be generated for request messages, but it's possible
155  	         * we expect replies to other messages right now so this can't be enforced.
156  	         */
157  	        pcmk__trace("Creating a reply for a non-request original message");
158  	    }
159  	
160  	    // Since this is a reply, we reverse the sender and recipient info
161  	    return pcmk__new_message_as(origin, server, crm_msg_reference, sys_to,
162  	                                host_from, sys_from, operation, data);
163  	}
164  	
165  	/*!
166  	 * \internal
167  	 * \brief Register handlers for server commands
168  	 *
169  	 * \param[in] handlers  Array of handler functions for supported server commands
170  	 *                      (the final entry must have a NULL command name, and if
171  	 *                      it has a handler it will be used as the default handler
172  	 *                      for unrecognized commands)
173  	 *
174  	 * \return Newly created hash table with commands and handlers
175  	 * \note The caller is responsible for freeing the return value with
176  	 *       g_hash_table_destroy().
177  	 */
178  	GHashTable *
179  	pcmk__register_handlers(const pcmk__server_command_t handlers[])
180  	{
181  	    GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal);
182  	
183  	    if (handlers != NULL) {
184  	        int i;
185  	
186  	        for (i = 0; handlers[i].command != NULL; ++i) {
187  	            g_hash_table_insert(commands, (gpointer) handlers[i].command,
188  	                                handlers[i].handler);
189  	        }
190  	        if (handlers[i].handler != NULL) {
191  	            // g_str_hash() can't handle NULL, so use empty string for default
192  	            g_hash_table_insert(commands, (gpointer) "", handlers[i].handler);
193  	        }
194  	    }
195  	    return commands;
196  	}
197  	
198  	/*!
199  	 * \internal
200  	 * \brief Process an incoming request
201  	 *
202  	 * \param[in,out] request   Request to process
203  	 * \param[in]     handlers  Command table created by pcmk__register_handlers()
204  	 *
205  	 * \return XML to send as reply (or NULL if no reply is needed)
206  	 */
207  	xmlNode *
208  	pcmk__process_request(pcmk__request_t *request, GHashTable *handlers)
209  	{
210  	    xmlNode *(*handler)(pcmk__request_t *request) = NULL;
211  	
212  	    CRM_CHECK((request != NULL) && (request->op != NULL) && (handlers != NULL),
213  	              return NULL);
214  	
215  	    if (pcmk__is_set(request->flags, pcmk__request_sync)
216  	        && (request->ipc_client != NULL)) {
217  	        CRM_CHECK(request->ipc_client->request_id == request->ipc_id,
218  	                  return NULL);
219  	    }
220  	
221  	    handler = g_hash_table_lookup(handlers, request->op);
222  	    if (handler == NULL) {
223  	        handler = g_hash_table_lookup(handlers, ""); // Default handler
224  	        if (handler == NULL) {
225  	            pcmk__info("Ignoring %s request from %s %s with no handler",
226  	                       request->op, pcmk__request_origin_type(request),
227  	                       pcmk__request_origin(request));
228  	            return NULL;
229  	        }
230  	    }
231  	
232  	    return handler(request);
233  	}
234  	
235  	/*!
236  	 * \internal
237  	 * \brief Free memory used within a request (but not the request itself)
238  	 *
239  	 * \param[in,out] request  Request to reset
240  	 */
241  	void
242  	pcmk__reset_request(pcmk__request_t *request)
243  	{
CID (unavailable; MK=6f41f68c1600ff83173a259bf072ebec) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
244  	    g_clear_pointer(&request->op, free);
245  	    pcmk__reset_result(&(request->result));
246  	}
247