1 /*
2 * Copyright 2008-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 <unistd.h>
13 #include <stdbool.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <stdarg.h>
17 #include <string.h>
18 #include <netdb.h>
19 #include <termios.h>
20 #include <sys/socket.h>
21
22 #include <glib.h>
23
24 #include <crm/crm.h>
25 #include <crm/cib/internal.h>
26 #include <crm/common/mainloop.h>
27 #include <crm/common/xml.h>
28
29 #include <gnutls/gnutls.h>
30
31 // GnuTLS handshake timeout in seconds
32 #define TLS_HANDSHAKE_TIMEOUT 5
33
34 static pcmk__tls_t *tls = NULL;
35
36 #include <arpa/inet.h>
37
38 typedef struct {
39 int port;
40 char *server;
41 char *user;
42 char *passwd;
43 gboolean encrypted;
44 pcmk__remote_t command;
45 pcmk__remote_t callback;
46 pcmk__output_t *out;
47 time_t start_time;
48 int timeout_sec;
49 } cib_remote_opaque_t;
50
51 static int
52 cib_remote_perform_op(cib_t *cib, const char *op, const char *host,
53 const char *section, xmlNode *data,
54 xmlNode **output_data, int call_options,
55 const char *user_name)
56 {
57 int rc;
58 int remaining_time = 0;
59 time_t start_time;
60
61 xmlNode *op_msg = NULL;
62 xmlNode *op_reply = NULL;
63
64 cib_remote_opaque_t *private = cib->variant_opaque;
65
66 if (cib->state == cib_disconnected) {
67 return -ENOTCONN;
68 }
69
70 if (output_data != NULL) {
71 *output_data = NULL;
72 }
73
74 if (op == NULL) {
75 pcmk__err("No operation specified");
76 return -EINVAL;
77 }
78
79 rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
80 NULL, &op_msg);
81 rc = pcmk_rc2legacy(rc);
82 if (rc != pcmk_ok) {
83 return rc;
84 }
85
86 if (pcmk__is_set(call_options, cib_transaction)) {
87 rc = cib__extend_transaction(cib, op_msg);
88 pcmk__xml_free(op_msg);
89 return pcmk_rc2legacy(rc);
90 }
91
92 pcmk__trace("Sending %s message to the CIB manager", op);
93 if (!(call_options & cib_sync_call)) {
94 pcmk__remote_send_xml(&private->callback, op_msg);
95 } else {
96 pcmk__remote_send_xml(&private->command, op_msg);
97 }
98 pcmk__xml_free(op_msg);
99
100 if ((call_options & cib_discard_reply)) {
101 pcmk__trace("Discarding reply");
102 return pcmk_ok;
103
104 } else if (!(call_options & cib_sync_call)) {
105 return cib->call_id;
106 }
107
108 pcmk__trace("Waiting for a synchronous reply");
109
110 start_time = time(NULL);
111 remaining_time = cib->call_timeout ? cib->call_timeout : 60;
112
113 rc = pcmk_rc_ok;
114 while (remaining_time > 0 && (rc != ENOTCONN)) {
115 int reply_id = -1;
116 int msg_id = cib->call_id;
117
118 rc = pcmk__read_remote_message(&private->command,
119 remaining_time * 1000);
120 op_reply = pcmk__remote_message_xml(&private->command);
121
122 if (!op_reply) {
123 break;
124 }
125
126 pcmk__xe_get_int(op_reply, PCMK__XA_CIB_CALLID, &reply_id);
127
128 if (reply_id == msg_id) {
129 break;
130
131 } else if (reply_id < msg_id) {
132 pcmk__debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
133 pcmk__log_xml_trace(op_reply, "Old reply");
134
135 } else if ((reply_id - 10000) > msg_id) {
136 /* wrap-around case */
137 pcmk__debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
138 pcmk__log_xml_trace(op_reply, "Old reply");
139 } else {
140 pcmk__err("Received a __future__ reply: %d (wanted %d)", reply_id,
141 msg_id);
142 }
143
144 pcmk__xml_free(op_reply);
145 op_reply = NULL;
146
147 /* wasn't the right reply, try and read some more */
148 remaining_time = time(NULL) - start_time;
149 }
150
151 if (rc == ENOTCONN) {
152 pcmk__err("Disconnected while waiting for reply");
153 return -ENOTCONN;
154 } else if (op_reply == NULL) {
155 pcmk__err("No reply message - empty");
156 return -ENOMSG;
157 }
158
159 pcmk__trace("Synchronous reply received");
160
161 /* Start processing the reply... */
162 if (pcmk__xe_get_int(op_reply, PCMK__XA_CIB_RC, &rc) != pcmk_rc_ok) {
163 rc = -EPROTO;
164 }
165
166 if (rc == pcmk_ok || rc == -EPERM) {
167 pcmk__log_xml_debug(op_reply, "passed");
168
169 } else {
170 pcmk__err("Call failed: %s", pcmk_strerror(rc));
171 pcmk__log_xml_warn(op_reply, "failed");
172 }
173
174 if (output_data == NULL) {
175 /* do nothing more */
176
177 } else if (!(call_options & cib_discard_reply)) {
178 xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA,
179 NULL, NULL);
180 xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
181
182 if (tmp == NULL) {
183 pcmk__trace("No output in reply to \"%s\" command %d", op,
184 (cib->call_id - 1));
185 } else {
186 *output_data = pcmk__xml_copy(NULL, tmp);
187 }
188 }
189
190 pcmk__xml_free(op_reply);
191
192 return rc;
193 }
194
195 static int
196 cib_remote_callback_dispatch(gpointer user_data)
197 {
198 int rc;
199 cib_t *cib = user_data;
200 cib_remote_opaque_t *private = cib->variant_opaque;
201
202 xmlNode *msg = NULL;
203 const char *type = NULL;
204
205 /* If start time is 0, we've previously handled a complete message and this
206 * connection is being reused for a new message. Reset the start_time,
207 * giving this new message timeout_sec from now to complete.
208 */
209 if (private->start_time == 0) {
210 private->start_time = time(NULL);
211 }
212
213 rc = pcmk__read_available_remote_data(&private->callback);
214 switch (rc) {
215 case pcmk_rc_ok:
216 /* We have the whole message so process it */
217 break;
218
219 case EAGAIN:
220 /* Have we timed out? */
221 if (time(NULL) >= private->start_time + private->timeout_sec) {
222 pcmk__info("Error reading from CIB manager connection: %s",
223 pcmk_rc_str(ETIME));
224 return -1;
225 }
226
227 /* We haven't read the whole message yet */
228 return 0;
229
230 default:
231 /* Error */
232 pcmk__info("Error reading from CIB manager connection: %s",
233 pcmk_rc_str(rc));
234 return -1;
235 }
236
237 // coverity[tainted_data] This can't easily be changed right now
238 msg = pcmk__remote_message_xml(&private->callback);
239 if (msg == NULL) {
240 private->start_time = 0;
241 return 0;
242 }
243
244 type = pcmk__xe_get(msg, PCMK__XA_T);
245
246 pcmk__trace("Activating %s callbacks...", type);
247
248 if (pcmk__str_eq(type, PCMK__VALUE_CIB, pcmk__str_none)) {
249 cib_native_callback(cib, msg, 0, 0);
250 } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
251 g_list_foreach(cib->notify_list, cib_native_notify, msg);
252 } else {
253 pcmk__err("Unknown message type: %s", type);
254 }
255
256 pcmk__xml_free(msg);
257 private->start_time = 0;
258 return 0;
259 }
260
261 static int
262 cib_remote_command_dispatch(gpointer user_data)
263 {
264 int rc;
265 cib_t *cib = user_data;
266 cib_remote_opaque_t *private = cib->variant_opaque;
267
268 /* See cib_remote_callback_dispatch */
269 if (private->start_time == 0) {
270 private->start_time = time(NULL);
271 }
272
273 rc = pcmk__read_available_remote_data(&private->command);
274 if (rc == EAGAIN) {
275 /* Have we timed out? */
276 if (time(NULL) >= private->start_time + private->timeout_sec) {
277 pcmk__info("Error reading from CIB manager connection: %s",
278 pcmk_rc_str(ETIME));
279 return -1;
280 }
281
282 /* We haven't read the whole message yet */
283 return 0;
284 }
285
286 free(private->command.buffer);
287 private->command.buffer = NULL;
288 pcmk__err("Received late reply for remote cib connection, discarding");
289
290 if (rc != pcmk_rc_ok) {
291 pcmk__info("Error reading from CIB manager connection: %s",
292 pcmk_rc_str(rc));
293 return -1;
294 }
295
296 private->start_time = 0;
297 return 0;
298 }
299
300 static int
301 cib_tls_close(cib_t *cib)
302 {
303 cib_remote_opaque_t *private = cib->variant_opaque;
304
305 if (private->encrypted) {
306 if (private->command.tls_session) {
307 gnutls_bye(private->command.tls_session, GNUTLS_SHUT_RDWR);
308 gnutls_deinit(private->command.tls_session);
309 }
310
311 if (private->callback.tls_session) {
312 gnutls_bye(private->callback.tls_session, GNUTLS_SHUT_RDWR);
313 gnutls_deinit(private->callback.tls_session);
314 }
315
316 private->command.tls_session = NULL;
317 private->callback.tls_session = NULL;
318 pcmk__free_tls(tls);
319 tls = NULL;
320 }
321
322 if (private->command.tcp_socket >= 0) {
323 shutdown(private->command.tcp_socket, SHUT_RDWR); /* no more receptions */
324 close(private->command.tcp_socket);
325 }
326 if (private->callback.tcp_socket >= 0) {
327 shutdown(private->callback.tcp_socket, SHUT_RDWR); /* no more receptions */
328 close(private->callback.tcp_socket);
329 }
330 private->command.tcp_socket = -1;
331 private->callback.tcp_socket = -1;
332
333 free(private->command.buffer);
334 free(private->callback.buffer);
335 private->command.buffer = NULL;
336 private->callback.buffer = NULL;
337
338 return 0;
339 }
340
341 static void
342 cib_remote_connection_destroy(gpointer user_data)
343 {
344 pcmk__err("Connection destroyed");
345 cib_tls_close(user_data);
346 }
347
348 static int
349 cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
350 {
351 cib_remote_opaque_t *private = cib->variant_opaque;
352 int rc;
353
354 xmlNode *answer = NULL;
355 xmlNode *login = NULL;
356
357 static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, };
358
359 cib_fd_callbacks.dispatch =
360 event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch;
361 cib_fd_callbacks.destroy = cib_remote_connection_destroy;
362
363 connection->tcp_socket = -1;
364 connection->tls_session = NULL;
365 rc = pcmk__connect_remote(private->server, private->port, 0, NULL,
366 &(connection->tcp_socket), NULL, NULL);
367 if (rc != pcmk_rc_ok) {
368 pcmk__info("Remote connection to %s:%d failed: %s " QB_XS " rc=%d",
369 private->server, private->port, pcmk_rc_str(rc), rc);
370 return -ENOTCONN;
371 }
372
373 if (private->encrypted) {
374 int tls_rc = GNUTLS_E_SUCCESS;
375
376 // @TODO Implement pre-shared key authentication (see T961)
377 rc = pcmk__init_tls(&tls, false, false);
378 if (rc != pcmk_rc_ok) {
379 return -1;
380 }
381
382 /* bind the socket to GnuTls lib */
383 connection->tls_session = pcmk__new_tls_session(tls, connection->tcp_socket);
384 if (connection->tls_session == NULL) {
385 cib_tls_close(cib);
386 return -1;
387 }
388
389 rc = pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT,
390 &tls_rc);
391 if (rc != pcmk_rc_ok) {
392 const bool proto_err = (rc == EPROTO);
393
394 pcmk__err("Remote CIB session creation for %s:%d failed: %s",
395 private->server, private->port,
396 (proto_err? gnutls_strerror(tls_rc) : pcmk_rc_str(rc)));
397 gnutls_deinit(connection->tls_session);
398 connection->tls_session = NULL;
399 cib_tls_close(cib);
400 return -1;
401 }
402 }
403
404 /* Now that the handshake is done, see if any client TLS certificate is
405 * close to its expiration date and log if so. If a TLS certificate is not
406 * in use, this function will just return so we don't need to check for the
407 * session type here.
408 */
409 pcmk__tls_check_cert_expiration(connection->tls_session);
410
411 /* login to server */
412 login = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
413 pcmk__xe_set(login, PCMK_XA_OP, "authenticate");
414 pcmk__xe_set(login, PCMK_XA_USER, private->user);
415 pcmk__xe_set(login, PCMK__XA_PASSWORD, private->passwd);
416 pcmk__xe_set(login, PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD);
417
418 pcmk__remote_send_xml(connection, login);
419 pcmk__xml_free(login);
420
421 rc = pcmk_ok;
422 if (pcmk__read_remote_message(connection, -1) == ENOTCONN) {
423 rc = -ENOTCONN;
424 }
425
426 answer = pcmk__remote_message_xml(connection);
427
428 pcmk__log_xml_trace(answer, "Reply");
429 if (answer == NULL) {
430 rc = -EPROTO;
431
432 } else {
433 /* grab the token */
434 const char *msg_type = pcmk__xe_get(answer, PCMK__XA_CIB_OP);
435 const char *tmp_ticket = pcmk__xe_get(answer, PCMK__XA_CIB_CLIENTID);
436
437 if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
438 pcmk__err("Invalid registration message: %s", msg_type);
439 rc = -EPROTO;
440
441 } else if (tmp_ticket == NULL) {
442 rc = -EPROTO;
443
444 } else {
445 connection->token = strdup(tmp_ticket);
446 }
447 }
448 pcmk__xml_free(answer);
449 answer = NULL;
450
451 if (rc != 0) {
452 cib_tls_close(cib);
453 return rc;
454 }
455
456 pcmk__trace("remote client connection established");
457 private->timeout_sec = 60;
458 connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH,
459 connection->tcp_socket, cib,
460 &cib_fd_callbacks);
461 return rc;
462 }
463
464 static int
465 cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type)
466 {
467 int rc = pcmk_ok;
468 cib_remote_opaque_t *private = cib->variant_opaque;
469
470 if (name == NULL) {
471 name = pcmk__s(crm_system_name, "client");
472 }
473
474 if (private->passwd == NULL) {
475 if (private->out == NULL) {
476 /* If no pcmk__output_t is set, just assume that a text prompt
477 * is good enough.
478 */
479 pcmk__text_prompt("Password", false, &(private->passwd));
480 } else {
481 private->out->prompt("Password", false, &(private->passwd));
482 }
483 }
484
485 if (private->server == NULL || private->user == NULL) {
486 rc = -EINVAL;
487 goto done;
488 }
489
490 rc = cib_tls_signon(cib, &(private->command), FALSE);
491 if (rc != pcmk_ok) {
492 goto done;
493 }
494
495 rc = cib_tls_signon(cib, &(private->callback), TRUE);
496
497 done:
498 if (rc == pcmk_ok) {
499 pcmk__info("Opened connection to %s:%d for %s", private->server,
500 private->port, name);
501 cib->state = cib_connected_command;
502 cib->type = cib_command;
503
504 } else {
505 pcmk__info("Connection to %s:%d for %s failed: %s\n", private->server,
506 private->port, name, pcmk_strerror(rc));
507 }
508
509 return rc;
510 }
511
512 static int
513 cib_remote_signoff(cib_t *cib)
514 {
515 int rc = pcmk_ok;
516
517 pcmk__debug("Disconnecting from the CIB manager");
518 cib_tls_close(cib);
519
520 cib->cmds->end_transaction(cib, false, cib_none);
521 cib->state = cib_disconnected;
522 cib->type = cib_no_connection;
523
524 return rc;
525 }
526
527 static int
528 cib_remote_free(cib_t *cib)
529 {
530 int rc = pcmk_ok;
531
532 pcmk__warn("Freeing CIB");
533 if (cib->state != cib_disconnected) {
534 rc = cib_remote_signoff(cib);
535 if (rc == pcmk_ok) {
536 cib_remote_opaque_t *private = cib->variant_opaque;
537
538 free(private->server);
539 free(private->user);
540 free(private->passwd);
541 free(cib->cmds);
542 free(cib->user);
543 free(private);
544 free(cib);
545 }
546 }
547
548 return rc;
549 }
550
551 static int
552 cib_remote_register_notification(cib_t * cib, const char *callback, int enabled)
553 {
554 xmlNode *notify_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
555 cib_remote_opaque_t *private = cib->variant_opaque;
556
557 pcmk__xe_set(notify_msg, PCMK__XA_CIB_OP, PCMK__VALUE_CIB_NOTIFY);
558 pcmk__xe_set(notify_msg, PCMK__XA_CIB_NOTIFY_TYPE, callback);
559 pcmk__xe_set_int(notify_msg, PCMK__XA_CIB_NOTIFY_ACTIVATE, enabled);
560 pcmk__remote_send_xml(&private->callback, notify_msg);
561 pcmk__xml_free(notify_msg);
562 return pcmk_ok;
563 }
564
565 static int
566 cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
567 {
568 return -EPROTONOSUPPORT;
569 }
570
571 /*!
572 * \internal
573 * \brief Get the given CIB connection's unique client identifiers
574 *
575 * These can be used to check whether this client requested the action that
576 * triggered a CIB notification.
577 *
578 * \param[in] cib CIB connection
579 * \param[out] async_id If not \p NULL, where to store asynchronous client ID
580 * \param[out] sync_id If not \p NULL, where to store synchronous client ID
581 *
582 * \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
583 *
584 * \note This is the \p cib_remote variant implementation of
585 * \p cib_api_operations_t:client_id().
586 * \note The client IDs are assigned during CIB sign-on.
587 */
588 static int
589 cib_remote_client_id(const cib_t *cib, const char **async_id,
590 const char **sync_id)
591 {
592 cib_remote_opaque_t *private = cib->variant_opaque;
593
594 if (async_id != NULL) {
595 // private->callback is the channel for async requests
596 *async_id = private->callback.token;
597 }
598 if (sync_id != NULL) {
599 // private->command is the channel for sync requests
600 *sync_id = private->command.token;
601 }
602 return pcmk_ok;
603 }
604
605 cib_t *
606 cib_remote_new(const char *server, const char *user, const char *passwd, int port,
607 gboolean encrypted)
608 {
609 cib_remote_opaque_t *private = NULL;
|
(1) Event alloc_arg: |
"cib_new_variant" allocates memory that is stored into "cib_new_variant()->cmds". [details] |
|
(2) Event var_assign: |
Assigning: "cib->cmds" = "cib_new_variant()->cmds". |
| Also see events: |
[leaked_storage] |
610 cib_t *cib = cib_new_variant();
611
|
(3) Event path: |
Condition "cib == NULL", taking false branch. |
612 if (cib == NULL) {
613 return NULL;
614 }
615
616 private = calloc(1, sizeof(cib_remote_opaque_t));
617
|
(4) Event path: |
Condition "private == NULL", taking true branch. |
618 if (private == NULL) {
|
CID (unavailable; MK=4a12853161e2d8c167707dd391f8150b) (#1 of 1): Resource leak (RESOURCE_LEAK): |
|
(5) Event leaked_storage: |
Freeing "cib" without freeing its pointer field "cmds" leaks the storage that "cmds" points to. |
| Also see events: |
[alloc_arg][var_assign] |
619 free(cib);
620 return NULL;
621 }
622
623 cib->variant = cib_remote;
624 cib->variant_opaque = private;
625
626 private->server = pcmk__str_copy(server);
627 private->user = pcmk__str_copy(user);
628 private->passwd = pcmk__str_copy(passwd);
629 private->port = port;
630 private->encrypted = encrypted;
631
632 /* assign variant specific ops */
633 cib->delegate_fn = cib_remote_perform_op;
634 cib->cmds->signon = cib_remote_signon;
635 cib->cmds->signoff = cib_remote_signoff;
636 cib->cmds->free = cib_remote_free;
637 cib->cmds->register_notification = cib_remote_register_notification;
638 cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify;
639
640 cib->cmds->client_id = cib_remote_client_id;
641
642 return cib;
643 }
644
645 void
646 cib__set_output(cib_t *cib, pcmk__output_t *out)
647 {
648 cib_remote_opaque_t *private;
649
650 if (cib->variant != cib_remote) {
651 return;
652 }
653
654 private = cib->variant_opaque;
655 private->out = out;
656 }
657