Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/tls_internal.h b/include/crm/common/tls_internal.h
index f1cd517e7c..0a113feb07 100644
--- a/include/crm/common/tls_internal.h
+++ b/include/crm/common/tls_internal.h
@@ -1,184 +1,184 @@
/*
* Copyright 2024-2025 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_TLS_INTERNAL__H
#define PCMK__CRM_COMMON_TLS_INTERNAL__H
#include <gnutls/gnutls.h> // gnutls_session_t, gnutls_dh_params_t, etc.
#include <crm/common/ipc_internal.h> // pcmk__client_t
#include <crm/common/remote_internal.h> // pcmk__remote_t
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
bool server;
gnutls_dh_params_t dh_params;
gnutls_credentials_type_t cred_type;
const char *ca_file;
const char *cert_file;
const char *crl_file;
const char *key_file;
union {
gnutls_anon_server_credentials_t anon_s;
gnutls_anon_client_credentials_t anon_c;
gnutls_certificate_credentials_t cert;
gnutls_psk_server_credentials_t psk_s;
gnutls_psk_client_credentials_t psk_c;
} credentials;
} pcmk__tls_t;
/*!
* \internal
* \brief Free a previously allocated \p pcmk__tls_t object
*
* \param[in,out] tls The object to free
*/
void pcmk__free_tls(pcmk__tls_t *tls);
/*!
* \internal
* \brief Initialize a new TLS object
*
* Unlike \p pcmk__new_tls_session, this function is used for creating the
* global environment for TLS connections.
*
* \param[in,out] tls The object to be allocated and initialized
* \param[in] server Is this a server or not?
* \param[in] cred_type What type of gnutls credentials are in use?
* (GNUTLS_CRD_* constants)
*
* \returns Standard Pacemaker return code
*/
int pcmk__init_tls(pcmk__tls_t **tls, bool server,
gnutls_credentials_type_t cred_type);
/*!
* \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);
/*!
* \internal
* \brief Initialize a new TLS session
*
- * \param[in] tls A TLS environment object
- * \param[in] csock Connected socket for TLS session
+ * \param[in] tls TLS environment object
+ * \param[in] csock Connected TCP socket for TLS session
*
* \return Pointer to newly created session object, or NULL on error
*/
gnutls_session_t pcmk__new_tls_session(pcmk__tls_t *tls, int csock);
int pcmk__tls_get_client_sock(const pcmk__remote_t *remote);
/*!
* \internal
* \brief Add the client PSK key to the TLS environment
*
* This function must be called for all TLS clients that are using PSK for
* authentication.
*
* \param[in,out] tls The TLS environment
* \param[in] key The client's PSK key
*/
void pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key);
/*!
* \internal
* \brief Register the server's PSK credential fetching callback
*
* This function must be called for all TLS servers that are using PSK for
* authentication.
*
* \param[in,out] tls The TLS environment
* \param[in] cb The server's PSK credential fetching callback
*/
void pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
gnutls_psk_server_credentials_function *cb);
/*!
* \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);
/*!
* \internal
* \brief Log if a TLS certificate is near its expiration date
*
* \param[in] session The gnutls session object after handshaking is
* complete
*/
void pcmk__tls_check_cert_expiration(gnutls_session_t session);
/*!
* \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);
/*!
* \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 Is X509 authentication supported by the environment?
*
* \return true if the appropriate environment variables are set (see
* etc/sysconfig/pacemaker.in), otherwise false
*/
bool pcmk__x509_enabled(void);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_TLS_INTERNAL__H
diff --git a/lib/common/tls.c b/lib/common/tls.c
index 9d9652643c..2bdd2aab92 100644
--- a/lib/common/tls.c
+++ b/lib/common/tls.c
@@ -1,573 +1,515 @@
/*
* Copyright 2024-2025 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 <errno.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <stdlib.h>
-#include <glib.h> // gpointer, GPOINTER_TO_INT(), GINT_TO_POINTER()
-
#include <crm/common/tls_internal.h>
static char *
get_gnutls_priorities(gnutls_credentials_type_t cred_type)
{
const char *prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES);
if (prio_base == NULL) {
prio_base = PCMK__GNUTLS_PRIORITIES;
}
if (cred_type == GNUTLS_CRD_ANON) {
return crm_strdup_printf("%s:+ANON-DH", prio_base);
} else if (cred_type == GNUTLS_CRD_PSK) {
return crm_strdup_printf("%s:+DHE-PSK:+PSK", prio_base);
} else {
return strdup(prio_base);
}
}
static const char *
tls_cred_str(gnutls_credentials_type_t cred_type)
{
if (cred_type == GNUTLS_CRD_ANON) {
return "unauthenticated";
} else if (cred_type == GNUTLS_CRD_PSK) {
return "shared-key-authenticated";
} else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
return "certificate-authenticated";
} else {
return "unknown";
}
}
static int
tls_load_x509_data(pcmk__tls_t *tls)
{
int rc;
CRM_CHECK(tls->cred_type == GNUTLS_CRD_CERTIFICATE, return EINVAL);
/* Load a trusted CA to be used to verify client certificates. Use
* of this function instead of gnutls_certificate_set_x509_system_trust
* means we do not look at the system-wide authorities installed in
* /etc/pki somewhere. This requires the cluster admin to set up their
* own CA.
*/
rc = gnutls_certificate_set_x509_trust_file(tls->credentials.cert,
tls->ca_file,
GNUTLS_X509_FMT_PEM);
if (rc <= 0) {
crm_err("Failed to set X509 CA file: %s", gnutls_strerror(rc));
return ENODATA;
}
/* If a Certificate Revocation List (CRL) file was given in the environment,
* load that now so we know which clients have been banned.
*/
if (tls->crl_file != NULL) {
rc = gnutls_certificate_set_x509_crl_file(tls->credentials.cert,
tls->crl_file,
GNUTLS_X509_FMT_PEM);
if (rc < 0) {
crm_err("Failed to set X509 CRL file: %s",
gnutls_strerror(rc));
return ENODATA;
}
}
/* NULL = no password for the key, GNUTLS_PKCS_PLAIN = unencrypted key
* file
*/
rc = gnutls_certificate_set_x509_key_file2(tls->credentials.cert,
tls->cert_file, tls->key_file,
GNUTLS_X509_FMT_PEM, NULL,
GNUTLS_PKCS_PLAIN);
if (rc < 0) {
crm_err("Failed to set X509 cert/key pair: %s",
gnutls_strerror(rc));
return ENODATA;
}
return pcmk_rc_ok;
}
-/*!
- * \internal
- * \brief Verify a peer's certificate
- *
- * \return 0 if the certificate is trusted and the gnutls handshake should
- * continue, -1 otherwise
- */
-static int
-verify_peer_cert(gnutls_session_t session)
-{
- int rc;
- int type;
- unsigned int status;
- gnutls_datum_t out;
-
- /* NULL = no hostname comparison will be performed */
- rc = gnutls_certificate_verify_peers3(session, NULL, &status);
-
- /* Success means it was able to perform the verification. We still have
- * to check status to see whether the cert is valid or not.
- */
- if (rc != GNUTLS_E_SUCCESS) {
- crm_err("Failed to verify peer certificate: %s", gnutls_strerror(rc));
- return -1;
- }
-
- if (status == 0) {
- /* The certificate is trusted. */
- return 0;
- }
-
- type = gnutls_certificate_type_get(session);
- gnutls_certificate_verification_status_print(status, type, &out, 0);
- crm_err("Peer certificate invalid: %s", out.data);
- gnutls_free(out.data);
- return GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR;
-}
-
static void
_gnutls_log_func(int level, const char *msg)
{
crm_trace("%s", msg);
}
void
pcmk__free_tls(pcmk__tls_t *tls)
{
if (tls == NULL) {
return;
}
/* This is only set on the server side. */
if (tls->server) {
gnutls_dh_params_deinit(tls->dh_params);
}
if (tls->cred_type == GNUTLS_CRD_ANON) {
if (tls->server) {
gnutls_anon_free_server_credentials(tls->credentials.anon_s);
} else {
gnutls_anon_free_client_credentials(tls->credentials.anon_c);
}
} else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
gnutls_certificate_free_credentials(tls->credentials.cert);
} else if (tls->cred_type == GNUTLS_CRD_PSK) {
if (tls->server) {
gnutls_psk_free_server_credentials(tls->credentials.psk_s);
} else {
gnutls_psk_free_client_credentials(tls->credentials.psk_c);
}
}
free(tls);
tls = NULL;
-
- gnutls_global_deinit();
}
int
pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type)
{
int rc = pcmk_rc_ok;
if (*tls != NULL) {
return rc;
}
*tls = pcmk__assert_alloc(1, sizeof(pcmk__tls_t));
signal(SIGPIPE, SIG_IGN);
- /* gnutls_global_init is safe to call multiple times, but we have to call
- * gnutls_global_deinit the same number of times for that function to do
- * anything.
- *
- * FIXME: When we can use gnutls >= 3.3.0, we don't have to call
- * gnutls_global_init anymore.
- */
- gnutls_global_init();
gnutls_global_set_log_level(8);
gnutls_global_set_log_function(_gnutls_log_func);
if (server) {
rc = pcmk__init_tls_dh(&(*tls)->dh_params);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
*tls = NULL;
return rc;
}
}
(*tls)->cred_type = cred_type;
(*tls)->server = server;
if (cred_type == GNUTLS_CRD_ANON) {
if (server) {
gnutls_anon_allocate_server_credentials(&(*tls)->credentials.anon_s);
gnutls_anon_set_server_dh_params((*tls)->credentials.anon_s,
(*tls)->dh_params);
} else {
gnutls_anon_allocate_client_credentials(&(*tls)->credentials.anon_c);
}
} else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
/* Try the PCMK_ version of each environment variable first, and if
* it's not set then try the CIB_ version.
*/
(*tls)->ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
if (pcmk__str_empty((*tls)->ca_file)) {
(*tls)->ca_file = getenv("CIB_ca_file");
}
(*tls)->cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
if (pcmk__str_empty((*tls)->cert_file)) {
(*tls)->cert_file = getenv("CIB_cert_file");
}
(*tls)->crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
if (pcmk__str_empty((*tls)->crl_file)) {
(*tls)->crl_file = getenv("CIB_crl_file");
}
(*tls)->key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);
if (pcmk__str_empty((*tls)->key_file)) {
(*tls)->key_file = getenv("CIB_key_file");
}
gnutls_certificate_allocate_credentials(&(*tls)->credentials.cert);
if (server) {
gnutls_certificate_set_dh_params((*tls)->credentials.cert,
(*tls)->dh_params);
}
rc = tls_load_x509_data(*tls);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
*tls = NULL;
return rc;
}
} else if (cred_type == GNUTLS_CRD_PSK) {
if (server) {
gnutls_psk_allocate_server_credentials(&(*tls)->credentials.psk_s);
gnutls_psk_set_server_dh_params((*tls)->credentials.psk_s,
(*tls)->dh_params);
} else {
gnutls_psk_allocate_client_credentials(&(*tls)->credentials.psk_c);
}
}
return rc;
}
int
pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
{
int rc = GNUTLS_E_SUCCESS;
unsigned int dh_bits = 0;
int dh_max_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;
}
pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, 0);
if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) {
dh_bits = dh_max_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 "
QB_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
gnutls_session_t
pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
{
unsigned int conn_type = GNUTLS_CLIENT;
int rc = GNUTLS_E_SUCCESS;
char *prio = NULL;
gnutls_session_t session = NULL;
CRM_CHECK((tls != NULL) && (csock >= 0), return NULL);
if (tls->server) {
conn_type = GNUTLS_SERVER;
}
rc = gnutls_init(&session, conn_type);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
/* 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 = get_gnutls_priorities(tls->cred_type);
/* @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;
}
- gnutls_transport_set_ptr(session,
- (gnutls_transport_ptr_t) GINT_TO_POINTER(csock));
+ gnutls_transport_set_int(session, csock);
/* gnutls does not make this easy */
if (tls->cred_type == GNUTLS_CRD_ANON && tls->server) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_s);
} else if (tls->cred_type == GNUTLS_CRD_ANON) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_c);
} else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.cert);
} else if (tls->cred_type == GNUTLS_CRD_PSK && tls->server) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_s);
} else if (tls->cred_type == GNUTLS_CRD_PSK) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_c);
} else {
crm_err("Unknown credential type: %d", tls->cred_type);
rc = EINVAL;
goto error;
}
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
free(prio);
if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
if (conn_type == GNUTLS_SERVER) {
/* Require the client to send a certificate for the server to verify. */
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
}
- /* Register a function to verify the peer's certificate.
- *
- * FIXME: When we can require gnutls >= 3.4.6, remove verify_peer_cert
- * and use gnutls_session_set_verify_cert instead.
- */
- gnutls_certificate_set_verify_function(tls->credentials.cert, verify_peer_cert);
+ // Register a function to verify the peer's certificate
+ gnutls_session_set_verify_cert(session, NULL, 0);
}
return session;
error:
crm_err("Could not initialize %s TLS %s session: %s " QB_XS " rc=%d priority='%s'",
tls_cred_str(tls->cred_type),
(conn_type == GNUTLS_SERVER)? "server" : "client",
gnutls_strerror(rc), rc, prio);
free(prio);
if (session != NULL) {
gnutls_deinit(session);
}
return NULL;
}
/*!
* \internal
* \brief Get the socket file descriptor for a remote connection's TLS session
*
* \param[in] remote Remote connection
*
* \return Socket file descriptor for \p remote
*
* \note The remote connection's \c tls_session must have already been
* initialized using \c pcmk__new_tls_session().
*/
int
pcmk__tls_get_client_sock(const pcmk__remote_t *remote)
{
- gpointer sock_ptr = NULL;
-
pcmk__assert((remote != NULL) && (remote->tls_session != NULL));
- sock_ptr = (gpointer) gnutls_transport_get_ptr(remote->tls_session);
- return GPOINTER_TO_INT(sock_ptr);
+ return gnutls_transport_get_int(remote->tls_session);
}
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 "
QB_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
return pcmk_rc_ok;
}
void
pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key)
{
gnutls_psk_set_client_credentials(tls->credentials.psk_c,
DEFAULT_REMOTE_USERNAME, key,
GNUTLS_PSK_KEY_RAW);
}
void
pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
gnutls_psk_server_credentials_function *cb)
{
gnutls_psk_set_server_credentials_function(tls->credentials.psk_s, cb);
}
void
pcmk__tls_check_cert_expiration(gnutls_session_t session)
{
gnutls_x509_crt_t cert;
const gnutls_datum_t *datum = NULL;
time_t expiry;
if (session == NULL) {
return;
}
if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
return;
}
datum = gnutls_certificate_get_ours(session);
if (datum == NULL) {
return;
}
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, datum, GNUTLS_X509_FMT_DER);
expiry = gnutls_x509_crt_get_expiration_time(cert);
if (expiry != -1) {
time_t now = time(NULL);
/* If the cert is going to expire within ~ one month (30 days), log it */
if (expiry - now <= 60 * 60 * 24 * 30) {
crm_time_t *expiry_t = pcmk__copy_timet(expiry);
crm_time_log(LOG_WARNING, "TLS certificate will expire on",
expiry_t, crm_time_log_date | crm_time_log_timeofday);
crm_time_free(expiry_t);
}
}
gnutls_x509_crt_deinit(cert);
}
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;
}
bool
pcmk__x509_enabled(void)
{
/* Environment variables for servers come through the sysconfig file, and
* have names like PCMK_<whatever>. Environment variables for clients come
* from the environment and have names like CIB_<whatever>. This function
* is used for both, so we need to check both.
*/
return (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CERT_FILE)) ||
!pcmk__str_empty(getenv("CIB_cert_file"))) &&
(!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CA_FILE)) ||
!pcmk__str_empty(getenv("CIB_ca_file"))) &&
(!pcmk__str_empty(pcmk__env_option(PCMK__ENV_KEY_FILE)) ||
!pcmk__str_empty(getenv("CIB_key_file")));
}
diff --git a/lib/common/utils.c b/lib/common/utils.c
index 774b468de1..fe1595dbad 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,504 +1,506 @@
/*
* Copyright 2004-2025 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 <sys/stat.h>
#include <sys/utsname.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <pwd.h>
#include <time.h>
#include <libgen.h>
#include <signal.h>
#include <grp.h>
#include <qb/qbdefs.h>
#include <crm/crm.h>
#include <crm/services.h>
#include <crm/cib/internal.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <crm/common/ipc.h>
#include <crm/common/iso8601.h>
#include <crm/common/mainloop.h>
#include <libxml/parser.h> // xmlCleanupParser()
#include <libxml2/libxml/relaxng.h>
#include "crmcommon_private.h"
CRM_TRACE_INIT_DATA(common);
bool pcmk__config_has_error = false;
bool pcmk__config_has_warning = false;
char *crm_system_name = NULL;
/*!
* \brief Free all memory used by libcrmcommon
*
* Free all global memory allocated by the libcrmcommon library. This should be
* called before exiting a process that uses the library, and the process should
* not call any libcrmcommon or libxml2 APIs after calling this one.
*/
void
pcmk_common_cleanup(void)
{
// @TODO This isn't really everything, move all cleanup here
mainloop_cleanup();
pcmk__schema_cleanup();
pcmk__free_common_logger();
free(crm_system_name);
crm_system_name = NULL;
// Clean up external library global state
qb_log_fini(); // Don't log anything after this point
xmlCleanupParser();
}
bool
pcmk__is_user_in_group(const char *user, const char *group)
{
struct group *grent;
char **gr_mem;
if (user == NULL || group == NULL) {
return false;
}
setgrent();
while ((grent = getgrent()) != NULL) {
if (grent->gr_mem == NULL) {
continue;
}
if(strcmp(group, grent->gr_name) != 0) {
continue;
}
gr_mem = grent->gr_mem;
while (*gr_mem != NULL) {
if (!strcmp(user, *gr_mem++)) {
endgrent();
return true;
}
}
}
endgrent();
return false;
}
int
crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
{
int rc = pcmk_ok;
char *buffer = NULL;
struct passwd pwd;
struct passwd *pwentry = NULL;
buffer = calloc(1, PCMK__PW_BUFFER_LEN);
if (buffer == NULL) {
return -ENOMEM;
}
rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
if (pwentry) {
if (uid) {
*uid = pwentry->pw_uid;
}
if (gid) {
*gid = pwentry->pw_gid;
}
crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
} else {
rc = rc? -rc : -EINVAL;
crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
}
free(buffer);
return rc;
}
/*!
* \brief Get user and group IDs of pacemaker daemon user
*
* \param[out] uid If non-NULL, where to store daemon user ID
* \param[out] gid If non-NULL, where to store daemon group ID
*
* \return pcmk_ok on success, -errno otherwise
*/
int
pcmk_daemon_user(uid_t *uid, gid_t *gid)
{
static uid_t daemon_uid;
static gid_t daemon_gid;
static bool found = false;
int rc = pcmk_ok;
if (!found) {
rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
if (rc == pcmk_ok) {
found = true;
}
}
if (found) {
if (uid) {
*uid = daemon_uid;
}
if (gid) {
*gid = daemon_gid;
}
}
return rc;
}
/*!
* \internal
* \brief Return the integer equivalent of a portion of a string
*
* \param[in] text Pointer to beginning of string portion
* \param[out] end_text This will point to next character after integer
*/
static int
version_helper(const char *text, const char **end_text)
{
int atoi_result = -1;
pcmk__assert(end_text != NULL);
errno = 0;
if (text != NULL && text[0] != 0) {
/* seemingly sacrificing const-correctness -- because while strtol
doesn't modify the input, it doesn't want to artificially taint the
"end_text" pointer-to-pointer-to-first-char-in-string with constness
in case the input wasn't actually constant -- by semantic definition
not a single character will get modified so it shall be perfectly
safe to make compiler happy with dropping "const" qualifier here */
atoi_result = (int) strtol(text, (char **) end_text, 10);
if (errno == EINVAL) {
crm_err("Conversion of '%s' %c failed", text, text[0]);
atoi_result = -1;
}
}
return atoi_result;
}
/*
* version1 < version2 : -1
* version1 = version2 : 0
* version1 > version2 : 1
*/
int
compare_version(const char *version1, const char *version2)
{
int rc = 0;
int lpc = 0;
const char *ver1_iter, *ver2_iter;
if (version1 == NULL && version2 == NULL) {
return 0;
} else if (version1 == NULL) {
return -1;
} else if (version2 == NULL) {
return 1;
}
ver1_iter = version1;
ver2_iter = version2;
while (1) {
int digit1 = 0;
int digit2 = 0;
lpc++;
if (ver1_iter == ver2_iter) {
break;
}
if (ver1_iter != NULL) {
digit1 = version_helper(ver1_iter, &ver1_iter);
}
if (ver2_iter != NULL) {
digit2 = version_helper(ver2_iter, &ver2_iter);
}
if (digit1 < digit2) {
rc = -1;
break;
} else if (digit1 > digit2) {
rc = 1;
break;
}
if (ver1_iter != NULL && *ver1_iter == '.') {
ver1_iter++;
}
if (ver1_iter != NULL && *ver1_iter == '\0') {
ver1_iter = NULL;
}
if (ver2_iter != NULL && *ver2_iter == '.') {
ver2_iter++;
}
if (ver2_iter != NULL && *ver2_iter == 0) {
ver2_iter = NULL;
}
}
if (rc == 0) {
crm_trace("%s == %s (%d)", version1, version2, lpc);
} else if (rc < 0) {
crm_trace("%s < %s (%d)", version1, version2, lpc);
} else if (rc > 0) {
crm_trace("%s > %s (%d)", version1, version2, lpc);
}
return rc;
}
/*!
* \internal
* \brief Convert the current process to a daemon process
*
* Fork a child process, exit the parent, create a PID file with the current
* process ID, and close the standard input/output/error file descriptors.
* Exit instead if a daemon is already running and using the PID file.
*
* \param[in] name Daemon executable name
* \param[in] pidfile File name to use as PID file
*/
void
pcmk__daemonize(const char *name, const char *pidfile)
{
int rc;
pid_t pid;
/* Check before we even try... */
rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
crm_err("%s: already running [pid %lld in %s]",
name, (long long) pid, pidfile);
printf("%s: already running [pid %lld in %s]\n",
name, (long long) pid, pidfile);
crm_exit(CRM_EX_ERROR);
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "%s: could not start daemon\n", name);
crm_perror(LOG_ERR, "fork");
crm_exit(CRM_EX_OSERR);
} else if (pid > 0) {
crm_exit(CRM_EX_OK);
}
rc = pcmk__lock_pidfile(pidfile, name);
if (rc != pcmk_rc_ok) {
crm_err("Could not lock '%s' for %s: %s " QB_XS " rc=%d",
pidfile, name, pcmk_rc_str(rc), rc);
printf("Could not lock '%s' for %s: %s (%d)\n",
pidfile, name, pcmk_rc_str(rc), rc);
crm_exit(CRM_EX_ERROR);
}
umask(S_IWGRP | S_IWOTH | S_IROTH);
close(STDIN_FILENO);
pcmk__open_devnull(O_RDONLY); // stdin (fd 0)
close(STDOUT_FILENO);
pcmk__open_devnull(O_WRONLY); // stdout (fd 1)
close(STDERR_FILENO);
pcmk__open_devnull(O_WRONLY); // stderr (fd 2)
}
#ifdef HAVE_UUID_UUID_H
# include <uuid/uuid.h>
#endif
char *
crm_generate_uuid(void)
{
unsigned char uuid[16];
char *buffer = malloc(37); /* Including NUL byte */
pcmk__mem_assert(buffer);
uuid_generate(uuid);
uuid_unparse(uuid, buffer);
return buffer;
}
/*!
* \internal
* \brief Sleep for given milliseconds
*
* \param[in] ms Time to sleep
*
* \note The full time might not be slept if a signal is received.
*/
void
pcmk__sleep_ms(unsigned int ms)
{
// @TODO Impose a sane maximum sleep to avoid hanging a process for long
//CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
// Use sleep() for any whole seconds
if (ms >= 1000) {
sleep(ms / 1000);
ms -= ms / 1000;
}
if (ms == 0) {
return;
}
#if defined(HAVE_NANOSLEEP)
// nanosleep() is POSIX-2008, so prefer that
{
struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
nanosleep(&req, NULL);
}
#elif defined(HAVE_USLEEP)
// usleep() is widely available, though considered obsolete
usleep((useconds_t) ms);
#else
// Otherwise use a trick with select() timeout
{
struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
select(0, NULL, NULL, NULL, &tv);
}
#endif
}
/*!
* \internal
* \brief Add a timer
*
* \param[in] interval_ms The interval for the function to be called, in ms
* \param[in] fn The function to be called
* \param[in] data Data to be passed to fn (can be NULL)
*
* \return The ID of the event source
*/
guint
pcmk__create_timer(guint interval_ms, GSourceFunc fn, gpointer data)
{
pcmk__assert(interval_ms != 0 && fn != NULL);
if (interval_ms % 1000 == 0) {
/* In case interval_ms is 0, the call to pcmk__timeout_ms2s ensures
* an interval of one second.
*/
return g_timeout_add_seconds(pcmk__timeout_ms2s(interval_ms), fn, data);
} else {
return g_timeout_add(interval_ms, fn, data);
}
}
/*!
* \internal
* \brief Convert milliseconds to seconds
*
* \param[in] timeout_ms The interval, in ms
*
* \return If \p timeout_ms is 0, return 0. Otherwise, return the number of
* seconds, rounded to the nearest integer, with a minimum of 1.
*/
guint
pcmk__timeout_ms2s(guint timeout_ms)
{
guint quot, rem;
if (timeout_ms == 0) {
return 0;
} else if (timeout_ms < 1000) {
return 1;
}
quot = timeout_ms / 1000;
rem = timeout_ms % 1000;
if (rem >= 500) {
quot += 1;
}
return quot;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
+#include <gnutls/gnutls.h> // gnutls_global_init(), etc.
+
#include <crm/common/util_compat.h>
static void
_gnutls_log_func(int level, const char *msg)
{
crm_trace("%s", msg);
}
void
crm_gnutls_global_init(void)
{
signal(SIGPIPE, SIG_IGN);
gnutls_global_init();
gnutls_global_set_log_level(8);
gnutls_global_set_log_function(_gnutls_log_func);
}
/*!
* \brief Check whether string represents a client name used by cluster daemons
*
* \param[in] name String to check
*
* \return true if name is standard client name used by daemons, false otherwise
*
* \note This is provided by the client, and so cannot be used by itself as a
* secure means of authentication.
*/
bool
crm_is_daemon_name(const char *name)
{
return pcmk__str_any_of(name,
"attrd",
CRM_SYSTEM_CIB,
CRM_SYSTEM_CRMD,
CRM_SYSTEM_DC,
CRM_SYSTEM_LRMD,
CRM_SYSTEM_MCP,
CRM_SYSTEM_PENGINE,
CRM_SYSTEM_TENGINE,
"pacemaker-attrd",
"pacemaker-based",
"pacemaker-controld",
"pacemaker-execd",
"pacemaker-fenced",
"pacemaker-remoted",
"pacemaker-schedulerd",
"stonith-ng",
"stonithd",
NULL);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 25, 2:43 AM (13 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1933797
Default Alt Text
(36 KB)

Event Timeline