/*****************************************************************************\
 *  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"

#define HANDLE_ENC_ARGS_MAGIC 0x2a4afb43
typedef struct {
	int magic; /* HANDLE_ENC_ARGS_MAGIC */
	int index;
	conmgr_fd_t *con;
	ssize_t wrote;
} handle_enc_args_t;

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);
}

static void _post_wait_close_fds(bool locked, conmgr_fd_t *con)
{
	if (!locked)
		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);

	if (!locked)
		slurm_mutex_unlock(&mgr.mutex);
}

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);

	_post_wait_close_fds(false, con);
}

/*
 * Check and enforce if TLS has requested wait on operations and then close
 * connection
 */
static void _wait_close(bool locked, conmgr_fd_t *con)
{
	timespec_t delay = { 0 };

	if (!locked)
		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_TLS_WAIT_ON_CLOSE);
	con_unset_flag(con, FLAG_CAN_WRITE);
	con_unset_flag(con, FLAG_CAN_READ);

	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] closing now",
			 __func__, con->name);

		_post_wait_close_fds(true, con);
	}

	if (!locked)
		slurm_mutex_unlock(&mgr.mutex);
}

extern void tls_close(conmgr_callback_args_t conmgr_args, void *arg)
{
	conmgr_fd_t *con = conmgr_args.con;
	void *tls = NULL;
	int rc = EINVAL;
	buf_t *tls_in = NULL;
	list_t *tls_out = NULL;

	slurm_mutex_lock(&mgr.mutex);

	xassert(con->tls);
	xassert(con_flag(con, FLAG_TLS_CLIENT) ^
		con_flag(con, FLAG_TLS_SERVER));
	xassert(con->input_fd == -1);
	xassert(con_flag(con, FLAG_READ_EOF));
	xassert(!con_flag(con, FLAG_TLS_WAIT_ON_CLOSE));

	tls = con->tls;

	slurm_mutex_unlock(&mgr.mutex);

	if (!tls) {
		log_flag(CONMGR, "%s: [%s] closing TLS state skipped",
			 __func__, con->name);
		return;
	}

	log_flag(CONMGR, "%s: [%s] closing via tls_g_destroy()",
		 __func__, con->name);

	errno = SLURM_SUCCESS;
	tls_g_destroy_conn(tls, false);
	if ((rc = errno))
		log_flag(CONMGR, "%s: [%s] tls_g_destroy() failed: %s",
			 __func__, con->name, slurm_strerror(rc));

	slurm_mutex_lock(&mgr.mutex);
	xassert(tls == con->tls);
	con->tls = NULL;

	SWAP(tls_in, con->tls_in);
	SWAP(tls_out, con->tls_out);
	slurm_mutex_unlock(&mgr.mutex);

	FREE_NULL_BUFFER(tls_in);
	FREE_NULL_LIST(tls_out);
}

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 (con_flag(con, FLAG_ON_DATA_TRIED) ||
	    con_flag(con, FLAG_TLS_WAIT_ON_CLOSE)) {
		if (slurm_conf.debug_flags & DEBUG_FLAG_CONMGR) {
			char *flags = con_flags_string(con->flags);
			log_flag(NET, "%s: [%s] skipping with flags=%s",
				 __func__, con->name, flags);
			xfree(flags);
		}
		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 ((need = get_buf_offset(con->tls_in)) <= 0) {
		log_flag(NET, "%s: [%s] already decrypted all incoming TLS data",
			 __func__, con->name);
		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(false, 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) {
		if (errno == EAGAIN || 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: %m",
			 __func__, con->name);

		_wait_close(false, 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));

		slurm_mutex_lock(&mgr.mutex);
		/* lock to tell mgr that we are done */
		con_set_flag(con, FLAG_READ_EOF);
		slurm_mutex_unlock(&mgr.mutex);

		return;
	} else {
		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));

		if (get_buf_offset(con->tls_in) > 0) {
			try++;
			goto again;
		}
	}
}

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);

	if (con_flag(con, FLAG_WATCH_WRITE_TIMEOUT))
		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_tls() failed: %s",
				 __func__, con->name, slurm_strerror(rc));
		_wait_close(false, 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,
	};
	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);

		close_con(false, con);
		close_con_output(false, con);
		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));

			close_con(false, con);
			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);

		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);

		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);
	} 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);
}

