summaryrefslogtreecommitdiffstats
path: root/daemons/based/based_remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/based/based_remote.c')
-rw-r--r--daemons/based/based_remote.c680
1 files changed, 680 insertions, 0 deletions
diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c
new file mode 100644
index 0000000..38136d2
--- /dev/null
+++ b/daemons/based/based_remote.c
@@ -0,0 +1,680 @@
+/*
+ * Copyright 2004-2021 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 <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/remote_internal.h>
+#include <crm/cib/internal.h>
+
+#include "pacemaker-based.h"
+
+/* #undef HAVE_PAM_PAM_APPL_H */
+/* #undef HAVE_GNUTLS_GNUTLS_H */
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+# include <gnutls/gnutls.h>
+#endif
+
+#include <pwd.h>
+#include <grp.h>
+#if HAVE_SECURITY_PAM_APPL_H
+# include <security/pam_appl.h>
+# define HAVE_PAM 1
+#else
+# if HAVE_PAM_PAM_APPL_H
+# include <pam/pam_appl.h>
+# define HAVE_PAM 1
+# endif
+#endif
+
+extern int remote_tls_fd;
+extern gboolean cib_shutdown_flag;
+
+int init_remote_listener(int port, gboolean encrypted);
+void cib_remote_connection_destroy(gpointer user_data);
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+gnutls_dh_params_t dh_params;
+gnutls_anon_server_credentials_t anon_cred_s;
+static void
+debug_log(int level, const char *str)
+{
+ fputs(str, stderr);
+}
+#endif
+
+#define REMOTE_AUTH_TIMEOUT 10000
+
+int num_clients;
+int authenticate_user(const char *user, const char *passwd);
+static int cib_remote_listen(gpointer data);
+static int cib_remote_msg(gpointer data);
+
+static void
+remote_connection_destroy(gpointer user_data)
+{
+ crm_info("No longer listening for remote connections");
+ return;
+}
+
+int
+init_remote_listener(int port, gboolean encrypted)
+{
+ int rc;
+ int *ssock = NULL;
+ struct sockaddr_in saddr;
+ int optval;
+
+ static struct mainloop_fd_callbacks remote_listen_fd_callbacks = {
+ .dispatch = cib_remote_listen,
+ .destroy = remote_connection_destroy,
+ };
+
+ if (port <= 0) {
+ /* don't start it */
+ return 0;
+ }
+
+ if (encrypted) {
+#ifndef HAVE_GNUTLS_GNUTLS_H
+ crm_warn("TLS support is not available");
+ return 0;
+#else
+ crm_notice("Starting TLS listener on port %d", port);
+ crm_gnutls_global_init();
+ /* gnutls_global_set_log_level (10); */
+ gnutls_global_set_log_function(debug_log);
+ if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) {
+ return -1;
+ }
+ gnutls_anon_allocate_server_credentials(&anon_cred_s);
+ gnutls_anon_set_server_dh_params(anon_cred_s, dh_params);
+#endif
+ } else {
+ crm_warn("Starting plain-text listener on port %d", port);
+ }
+#ifndef HAVE_PAM
+ crm_warn("PAM is _not_ enabled!");
+#endif
+
+ /* create server socket */
+ ssock = malloc(sizeof(int));
+ if(ssock == NULL) {
+ crm_perror(LOG_ERR, "Listener socket allocation failed");
+ return -1;
+ }
+
+ *ssock = socket(AF_INET, SOCK_STREAM, 0);
+ if (*ssock == -1) {
+ crm_perror(LOG_ERR, "Listener socket creation failed");
+ free(ssock);
+ return -1;
+ }
+
+ /* reuse address */
+ optval = 1;
+ rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+ if (rc < 0) {
+ crm_perror(LOG_WARNING,
+ "Local address reuse not allowed on listener socket");
+ }
+
+ /* 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_perror(LOG_ERR, "Cannot bind to listener socket");
+ close(*ssock);
+ free(ssock);
+ return -2;
+ }
+ if (listen(*ssock, 10) == -1) {
+ crm_perror(LOG_ERR, "Cannot listen on socket");
+ 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;
+
+ CRM_CHECK(usr != NULL, return FALSE);
+ CRM_CHECK(grp != NULL, return FALSE);
+
+ pwd = getpwnam(usr);
+ if (pwd == NULL) {
+ crm_err("No user named '%s' exists!", 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("No group named '%s' exists!", 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;
+ }
+ };
+
+ return FALSE;
+}
+
+static gboolean
+cib_remote_auth(xmlNode * login)
+{
+ const char *user = NULL;
+ const char *pass = NULL;
+ const char *tmp = NULL;
+
+ crm_log_xml_info(login, "Login: ");
+ if (login == NULL) {
+ return FALSE;
+ }
+
+ tmp = crm_element_name(login);
+ if (!pcmk__str_eq(tmp, "cib_command", pcmk__str_casei)) {
+ crm_err("Wrong tag: %s", tmp);
+ return FALSE;
+ }
+
+ tmp = crm_element_value(login, "op");
+ if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) {
+ crm_err("Wrong operation: %s", tmp);
+ return FALSE;
+ }
+
+ user = crm_element_value(login, "user");
+ pass = crm_element_value(login, "password");
+
+ if (!user || !pass) {
+ crm_err("missing auth credentials");
+ return FALSE;
+ }
+
+ /* Non-root daemons can only validate the password of the
+ * user they're running as
+ */
+ if (check_group_membership(user, CRM_DAEMON_GROUP) == FALSE) {
+ crm_err("User is not a member of the required group");
+ return FALSE;
+
+ } else if (authenticate_user(user, pass) == FALSE) {
+ crm_err("PAM auth failed");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+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_perror(LOG_ERR, "Could not accept socket connection");
+ return TRUE;
+ }
+
+ pcmk__sockaddr2str(&addr, ipstr);
+ crm_debug("New %s connection from %s",
+ ((ssock == remote_tls_fd)? "secure" : "clear-text"), ipstr);
+
+ rc = pcmk__set_nonblocking(csock);
+ if (rc != pcmk_rc_ok) {
+ crm_err("Could not set socket non-blocking: %s " CRM_XS " rc=%d",
+ pcmk_rc_str(rc), rc);
+ close(csock);
+ return TRUE;
+ }
+
+ num_clients++;
+
+ new_client = pcmk__new_unauth_client(NULL);
+ new_client->remote = calloc(1, sizeof(pcmk__remote_t));
+
+ if (ssock == remote_tls_fd) {
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ pcmk__set_client_flags(new_client, pcmk__client_tls);
+
+ /* create gnutls session for the server socket */
+ new_client->remote->tls_session = pcmk__new_tls_session(csock,
+ GNUTLS_SERVER,
+ GNUTLS_CRD_ANON,
+ anon_cred_s);
+ if (new_client->remote->tls_session == NULL) {
+ close(csock);
+ return TRUE;
+ }
+#endif
+ } else {
+ pcmk__set_client_flags(new_client, pcmk__client_tcp);
+ new_client->remote->tcp_socket = csock;
+ }
+
+ // Require the client to authenticate within this time
+ new_client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
+ remote_auth_timeout_cb,
+ new_client);
+ crm_info("Remote CIB client pending authentication "
+ CRM_XS " %p id: %s", new_client, new_client->id);
+
+ new_client->remote->source =
+ mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client,
+ &remote_client_fd_callbacks);
+
+ return TRUE;
+}
+
+void
+cib_remote_connection_destroy(gpointer user_data)
+{
+ pcmk__client_t *client = user_data;
+ int csock = 0;
+
+ if (client == NULL) {
+ return;
+ }
+
+ crm_trace("Cleaning up after client %s disconnect",
+ pcmk__client_name(client));
+
+ num_clients--;
+ crm_trace("Num unfree'd clients: %d", num_clients);
+
+ switch (PCMK__CLIENT_TYPE(client)) {
+ case pcmk__client_tcp:
+ csock = client->remote->tcp_socket;
+ break;
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ case pcmk__client_tls:
+ if (client->remote->tls_session) {
+ void *sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session);
+
+ csock = GPOINTER_TO_INT(sock_ptr);
+ if (pcmk_is_set(client->flags,
+ pcmk__client_tls_handshake_complete)) {
+ gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_WR);
+ }
+ gnutls_deinit(*client->remote->tls_session);
+ gnutls_free(client->remote->tls_session);
+ client->remote->tls_session = NULL;
+ }
+ break;
+#endif
+ default:
+ crm_warn("Unknown transport for client %s "
+ CRM_XS " flags=%#016" PRIx64,
+ pcmk__client_name(client), client->flags);
+ }
+
+ if (csock > 0) {
+ close(csock);
+ }
+
+ pcmk__free_client(client);
+
+ crm_trace("Freed the cib client");
+
+ if (cib_shutdown_flag) {
+ cib_shutdown(0);
+ }
+ return;
+}
+
+static void
+cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command)
+{
+ const char *value = NULL;
+
+ value = crm_element_name(command);
+ if (!pcmk__str_eq(value, "cib_command", pcmk__str_casei)) {
+ crm_log_xml_trace(command, "Bad command: ");
+ return;
+ }
+
+ if (client->name == NULL) {
+ value = crm_element_value(command, F_CLIENTNAME);
+ if (value == NULL) {
+ client->name = strdup(client->id);
+ } else {
+ client->name = strdup(value);
+ }
+ }
+
+ /* unset dangerous options */
+ xml_remove_prop(command, F_ORIG);
+ xml_remove_prop(command, F_CIB_HOST);
+ xml_remove_prop(command, F_CIB_GLOBAL_UPDATE);
+
+ crm_xml_add(command, F_TYPE, T_CIB);
+ crm_xml_add(command, F_CIB_CLIENTID, client->id);
+ crm_xml_add(command, F_CIB_CLIENTNAME, client->name);
+ crm_xml_add(command, F_CIB_USER, client->user);
+
+ if (crm_element_value(command, F_CIB_CALLID) == NULL) {
+ char *call_uuid = crm_generate_uuid();
+
+ /* fix the command */
+ crm_xml_add(command, F_CIB_CALLID, call_uuid);
+ free(call_uuid);
+ }
+
+ if (crm_element_value(command, F_CIB_CALLOPTS) == NULL) {
+ crm_xml_add_int(command, F_CIB_CALLOPTS, 0);
+ }
+
+ crm_log_xml_trace(command, "Remote command: ");
+ cib_common_callback_worker(0, 0, command, client, TRUE);
+}
+
+static int
+cib_remote_msg(gpointer data)
+{
+ xmlNode *command = NULL;
+ pcmk__client_t *client = data;
+ int rc;
+ int timeout = 1000;
+
+ if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
+ timeout = -1;
+ }
+
+ crm_trace("Remote %s message received for client %s",
+ pcmk__client_type_str(PCMK__CLIENT_TYPE(client)),
+ pcmk__client_name(client));
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+ if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls)
+ && !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) {
+
+ int rc = pcmk__read_handshake_data(client);
+
+ if (rc == EAGAIN) {
+ /* No more data is available at the moment. Just return for now;
+ * we'll get invoked again once the client sends more.
+ */
+ return 0;
+ } else if (rc != pcmk_rc_ok) {
+ return -1;
+ }
+
+ crm_debug("TLS handshake with remote CIB client completed");
+ pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);
+ if (client->remote->auth_timeout) {
+ g_source_remove(client->remote->auth_timeout);
+ }
+
+ // Require the client to authenticate within this time
+ client->remote->auth_timeout = g_timeout_add(REMOTE_AUTH_TIMEOUT,
+ remote_auth_timeout_cb,
+ client);
+ return 0;
+ }
+#endif
+
+ rc = pcmk__read_remote_message(client->remote, timeout);
+
+ /* must pass auth before we will process anything else */
+ if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) {
+ xmlNode *reg;
+ const char *user = NULL;
+
+ command = pcmk__remote_message_xml(client->remote);
+ if (cib_remote_auth(command) == FALSE) {
+ free_xml(command);
+ return -1;
+ }
+
+ crm_notice("Remote CIB client connection accepted");
+ 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, "name");
+
+ user = crm_element_value(command, "user");
+ if (user) {
+ client->user = strdup(user);
+ }
+
+ /* send ACK */
+ reg = create_xml_node(NULL, "cib_result");
+ crm_xml_add(reg, F_CIB_OPERATION, CRM_OP_REGISTER);
+ crm_xml_add(reg, F_CIB_CLIENTID, client->id);
+ pcmk__remote_send_xml(client->remote, reg);
+ free_xml(reg);
+ free_xml(command);
+ }
+
+ command = pcmk__remote_message_xml(client->remote);
+ while (command) {
+ crm_trace("Remote client message received");
+ cib_handle_remote_msg(client, command);
+ free_xml(command);
+ command = pcmk__remote_message_xml(client->remote);
+ }
+
+ if (rc == ENOTCONN) {
+ crm_trace("Remote CIB client disconnected while reading from it");
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_PAM
+static int
+construct_pam_passwd(int num_msg, const struct pam_message **msg,
+ struct pam_response **response, void *data)
+{
+ int count = 0;
+ struct pam_response *reply;
+ char *string = (char *)data;
+
+ CRM_CHECK(data, return PAM_CONV_ERR);
+ CRM_CHECK(num_msg == 1, return PAM_CONV_ERR); /* We only want to handle one message */
+
+ reply = calloc(1, sizeof(struct pam_response));
+ CRM_ASSERT(reply != NULL);
+
+ for (count = 0; count < num_msg; ++count) {
+ switch (msg[count]->msg_style) {
+ case PAM_TEXT_INFO:
+ crm_info("PAM: %s", msg[count]->msg);
+ break;
+ case PAM_PROMPT_ECHO_OFF:
+ case PAM_PROMPT_ECHO_ON:
+ reply[count].resp_retcode = 0;
+ reply[count].resp = string; /* We already made a copy */
+ break;
+ case PAM_ERROR_MSG:
+ /* In theory we'd want to print this, but then
+ * we see the password prompt in the logs
+ */
+ /* crm_err("PAM error: %s", msg[count]->msg); */
+ break;
+ default:
+ crm_err("Unhandled conversation type: %d", msg[count]->msg_style);
+ goto bail;
+ }
+ }
+
+ *response = reply;
+ reply = NULL;
+
+ return PAM_SUCCESS;
+
+ bail:
+ for (count = 0; count < num_msg; ++count) {
+ if (reply[count].resp != NULL) {
+ switch (msg[count]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ case PAM_PROMPT_ECHO_OFF:
+ /* Erase the data - it contained a password */
+ while (*(reply[count].resp)) {
+ *(reply[count].resp)++ = '\0';
+ }
+ free(reply[count].resp);
+ break;
+ }
+ reply[count].resp = NULL;
+ }
+ }
+ free(reply);
+ reply = NULL;
+
+ return PAM_CONV_ERR;
+}
+#endif
+
+int
+authenticate_user(const char *user, const char *passwd)
+{
+#ifndef HAVE_PAM
+ gboolean pass = TRUE;
+#else
+ int rc = 0;
+ gboolean 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 = strdup(passwd);
+
+ rc = pam_start(pam_name, user, &p_conv, &pam_h);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Could not initialize PAM: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+
+ rc = pam_authenticate(pam_h, 0);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Authentication failed for %s: %s (%d)", user, pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+
+ /* Make sure we authenticated the user we wanted to authenticate.
+ * Since we also run as non-root, it might be worth pre-checking
+ * the user has the same EID as us, since that the only user we
+ * can authenticate.
+ */
+ rc = pam_get_item(pam_h, PAM_USER, &p_user);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Internal PAM error: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+
+ } else if (p_user == NULL) {
+ crm_err("Unknown user authenticated.");
+ goto bail;
+
+ } else if (!pcmk__str_eq(p_user, user, pcmk__str_casei)) {
+ crm_err("User mismatch: %s vs. %s.", (const char *)p_user, (const char *)user);
+ goto bail;
+ }
+
+ rc = pam_acct_mgmt(pam_h, 0);
+ if (rc != PAM_SUCCESS) {
+ crm_err("Access denied: %s (%d)", pam_strerror(pam_h, rc), rc);
+ goto bail;
+ }
+ pass = TRUE;
+
+ bail:
+ pam_end(pam_h, rc);
+#endif
+ return pass;
+}