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