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