static int _foreach_write_tls(void *x, void *key)
{
	buf_t *out = x;
	handle_enc_args_t *args = key;
	conmgr_fd_t *con = args->con;
	void *start = ((void *) get_buf_data(out)) + get_buf_offset(out);

	xassert(out->magic == BUF_MAGIC);
	xassert(args->magic == HANDLE_ENC_ARGS_MAGIC);
	xassert(con->magic == MAGIC_CON_MGR_FD);

	args->wrote = tls_g_send(con->tls, start, remaining_buf(out));
	if (args->wrote < 0) {
		error("%s: [%s] tls_g_send() failed: %m", __func__, con->name);
		return SLURM_ERROR;
	} else if (!args->wrote) {
		log_flag(NET, "%s: [%s] encrypt[%d] of 0/%u bytes to outgoing fd %u",
			 __func__, args->con->name, args->index,
			 remaining_buf(out), args->con->output_fd);
		return 0;
	}

	if (args->wrote >= remaining_buf(out)) {
		log_flag(NET, "%s: [%s] completed encrypt[%d] of %u/%u bytes to outgoing fd %u",
			 __func__, args->con->name, args->index,
			 remaining_buf(out), size_buf(out),
			 args->con->output_fd);
		log_flag_hex_range(NET_RAW, get_buf_data(out), size_buf(out),
				   get_buf_offset(out),
				   (get_buf_offset(out) + args->wrote),
				   "%s: [%s] completed encrypt[%d] of %u/%u bytes",
				   __func__, args->con->name, args->index,
				   remaining_buf(out), size_buf(out));

		args->wrote -= remaining_buf(out);
		args->index++;
		return 1;
	} else {
		log_flag(CONMGR, "%s: [%s] partial encrypt[%d] of %zd/%u bytes to outgoing fd %u",
			 __func__, args->con->name, args->index,
			 args->wrote, size_buf(out), args->con->output_fd);
		log_flag_hex_range(NET_RAW, get_buf_data(out), size_buf(out),
				   get_buf_offset(out),
				   (get_buf_offset(out) + args->wrote),
				   "%s: [%s] partial encrypt[%d] of %zd/%u bytes",
				   __func__, args->con->name, args->index,
				   args->wrote, size_buf(out));

		set_buf_offset(out, get_buf_offset(out) + args->wrote);
		args->wrote = 0;
		args->index++;
		return 0;
	}
}

extern void tls_handle_encrypt(conmgr_callback_args_t conmgr_args, void *arg)
{
	conmgr_fd_t *con = conmgr_args.con;
	handle_enc_args_t args = {
		.magic = HANDLE_ENC_ARGS_MAGIC,
		.con = con,
	};

	xassert(con->magic == MAGIC_CON_MGR_FD);
	xassert(con->tls);
	xassert(con_flag(con, FLAG_TLS_CLIENT) ||
		con_flag(con, FLAG_TLS_SERVER));

	if (list_delete_all(con->out, _foreach_write_tls, &args) < 0) {
		error("%s: [%s] _foreach_write_tls() failed",
		      __func__, con->name);
		/* drop outbound data on the floor */
		list_flush(con->out);
		_wait_close(false, con);
	}
}

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_READ_EOF) || con_flag(con, FLAG_CAN_READ)) {
		slurm_mutex_unlock(&mgr.mutex);

		log_flag(CONMGR, "%s: [%s] skipping TLS fingerprint match on closed connection",
			 __func__, con->name);
		return;
	}

	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);

		close_con(false, con);
	}
}
