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