Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4511819
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
36 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Jun 25, 2:43 AM (20 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1933797
Default Alt Text
(36 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment