diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c
index d3995e8a9e..c6daf6558a 100644
--- a/daemons/based/based_remote.c
+++ b/daemons/based/based_remote.c
@@ -1,670 +1,663 @@
 /*
  * 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"
 
 #include <gnutls/gnutls.h>
 
 #include <pwd.h>
 #include <grp.h>
 #if HAVE_SECURITY_PAM_APPL_H
 #  include <security/pam_appl.h>
 #  define HAVE_PAM 1
 #elif HAVE_PAM_PAM_APPL_H
 #  include <pam/pam_appl.h>
 #  define HAVE_PAM 1
 #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);
 
 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);
-}
 
 // @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) {
         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);
     } else {
         crm_warn("Starting plain-text listener on port %d", port);
     }
 #ifndef HAVE_PAM
     crm_warn("This build does not support remote administrators "
              "because PAM support is not available");
 #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) {
         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;
         }
     } 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 = pcmk__create_timer(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;
         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;
         default:
             crm_warn("Unknown transport for client %s "
                      QB_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;
     const char *client_name = pcmk__client_name(client);
 
     crm_trace("Remote %s message received for client %s",
               pcmk__client_type_str(PCMK__CLIENT_TYPE(client)), client_name);
 
     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 = pcmk__create_timer(REMOTE_AUTH_TIMEOUT,
                                                           remote_auth_timeout_cb,
                                                           client);
         return 0;
     }
 
     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) {
             pcmk__xml_free(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 "
                    QB_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);
         pcmk__xml_free(reg);
         pcmk__xml_free(command);
     }
 
     command = pcmk__remote_message_xml(client->remote);
     if (command != NULL) {
         crm_trace("Remote message received from client %s", client_name);
         cib_handle_remote_msg(client, command);
         pcmk__xml_free(command);
     }
 
     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 rejects all credentials 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
     crm_warn("Rejecting remote user %s because this build does not have "
              "PAM support", user);
     return false;
 #endif
 }
diff --git a/lib/common/utils.c b/lib/common/utils.c
index c137330a95..5d1416fc3a 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,492 +1,500 @@
 /*
  * 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 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 <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__xml_cleanup();
     pcmk__free_common_logger();
     qb_log_fini(); // Don't log anything after this point
 
     free(crm_system_name);
     crm_system_name = NULL;
 }
 
 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;
 }
 
+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);
 }
 
 /*!
  * \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 <crm/common/util_compat.h>
 
 /*!
  * \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