Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c
index f2e48d6f8b..1eb2ac6c32 100644
--- a/daemons/based/based_remote.c
+++ b/daemons/based/based_remote.c
@@ -1,687 +1,688 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <inttypes.h> // PRIx64
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <errno.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/common/ipc.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/xml.h>
#include <crm/common/remote_internal.h>
#include <crm/cib/internal.h>
#include "pacemaker-based.h"
/* #undef HAVE_PAM_PAM_APPL_H */
/* #undef HAVE_GNUTLS_GNUTLS_H */
#ifdef HAVE_GNUTLS_GNUTLS_H
# include <gnutls/gnutls.h>
#endif
#include <pwd.h>
#include <grp.h>
#if HAVE_SECURITY_PAM_APPL_H
# include <security/pam_appl.h>
# define HAVE_PAM 1
#else
# if HAVE_PAM_PAM_APPL_H
# include <pam/pam_appl.h>
# define HAVE_PAM 1
# endif
#endif
extern int remote_tls_fd;
extern gboolean cib_shutdown_flag;
int init_remote_listener(int port, gboolean encrypted);
void cib_remote_connection_destroy(gpointer user_data);
#ifdef HAVE_GNUTLS_GNUTLS_H
gnutls_dh_params_t dh_params;
gnutls_anon_server_credentials_t anon_cred_s;
static void
debug_log(int level, const char *str)
{
fputs(str, stderr);
}
#endif
// @TODO This is rather short for someone to type their password
#define REMOTE_AUTH_TIMEOUT 10000
int num_clients;
static bool authenticate_user(const char *user, const char *passwd);
static int cib_remote_listen(gpointer data);
static int cib_remote_msg(gpointer data);
static void
remote_connection_destroy(gpointer user_data)
{
crm_info("No longer listening for remote connections");
return;
}
int
init_remote_listener(int port, gboolean encrypted)
{
int rc;
int *ssock = NULL;
struct sockaddr_in saddr;
int optval;
static struct mainloop_fd_callbacks remote_listen_fd_callbacks = {
.dispatch = cib_remote_listen,
.destroy = remote_connection_destroy,
};
if (port <= 0) {
/* don't start it */
return 0;
}
if (encrypted) {
#ifndef HAVE_GNUTLS_GNUTLS_H
crm_warn("TLS support is not available");
return 0;
#else
crm_notice("Starting TLS listener on port %d", port);
crm_gnutls_global_init();
/* gnutls_global_set_log_level (10); */
gnutls_global_set_log_function(debug_log);
if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) {
return -1;
}
gnutls_anon_allocate_server_credentials(&anon_cred_s);
gnutls_anon_set_server_dh_params(anon_cred_s, dh_params);
#endif
} else {
crm_warn("Starting plain-text listener on port %d", port);
}
#ifndef HAVE_PAM
crm_warn("PAM is _not_ enabled!");
#endif
/* create server socket */
ssock = malloc(sizeof(int));
if(ssock == NULL) {
crm_err("Listener socket allocation failed: %s", pcmk_rc_str(errno));
return -1;
}
*ssock = socket(AF_INET, SOCK_STREAM, 0);
if (*ssock == -1) {
crm_err("Listener socket creation failed: %s", pcmk_rc_str(errno));
free(ssock);
return -1;
}
/* reuse address */
optval = 1;
rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (rc < 0) {
crm_err("Local address reuse not allowed on listener socket: %s",
pcmk_rc_str(errno));
}
/* bind server socket */
memset(&saddr, '\0', sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(port);
if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
crm_err("Cannot bind to listener socket: %s", pcmk_rc_str(errno));
close(*ssock);
free(ssock);
return -2;
}
if (listen(*ssock, 10) == -1) {
crm_err("Cannot listen on socket: %s", pcmk_rc_str(errno));
close(*ssock);
free(ssock);
return -3;
}
mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks);
crm_debug("Started listener on port %d", port);
return *ssock;
}
static int
check_group_membership(const char *usr, const char *grp)
{
int index = 0;
struct passwd *pwd = NULL;
struct group *group = NULL;
pwd = getpwnam(usr);
if (pwd == NULL) {
crm_notice("Rejecting remote client: '%s' is not a valid user", usr);
return FALSE;
}
group = getgrgid(pwd->pw_gid);
if (group != NULL && pcmk__str_eq(grp, group->gr_name, pcmk__str_none)) {
return TRUE;
}
group = getgrnam(grp);
if (group == NULL) {
crm_err("Rejecting remote client: '%s' is not a valid group", grp);
return FALSE;
}
while (TRUE) {
char *member = group->gr_mem[index++];
if (member == NULL) {
break;
} else if (pcmk__str_eq(usr, member, pcmk__str_none)) {
return TRUE;
}
}
crm_notice("Rejecting remote client: User '%s' is not a member of "
"group '%s'", usr, grp);
return FALSE;
}
static gboolean
cib_remote_auth(xmlNode * login)
{
const char *user = NULL;
const char *pass = NULL;
const char *tmp = NULL;
if (login == NULL) {
return FALSE;
}
if (!pcmk__xe_is(login, PCMK__XE_CIB_COMMAND)) {
crm_warn("Rejecting remote client: Unrecognizable message "
"(element '%s' not '" PCMK__XE_CIB_COMMAND "')", login->name);
crm_log_xml_debug(login, "bad");
return FALSE;
}
tmp = crm_element_value(login, PCMK_XA_OP);
if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) {
crm_warn("Rejecting remote client: Unrecognizable message "
"(operation '%s' not 'authenticate')", tmp);
crm_log_xml_debug(login, "bad");
return FALSE;
}
user = crm_element_value(login, PCMK_XA_USER);
pass = crm_element_value(login, PCMK__XA_PASSWORD);
if (!user || !pass) {
crm_warn("Rejecting remote client: No %s given",
((user == NULL)? "username" : "password"));
crm_log_xml_debug(login, "bad");
return FALSE;
}
crm_log_xml_debug(login, "auth");
return check_group_membership(user, CRM_DAEMON_GROUP)
&& authenticate_user(user, pass);
}
static gboolean
remote_auth_timeout_cb(gpointer data)
{
pcmk__client_t *client = data;
client->remote->auth_timeout = 0;
if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
return FALSE;
}
mainloop_del_fd(client->remote->source);
crm_err("Remote client authentication timed out");
return FALSE;
}
static int
cib_remote_listen(gpointer data)
{
int csock = 0;
unsigned laddr;
struct sockaddr_storage addr;
char ipstr[INET6_ADDRSTRLEN];
int ssock = *(int *)data;
int rc;
pcmk__client_t *new_client = NULL;
static struct mainloop_fd_callbacks remote_client_fd_callbacks = {
.dispatch = cib_remote_msg,
.destroy = cib_remote_connection_destroy,
};
/* accept the connection */
laddr = sizeof(addr);
memset(&addr, 0, sizeof(addr));
csock = accept(ssock, (struct sockaddr *)&addr, &laddr);
if (csock == -1) {
crm_warn("Could not accept remote connection: %s", pcmk_rc_str(errno));
return TRUE;
}
pcmk__sockaddr2str(&addr, ipstr);
rc = pcmk__set_nonblocking(csock);
if (rc != pcmk_rc_ok) {
crm_warn("Dropping remote connection from %s because "
"it could not be set to non-blocking: %s",
ipstr, pcmk_rc_str(rc));
close(csock);
return TRUE;
}
num_clients++;
new_client = pcmk__new_unauth_client(NULL);
new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t));
if (ssock == remote_tls_fd) {
#ifdef HAVE_GNUTLS_GNUTLS_H
pcmk__set_client_flags(new_client, pcmk__client_tls);
/* create gnutls session for the server socket */
new_client->remote->tls_session = pcmk__new_tls_session(csock,
GNUTLS_SERVER,
GNUTLS_CRD_ANON,
anon_cred_s);
if (new_client->remote->tls_session == NULL) {
close(csock);
return TRUE;
}
#endif
} else {
pcmk__set_client_flags(new_client, pcmk__client_tcp);
new_client->remote->tcp_socket = csock;
}
// Require the client to authenticate within this time
new_client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
remote_auth_timeout_cb,
new_client);
crm_info("%s connection from %s pending authentication for client %s",
((ssock == remote_tls_fd)? "Encrypted" : "Clear-text"),
ipstr, new_client->id);
new_client->remote->source =
mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client,
&remote_client_fd_callbacks);
return TRUE;
}
void
cib_remote_connection_destroy(gpointer user_data)
{
pcmk__client_t *client = user_data;
int csock = 0;
if (client == NULL) {
return;
}
crm_trace("Cleaning up after client %s disconnect",
pcmk__client_name(client));
num_clients--;
crm_trace("Num unfree'd clients: %d", num_clients);
switch (PCMK__CLIENT_TYPE(client)) {
case pcmk__client_tcp:
csock = client->remote->tcp_socket;
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case pcmk__client_tls:
if (client->remote->tls_session) {
void *sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session);
csock = GPOINTER_TO_INT(sock_ptr);
if (pcmk_is_set(client->flags,
pcmk__client_tls_handshake_complete)) {
gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_WR);
}
gnutls_deinit(*client->remote->tls_session);
gnutls_free(client->remote->tls_session);
client->remote->tls_session = NULL;
}
break;
#endif
default:
crm_warn("Unknown transport for client %s "
CRM_XS " flags=%#016" PRIx64,
pcmk__client_name(client), client->flags);
}
if (csock > 0) {
close(csock);
}
pcmk__free_client(client);
crm_trace("Freed the cib client");
if (cib_shutdown_flag) {
cib_shutdown(0);
}
return;
}
static void
cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command)
{
if (!pcmk__xe_is(command, PCMK__XE_CIB_COMMAND)) {
crm_log_xml_trace(command, "bad");
return;
}
if (client->name == NULL) {
client->name = pcmk__str_copy(client->id);
}
/* unset dangerous options */
pcmk__xe_remove_attr(command, PCMK__XA_SRC);
pcmk__xe_remove_attr(command, PCMK__XA_CIB_HOST);
pcmk__xe_remove_attr(command, PCMK__XA_CIB_UPDATE);
crm_xml_add(command, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(command, PCMK__XA_CIB_CLIENTID, client->id);
crm_xml_add(command, PCMK__XA_CIB_CLIENTNAME, client->name);
crm_xml_add(command, PCMK__XA_CIB_USER, client->user);
if (crm_element_value(command, PCMK__XA_CIB_CALLID) == NULL) {
char *call_uuid = crm_generate_uuid();
/* fix the command */
crm_xml_add(command, PCMK__XA_CIB_CALLID, call_uuid);
free(call_uuid);
}
if (crm_element_value(command, PCMK__XA_CIB_CALLOPT) == NULL) {
crm_xml_add_int(command, PCMK__XA_CIB_CALLOPT, 0);
}
crm_log_xml_trace(command, "Remote command: ");
cib_common_callback_worker(0, 0, command, client, TRUE);
}
static int
cib_remote_msg(gpointer data)
{
xmlNode *command = NULL;
pcmk__client_t *client = data;
int rc;
- int timeout = 1000;
const char *client_name = pcmk__client_name(client);
- if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
- timeout = -1;
- }
-
crm_trace("Remote %s message received for client %s",
pcmk__client_type_str(PCMK__CLIENT_TYPE(client)), client_name);
#ifdef HAVE_GNUTLS_GNUTLS_H
if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls)
&& !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) {
int rc = pcmk__read_handshake_data(client);
if (rc == EAGAIN) {
/* No more data is available at the moment. Just return for now;
* we'll get invoked again once the client sends more.
*/
return 0;
} else if (rc != pcmk_rc_ok) {
return -1;
}
crm_debug("Completed TLS handshake with remote client %s", client_name);
pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);
if (client->remote->auth_timeout) {
g_source_remove(client->remote->auth_timeout);
}
// Require the client to authenticate within this time
client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
remote_auth_timeout_cb,
client);
return 0;
}
#endif
- rc = pcmk__read_remote_message(client->remote, timeout);
+ rc = pcmk__read_available_remote_data(client->remote);
+ switch (rc) {
+ case pcmk_rc_ok:
+ break;
+
+ case EAGAIN:
+ /* We haven't read the whole message yet */
+ return 0;
+
+ default:
+ /* Error */
+ crm_trace("Error reading from remote client: %s", pcmk_rc_str(rc));
+ return -1;
+ }
/* must pass auth before we will process anything else */
if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) {
xmlNode *reg;
const char *user = NULL;
command = pcmk__remote_message_xml(client->remote);
if (cib_remote_auth(command) == FALSE) {
free_xml(command);
return -1;
}
pcmk__set_client_flags(client, pcmk__client_authenticated);
g_source_remove(client->remote->auth_timeout);
client->remote->auth_timeout = 0;
client->name = crm_element_value_copy(command, PCMK_XA_NAME);
user = crm_element_value(command, PCMK_XA_USER);
if (user) {
client->user = pcmk__str_copy(user);
}
crm_notice("Remote connection accepted for authenticated user %s "
CRM_XS " client %s",
pcmk__s(user, ""), client_name);
/* send ACK */
reg = pcmk__xe_create(NULL, PCMK__XE_CIB_RESULT);
crm_xml_add(reg, PCMK__XA_CIB_OP, CRM_OP_REGISTER);
crm_xml_add(reg, PCMK__XA_CIB_CLIENTID, client->id);
pcmk__remote_send_xml(client->remote, reg);
free_xml(reg);
free_xml(command);
}
command = pcmk__remote_message_xml(client->remote);
- while (command) {
+ if (command != NULL) {
crm_trace("Remote message received from client %s", client_name);
cib_handle_remote_msg(client, command);
free_xml(command);
- command = pcmk__remote_message_xml(client->remote);
- }
-
- if (rc == ENOTCONN) {
- crm_trace("Remote CIB client %s disconnected while reading from it",
- client_name);
- return -1;
}
return 0;
}
#ifdef HAVE_PAM
/*!
* \internal
* \brief Pass remote user's password to PAM
*
* \param[in] num_msg Number of entries in \p msg
* \param[in] msg Array of PAM messages
* \param[out] response Where to set response to PAM
* \param[in] data User data (the password string)
*
* \return PAM return code (PAM_BUF_ERR for memory errors, PAM_CONV_ERR for all
* other errors, or PAM_SUCCESS on success)
* \note See pam_conv(3) for more explanation
*/
static int
construct_pam_passwd(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *data)
{
/* In theory, multiple messages are allowed, but due to OS compatibility
* issues, PAM implementations are recommended to only send one message at a
* time. We can require that here for simplicity.
*/
CRM_CHECK((num_msg == 1) && (msg != NULL) && (response != NULL)
&& (data != NULL), return PAM_CONV_ERR);
switch (msg[0]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
// Password requested
break;
case PAM_TEXT_INFO:
crm_info("PAM: %s", msg[0]->msg);
data = NULL;
break;
case PAM_ERROR_MSG:
/* In theory we should show msg[0]->msg, but that might
* contain the password, which we don't want in the logs
*/
crm_err("PAM reported an error");
data = NULL;
break;
default:
crm_warn("Ignoring PAM message of unrecognized type %d",
msg[0]->msg_style);
return PAM_CONV_ERR;
}
*response = calloc(1, sizeof(struct pam_response));
if (*response == NULL) {
return PAM_BUF_ERR;
}
(*response)->resp_retcode = 0;
(*response)->resp = pcmk__str_copy((const char *) data); // Caller will free
return PAM_SUCCESS;
}
#endif
/*!
* \internal
* \brief Verify the username and password passed for a remote CIB connection
*
* \param[in] user Username passed for remote CIB connection
* \param[in] passwd Password passed for remote CIB connection
*
* \return \c true if the username and password are accepted, otherwise \c false
* \note This function accepts any username and password when built without PAM
* support.
*/
static bool
authenticate_user(const char *user, const char *passwd)
{
#ifdef HAVE_PAM
int rc = 0;
bool pass = false;
const void *p_user = NULL;
struct pam_conv p_conv;
struct pam_handle *pam_h = NULL;
static const char *pam_name = NULL;
if (pam_name == NULL) {
pam_name = getenv("CIB_pam_service");
if (pam_name == NULL) {
pam_name = "login";
}
}
p_conv.conv = construct_pam_passwd;
p_conv.appdata_ptr = (void *) passwd;
rc = pam_start(pam_name, user, &p_conv, &pam_h);
if (rc != PAM_SUCCESS) {
crm_warn("Rejecting remote client for user %s "
"because PAM initialization failed: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
// Check user credentials
rc = pam_authenticate(pam_h, PAM_SILENT);
if (rc != PAM_SUCCESS) {
crm_notice("Access for remote user %s denied: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
/* Get the authenticated user name (PAM modules can map the original name to
* something else). Since the CIB manager runs as the daemon user (not
* root), that is the only user that can be successfully authenticated.
*/
rc = pam_get_item(pam_h, PAM_USER, &p_user);
if (rc != PAM_SUCCESS) {
crm_warn("Rejecting remote client for user %s "
"because PAM failed to return final user name: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
if (p_user == NULL) {
crm_warn("Rejecting remote client for user %s "
"because PAM returned no final user name", user);
goto bail;
}
// @TODO Why do we require these to match?
if (!pcmk__str_eq(p_user, user, pcmk__str_none)) {
crm_warn("Rejecting remote client for user %s "
"because PAM returned different final user name %s",
user, p_user);
goto bail;
}
// Check user account restrictions (expiration, etc.)
rc = pam_acct_mgmt(pam_h, PAM_SILENT);
if (rc != PAM_SUCCESS) {
crm_notice("Access for remote user %s denied: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
pass = true;
bail:
pam_end(pam_h, rc);
return pass;
#else
// @TODO Implement for non-PAM environments
return true;
#endif
}
diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h
index 0ce4208cd1..f8c0beae40 100644
--- a/include/crm/common/remote_internal.h
+++ b/include/crm/common/remote_internal.h
@@ -1,116 +1,117 @@
/*
* Copyright 2008-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_REMOTE_INTERNAL__H
#define PCMK__CRM_COMMON_REMOTE_INTERNAL__H
#include <stdbool.h> // bool
#include <crm/common/nodes.h> // pcmk_node_variant_remote
#include <crm/common/scheduler_types.h> // pcmk_node_t
// internal functions from remote.c
typedef struct pcmk__remote_s pcmk__remote_t;
int pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg);
int pcmk__remote_ready(const pcmk__remote_t *remote, int timeout_ms);
+int pcmk__read_available_remote_data(pcmk__remote_t *remote);
int pcmk__read_remote_message(pcmk__remote_t *remote, int timeout_ms);
xmlNode *pcmk__remote_message_xml(pcmk__remote_t *remote);
int pcmk__connect_remote(const char *host, int port, int timeout_ms,
int *timer_id, int *sock_fd, void *userdata,
void (*callback) (void *userdata, int rc, int sock));
int pcmk__accept_remote_connection(int ssock, int *csock);
void pcmk__sockaddr2str(const void *sa, char *s);
/*!
* \internal
* \brief Check whether a node is a Pacemaker Remote node of any kind
*
* \param[in] node Node to check
*
* \return true if \p node is a remote, guest, or bundle node, otherwise false
*/
static inline bool
pcmk__is_pacemaker_remote_node(const pcmk_node_t *node)
{
return (node != NULL) && (node->details->type == pcmk_node_variant_remote);
}
/*!
* \internal
* \brief Check whether a node is a remote node
*
* \param[in] node Node to check
*
* \return true if \p node is a remote node, otherwise false
*/
static inline bool
pcmk__is_remote_node(const pcmk_node_t *node)
{
return pcmk__is_pacemaker_remote_node(node)
&& ((node->details->remote_rsc == NULL)
|| (node->details->remote_rsc->container == NULL));
}
/*!
* \internal
* \brief Check whether a node is a guest or bundle node
*
* \param[in] node Node to check
*
* \return true if \p node is a guest or bundle node, otherwise false
*/
static inline bool
pcmk__is_guest_or_bundle_node(const pcmk_node_t *node)
{
return pcmk__is_pacemaker_remote_node(node)
&& (node->details->remote_rsc != NULL)
&& (node->details->remote_rsc->container != NULL);
}
#ifdef HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
gnutls_session_t *pcmk__new_tls_session(int csock, unsigned int conn_type,
gnutls_credentials_type_t cred_type,
void *credentials);
int pcmk__init_tls_dh(gnutls_dh_params_t *dh_params);
int pcmk__read_handshake_data(const pcmk__client_t *client);
/*!
* \internal
* \brief Make a single attempt to perform the client TLS handshake
*
* \param[in,out] remote Newly established remote connection
* \param[out] gnutls_rc If this is non-NULL, it will be set to the GnuTLS
* rc (for logging) if this function returns EPROTO,
* otherwise GNUTLS_E_SUCCESS
*
* \return Standard Pacemaker return code
*/
int pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc);
/*!
* \internal
* \brief Perform client TLS handshake after establishing TCP socket
*
* \param[in,out] remote Newly established remote connection
* \param[in] timeout_sec Abort handshake if not completed within this time
* \param[out] gnutls_rc If this is non-NULL, it will be set to the GnuTLS
* rc (for logging) if this function returns EPROTO,
* otherwise GNUTLS_E_SUCCESS
*
* \return Standard Pacemaker return code
*/
int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
int *gnutls_rc);
#endif // HAVE_GNUTLS_GNUTLS_H
#endif // PCMK__CRM_COMMON_REMOTE_INTERNAL__H
diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c
index 980b363d97..b9b44298cd 100644
--- a/lib/cib/cib_remote.c
+++ b/lib/cib/cib_remote.c
@@ -1,646 +1,694 @@
/*
* Copyright 2008-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <netdb.h>
#include <termios.h>
#include <sys/socket.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
#include <crm/common/remote_internal.h>
#include <crm/common/output_internal.h>
#ifdef HAVE_GNUTLS_GNUTLS_H
# include <gnutls/gnutls.h>
// GnuTLS handshake timeout in seconds
#define TLS_HANDSHAKE_TIMEOUT 5
static gnutls_anon_client_credentials_t anon_cred_c;
static gboolean remote_gnutls_credentials_init = FALSE;
#endif // HAVE_GNUTLS_GNUTLS_H
#include <arpa/inet.h>
typedef struct cib_remote_opaque_s {
int port;
char *server;
char *user;
char *passwd;
gboolean encrypted;
pcmk__remote_t command;
pcmk__remote_t callback;
pcmk__output_t *out;
+ time_t start_time;
+ int timeout_sec;
} cib_remote_opaque_t;
static int
cib_remote_perform_op(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data,
xmlNode **output_data, int call_options,
const char *user_name)
{
int rc;
int remaining_time = 0;
time_t start_time;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
cib_remote_opaque_t *private = cib->variant_opaque;
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
if (output_data != NULL) {
*output_data = NULL;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
NULL, &op_msg);
if (rc != pcmk_ok) {
return rc;
}
if (pcmk_is_set(call_options, cib_transaction)) {
rc = cib__extend_transaction(cib, op_msg);
free_xml(op_msg);
return rc;
}
crm_trace("Sending %s message to the CIB manager", op);
if (!(call_options & cib_sync_call)) {
pcmk__remote_send_xml(&private->callback, op_msg);
} else {
pcmk__remote_send_xml(&private->command, op_msg);
}
free_xml(op_msg);
if ((call_options & cib_discard_reply)) {
crm_trace("Discarding reply");
return pcmk_ok;
} else if (!(call_options & cib_sync_call)) {
return cib->call_id;
}
crm_trace("Waiting for a synchronous reply");
start_time = time(NULL);
remaining_time = cib->call_timeout ? cib->call_timeout : 60;
rc = pcmk_rc_ok;
while (remaining_time > 0 && (rc != ENOTCONN)) {
int reply_id = -1;
int msg_id = cib->call_id;
rc = pcmk__read_remote_message(&private->command,
remaining_time * 1000);
op_reply = pcmk__remote_message_xml(&private->command);
if (!op_reply) {
break;
}
crm_element_value_int(op_reply, PCMK__XA_CIB_CALLID, &reply_id);
if (reply_id == msg_id) {
break;
} else if (reply_id < msg_id) {
crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
crm_log_xml_trace(op_reply, "Old reply");
} else if ((reply_id - 10000) > msg_id) {
/* wrap-around case */
crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
crm_log_xml_trace(op_reply, "Old reply");
} else {
crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id);
}
free_xml(op_reply);
op_reply = NULL;
/* wasn't the right reply, try and read some more */
remaining_time = time(NULL) - start_time;
}
if (rc == ENOTCONN) {
crm_err("Disconnected while waiting for reply.");
return -ENOTCONN;
} else if (op_reply == NULL) {
crm_err("No reply message - empty");
return -ENOMSG;
}
crm_trace("Synchronous reply received");
/* Start processing the reply... */
if (crm_element_value_int(op_reply, PCMK__XA_CIB_RC, &rc) != 0) {
rc = -EPROTO;
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (rc == pcmk_ok || rc == -EPERM) {
crm_log_xml_debug(op_reply, "passed");
} else {
crm_err("Call failed: %s", pcmk_strerror(rc));
crm_log_xml_warn(op_reply, "failed");
}
if (output_data == NULL) {
/* do nothing more */
} else if (!(call_options & cib_discard_reply)) {
xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA,
NULL, NULL);
xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (tmp == NULL) {
crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1);
} else {
*output_data = pcmk__xml_copy(NULL, tmp);
}
}
free_xml(op_reply);
return rc;
}
static int
cib_remote_callback_dispatch(gpointer user_data)
{
int rc;
cib_t *cib = user_data;
cib_remote_opaque_t *private = cib->variant_opaque;
xmlNode *msg = NULL;
+ const char *type = NULL;
- crm_info("Message on callback channel");
-
- rc = pcmk__read_remote_message(&private->callback, -1);
-
- msg = pcmk__remote_message_xml(&private->callback);
- while (msg) {
- const char *type = crm_element_value(msg, PCMK__XA_T);
+ /* If start time is 0, we've previously handled a complete message and this
+ * connection is being reused for a new message. Reset the start_time,
+ * giving this new message timeout_sec from now to complete.
+ */
+ if (private->start_time == 0) {
+ private->start_time = time(NULL);
+ }
- crm_trace("Activating %s callbacks...", type);
+ rc = pcmk__read_available_remote_data(&private->callback);
+ switch (rc) {
+ case pcmk_rc_ok:
+ /* We have the whole message so process it */
+ break;
- if (pcmk__str_eq(type, PCMK__VALUE_CIB, pcmk__str_none)) {
- cib_native_callback(cib, msg, 0, 0);
+ case EAGAIN:
+ /* Have we timed out? */
+ if (time(NULL) >= private->start_time + private->timeout_sec) {
+ crm_info("Error reading from CIB manager connection: %s",
+ pcmk_rc_str(ETIME));
+ return -1;
+ }
+
+ /* We haven't read the whole message yet */
+ return 0;
+
+ default:
+ /* Error */
+ crm_info("Error reading from CIB manager connection: %s",
+ pcmk_rc_str(rc));
+ return -1;
+ }
- } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
- g_list_foreach(cib->notify_list, cib_native_notify, msg);
+ msg = pcmk__remote_message_xml(&private->callback);
+ if (msg == NULL) {
+ private->start_time = 0;
+ return 0;
+ }
- } else {
- crm_err("Unknown message type: %s", type);
- }
+ type = crm_element_value(msg, PCMK__XA_T);
- free_xml(msg);
- msg = pcmk__remote_message_xml(&private->callback);
- }
+ crm_trace("Activating %s callbacks...", type);
- if (rc == ENOTCONN) {
- return -1;
+ if (pcmk__str_eq(type, PCMK__VALUE_CIB, pcmk__str_none)) {
+ cib_native_callback(cib, msg, 0, 0);
+ } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
+ g_list_foreach(cib->notify_list, cib_native_notify, msg);
+ } else {
+ crm_err("Unknown message type: %s", type);
}
+ free_xml(msg);
+ private->start_time = 0;
return 0;
}
static int
cib_remote_command_dispatch(gpointer user_data)
{
int rc;
cib_t *cib = user_data;
cib_remote_opaque_t *private = cib->variant_opaque;
- rc = pcmk__read_remote_message(&private->command, -1);
+ /* See cib_remote_callback_dispatch */
+ if (private->start_time == 0) {
+ private->start_time = time(NULL);
+ }
+
+ rc = pcmk__read_available_remote_data(&private->command);
+ if (rc == EAGAIN) {
+ /* Have we timed out? */
+ if (time(NULL) >= private->start_time + private->timeout_sec) {
+ crm_info("Error reading from CIB manager connection: %s",
+ pcmk_rc_str(ETIME));
+ return -1;
+ }
+
+ /* We haven't read the whole message yet */
+ return 0;
+ }
free(private->command.buffer);
private->command.buffer = NULL;
crm_err("received late reply for remote cib connection, discarding");
- if (rc == ENOTCONN) {
+ if (rc != pcmk_rc_ok) {
+ crm_info("Error reading from CIB manager connection: %s",
+ pcmk_rc_str(rc));
return -1;
}
+
+ private->start_time = 0;
return 0;
}
static int
cib_tls_close(cib_t *cib)
{
cib_remote_opaque_t *private = cib->variant_opaque;
#ifdef HAVE_GNUTLS_GNUTLS_H
if (private->encrypted) {
if (private->command.tls_session) {
gnutls_bye(*(private->command.tls_session), GNUTLS_SHUT_RDWR);
gnutls_deinit(*(private->command.tls_session));
gnutls_free(private->command.tls_session);
}
if (private->callback.tls_session) {
gnutls_bye(*(private->callback.tls_session), GNUTLS_SHUT_RDWR);
gnutls_deinit(*(private->callback.tls_session));
gnutls_free(private->callback.tls_session);
}
private->command.tls_session = NULL;
private->callback.tls_session = NULL;
if (remote_gnutls_credentials_init) {
gnutls_anon_free_client_credentials(anon_cred_c);
gnutls_global_deinit();
remote_gnutls_credentials_init = FALSE;
}
}
#endif
if (private->command.tcp_socket) {
shutdown(private->command.tcp_socket, SHUT_RDWR); /* no more receptions */
close(private->command.tcp_socket);
}
if (private->callback.tcp_socket) {
shutdown(private->callback.tcp_socket, SHUT_RDWR); /* no more receptions */
close(private->callback.tcp_socket);
}
private->command.tcp_socket = 0;
private->callback.tcp_socket = 0;
free(private->command.buffer);
free(private->callback.buffer);
private->command.buffer = NULL;
private->callback.buffer = NULL;
return 0;
}
static void
cib_remote_connection_destroy(gpointer user_data)
{
crm_err("Connection destroyed");
#ifdef HAVE_GNUTLS_GNUTLS_H
cib_tls_close(user_data);
#endif
}
static int
cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
{
cib_remote_opaque_t *private = cib->variant_opaque;
int rc;
xmlNode *answer = NULL;
xmlNode *login = NULL;
static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, };
cib_fd_callbacks.dispatch =
event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch;
cib_fd_callbacks.destroy = cib_remote_connection_destroy;
connection->tcp_socket = -1;
#ifdef HAVE_GNUTLS_GNUTLS_H
connection->tls_session = NULL;
#endif
rc = pcmk__connect_remote(private->server, private->port, 0, NULL,
&(connection->tcp_socket), NULL, NULL);
if (rc != pcmk_rc_ok) {
crm_info("Remote connection to %s:%d failed: %s " CRM_XS " rc=%d",
private->server, private->port, pcmk_rc_str(rc), rc);
return -ENOTCONN;
}
if (private->encrypted) {
int tls_rc = GNUTLS_E_SUCCESS;
/* initialize GnuTls lib */
#ifdef HAVE_GNUTLS_GNUTLS_H
if (remote_gnutls_credentials_init == FALSE) {
crm_gnutls_global_init();
gnutls_anon_allocate_client_credentials(&anon_cred_c);
remote_gnutls_credentials_init = TRUE;
}
/* bind the socket to GnuTls lib */
connection->tls_session = pcmk__new_tls_session(connection->tcp_socket,
GNUTLS_CLIENT,
GNUTLS_CRD_ANON,
anon_cred_c);
if (connection->tls_session == NULL) {
cib_tls_close(cib);
return -1;
}
rc = pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT,
&tls_rc);
if (rc != pcmk_rc_ok) {
crm_err("Remote CIB session creation for %s:%d failed: %s",
private->server, private->port,
(rc == EPROTO)? gnutls_strerror(tls_rc) : pcmk_rc_str(rc));
gnutls_deinit(*connection->tls_session);
gnutls_free(connection->tls_session);
connection->tls_session = NULL;
cib_tls_close(cib);
return -1;
}
#else
return -EPROTONOSUPPORT;
#endif
}
/* login to server */
login = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
crm_xml_add(login, PCMK_XA_OP, "authenticate");
crm_xml_add(login, PCMK_XA_USER, private->user);
crm_xml_add(login, PCMK__XA_PASSWORD, private->passwd);
crm_xml_add(login, PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD);
pcmk__remote_send_xml(connection, login);
free_xml(login);
rc = pcmk_ok;
if (pcmk__read_remote_message(connection, -1) == ENOTCONN) {
rc = -ENOTCONN;
}
answer = pcmk__remote_message_xml(connection);
crm_log_xml_trace(answer, "Reply");
if (answer == NULL) {
rc = -EPROTO;
} else {
/* grab the token */
const char *msg_type = crm_element_value(answer, PCMK__XA_CIB_OP);
const char *tmp_ticket = crm_element_value(answer,
PCMK__XA_CIB_CLIENTID);
if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
crm_err("Invalid registration message: %s", msg_type);
rc = -EPROTO;
} else if (tmp_ticket == NULL) {
rc = -EPROTO;
} else {
connection->token = strdup(tmp_ticket);
}
}
free_xml(answer);
answer = NULL;
if (rc != 0) {
cib_tls_close(cib);
return rc;
}
crm_trace("remote client connection established");
+ private->timeout_sec = 60;
connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH,
connection->tcp_socket, cib,
&cib_fd_callbacks);
return rc;
}
static int
cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_remote_opaque_t *private = cib->variant_opaque;
xmlNode *hello = NULL;
if (private->passwd == NULL) {
if (private->out == NULL) {
/* If no pcmk__output_t is set, just assume that a text prompt
* is good enough.
*/
pcmk__text_prompt("Password", false, &(private->passwd));
} else {
private->out->prompt("Password", false, &(private->passwd));
}
}
if (private->server == NULL || private->user == NULL) {
rc = -EINVAL;
}
if (rc == pcmk_ok) {
rc = cib_tls_signon(cib, &(private->command), FALSE);
}
if (rc == pcmk_ok) {
rc = cib_tls_signon(cib, &(private->callback), TRUE);
}
if (rc == pcmk_ok) {
rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, cib_none,
NULL, name, &hello);
}
if (rc == pcmk_ok) {
rc = pcmk__remote_send_xml(&private->command, hello);
rc = pcmk_rc2legacy(rc);
free_xml(hello);
}
if (rc == pcmk_ok) {
crm_info("Opened connection to %s:%d for %s",
private->server, private->port, name);
cib->state = cib_connected_command;
cib->type = cib_command;
} else {
crm_info("Connection to %s:%d for %s failed: %s\n",
private->server, private->port, name, pcmk_strerror(rc));
}
return rc;
}
static int
cib_remote_signoff(cib_t *cib)
{
int rc = pcmk_ok;
crm_debug("Disconnecting from the CIB manager");
#ifdef HAVE_GNUTLS_GNUTLS_H
cib_tls_close(cib);
#endif
cib->cmds->end_transaction(cib, false, cib_none);
cib->state = cib_disconnected;
cib->type = cib_no_connection;
return rc;
}
static int
cib_remote_free(cib_t *cib)
{
int rc = pcmk_ok;
crm_warn("Freeing CIB");
if (cib->state != cib_disconnected) {
rc = cib_remote_signoff(cib);
if (rc == pcmk_ok) {
cib_remote_opaque_t *private = cib->variant_opaque;
free(private->server);
free(private->user);
free(private->passwd);
free(cib->cmds);
free(cib->user);
free(private);
free(cib);
}
}
return rc;
}
static int
cib_remote_inputfd(cib_t * cib)
{
cib_remote_opaque_t *private = cib->variant_opaque;
return private->callback.tcp_socket;
}
static int
cib_remote_register_notification(cib_t * cib, const char *callback, int enabled)
{
xmlNode *notify_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
cib_remote_opaque_t *private = cib->variant_opaque;
crm_xml_add(notify_msg, PCMK__XA_CIB_OP, PCMK__VALUE_CIB_NOTIFY);
crm_xml_add(notify_msg, PCMK__XA_CIB_NOTIFY_TYPE, callback);
crm_xml_add_int(notify_msg, PCMK__XA_CIB_NOTIFY_ACTIVATE, enabled);
pcmk__remote_send_xml(&private->callback, notify_msg);
free_xml(notify_msg);
return pcmk_ok;
}
static int
cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Get the given CIB connection's unique client identifiers
*
* These can be used to check whether this client requested the action that
* triggered a CIB notification.
*
* \param[in] cib CIB connection
* \param[out] async_id If not \p NULL, where to store asynchronous client ID
* \param[out] sync_id If not \p NULL, where to store synchronous client ID
*
* \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
*
* \note This is the \p cib_remote variant implementation of
* \p cib_api_operations_t:client_id().
* \note The client IDs are assigned during CIB sign-on.
*/
static int
cib_remote_client_id(const cib_t *cib, const char **async_id,
const char **sync_id)
{
cib_remote_opaque_t *private = cib->variant_opaque;
if (async_id != NULL) {
// private->callback is the channel for async requests
*async_id = private->callback.token;
}
if (sync_id != NULL) {
// private->command is the channel for sync requests
*sync_id = private->command.token;
}
return pcmk_ok;
}
cib_t *
cib_remote_new(const char *server, const char *user, const char *passwd, int port,
gboolean encrypted)
{
cib_remote_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
private = calloc(1, sizeof(cib_remote_opaque_t));
if (private == NULL) {
free(cib);
return NULL;
}
cib->variant = cib_remote;
cib->variant_opaque = private;
private->server = pcmk__str_copy(server);
private->user = pcmk__str_copy(user);
private->passwd = pcmk__str_copy(passwd);
private->port = port;
private->encrypted = encrypted;
/* assign variant specific ops */
cib->delegate_fn = cib_remote_perform_op;
cib->cmds->signon = cib_remote_signon;
cib->cmds->signoff = cib_remote_signoff;
cib->cmds->free = cib_remote_free;
cib->cmds->inputfd = cib_remote_inputfd; // Deprecated method
cib->cmds->register_notification = cib_remote_register_notification;
cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify;
cib->cmds->client_id = cib_remote_client_id;
return cib;
}
void
cib__set_output(cib_t *cib, pcmk__output_t *out)
{
cib_remote_opaque_t *private;
if (cib->variant != cib_remote) {
return;
}
private = cib->variant_opaque;
private->out = out;
}
diff --git a/lib/common/remote.c b/lib/common/remote.c
index a9c7c93d47..b25e5ce69f 100644
--- a/lib/common/remote.c
+++ b/lib/common/remote.c
@@ -1,1311 +1,1312 @@
/*
* Copyright 2008-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <inttypes.h> // PRIx32
#include <glib.h>
#include <bzlib.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#include <crm/common/remote_internal.h>
#ifdef HAVE_GNUTLS_GNUTLS_H
# include <gnutls/gnutls.h>
#endif
/* Swab macros from linux/swab.h */
#ifdef HAVE_LINUX_SWAB_H
# include <linux/swab.h>
#else
/*
* casts are necessary for constants, because we never know how for sure
* how U/UL/ULL map to __u16, __u32, __u64. At least not in a portable way.
*/
#define __swab16(x) ((uint16_t)( \
(((uint16_t)(x) & (uint16_t)0x00ffU) << 8) | \
(((uint16_t)(x) & (uint16_t)0xff00U) >> 8)))
#define __swab32(x) ((uint32_t)( \
(((uint32_t)(x) & (uint32_t)0x000000ffUL) << 24) | \
(((uint32_t)(x) & (uint32_t)0x0000ff00UL) << 8) | \
(((uint32_t)(x) & (uint32_t)0x00ff0000UL) >> 8) | \
(((uint32_t)(x) & (uint32_t)0xff000000UL) >> 24)))
#define __swab64(x) ((uint64_t)( \
(((uint64_t)(x) & (uint64_t)0x00000000000000ffULL) << 56) | \
(((uint64_t)(x) & (uint64_t)0x000000000000ff00ULL) << 40) | \
(((uint64_t)(x) & (uint64_t)0x0000000000ff0000ULL) << 24) | \
(((uint64_t)(x) & (uint64_t)0x00000000ff000000ULL) << 8) | \
(((uint64_t)(x) & (uint64_t)0x000000ff00000000ULL) >> 8) | \
(((uint64_t)(x) & (uint64_t)0x0000ff0000000000ULL) >> 24) | \
(((uint64_t)(x) & (uint64_t)0x00ff000000000000ULL) >> 40) | \
(((uint64_t)(x) & (uint64_t)0xff00000000000000ULL) >> 56)))
#endif
#define REMOTE_MSG_VERSION 1
#define ENDIAN_LOCAL 0xBADADBBD
struct remote_header_v0 {
uint32_t endian; /* Detect messages from hosts with different endian-ness */
uint32_t version;
uint64_t id;
uint64_t flags;
uint32_t size_total;
uint32_t payload_offset;
uint32_t payload_compressed;
uint32_t payload_uncompressed;
/* New fields get added here */
} __attribute__ ((packed));
/*!
* \internal
* \brief Retrieve remote message header, in local endianness
*
* Return a pointer to the header portion of a remote connection's message
* buffer, converting the header to local endianness if needed.
*
* \param[in,out] remote Remote connection with new message
*
* \return Pointer to message header, localized if necessary
*/
static struct remote_header_v0 *
localized_remote_header(pcmk__remote_t *remote)
{
struct remote_header_v0 *header = (struct remote_header_v0 *)remote->buffer;
if(remote->buffer_offset < sizeof(struct remote_header_v0)) {
return NULL;
} else if(header->endian != ENDIAN_LOCAL) {
uint32_t endian = __swab32(header->endian);
CRM_LOG_ASSERT(endian == ENDIAN_LOCAL);
if(endian != ENDIAN_LOCAL) {
crm_err("Invalid message detected, endian mismatch: %" PRIx32
" is neither %" PRIx32 " nor the swab'd %" PRIx32,
ENDIAN_LOCAL, header->endian, endian);
return NULL;
}
header->id = __swab64(header->id);
header->flags = __swab64(header->flags);
header->endian = __swab32(header->endian);
header->version = __swab32(header->version);
header->size_total = __swab32(header->size_total);
header->payload_offset = __swab32(header->payload_offset);
header->payload_compressed = __swab32(header->payload_compressed);
header->payload_uncompressed = __swab32(header->payload_uncompressed);
}
return header;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
int
pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
{
int rc = pcmk_rc_ok;
if (gnutls_rc != NULL) {
*gnutls_rc = GNUTLS_E_SUCCESS;
}
rc = gnutls_handshake(*remote->tls_session);
switch (rc) {
case GNUTLS_E_SUCCESS:
rc = pcmk_rc_ok;
break;
case GNUTLS_E_INTERRUPTED:
case GNUTLS_E_AGAIN:
rc = EAGAIN;
break;
default:
if (gnutls_rc != NULL) {
*gnutls_rc = rc;
}
rc = EPROTO;
break;
}
return rc;
}
int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
int *gnutls_rc)
{
const time_t time_limit = time(NULL) + timeout_sec;
do {
int rc = pcmk__tls_client_try_handshake(remote, gnutls_rc);
if (rc != EAGAIN) {
return rc;
}
} while (time(NULL) < time_limit);
return ETIME;
}
/*!
* \internal
* \brief Set minimum prime size required by TLS client
*
* \param[in] session TLS session to affect
*/
static void
set_minimum_dh_bits(const gnutls_session_t *session)
{
int dh_min_bits;
pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MIN_BITS), &dh_min_bits,
0);
/* This function is deprecated since GnuTLS 3.1.7, in favor of letting
* the priority string imply the DH requirements, but this is the only
* way to give the user control over compatibility with older servers.
*/
if (dh_min_bits > 0) {
crm_info("Requiring server use a Diffie-Hellman prime of at least %d bits",
dh_min_bits);
crm_warn("Support for the " PCMK__ENV_DH_MIN_BITS " "
"environment variable is deprecated and will be removed "
"in a future release");
gnutls_dh_set_prime_bits(*session, dh_min_bits);
}
}
static unsigned int
get_bound_dh_bits(unsigned int dh_bits)
{
int dh_min_bits;
int dh_max_bits;
pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MIN_BITS), &dh_min_bits,
0);
pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits,
0);
if ((dh_max_bits > 0) && (dh_max_bits < dh_min_bits)) {
crm_warn("Ignoring PCMK_dh_max_bits less than PCMK_dh_min_bits");
dh_max_bits = 0;
}
if ((dh_min_bits > 0) && (dh_bits < dh_min_bits)) {
return dh_min_bits;
}
if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) {
return dh_max_bits;
}
return dh_bits;
}
/*!
* \internal
* \brief Initialize a new TLS session
*
* \param[in] csock Connected socket for TLS session
* \param[in] conn_type GNUTLS_SERVER or GNUTLS_CLIENT
* \param[in] cred_type GNUTLS_CRD_ANON or GNUTLS_CRD_PSK
* \param[in] credentials TLS session credentials
*
* \return Pointer to newly created session object, or NULL on error
*/
gnutls_session_t *
pcmk__new_tls_session(int csock, unsigned int conn_type,
gnutls_credentials_type_t cred_type, void *credentials)
{
int rc = GNUTLS_E_SUCCESS;
const char *prio_base = NULL;
char *prio = NULL;
gnutls_session_t *session = NULL;
/* Determine list of acceptable ciphers, etc. Pacemaker always adds the
* values required for its functionality.
*
* For an example of anonymous authentication, see:
* http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication
*/
prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES);
if (prio_base == NULL) {
prio_base = PCMK_GNUTLS_PRIORITIES;
}
prio = crm_strdup_printf("%s:%s", prio_base,
(cred_type == GNUTLS_CRD_ANON)? "+ANON-DH" : "+DHE-PSK:+PSK");
session = gnutls_malloc(sizeof(gnutls_session_t));
if (session == NULL) {
rc = GNUTLS_E_MEMORY_ERROR;
goto error;
}
rc = gnutls_init(session, conn_type);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
/* @TODO On the server side, it would be more efficient to cache the
* priority with gnutls_priority_init2() and set it with
* gnutls_priority_set() for all sessions.
*/
rc = gnutls_priority_set_direct(*session, prio, NULL);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
if (conn_type == GNUTLS_CLIENT) {
set_minimum_dh_bits(session);
}
gnutls_transport_set_ptr(*session,
(gnutls_transport_ptr_t) GINT_TO_POINTER(csock));
rc = gnutls_credentials_set(*session, cred_type, credentials);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
free(prio);
return session;
error:
crm_err("Could not initialize %s TLS %s session: %s "
CRM_XS " rc=%d priority='%s'",
(cred_type == GNUTLS_CRD_ANON)? "anonymous" : "PSK",
(conn_type == GNUTLS_SERVER)? "server" : "client",
gnutls_strerror(rc), rc, prio);
free(prio);
if (session != NULL) {
gnutls_free(session);
}
return NULL;
}
/*!
* \internal
* \brief Initialize Diffie-Hellman parameters for a TLS server
*
* \param[out] dh_params Parameter object to initialize
*
* \return Standard Pacemaker return code
* \todo The current best practice is to allow the client and server to
* negotiate the Diffie-Hellman parameters via a TLS extension (RFC 7919).
* However, we have to support both older versions of GnuTLS (<3.6) that
* don't support the extension on our side, and older Pacemaker versions
* that don't support the extension on the other side. The next best
* practice would be to use a known good prime (see RFC 5114 section 2.2),
* possibly stored in a file distributed with Pacemaker.
*/
int
pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
{
int rc = GNUTLS_E_SUCCESS;
unsigned int dh_bits = 0;
rc = gnutls_dh_params_init(dh_params);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
GNUTLS_SEC_PARAM_NORMAL);
if (dh_bits == 0) {
rc = GNUTLS_E_DH_PRIME_UNACCEPTABLE;
goto error;
}
dh_bits = get_bound_dh_bits(dh_bits);
crm_info("Generating Diffie-Hellman parameters with %u-bit prime for TLS",
dh_bits);
rc = gnutls_dh_params_generate2(*dh_params, dh_bits);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
return pcmk_rc_ok;
error:
crm_err("Could not initialize Diffie-Hellman parameters for TLS: %s "
CRM_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
/*!
* \internal
* \brief Process handshake data from TLS client
*
* Read as much TLS handshake data as is available.
*
* \param[in] client Client connection
*
* \return Standard Pacemaker return code (of particular interest, EAGAIN
* if some data was successfully read but more data is needed)
*/
int
pcmk__read_handshake_data(const pcmk__client_t *client)
{
int rc = 0;
pcmk__assert((client != NULL) && (client->remote != NULL)
&& (client->remote->tls_session != NULL));
do {
rc = gnutls_handshake(*client->remote->tls_session);
} while (rc == GNUTLS_E_INTERRUPTED);
if (rc == GNUTLS_E_AGAIN) {
/* No more data is available at the moment. This function should be
* invoked again once the client sends more.
*/
return EAGAIN;
} else if (rc != GNUTLS_E_SUCCESS) {
crm_err("TLS handshake with remote client failed: %s "
CRM_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
return pcmk_rc_ok;
}
// \return Standard Pacemaker return code
static int
send_tls(gnutls_session_t *session, struct iovec *iov)
{
const char *unsent = iov->iov_base;
size_t unsent_len = iov->iov_len;
ssize_t gnutls_rc;
if (unsent == NULL) {
return EINVAL;
}
crm_trace("Sending TLS message of %llu bytes",
(unsigned long long) unsent_len);
while (true) {
gnutls_rc = gnutls_record_send(*session, unsent, unsent_len);
if (gnutls_rc == GNUTLS_E_INTERRUPTED || gnutls_rc == GNUTLS_E_AGAIN) {
crm_trace("Retrying to send %llu bytes remaining",
(unsigned long long) unsent_len);
} else if (gnutls_rc < 0) {
// Caller can log as error if necessary
crm_info("TLS connection terminated: %s " CRM_XS " rc=%lld",
gnutls_strerror((int) gnutls_rc),
(long long) gnutls_rc);
return ECONNABORTED;
} else if (gnutls_rc < unsent_len) {
crm_trace("Sent %lld of %llu bytes remaining",
(long long) gnutls_rc, (unsigned long long) unsent_len);
unsent_len -= gnutls_rc;
unsent += gnutls_rc;
} else {
crm_trace("Sent all %lld bytes remaining", (long long) gnutls_rc);
break;
}
}
return pcmk_rc_ok;
}
#endif
// \return Standard Pacemaker return code
static int
send_plaintext(int sock, struct iovec *iov)
{
const char *unsent = iov->iov_base;
size_t unsent_len = iov->iov_len;
ssize_t write_rc;
if (unsent == NULL) {
return EINVAL;
}
crm_debug("Sending plaintext message of %llu bytes to socket %d",
(unsigned long long) unsent_len, sock);
while (true) {
write_rc = write(sock, unsent, unsent_len);
if (write_rc < 0) {
int rc = errno;
if ((errno == EINTR) || (errno == EAGAIN)) {
crm_trace("Retrying to send %llu bytes remaining to socket %d",
(unsigned long long) unsent_len, sock);
continue;
}
// Caller can log as error if necessary
crm_info("Could not send message: %s " CRM_XS " rc=%d socket=%d",
pcmk_rc_str(rc), rc, sock);
return rc;
} else if (write_rc < unsent_len) {
crm_trace("Sent %lld of %llu bytes remaining",
(long long) write_rc, (unsigned long long) unsent_len);
unsent += write_rc;
unsent_len -= write_rc;
continue;
} else {
crm_trace("Sent all %lld bytes remaining: %.100s",
(long long) write_rc, (char *) (iov->iov_base));
break;
}
}
return pcmk_rc_ok;
}
// \return Standard Pacemaker return code
static int
remote_send_iovs(pcmk__remote_t *remote, struct iovec *iov, int iovs)
{
int rc = pcmk_rc_ok;
for (int lpc = 0; (lpc < iovs) && (rc == pcmk_rc_ok); lpc++) {
#ifdef HAVE_GNUTLS_GNUTLS_H
if (remote->tls_session) {
rc = send_tls(remote->tls_session, &(iov[lpc]));
continue;
}
#endif
if (remote->tcp_socket) {
rc = send_plaintext(remote->tcp_socket, &(iov[lpc]));
} else {
rc = ESOCKTNOSUPPORT;
}
}
return rc;
}
/*!
* \internal
* \brief Send an XML message over a Pacemaker Remote connection
*
* \param[in,out] remote Pacemaker Remote connection to use
* \param[in] msg XML to send
*
* \return Standard Pacemaker return code
*/
int
pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg)
{
int rc = pcmk_rc_ok;
static uint64_t id = 0;
GString *xml_text = NULL;
struct iovec iov[2];
struct remote_header_v0 *header;
CRM_CHECK((remote != NULL) && (msg != NULL), return EINVAL);
xml_text = g_string_sized_new(1024);
pcmk__xml_string(msg, 0, xml_text, 0);
CRM_CHECK(xml_text->len > 0,
g_string_free(xml_text, TRUE); return EINVAL);
header = pcmk__assert_alloc(1, sizeof(struct remote_header_v0));
iov[0].iov_base = header;
iov[0].iov_len = sizeof(struct remote_header_v0);
iov[1].iov_len = 1 + xml_text->len;
iov[1].iov_base = g_string_free(xml_text, FALSE);
id++;
header->id = id;
header->endian = ENDIAN_LOCAL;
header->version = REMOTE_MSG_VERSION;
header->payload_offset = iov[0].iov_len;
header->payload_uncompressed = iov[1].iov_len;
header->size_total = iov[0].iov_len + iov[1].iov_len;
rc = remote_send_iovs(remote, iov, 2);
if (rc != pcmk_rc_ok) {
crm_err("Could not send remote message: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
free(iov[0].iov_base);
g_free((gchar *) iov[1].iov_base);
return rc;
}
/*!
* \internal
* \brief Obtain the XML from the currently buffered remote connection message
*
* \param[in,out] remote Remote connection possibly with message available
*
* \return Newly allocated XML object corresponding to message data, or NULL
* \note This effectively removes the message from the connection buffer.
*/
xmlNode *
pcmk__remote_message_xml(pcmk__remote_t *remote)
{
xmlNode *xml = NULL;
struct remote_header_v0 *header = localized_remote_header(remote);
if (header == NULL) {
return NULL;
}
/* Support compression on the receiving end now, in case we ever want to add it later */
if (header->payload_compressed) {
int rc = 0;
unsigned int size_u = 1 + header->payload_uncompressed;
char *uncompressed =
pcmk__assert_alloc(1, header->payload_offset + size_u);
crm_trace("Decompressing message data %d bytes into %d bytes",
header->payload_compressed, size_u);
rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset, &size_u,
remote->buffer + header->payload_offset,
header->payload_compressed, 1, 0);
rc = pcmk__bzlib2rc(rc);
if (rc != pcmk_rc_ok && header->version > REMOTE_MSG_VERSION) {
crm_warn("Couldn't decompress v%d message, we only understand v%d",
header->version, REMOTE_MSG_VERSION);
free(uncompressed);
return NULL;
} else if (rc != pcmk_rc_ok) {
crm_err("Decompression failed: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
free(uncompressed);
return NULL;
}
pcmk__assert(size_u == header->payload_uncompressed);
memcpy(uncompressed, remote->buffer, header->payload_offset); /* Preserve the header */
remote->buffer_size = header->payload_offset + size_u;
free(remote->buffer);
remote->buffer = uncompressed;
header = localized_remote_header(remote);
}
/* take ownership of the buffer */
remote->buffer_offset = 0;
CRM_LOG_ASSERT(remote->buffer[sizeof(struct remote_header_v0) + header->payload_uncompressed - 1] == 0);
xml = pcmk__xml_parse(remote->buffer + header->payload_offset);
if (xml == NULL && header->version > REMOTE_MSG_VERSION) {
crm_warn("Couldn't parse v%d message, we only understand v%d",
header->version, REMOTE_MSG_VERSION);
} else if (xml == NULL) {
crm_err("Couldn't parse: '%.120s'", remote->buffer + header->payload_offset);
}
+ crm_log_xml_trace(xml, "[remote msg]");
return xml;
}
static int
get_remote_socket(const pcmk__remote_t *remote)
{
#ifdef HAVE_GNUTLS_GNUTLS_H
if (remote->tls_session) {
void *sock_ptr = gnutls_transport_get_ptr(*remote->tls_session);
return GPOINTER_TO_INT(sock_ptr);
}
#endif
if (remote->tcp_socket) {
return remote->tcp_socket;
}
crm_err("Remote connection type undetermined (bug?)");
return -1;
}
/*!
* \internal
* \brief Wait for a remote session to have data to read
*
* \param[in] remote Connection to check
* \param[in] timeout_ms Maximum time (in ms) to wait
*
* \return Standard Pacemaker return code (of particular interest, pcmk_rc_ok if
* there is data ready to be read, and ETIME if there is no data within
* the specified timeout)
*/
int
pcmk__remote_ready(const pcmk__remote_t *remote, int timeout_ms)
{
struct pollfd fds = { 0, };
int sock = 0;
int rc = 0;
time_t start;
int timeout = timeout_ms;
sock = get_remote_socket(remote);
if (sock <= 0) {
crm_trace("No longer connected");
return ENOTCONN;
}
start = time(NULL);
errno = 0;
do {
fds.fd = sock;
fds.events = POLLIN;
/* If we got an EINTR while polling, and we have a
* specific timeout we are trying to honor, attempt
* to adjust the timeout to the closest second. */
if (errno == EINTR && (timeout > 0)) {
timeout = timeout_ms - ((time(NULL) - start) * 1000);
if (timeout < 1000) {
timeout = 1000;
}
}
rc = poll(&fds, 1, timeout);
} while (rc < 0 && errno == EINTR);
if (rc < 0) {
return errno;
}
return (rc == 0)? ETIME : pcmk_rc_ok;
}
/*!
* \internal
* \brief Read bytes from non-blocking remote connection
*
* \param[in,out] remote Remote connection to read
*
* \return Standard Pacemaker return code (of particular interest, pcmk_rc_ok if
* a full message has been received, or EAGAIN for a partial message)
* \note Use only with non-blocking sockets after polling the socket.
* \note This function will return when the socket read buffer is empty or an
* error is encountered.
*/
-static int
-read_available_remote_data(pcmk__remote_t *remote)
+int
+pcmk__read_available_remote_data(pcmk__remote_t *remote)
{
int rc = pcmk_rc_ok;
size_t read_len = sizeof(struct remote_header_v0);
struct remote_header_v0 *header = localized_remote_header(remote);
bool received = false;
ssize_t read_rc;
if(header) {
/* Stop at the end of the current message */
read_len = header->size_total;
}
/* automatically grow the buffer when needed */
if(remote->buffer_size < read_len) {
remote->buffer_size = 2 * read_len;
crm_trace("Expanding buffer to %llu bytes",
(unsigned long long) remote->buffer_size);
remote->buffer = pcmk__realloc(remote->buffer, remote->buffer_size + 1);
}
#ifdef HAVE_GNUTLS_GNUTLS_H
if (!received && remote->tls_session) {
read_rc = gnutls_record_recv(*(remote->tls_session),
remote->buffer + remote->buffer_offset,
remote->buffer_size - remote->buffer_offset);
if (read_rc == GNUTLS_E_INTERRUPTED) {
rc = EINTR;
} else if (read_rc == GNUTLS_E_AGAIN) {
rc = EAGAIN;
} else if (read_rc < 0) {
crm_debug("TLS receive failed: %s (%lld)",
gnutls_strerror(read_rc), (long long) read_rc);
rc = EIO;
}
received = true;
}
#endif
if (!received && remote->tcp_socket) {
read_rc = read(remote->tcp_socket,
remote->buffer + remote->buffer_offset,
remote->buffer_size - remote->buffer_offset);
if (read_rc < 0) {
rc = errno;
}
received = true;
}
if (!received) {
crm_err("Remote connection type undetermined (bug?)");
return ESOCKTNOSUPPORT;
}
/* process any errors. */
if (read_rc > 0) {
remote->buffer_offset += read_rc;
/* always null terminate buffer, the +1 to alloc always allows for this. */
remote->buffer[remote->buffer_offset] = '\0';
crm_trace("Received %lld more bytes (%llu total)",
(long long) read_rc,
(unsigned long long) remote->buffer_offset);
} else if ((rc == EINTR) || (rc == EAGAIN)) {
crm_trace("No data available for non-blocking remote read: %s (%d)",
pcmk_rc_str(rc), rc);
} else if (read_rc == 0) {
crm_debug("End of remote data encountered after %llu bytes",
(unsigned long long) remote->buffer_offset);
return ENOTCONN;
} else {
crm_debug("Error receiving remote data after %llu bytes: %s (%d)",
(unsigned long long) remote->buffer_offset,
pcmk_rc_str(rc), rc);
return ENOTCONN;
}
header = localized_remote_header(remote);
if(header) {
if(remote->buffer_offset < header->size_total) {
crm_trace("Read partial remote message (%llu of %u bytes)",
(unsigned long long) remote->buffer_offset,
header->size_total);
} else {
crm_trace("Read full remote message of %llu bytes",
(unsigned long long) remote->buffer_offset);
return pcmk_rc_ok;
}
}
return EAGAIN;
}
/*!
* \internal
* \brief Read one message from a remote connection
*
* \param[in,out] remote Remote connection to read
* \param[in] timeout_ms Fail if message not read in this many milliseconds
* (10s will be used if 0, and 60s if negative)
*
* \return Standard Pacemaker return code
*/
int
pcmk__read_remote_message(pcmk__remote_t *remote, int timeout_ms)
{
int rc = pcmk_rc_ok;
time_t start = time(NULL);
int remaining_timeout = 0;
if (timeout_ms == 0) {
timeout_ms = 10000;
} else if (timeout_ms < 0) {
timeout_ms = 60000;
}
remaining_timeout = timeout_ms;
while (remaining_timeout > 0) {
crm_trace("Waiting for remote data (%d ms of %d ms timeout remaining)",
remaining_timeout, timeout_ms);
rc = pcmk__remote_ready(remote, remaining_timeout);
if (rc == ETIME) {
crm_err("Timed out (%d ms) while waiting for remote data",
remaining_timeout);
return rc;
} else if (rc != pcmk_rc_ok) {
crm_debug("Wait for remote data aborted (will retry): %s "
CRM_XS " rc=%d", pcmk_rc_str(rc), rc);
} else {
- rc = read_available_remote_data(remote);
+ rc = pcmk__read_available_remote_data(remote);
if (rc == pcmk_rc_ok) {
return rc;
} else if (rc == EAGAIN) {
crm_trace("Waiting for more remote data");
} else {
crm_debug("Could not receive remote data: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
}
// Don't waste time retrying after fatal errors
if ((rc == ENOTCONN) || (rc == ESOCKTNOSUPPORT)) {
return rc;
}
remaining_timeout = timeout_ms - ((time(NULL) - start) * 1000);
}
return ETIME;
}
struct tcp_async_cb_data {
int sock;
int timeout_ms;
time_t start;
void *userdata;
void (*callback) (void *userdata, int rc, int sock);
};
// \return TRUE if timer should be rescheduled, FALSE otherwise
static gboolean
check_connect_finished(gpointer userdata)
{
struct tcp_async_cb_data *cb_data = userdata;
int rc;
fd_set rset, wset;
struct timeval ts = { 0, };
if (cb_data->start == 0) {
// Last connect() returned success immediately
rc = pcmk_rc_ok;
goto dispatch_done;
}
// If the socket is ready for reading or writing, the connect succeeded
FD_ZERO(&rset);
FD_SET(cb_data->sock, &rset);
wset = rset;
rc = select(cb_data->sock + 1, &rset, &wset, NULL, &ts);
if (rc < 0) { // select() error
rc = errno;
if ((rc == EINPROGRESS) || (rc == EAGAIN)) {
if ((time(NULL) - cb_data->start) < (cb_data->timeout_ms / 1000)) {
return TRUE; // There is time left, so reschedule timer
} else {
rc = ETIMEDOUT;
}
}
crm_trace("Could not check socket %d for connection success: %s (%d)",
cb_data->sock, pcmk_rc_str(rc), rc);
} else if (rc == 0) { // select() timeout
if ((time(NULL) - cb_data->start) < (cb_data->timeout_ms / 1000)) {
return TRUE; // There is time left, so reschedule timer
}
crm_debug("Timed out while waiting for socket %d connection success",
cb_data->sock);
rc = ETIMEDOUT;
// select() returned number of file descriptors that are ready
} else if (FD_ISSET(cb_data->sock, &rset)
|| FD_ISSET(cb_data->sock, &wset)) {
// The socket is ready; check it for connection errors
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(cb_data->sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
rc = errno;
crm_trace("Couldn't check socket %d for connection errors: %s (%d)",
cb_data->sock, pcmk_rc_str(rc), rc);
} else if (error != 0) {
rc = error;
crm_trace("Socket %d connected with error: %s (%d)",
cb_data->sock, pcmk_rc_str(rc), rc);
} else {
rc = pcmk_rc_ok;
}
} else { // Should not be possible
crm_trace("select() succeeded, but socket %d not in resulting "
"read/write sets", cb_data->sock);
rc = EAGAIN;
}
dispatch_done:
if (rc == pcmk_rc_ok) {
crm_trace("Socket %d is connected", cb_data->sock);
} else {
close(cb_data->sock);
cb_data->sock = -1;
}
if (cb_data->callback) {
cb_data->callback(cb_data->userdata, rc, cb_data->sock);
}
free(cb_data);
return FALSE; // Do not reschedule timer
}
/*!
* \internal
* \brief Attempt to connect socket, calling callback when done
*
* Set a given socket non-blocking, then attempt to connect to it,
* retrying periodically until success or a timeout is reached.
* Call a caller-supplied callback function when completed.
*
* \param[in] sock Newly created socket
* \param[in] addr Socket address information for connect
* \param[in] addrlen Size of socket address information in bytes
* \param[in] timeout_ms Fail if not connected within this much time
* \param[out] timer_id If not NULL, store retry timer ID here
* \param[in] userdata User data to pass to callback
* \param[in] callback Function to call when connection attempt completes
*
* \return Standard Pacemaker return code
*/
static int
connect_socket_retry(int sock, const struct sockaddr *addr, socklen_t addrlen,
int timeout_ms, int *timer_id, void *userdata,
void (*callback) (void *userdata, int rc, int sock))
{
int rc = 0;
int interval = 500;
int timer;
struct tcp_async_cb_data *cb_data = NULL;
rc = pcmk__set_nonblocking(sock);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set socket non-blocking: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
return rc;
}
rc = connect(sock, addr, addrlen);
if (rc < 0 && (errno != EINPROGRESS) && (errno != EAGAIN)) {
rc = errno;
crm_warn("Could not connect socket: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
return rc;
}
cb_data = pcmk__assert_alloc(1, sizeof(struct tcp_async_cb_data));
cb_data->userdata = userdata;
cb_data->callback = callback;
cb_data->sock = sock;
cb_data->timeout_ms = timeout_ms;
if (rc == 0) {
/* The connect was successful immediately, we still return to mainloop
* and let this callback get called later. This avoids the user of this api
* to have to account for the fact the callback could be invoked within this
* function before returning. */
cb_data->start = 0;
interval = 1;
} else {
cb_data->start = time(NULL);
}
/* This timer function does a non-blocking poll on the socket to see if we
* can use it. Once we can, the connect has completed. This method allows us
* to connect without blocking the mainloop.
*
* @TODO Use a mainloop fd callback for this instead of polling. Something
* about the way mainloop is currently polling prevents this from
* working at the moment though. (See connect(2) regarding EINPROGRESS
* for possible new handling needed.)
*/
crm_trace("Scheduling check in %dms for whether connect to fd %d finished",
interval, sock);
timer = g_timeout_add(interval, check_connect_finished, cb_data);
if (timer_id) {
*timer_id = timer;
}
// timer callback should be taking care of cb_data
// cppcheck-suppress memleak
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Attempt once to connect socket and set it non-blocking
*
* \param[in] sock Newly created socket
* \param[in] addr Socket address information for connect
* \param[in] addrlen Size of socket address information in bytes
*
* \return Standard Pacemaker return code
*/
static int
connect_socket_once(int sock, const struct sockaddr *addr, socklen_t addrlen)
{
int rc = connect(sock, addr, addrlen);
if (rc < 0) {
rc = errno;
crm_warn("Could not connect socket: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
return rc;
}
rc = pcmk__set_nonblocking(sock);
if (rc != pcmk_rc_ok) {
crm_warn("Could not set socket non-blocking: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
return rc;
}
return pcmk_ok;
}
/*!
* \internal
* \brief Connect to server at specified TCP port
*
* \param[in] host Name of server to connect to
* \param[in] port Server port to connect to
* \param[in] timeout_ms If asynchronous, fail if not connected in this time
* \param[out] timer_id If asynchronous and this is non-NULL, retry timer ID
* will be put here (for ease of cancelling by caller)
* \param[out] sock_fd Where to store socket file descriptor
* \param[in] userdata If asynchronous, data to pass to callback
* \param[in] callback If NULL, attempt a single synchronous connection,
* otherwise retry asynchronously then call this
*
* \return Standard Pacemaker return code
*/
int
pcmk__connect_remote(const char *host, int port, int timeout, int *timer_id,
int *sock_fd, void *userdata,
void (*callback) (void *userdata, int rc, int sock))
{
char buffer[INET6_ADDRSTRLEN];
struct addrinfo *res = NULL;
struct addrinfo *rp = NULL;
struct addrinfo hints;
const char *server = host;
int rc;
int sock = -1;
CRM_CHECK((host != NULL) && (sock_fd != NULL), return EINVAL);
// Get host's IP address(es)
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
rc = getaddrinfo(server, NULL, &hints, &res);
rc = pcmk__gaierror2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Unable to get IP address info for %s: %s",
server, pcmk_rc_str(rc));
goto async_cleanup;
}
if (!res || !res->ai_addr) {
crm_err("Unable to get IP address info for %s: no result", server);
rc = ENOTCONN;
goto async_cleanup;
}
// getaddrinfo() returns a list of host's addresses, try them in order
for (rp = res; rp != NULL; rp = rp->ai_next) {
struct sockaddr *addr = rp->ai_addr;
if (!addr) {
continue;
}
if (rp->ai_canonname) {
server = res->ai_canonname;
}
crm_debug("Got canonical name %s for %s", server, host);
sock = socket(rp->ai_family, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
rc = errno;
crm_warn("Could not create socket for remote connection to %s:%d: "
"%s " CRM_XS " rc=%d", server, port, pcmk_rc_str(rc), rc);
continue;
}
/* Set port appropriately for address family */
/* (void*) casts avoid false-positive compiler alignment warnings */
if (addr->sa_family == AF_INET6) {
((struct sockaddr_in6 *)(void*)addr)->sin6_port = htons(port);
} else {
((struct sockaddr_in *)(void*)addr)->sin_port = htons(port);
}
memset(buffer, 0, PCMK__NELEM(buffer));
pcmk__sockaddr2str(addr, buffer);
crm_info("Attempting remote connection to %s:%d", buffer, port);
if (callback) {
if (connect_socket_retry(sock, rp->ai_addr, rp->ai_addrlen, timeout,
timer_id, userdata, callback) == pcmk_rc_ok) {
goto async_cleanup; /* Success for now, we'll hear back later in the callback */
}
} else if (connect_socket_once(sock, rp->ai_addr,
rp->ai_addrlen) == pcmk_rc_ok) {
break; /* Success */
}
// Connect failed
close(sock);
sock = -1;
rc = ENOTCONN;
}
async_cleanup:
if (res) {
freeaddrinfo(res);
}
*sock_fd = sock;
return rc;
}
/*!
* \internal
* \brief Convert an IP address (IPv4 or IPv6) to a string for logging
*
* \param[in] sa Socket address for IP
* \param[out] s Storage for at least INET6_ADDRSTRLEN bytes
*
* \note sa The socket address can be a pointer to struct sockaddr_in (IPv4),
* struct sockaddr_in6 (IPv6) or struct sockaddr_storage (either),
* as long as its sa_family member is set correctly.
*/
void
pcmk__sockaddr2str(const void *sa, char *s)
{
switch (((const struct sockaddr *) sa)->sa_family) {
case AF_INET:
inet_ntop(AF_INET, &(((const struct sockaddr_in *) sa)->sin_addr),
s, INET6_ADDRSTRLEN);
break;
case AF_INET6:
inet_ntop(AF_INET6,
&(((const struct sockaddr_in6 *) sa)->sin6_addr),
s, INET6_ADDRSTRLEN);
break;
default:
strcpy(s, "<invalid>");
}
}
/*!
* \internal
* \brief Accept a client connection on a remote server socket
*
* \param[in] ssock Server socket file descriptor being listened on
* \param[out] csock Where to put new client socket's file descriptor
*
* \return Standard Pacemaker return code
*/
int
pcmk__accept_remote_connection(int ssock, int *csock)
{
int rc;
struct sockaddr_storage addr;
socklen_t laddr = sizeof(addr);
char addr_str[INET6_ADDRSTRLEN];
#ifdef TCP_USER_TIMEOUT
long sbd_timeout = 0;
#endif
/* accept the connection */
memset(&addr, 0, sizeof(addr));
*csock = accept(ssock, (struct sockaddr *)&addr, &laddr);
if (*csock == -1) {
rc = errno;
crm_err("Could not accept remote client connection: %s "
CRM_XS " rc=%d", pcmk_rc_str(rc), rc);
return rc;
}
pcmk__sockaddr2str(&addr, addr_str);
crm_info("Accepted new remote client connection from %s", addr_str);
rc = pcmk__set_nonblocking(*csock);
if (rc != pcmk_rc_ok) {
crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
close(*csock);
*csock = -1;
return rc;
}
#ifdef TCP_USER_TIMEOUT
sbd_timeout = pcmk__get_sbd_watchdog_timeout();
if (sbd_timeout > 0) {
// Time to fail and retry before watchdog
long half = sbd_timeout / 2;
unsigned int optval = (half <= UINT_MAX)? half : UINT_MAX;
rc = setsockopt(*csock, SOL_TCP, TCP_USER_TIMEOUT,
&optval, sizeof(optval));
if (rc < 0) {
rc = errno;
crm_err("Could not set TCP timeout to %d ms on remote connection: "
"%s " CRM_XS " rc=%d", optval, pcmk_rc_str(rc), rc);
close(*csock);
*csock = -1;
return rc;
}
}
#endif
return rc;
}
/*!
* \brief Get the default remote connection TCP port on this host
*
* \return Remote connection TCP port number
*/
int
crm_default_remote_port(void)
{
static int port = 0;
if (port == 0) {
const char *env = pcmk__env_option(PCMK__ENV_REMOTE_PORT);
if (env) {
errno = 0;
port = strtol(env, NULL, 10);
if (errno || (port < 1) || (port > 65535)) {
crm_warn("Environment variable PCMK_" PCMK__ENV_REMOTE_PORT
" has invalid value '%s', using %d instead",
env, DEFAULT_REMOTE_PORT);
port = DEFAULT_REMOTE_PORT;
}
} else {
port = DEFAULT_REMOTE_PORT;
}
}
return port;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 1:34 AM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009537
Default Alt Text
(88 KB)

Event Timeline