1    	/*
2    	 * Copyright 2012-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 <errno.h>                          // EINPROGRESS, ENODEV
13   	#include <stdbool.h>                        // bool
14   	#include <stddef.h>                         // NULL
15   	#include <stdlib.h>                         // free
16   	
17   	#include <glib.h>                           // g_hash_table_destroy
18   	#include <libxml/parser.h>                  // xmlNode
19   	#include <qb/qblog.h>                       // QB_XS
20   	
21   	#include <crm/crm.h>                        // CRM_OP_*, CRM_SYSTEM_LRMD
22   	#include <crm/common/internal.h>            // pcmk__process_request, pcmk__xml_free
23   	#include <crm/common/results.h>             // pcmk_exec_status, pcmk_rc_*, pcmk_rc_str
24   	#include <crm/lrmd.h>                       // LRMD_OP_*
25   	
26   	#include "pacemaker-execd.h"                // execd_*
27   	
28   	
29   	static GHashTable *execd_handlers = NULL;
30   	static int lrmd_call_id = 0;
31   	
32   	static xmlNode *
33   	handle_ipc_fwd_request(pcmk__request_t *request)
34   	{
35   	    int call_id = 0;
36   	    int rc = pcmk_rc_ok;
37   	    xmlNode *reply = NULL;
38   	
39   	#ifdef PCMK__COMPILE_REMOTE
40   	    bool allowed = pcmk__is_set(request->ipc_client->flags,
41   	                                pcmk__client_privileged);
42   	
43   	    if (!allowed) {
44   	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
45   	                         PCMK_EXEC_ERROR, NULL);
46   	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
47   	                   request->op, pcmk__client_name(request->ipc_client));
48   	        return NULL;
49   	    }
50   	
51   	    rc = ipc_proxy_forward_client(request->ipc_client, request->xml);
52   	#else
53   	    rc = EPROTONOSUPPORT;
54   	#endif
55   	
56   	    if (rc == pcmk_rc_ok) {
57   	        /* Coverity gets confused by the #ifdef above and thinks this block
58   	         * is unreachable due to rc always being EPROTONOSUPPORT.
59   	         */
60   	        // coverity[dead_error_line]
61   	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
62   	    } else {
63   	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
64   	                         pcmk_rc_str(rc));
65   	    }
66   	
67   	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
68   	
69   	    /* Create a generic reply since forwarding doesn't create a more specific one */
70   	    reply = execd_create_reply(pcmk_rc2legacy(rc), call_id);
71   	    return reply;
72   	}
73   	
74   	static xmlNode *
75   	handle_register_request(pcmk__request_t *request)
76   	{
77   	    int call_id = 0;
78   	    int rc = pcmk_rc_ok;
79   	    xmlNode *reply = NULL;
80   	
81   	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
82   	    rc = execd_process_signon(request->ipc_client, request->xml, call_id, &reply);
83   	
84   	    if (rc != pcmk_rc_ok) {
85   	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
86   	                         pcmk_rc_str(rc));
87   	        return NULL;
88   	    }
89   	
90   	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
91   	    return reply;
92   	}
93   	
94   	static xmlNode *
95   	handle_alert_exec_request(pcmk__request_t *request)
96   	{
97   	    int call_id = 0;
98   	    int rc = pcmk_rc_ok;
99   	    bool allowed = pcmk__is_set(request->ipc_client->flags,
100  	                                pcmk__client_privileged);
101  	    xmlNode *reply = NULL;
102  	
103  	    if (!allowed) {
104  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
105  	                         PCMK_EXEC_ERROR, NULL);
106  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
107  	                   request->op, pcmk__client_name(request->ipc_client));
108  	        return NULL;
109  	    }
110  	
111  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
112  	
113  	    rc = execd_process_alert_exec(request->ipc_client, request->xml);
114  	
115  	    if (rc == pcmk_rc_ok) {
116  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
117  	    } else {
118  	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
119  	                         pcmk_rc_str(rc));
120  	    }
121  	
122  	    /* Create a generic reply since executing an alert doesn't create a
123  	     * more specific one.
124  	     */
125  	    reply = execd_create_reply(pcmk_rc2legacy(rc), call_id);
126  	    return reply;
127  	}
128  	
129  	static xmlNode *
130  	handle_check_request(pcmk__request_t *request)
131  	{
132  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
133  	                                pcmk__client_privileged);
134  	    xmlNode *wrapper = NULL;
135  	    xmlNode *data = NULL;
136  	    const char *timeout = NULL;
137  	
138  	    if (!allowed) {
139  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
140  	                         PCMK_EXEC_ERROR, NULL);
141  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
142  	                   request->op, pcmk__client_name(request->ipc_client));
143  	        return NULL;
144  	    }
145  	
146  	    wrapper = pcmk__xe_first_child(request->xml,
147  	                                   PCMK__XE_LRMD_CALLDATA,
148  	                                   NULL, NULL);
149  	    data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
150  	
151  	    if (data == NULL) {
152  	        pcmk__set_result(&request->result, CRM_EX_SOFTWARE, PCMK_EXEC_INVALID,
153  	                         NULL);
154  	        return NULL;
155  	    }
156  	
157  	    timeout = pcmk__xe_get(data, PCMK__XA_LRMD_WATCHDOG);
158  	    /* FIXME: This just exits on certain conditions, which seems like a pretty
159  	     * extreme reaction for a daemon to take.
160  	     */
161  	    pcmk__valid_fencing_watchdog_timeout(timeout);
162  	
163  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
164  	    return NULL;
165  	}
166  	
167  	static xmlNode *
168  	handle_get_recurring_request(pcmk__request_t *request)
169  	{
170  	    int call_id = 0;
171  	    int rc = pcmk_rc_ok;
172  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
173  	                                pcmk__client_privileged);
174  	    xmlNode *reply = NULL;
175  	
176  	    if (!allowed) {
177  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
178  	                         PCMK_EXEC_ERROR, NULL);
179  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
180  	                   request->op, pcmk__client_name(request->ipc_client));
181  	        return NULL;
182  	    }
183  	
184  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
185  	
186  	    rc = execd_process_get_recurring(request->xml, call_id, &reply);
187  	
188  	    if (rc == pcmk_rc_ok) {
189  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
190  	    } else {
191  	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
192  	                         pcmk_rc_str(rc));
193  	    }
194  	
195  	    return reply;
196  	}
197  	
198  	static xmlNode *
199  	handle_poke_request(pcmk__request_t *request)
200  	{
201  	    int call_id = 0;
202  	    xmlNode *reply = NULL;
203  	
204  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
205  	
206  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
207  	
208  	    /* Create a generic reply since this doesn't create a more specific one */
209  	    reply = execd_create_reply(pcmk_ok, call_id);
210  	    return reply;
211  	}
212  	
213  	static xmlNode *
214  	handle_rsc_cancel_request(pcmk__request_t *request)
215  	{
216  	    int call_id = 0;
217  	    int rc = pcmk_rc_ok;
218  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
219  	                                pcmk__client_privileged);
220  	    xmlNode *reply = NULL;
221  	
222  	    if (!allowed) {
223  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
224  	                         PCMK_EXEC_ERROR, NULL);
225  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
226  	                   request->op, pcmk__client_name(request->ipc_client));
227  	        return NULL;
228  	    }
229  	
230  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
231  	
232  	    rc = execd_process_rsc_cancel(request->ipc_client, request->xml);
233  	
234  	    if (rc == pcmk_rc_ok) {
235  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
236  	    } else {
237  	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
238  	                         pcmk_rc_str(rc));
239  	    }
240  	
241  	    /* Create a generic reply since canceling a resource doesn't create a
242  	     * more specific one.
243  	     */
244  	    reply = execd_create_reply(pcmk_rc2legacy(rc), call_id);
245  	    return reply;
246  	}
247  	
248  	static xmlNode *
249  	handle_rsc_exec_request(pcmk__request_t *request)
250  	{
251  	    int call_id = 0;
252  	    int rc = pcmk_rc_ok;
253  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
254  	                                pcmk__client_privileged);
255  	    xmlNode *reply = NULL;
256  	
257  	    if (!allowed) {
258  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
259  	                         PCMK_EXEC_ERROR, NULL);
260  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
261  	                   request->op, pcmk__client_name(request->ipc_client));
262  	        return NULL;
263  	    }
264  	
265  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
266  	
267  	    rc = execd_process_rsc_exec(request->ipc_client, request->xml);
268  	
269  	    if (rc == pcmk_rc_ok) {
270  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
271  	
272  	        /* This looks redundant, but it's unfortunately necessary.  The first
273  	         * argument is set as the PCMK__XA_LRMD_RC attribute in the response.
274  	         * On the other side of the connection, lrmd_send_command will read
275  	         * this and use it as its return value, which passes back up to the
276  	         * public API function lrmd_api_exec.
277  	         */
278  	        reply = execd_create_reply(call_id, call_id);
279  	    } else {
280  	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
281  	                         pcmk_rc_str(rc));
282  	        reply = execd_create_reply(pcmk_rc2legacy(rc), call_id);
283  	    }
284  	
285  	    return reply;
286  	}
287  	
288  	static xmlNode *
289  	handle_rsc_info_request(pcmk__request_t *request)
290  	{
291  	    int call_id = 0;
292  	    int rc = pcmk_rc_ok;
293  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
294  	                                pcmk__client_privileged);
295  	    xmlNode *reply = NULL;
296  	
297  	    if (!allowed) {
298  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
299  	                         PCMK_EXEC_ERROR, NULL);
300  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
301  	                   request->op, pcmk__client_name(request->ipc_client));
302  	        return NULL;
303  	    }
304  	
305  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
306  	
307  	    /* This returns ENODEV if the resource isn't in the cache which will be
308  	     * logged as an error.  However, this isn't fatal to the client - it may
309  	     * be querying to see if the resource exists before deciding to register it.
310  	     * Thus, we'll ignore an ENODEV to prevent a warning message from being
311  	     * logged.
312  	     */
313  	    rc = execd_process_get_rsc_info(request->xml, call_id, &reply);
314  	
315  	    if ((rc == pcmk_rc_ok) || (rc == ENODEV)) {
316  	        pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
317  	    } else {
318  	        pcmk__set_result(&request->result, pcmk_rc2exitc(rc), PCMK_EXEC_ERROR,
319  	                         pcmk_rc_str(rc));
320  	    }
321  	
322  	    return reply;
323  	}
324  	
325  	static xmlNode *
326  	handle_rsc_reg_request(pcmk__request_t *request)
327  	{
328  	    int call_id = 0;
329  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
330  	                                pcmk__client_privileged);
331  	    xmlNode *reply = NULL;
332  	
333  	    if (!allowed) {
334  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
335  	                         PCMK_EXEC_ERROR, NULL);
336  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
337  	                   request->op, pcmk__client_name(request->ipc_client));
338  	        return NULL;
339  	    }
340  	
341  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
342  	
343  	    execd_process_rsc_register(request->ipc_client, request->ipc_id, request->xml);
344  	
345  	    /* Create a generic reply since registering a resource doesn't create
346  	     * a more specific one.
347  	     */
348  	    reply = execd_create_reply(pcmk_ok, call_id);
349  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
350  	    return reply;
351  	}
352  	
353  	static xmlNode *
354  	handle_rsc_unreg_request(pcmk__request_t *request)
355  	{
356  	    int call_id = 0;
357  	    int rc = pcmk_rc_ok;
358  	    bool allowed = pcmk__is_set(request->ipc_client->flags,
359  	                                pcmk__client_privileged);
360  	    xmlNode *reply = NULL;
361  	
362  	    if (!allowed) {
363  	        pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
364  	                         PCMK_EXEC_ERROR, NULL);
365  	        pcmk__warn("Rejecting IPC request '%s' from unprivileged client %s",
366  	                   request->op, pcmk__client_name(request->ipc_client));
367  	        return NULL;
368  	    }
369  	
370  	    pcmk__xe_get_int(request->xml, PCMK__XA_LRMD_CALLID, &call_id);
371  	
372  	    rc = execd_process_rsc_unregister(request->ipc_client, request->xml);
373  	
374  	    /* Create a generic reply since unregistering a resource doesn't create
375  	     * a more specific one.
376  	     */
377  	    reply = execd_create_reply(pcmk_rc2legacy(rc), call_id);
378  	    pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
379  	    return reply;
380  	}
381  	
382  	static bool
383  	requires_notify(const char *command, int rc)
384  	{
385  	    if (pcmk__str_eq(command, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
386  	        /* Don't notify about failed unregisters */
387  	        return (rc == pcmk_ok) || (rc == -EINPROGRESS);
388  	    } else {
389  	        return pcmk__str_any_of(command, LRMD_OP_POKE, LRMD_OP_RSC_REG, NULL);
390  	    }
391  	}
392  	
393  	
394  	static xmlNode *
395  	handle_unknown_request(pcmk__request_t *request)
396  	{
397  	    pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
398  	                       NULL, CRM_EX_PROTOCOL);
399  	
400  	    pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
401  	                        "Unknown request type '%s' (bug?)",
402  	                        pcmk__s(request->op, ""));
403  	    return NULL;
404  	}
405  	
406  	static void
407  	execd_register_handlers(void)
408  	{
409  	    pcmk__server_command_t handlers[] = {
410  	        { CRM_OP_IPC_FWD, handle_ipc_fwd_request },
411  	        { CRM_OP_REGISTER, handle_register_request },
412  	        { LRMD_OP_ALERT_EXEC, handle_alert_exec_request },
413  	        { LRMD_OP_CHECK, handle_check_request },
414  	        { LRMD_OP_GET_RECURRING, handle_get_recurring_request },
415  	        { LRMD_OP_POKE, handle_poke_request },
416  	        { LRMD_OP_RSC_CANCEL, handle_rsc_cancel_request },
417  	        { LRMD_OP_RSC_EXEC, handle_rsc_exec_request },
418  	        { LRMD_OP_RSC_INFO, handle_rsc_info_request },
419  	        { LRMD_OP_RSC_REG, handle_rsc_reg_request },
420  	        { LRMD_OP_RSC_UNREG, handle_rsc_unreg_request },
421  	        { NULL, handle_unknown_request },
422  	    };
423  	
424  	    execd_handlers = pcmk__register_handlers(handlers);
425  	}
426  	
427  	void
428  	execd_unregister_handlers(void)
429  	{
CID (unavailable; MK=624b0da03e396bfbc6eb631a5f30613a) (#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".
430  	    g_clear_pointer(&execd_handlers, g_hash_table_destroy);
431  	}
432  	
433  	bool
434  	execd_invalid_msg(xmlNode *msg)
435  	{
436  	    const char *to = NULL;
437  	    bool invalid = true;
438  	
439  	    CRM_CHECK(msg != NULL, return invalid);
440  	
441  	    to = pcmk__xe_get(msg, PCMK__XA_T);
442  	
443  	    /* IPC proxy messages do not get a t="" attribute set on them. */
444  	    invalid = !pcmk__str_eq(to, CRM_SYSTEM_LRMD, pcmk__str_none)
445  	              && !pcmk__xe_is(msg, PCMK__XE_LRMD_IPC_PROXY);
446  	
447  	    if (invalid) {
448  	        pcmk__info("Ignoring invalid IPC message: to '%s' not " CRM_SYSTEM_LRMD,
449  	                   pcmk__s(to, ""));
450  	        pcmk__log_xml_info(msg, "[Invalid]");
451  	    }
452  	
453  	    return invalid;
454  	}
455  	
456  	void
457  	execd_handle_request(pcmk__request_t *request)
458  	{
459  	    char *log_msg = NULL;
460  	    const char *reason = NULL;
461  	    const char *exec_status_s = NULL;
462  	    xmlNode *reply = NULL;
463  	
464  	    if (execd_handlers == NULL) {
465  	        execd_register_handlers();
466  	    }
467  	
468  	    if (request->ipc_client->name == NULL) {
469  	        request->ipc_client->name = pcmk__xe_get_copy(request->xml,
470  	                                                      PCMK__XA_LRMD_CLIENTNAME);
471  	    }
472  	
473  	    lrmd_call_id++;
474  	    if (lrmd_call_id < 1) {
475  	        lrmd_call_id = 1;
476  	    }
477  	
478  	    pcmk__xe_set(request->xml, PCMK__XA_LRMD_CLIENTID, request->ipc_client->id);
479  	    pcmk__xe_set(request->xml, PCMK__XA_LRMD_CLIENTNAME,
480  	                 request->ipc_client->name);
481  	    pcmk__xe_set_int(request->xml, PCMK__XA_LRMD_CALLID, lrmd_call_id);
482  	
483  	    reply = pcmk__process_request(request, execd_handlers);
484  	
485  	    if (reply != NULL) {
486  	        int rc = pcmk_rc_ok;
487  	        int reply_rc = pcmk_ok;
488  	
489  	        pcmk__log_xml_trace(reply, "Reply");
490  	
491  	        rc = lrmd_server_send_reply(request->ipc_client, request->ipc_id, reply);
492  	        if (rc != pcmk_rc_ok) {
493  	            pcmk__warn("Reply to client %s failed: %s " QB_XS " rc=%d",
494  	                       pcmk__client_name(request->ipc_client), pcmk_rc_str(rc),
495  	                       rc);
496  	        }
497  	
498  	        pcmk__xe_get_int(reply, PCMK__XA_LRMD_RC, &reply_rc);
499  	        if (requires_notify(request->op, reply_rc)) {
500  	            execd_send_generic_notify(reply_rc, request->xml);
501  	        }
502  	
503  	        pcmk__xml_free(reply);
504  	    }
505  	
506  	    exec_status_s = pcmk_exec_status_str(request->result.execution_status);
507  	    reason = request->result.exit_reason;
508  	
509  	    log_msg = pcmk__assert_asprintf("Processed %s request from %s %s: "
510  	                                    "%s%s%s%s",
511  	                                    request->op,
512  	                                    pcmk__request_origin_type(request),
513  	                                    pcmk__request_origin(request),
514  	                                    exec_status_s,
515  	                                    ((reason == NULL)? "" : " ("),
516  	                                    pcmk__s(reason, ""),
517  	                                    ((reason == NULL)? "" : ")"));
518  	
519  	    if (!pcmk__result_ok(&request->result)) {
520  	        pcmk__warn("%s", log_msg);
521  	    } else {
522  	        pcmk__debug("%s", log_msg);
523  	    }
524  	
525  	    free(log_msg);
526  	    pcmk__reset_request(request);
527  	}
528