/*****************************************************************************\
 *  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);
}

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

	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_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->output_fd < 0)) {
		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->output_fd < 0)) {
			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)) {
		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 ((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;
		}
	}
}

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