blob: 1242eb6761dff165e04952f5d3b50e10e2a4605c [file]
/*****************************************************************************\
* tls.c - definitions for TLS work in connection manager
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "src/common/fd.h"
#include "src/common/log.h"
#include "src/common/macros.h"
#include "src/common/pack.h"
#include "src/common/read_config.h"
#include "src/common/xmalloc.h"
#include "src/conmgr/tls.h"
#include "src/conmgr/tls_fingerprint.h"
#include "src/conmgr/conmgr.h"
#include "src/conmgr/mgr.h"
#include "src/interfaces/tls.h"
static void _shift_buf_bytes(buf_t *buf, const size_t bytes)
{
void *start = NULL;
size_t remain = 0;
xassert(get_buf_offset(buf) >= bytes);
if (get_buf_offset(buf) == bytes) {
set_buf_offset(buf, 0);
return;
}
start = (((void *) get_buf_data(buf)) + bytes);
remain = (get_buf_offset(buf) - bytes);
xassert(remain > 0);
xassert(remain < size_buf(buf));
(void) memcpy(get_buf_data(buf), start, remain);
set_buf_offset(buf, remain);
}
extern void tls_destroy(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
void *tls = NULL;
buf_t *tls_in = NULL;
list_t *tls_out = NULL;
int rc = EINVAL;
slurm_mutex_lock(&mgr.mutex);
/* Connection must already be soft closed */
xassert(con_flag(con, FLAG_READ_EOF));
xassert(con_flag(con, FLAG_WRITE_EOF));
SWAP(tls, con->tls);
xassert(tls);
SWAP(tls_in, con->tls_in);
xassert(tls_in);
SWAP(tls_out, con->tls_out);
xassert(tls_out);
slurm_mutex_unlock(&mgr.mutex);
errno = SLURM_SUCCESS;
tls_g_destroy_conn(tls, false);
if ((rc = errno))
log_flag(CONMGR, "%s: [%s] tls_g_destroy_conn() failed: %s",
__func__, con->name, slurm_strerror(rc));
#ifndef NDEBUG
slurm_mutex_lock(&mgr.mutex);
xassert(!con->tls);
xassert(!con->tls_in);
xassert(!con->tls_out);
slurm_mutex_unlock(&mgr.mutex);
#endif
FREE_NULL_BUFFER(tls_in);
FREE_NULL_LIST(tls_out);
log_flag(CONMGR, "%s: [%s] TLS destroy finished", __func__, con->name);
}
static void _delayed_close(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
log_flag(CONMGR, "%s: [%s] close wait complete", __func__, con->name);
slurm_mutex_lock(&mgr.mutex);
xassert(con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));
close_con(true, con);
con_unset_flag(con, FLAG_TLS_WAIT_ON_CLOSE);
slurm_mutex_unlock(&mgr.mutex);
}
/*
* Check and enforce if TLS has requested wait on operations and then close
* connection
*/
static void _wait_close(conmgr_fd_t *con)
{
timespec_t delay = { 0 };
slurm_mutex_lock(&mgr.mutex);
xassert(!con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));
/* Soft close the connection to stop any more activity */
con_set_polling(con, PCTL_TYPE_NONE, __func__);
con_set_flag(con, FLAG_READ_EOF);
con_set_flag(con, FLAG_WRITE_EOF);
con_set_flag(con, FLAG_TLS_WAIT_ON_CLOSE);
con_unset_flag(con, FLAG_ON_DATA_TRIED);
con_unset_flag(con, FLAG_CAN_WRITE);
con_unset_flag(con, FLAG_CAN_READ);
con_unset_flag(con, FLAG_IS_TLS_SHUTTING_DOWN);
xassert(con->tls);
delay = tls_g_get_delay(con->tls);
if (delay.tv_sec) {
log_flag(CONMGR, "%s: [%s] deferring close",
__func__, con->name);
add_work_con_delayed_abs_fifo(true, con, _delayed_close, NULL,
delay);
} else {
log_flag(CONMGR, "%s: [%s] queuing close",
__func__, con->name);
add_work_con_fifo(true, con, _delayed_close, NULL);
}
slurm_mutex_unlock(&mgr.mutex);
}
static void _shutdown_complete(bool locked, conmgr_fd_t *con)
{
if (!locked)
slurm_mutex_lock(&mgr.mutex);
xassert(con_flag(con, FLAG_IS_TLS_SHUTTING_DOWN));
con_unset_flag(con, FLAG_IS_TLS_SHUTTING_DOWN);
con_unset_flag(con, FLAG_INITIATE_TLS_SHUTDOWN);
/*
* Now that TLS shutdown is done, continue with normal
* connection shutdown.
*/
con_unset_flag(con, FLAG_IS_TLS_CONNECTED);
close_con(true, con);
if (!locked)
slurm_mutex_unlock(&mgr.mutex);
}
extern void tls_shutdown(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
void *tls = NULL;
int rc = EINVAL;
slurm_mutex_lock(&mgr.mutex);
xassert(con->tls);
xassert(con_flag(con, FLAG_TLS_CLIENT) ^
con_flag(con, FLAG_TLS_SERVER));
xassert(!con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));
xassert(con_flag(con, FLAG_INITIATE_TLS_SHUTDOWN) ||
con_flag(con, FLAG_IS_TLS_SHUTTING_DOWN));
con_set_flag(con, FLAG_IS_TLS_SHUTTING_DOWN);
con_unset_flag(con, FLAG_INITIATE_TLS_SHUTDOWN);
if (con_flag(con, FLAG_READ_EOF) || con_flag(con, FLAG_WRITE_EOF)) {
log_flag(CONMGR, "%s: [%s] cancelling TLS shutdown",
__func__, con->name);
_shutdown_complete(true, con);
slurm_mutex_unlock(&mgr.mutex);
return;
}
tls = con->tls;
/*
* tls_g_shutdown_conn() may attempt to read or write data. In case it
* is attempting to write, FLAG_ON_DATA_TRIED is unset here to ensure
* that tls_shutdown() is called again if tls_g_shutdown_conn() blocks
* on writing.
*
* If tls_g_shutdown_conn() is blocked on reading, FLAG_ON_DATA_TRIED
* will be set.
*/
con_unset_flag(con, FLAG_ON_DATA_TRIED);
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] attempting graceful TLS shutdown",
__func__, con->name);
/*
* Attempt to do TLS shutdown sequence. This may block on either
* reading/writing data.
*/
rc = tls_g_shutdown_conn(tls);
if (rc == SLURM_BLOCKED_ON_READ) {
slurm_mutex_lock(&mgr.mutex);
xassert(tls == con->tls);
xassert(con_flag(con, FLAG_IS_TLS_SHUTTING_DOWN));
if (con_flag(con, FLAG_READ_EOF) ||
con_flag(con, FLAG_WRITE_EOF)) {
log_flag(CONMGR, "%s: [%s] tls_g_shutdown_conn() completed after connection closed",
__func__, con->name);
_shutdown_complete(true, con);
} else {
/* Wait for more incoming data before trying again */
con_set_flag(con, FLAG_ON_DATA_TRIED);
log_flag(CONMGR, "%s: [%s] tls_g_shutdown_conn() requires more incoming data",
__func__, con->name);
}
slurm_mutex_unlock(&mgr.mutex);
} else if (rc == SLURM_BLOCKED_ON_WRITE) {
log_flag(NET, "%s: [%s] write required for tls_g_shutdown_conn() blocked, will try again",
__func__, con->name);
} else if (rc) {
log_flag(CONMGR, "%s: [%s] tls_g_shutdown_conn() failed: %s",
__func__, con->name, slurm_strerror(rc));
_wait_close(con);
} else {
log_flag(CONMGR, "%s: [%s] tls_g_shutdown_conn() complete",
__func__, con->name);
_shutdown_complete(false, con);
}
}
static int _recv(void *io_context, uint8_t *buf, uint32_t len)
{
conmgr_fd_t *con = io_context;
size_t bytes = 0;
xassert(con->magic == MAGIC_CON_MGR_FD);
xassert(con->tls);
xassert(con->tls_in);
xassert(con_flag(con, FLAG_WORK_ACTIVE));
if (!(bytes = get_buf_offset(con->tls_in))) {
if (con_flag(con, FLAG_READ_EOF)) {
log_flag(CONMGR, "%s: [%s] recv() returning EOF",
__func__, con->name);
return 0;
}
log_flag(CONMGR, "%s: [%s] recv() returning EWOULDBLOCK",
__func__, con->name);
errno = EWOULDBLOCK;
return -1;
}
if (bytes > len)
bytes = len;
log_flag_hex_range(NET_RAW, get_buf_data(con->tls_in),
get_buf_offset(con->tls_in), 0, bytes,
"[%s] TLS recv() %u/%u bytes encrypted",
con->name, bytes, len);
(void) memcpy(buf, get_buf_data(con->tls_in), bytes);
_shift_buf_bytes(con->tls_in, bytes);
return bytes;
}
extern void tls_handle_decrypt(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
buf_t *buf = con->in;
void *start = NULL;
size_t need = -1, readable = -1;
ssize_t read_c = -1;
int rc = EINVAL;
int try = 0;
again:
slurm_mutex_lock(&mgr.mutex);
if ((need = get_buf_offset(con->tls_in)) <= 0) {
log_flag(NET, "%s: [%s] already decrypted all incoming TLS data",
__func__, con->name);
slurm_mutex_unlock(&mgr.mutex);
return;
}
if (con_flag(con, FLAG_ON_DATA_TRIED) ||
con_flag(con, FLAG_TLS_WAIT_ON_CLOSE)) {
char flags_str[CON_FLAGS_STR_BYTES];
log_flag(NET, "%s: [%s] skipping with flags=%s",
__func__, con->name,
con_flags_print(con->flags, flags_str,
sizeof(flags_str)));
slurm_mutex_unlock(&mgr.mutex);
return;
}
slurm_mutex_unlock(&mgr.mutex);
if (try > 1) {
log_flag(NET, "%s: [%s] need >%d bytes of incoming data to decrypted TLS",
__func__, con->name,
get_buf_offset(con->tls_in));
slurm_mutex_lock(&mgr.mutex);
/* lock to tell mgr that we are done for now */
con_set_flag(con, FLAG_ON_DATA_TRIED);
slurm_mutex_unlock(&mgr.mutex);
return;
}
if ((rc = try_grow_buf_remaining(buf, need))) {
error("%s: [%s] unable to allocate larger input buffer for TLS data: %s",
__func__, con->name, slurm_strerror(rc));
_wait_close(con);
return;
}
readable = remaining_buf(buf);
start = ((void *) get_buf_data(buf)) + get_buf_offset(buf);
xassert(readable >= need);
xassert(con->tls);
xassert(con->magic == MAGIC_CON_MGR_FD);
/* TLS will callback to _recv() to read from con->tls_in*/
read_c = tls_g_recv(con->tls, start, readable);
if (read_c < 0) {
int recv_errno = errno;
if ((recv_errno == EAGAIN) || (recv_errno == EWOULDBLOCK)) {
log_flag(NET, "%s: [%s] TLS would block on tls_g_recv()",
__func__, con->name);
return;
}
log_flag(NET, "%s: [%s] error while decrypting TLS: %s",
__func__, con->name, slurm_strerror(recv_errno));
slurm_mutex_lock(&mgr.mutex);
con_set_status_code(con, recv_errno);
slurm_mutex_unlock(&mgr.mutex);
_wait_close(con);
return;
} else if (read_c == 0) {
log_flag(NET, "%s: [%s] read EOF with %u bytes previously decrypted",
__func__, con->name, get_buf_offset(buf));
close_con(false, con);
return;
} else {
size_t offset = 0;
slurm_mutex_lock(&mgr.mutex);
log_flag(NET, "%s: [%s] decrypted TLS %zd/%zd bytes with %u bytes previously decrypted",
__func__, con->name, read_c, readable,
get_buf_offset(buf));
log_flag_hex_range(NET_RAW, get_buf_data(buf),
(get_buf_offset(buf) + read_c),
get_buf_offset(buf),
(get_buf_offset(buf) + read_c),
"%s: [%s] decrypted", __func__, con->name);
set_buf_offset(buf, (get_buf_offset(buf) + read_c));
offset = get_buf_offset(con->tls_in);
slurm_mutex_unlock(&mgr.mutex);
if (offset) {
try++;
goto again;
}
}
}
/*
* WARNING: This acquires mgr.mutex and then the con->tls_out rwlock. To avoid
* deadlock, callers of any tls_g_*() function that may result in a call to
* _send() must not hold the con->out or con->tls_out rwlock across the call.
* See tls_handle_encrypt() for the con->out case this rule prevents.
*/
static int _send(void *io_context, const uint8_t *src, uint32_t len)
{
conmgr_fd_t *con = io_context;
buf_t *buf = NULL;
void *dst = NULL;
xassert(con->magic == MAGIC_CON_MGR_FD);
xassert(con->tls);
xassert(con_flag(con, FLAG_WORK_ACTIVE));
if (!(dst = try_xmalloc(len)) || !(buf = create_buf(dst, len))) {
xfree(dst);
errno = ENOMEM;
return -1;
}
log_flag_hex(NET_RAW, src, len, "[%s] TLS send encrypted", con->name);
(void) memcpy(dst, src, len);
slurm_mutex_lock(&mgr.mutex);
list_append(con->tls_out, buf);
con->last_write = timespec_now();
slurm_mutex_unlock(&mgr.mutex);
return len;
}
/* WARNING: caller must not hold mgr->mutex lock */
static void _negotiate(conmgr_fd_t *con, void *tls)
{
int rc = tls_g_negotiate_conn(tls);
if (rc == EWOULDBLOCK) {
log_flag(CONMGR, "%s: [%s] tls_g_negotiate_conn() requires more incoming data",
__func__, con->name);
slurm_mutex_lock(&mgr.mutex);
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
xassert(con_flag(con, FLAG_TLS_SERVER) ||
con_flag(con, FLAG_TLS_CLIENT));
xassert(!con_flag(con, FLAG_TLS_FINGERPRINT));
xassert(con_flag(con, FLAG_WORK_ACTIVE));
xassert(!con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));
/* Wait for more incoming data before trying again */
con_set_flag(con, FLAG_ON_DATA_TRIED);
slurm_mutex_unlock(&mgr.mutex);
return;
} else if (rc) {
log_flag(CONMGR, "%s: [%s] tls_g_negotiate_conn() failed: %s",
__func__, con->name, slurm_strerror(rc));
_wait_close(con);
return;
} else {
slurm_mutex_lock(&mgr.mutex);
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
xassert(con_flag(con, FLAG_TLS_SERVER) ||
con_flag(con, FLAG_TLS_CLIENT));
xassert(!con_flag(con, FLAG_TLS_FINGERPRINT));
xassert(con_flag(con, FLAG_WORK_ACTIVE));
xassert(!con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));
xassert(con->tls == tls);
con_set_flag(con, FLAG_IS_TLS_CONNECTED);
if (con->events->on_connection)
queue_on_connection(con);
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] TLS connected", __func__, con->name);
}
}
extern void tls_create(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
conn_args_t conn_args = {
.input_fd = -1,
.output_fd = -1,
.defer_blinding = true,
.callbacks = {
.recv = _recv,
.send = _send,
.io_context = con,
},
.defer_negotiation = true,
.cert = con->tls_cert,
};
int rc = SLURM_ERROR;
void *tls = NULL;
buf_t *tls_in = NULL;
list_t *tls_out = NULL;
if (tls_g_init() || !tls_available()) {
log_flag(CONMGR, "%s: [%s] TLS disabled: Unable to secure connection. Closing connection.",
__func__, con->name);
slurm_mutex_lock(&mgr.mutex);
con_set_status_code(con, ESLURM_TLS_REQUIRED);
close_con(true, con);
close_con_output(true, con);
slurm_mutex_unlock(&mgr.mutex);
return;
}
slurm_mutex_lock(&mgr.mutex);
xassert(con_flag(con, FLAG_TLS_CLIENT) ^
con_flag(con, FLAG_TLS_SERVER));
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
xassert(con_flag(con, FLAG_IS_CONNECTED));
xassert(!con_flag(con, FLAG_TLS_FINGERPRINT));
if ((con->input_fd < 0) || (con->output_fd < 0)) {
xassert(con_flag(con, FLAG_READ_EOF));
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] skip TLS create due to partial connection",
__func__, con->name);
return;
}
if ((tls = con->tls)) {
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] attempting TLS negotiation again",
__func__, con->name);
_negotiate(con, tls);
return;
}
xassert(con->input_fd >= 0);
xassert(con->output_fd >= 0);
xassert(!con->tls_in);
xassert(!con->tls_out);
/* Should not be any outgoing data yet */
xassert(list_is_empty(con->out));
slurm_mutex_unlock(&mgr.mutex);
tls_in = create_buf(xmalloc(BUFFER_START_SIZE), BUFFER_START_SIZE);
tls_out = list_create((ListDelF) free_buf);
if (get_buf_offset(con->in)) {
/*
* Need to move the TLS handshake to con->tls_in to allow tls
* plugin to read the handshake
*/
const size_t bytes = get_buf_offset(con->in);
if ((rc = try_grow_buf_remaining(tls_in, bytes))) {
FREE_NULL_BUFFER(tls_in);
FREE_NULL_LIST(tls_out);
log_flag(CONMGR, "%s: [%s] out of memory for TLS handshake: %s",
__func__, con->name, slurm_strerror(rc));
slurm_mutex_lock(&mgr.mutex);
con_set_status_code(con, rc);
close_con(true, con);
slurm_mutex_unlock(&mgr.mutex);
return;
}
log_flag_hex(NET_RAW, get_buf_data(con->in), bytes,
"[%s] transferring for decryption", con->name);
(void) memcpy(get_buf_data(tls_in), get_buf_data(con->in),
bytes);
set_buf_offset(con->in, 0);
set_buf_offset(tls_in, bytes);
xassert(!con_flag(con, FLAG_ON_DATA_TRIED));
}
slurm_mutex_lock(&mgr.mutex);
xassert(!con->tls);
xassert(!con->tls_in);
xassert(!con->tls_out);
xassert(con_flag(con, FLAG_TLS_CLIENT) ^
con_flag(con, FLAG_TLS_SERVER));
if (con_flag(con, FLAG_TLS_CLIENT))
conn_args.mode = CONN_CLIENT;
else if (con_flag(con, FLAG_TLS_SERVER))
conn_args.mode = CONN_SERVER;
xassert(conn_args.mode != CONN_NULL);
xassert(con->input_fd >= 0);
xassert(con->output_fd >= 0);
con->tls_in = tls_in;
con->tls_out = tls_out;
slurm_mutex_unlock(&mgr.mutex);
if (!(tls = tls_g_create_conn(&conn_args))) {
rc = errno;
log_flag(CONMGR, "%s: [%s] tls_g_create() failed: %s",
__func__, con->name, slurm_strerror(rc));
slurm_mutex_lock(&mgr.mutex);
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
xassert(!con->tls);
con_set_status_code(con, rc);
close_con(true, con);
con->tls_in = NULL;
con->tls_out = NULL;
slurm_mutex_unlock(&mgr.mutex);
FREE_NULL_BUFFER(tls_in);
FREE_NULL_LIST(tls_out);
} else {
log_flag(CONMGR, "%s: [%s] tls_g_create() success",
__func__, con->name);
slurm_mutex_lock(&mgr.mutex);
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
xassert(!con->tls);
con->tls = tls;
xassert(con->tls_in == tls_in);
xassert(con->tls_out == tls_out);
/* Release cert that is no longer needed */
xfree(con->tls_cert);
slurm_mutex_unlock(&mgr.mutex);
_negotiate(con, tls);
}
}
extern void tls_adopt(conmgr_fd_t *con, void *tls_conn)
{
conn_callbacks_t callbacks = {
.recv = _recv,
.send = _send,
.io_context = con,
};
int rc;
xassert(tls_conn);
xassert(con->magic == MAGIC_CON_MGR_FD);
xassert(con_flag(con, FLAG_TLS_CLIENT) ||
con_flag(con, FLAG_TLS_SERVER));
con->tls = tls_conn;
con->tls_in = create_buf(xmalloc(BUFFER_START_SIZE), BUFFER_START_SIZE);
con->tls_out = list_create((ListDelF) free_buf);
/* Can't finger print existing TLS connections */
con_unset_flag(con, FLAG_TLS_FINGERPRINT);
if ((rc = tls_g_set_conn_callbacks(tls_conn, &callbacks))) {
log_flag(CONMGR, "%s: [%s] adopting TLS state failed: %s",
__func__, con->name, slurm_strerror(rc));
con_set_flag(con, FLAG_READ_EOF);
con_set_flag(con, FLAG_WRITE_EOF);
} else {
log_flag(CONMGR, "%s: [%s] adopted TLS state",
__func__, con->name);
/* TLS state must already be connected */
con_set_flag(con, FLAG_IS_TLS_CONNECTED);
}
}
extern void tls_handle_read(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
xassert(!con_flag(con, FLAG_TLS_FINGERPRINT));
xassert(con->tls);
xassert(con_flag(con, FLAG_TLS_CLIENT) ||
con_flag(con, FLAG_TLS_SERVER));
read_input(con, con->tls_in, "input TLS buffer");
}
extern void tls_handle_write(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
const size_t count = list_count(con->tls_out);
xassert(con->magic == MAGIC_CON_MGR_FD);
xassert(con_flag(con, FLAG_TLS_CLIENT) ||
con_flag(con, FLAG_TLS_SERVER));
xassert(!con_flag(con, FLAG_TLS_FINGERPRINT));
if (count)
write_output(con, count, con->tls_out);
}
extern void tls_handle_encrypt(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
buf_t *out = NULL;
int index = 0;
xassert(con->magic == MAGIC_CON_MGR_FD);
xassert(con->tls);
xassert(con_flag(con, FLAG_TLS_CLIENT) ||
con_flag(con, FLAG_TLS_SERVER));
/*
* Handle con->out buffers one at a time with list_pop() instead of
* holding the lock across the entire iteration with list_delete_all()
* to avoid this deadlock:
*
* Thread 1
* tls_handle_encrypt()
* list_delete_all() acquires con->out rwlock
* tls_g_send()
* _send() acquires mgr.mutex
*
* Thread 2
* _on_data()
* conmgr_con_xfer_out_buffer()
* conmgr_queue_write_data()
* _write_data() acquires mgr.mutex
* _append_output() acquires con->out rwlock
*/
while ((out = list_pop(con->out))) {
void *start =
((void *) get_buf_data(out)) + get_buf_offset(out);
uint32_t remaining = remaining_buf(out);
ssize_t wrote;
xassert(out->magic == BUF_MAGIC);
wrote = tls_g_send(con->tls, start, remaining);
if (wrote < 0) {
error("%s: [%s] tls_g_send() failed: %m",
__func__, con->name);
free_buf(out);
/* drop outbound data on the floor and close */
list_flush(con->out);
_wait_close(con);
return;
}
if (wrote >= remaining) {
log_flag(NET, "%s: [%s] completed encrypt[%d] of %u/%u bytes to outgoing fd %u",
__func__, con->name, index, remaining,
size_buf(out), con->output_fd);
log_flag_hex_range(NET_RAW, get_buf_data(out),
size_buf(out), get_buf_offset(out),
(get_buf_offset(out) + wrote),
"%s: [%s] completed encrypt[%d] of %u/%u bytes",
__func__, con->name, index, remaining,
size_buf(out));
free_buf(out);
index++;
continue;
}
/*
* Partial write: advance past the bytes that were encrypted and
* requeue the remainder at the head of con->out
*/
log_flag(CONMGR, "%s: [%s] partial encrypt[%d] of %zd/%u bytes to outgoing fd %u",
__func__, con->name, index, wrote, size_buf(out),
con->output_fd);
log_flag_hex_range(NET_RAW, get_buf_data(out), size_buf(out),
get_buf_offset(out),
(get_buf_offset(out) + wrote),
"%s: [%s] partial encrypt[%d] of %zd/%u bytes",
__func__, con->name, index, wrote,
size_buf(out));
set_buf_offset(out, get_buf_offset(out) + wrote);
list_push(con->out, out);
break;
}
}
extern void tls_check_fingerprint(conmgr_callback_args_t conmgr_args, void *arg)
{
conmgr_fd_t *con = conmgr_args.con;
int match = EINVAL;
xassert(con->magic == MAGIC_CON_MGR_FD);
slurm_mutex_lock(&mgr.mutex);
if (con_flag(con, FLAG_TLS_CLIENT) || con_flag(con, FLAG_TLS_SERVER)) {
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] skipping TLS fingerprinting as TLS already activated",
__func__, con->name);
return;
}
/* fingerprinting must be done before reaching CONNECTED status */
xassert(con_flag(con, FLAG_IS_CONNECTED));
xassert(!con_flag(con, FLAG_IS_TLS_CONNECTED));
/* verify connection is not a listener which can't be fingerprinted */
xassert(!con_flag(con, FLAG_IS_LISTEN));
/* Verify TLS has not already started */
xassert(!con->tls);
xassert(!con->tls_in);
xassert(!con->tls_out);
xassert(!con_flag(con, FLAG_TLS_CLIENT));
xassert(!con_flag(con, FLAG_TLS_SERVER));
xassert(con_flag(con, FLAG_TLS_FINGERPRINT));
slurm_mutex_unlock(&mgr.mutex);
match = tls_is_handshake(get_buf_data(con->in), get_buf_offset(con->in),
con->name);
if (!match) {
slurm_mutex_lock(&mgr.mutex);
/* Only servers can accept an incoming TLS handshake requests */
con_set_flag(con, FLAG_TLS_SERVER);
/* Deactivate fingerprinting */
con_unset_flag(con, FLAG_TLS_FINGERPRINT);
con_unset_flag(con, FLAG_ON_DATA_TRIED);
/*
* _negotiate() will queue on_connection() callback once TLS
* negotiation is complete since it was deferred for
* fingerprinting
*/
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] TLS fingerprint matched",
__func__, con->name);
} else if (match == EWOULDBLOCK) {
slurm_mutex_lock(&mgr.mutex);
con_set_flag(con, FLAG_ON_DATA_TRIED);
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] waiting for more bytes for TLS fingerprint",
__func__, con->name);
} else if (match == ENOENT) {
slurm_mutex_lock(&mgr.mutex);
/* Deactivate fingerprinting */
con_unset_flag(con, FLAG_TLS_FINGERPRINT);
con_unset_flag(con, FLAG_ON_DATA_TRIED);
/* connection should never be TLS wrapped at this point */
xassert(!con_flag(con, FLAG_TLS_SERVER));
xassert(!con_flag(con, FLAG_TLS_CLIENT));
/*
* Run on_connection() callback now since it was deferred for
* fingerprinting
*/
if (con->events->on_connection)
queue_on_connection(con);
slurm_mutex_unlock(&mgr.mutex);
log_flag(CONMGR, "%s: [%s] TLS not detected",
__func__, con->name);
} else {
log_flag(CONMGR, "%s: [%s] TLS fingerprint failed: %s",
__func__, con->name, slurm_strerror(match));
/* should never happen */
xassert(false);
slurm_mutex_lock(&mgr.mutex);
con_set_status_code(con, match);
close_con(true, con);
slurm_mutex_unlock(&mgr.mutex);
}
}
extern bool tls_is_client_authenticated(conmgr_fd_t *con)
{
xassert(con->tls);
return tls_g_is_client_authenticated(con->tls);
}