root/lib/cib/cib_remote.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. cib_remote_perform_op
  2. cib_remote_callback_dispatch
  3. cib_remote_command_dispatch
  4. cib_tls_close
  5. cib_remote_connection_destroy
  6. cib_tls_signon
  7. cib_remote_signon
  8. cib_remote_signoff
  9. cib_remote_free
  10. cib_remote_inputfd
  11. cib_remote_register_notification
  12. cib_remote_set_connection_dnotify
  13. cib_remote_client_id
  14. cib_remote_new
  15. cib__set_output

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

/* [previous][next][first][last][top][bottom][index][help] */