/*
** Copyright (C) 2001-2026 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "zbxcomms.h"

#include "comms.h"
#include "tls.h"

#include "zbxlog.h"
#include "zbxstr.h"
#include "zbxcrypto.h"

#if OPENSSL_VERSION_NUMBER < 0x1010000fL
/* for OpenSSL 1.0.1/1.0.2 (before 1.1.0) */

/* mutexes for multi-threaded OpenSSL (see "man 3ssl threads" and example in crypto/threads/mttest.c) */

#include "zbxmutexs.h"

#ifdef _WINDOWS
typedef zbx_mutex_t tls_mutex_t;

int	tls_mutex_create(tls_mutex_t *mutex, char **error)
{
	return zbx_mutex_create(mutex, NULL, &error);
}

void	tls_mutex_destroy(tls_mutex_t *mutex)
{
	zbx_mutex_destroy(mutex);
}

void	tls_mutex_lock(tls_mutex_t mutex)
{
	zbx_mutex_lock(mutex);
}

void	tls_mutex_unlock(tls_mutex_t mutex)
{
	zbx_mutex_unlock(mutex);
}

#else
typedef pthread_mutex_t * tls_mutex_t;

int	tls_mutex_create(tls_mutex_t *mutex, char **error)
{
	*mutex = (pthread_mutex_t *)zbx_malloc(NULL, sizeof(pthread_mutex_t));

	if (0 != pthread_mutex_init(*mutex, NULL))
	{
		*error = zbx_dsprintf(*error, "cannot create mutex: %s", zbx_strerror(errno));
		return FAIL;
	}

	return SUCCEED;
}

void	tls_mutex_destroy(tls_mutex_t *mutex)
{
	pthread_mutex_destroy(*mutex);
	zbx_free(*mutex);
}

void	tls_mutex_lock(tls_mutex_t mutex)
{
	pthread_mutex_lock(mutex);
}

void	tls_mutex_unlock(tls_mutex_t mutex)
{
	pthread_mutex_unlock(mutex);
}

#endif

static tls_mutex_t	*crypto_mutexes = NULL;

static void	zbx_openssl_locking_cb(int mode, int n, const char *file, int line)
{
	ZBX_UNUSED(file);
	ZBX_UNUSED(line);

	if (0 != (mode & CRYPTO_LOCK))
		tls_mutex_lock(*(crypto_mutexes + n));
	else
		tls_mutex_unlock(*(crypto_mutexes + n));
}

static void	zbx_openssl_thread_setup(void)
{
	int	i, num_locks;

	num_locks = CRYPTO_num_locks();

	if (NULL == (crypto_mutexes = zbx_malloc(crypto_mutexes, num_locks * sizeof(tls_mutex_t))))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot allocate mutexes for OpenSSL library");
		exit(EXIT_FAILURE);
	}

	zabbix_log(LOG_LEVEL_DEBUG, "%s() creating %d mutexes", __func__, num_locks);

	for (i = 0; i < num_locks; i++)
	{
		char	*error = NULL;

		if (SUCCEED != tls_mutex_create(crypto_mutexes + i, &error))
		{
			zabbix_log(LOG_LEVEL_CRIT, "cannot create mutex #%d for OpenSSL library: %s", i, error);
			zbx_free(error);
			exit(EXIT_FAILURE);
		}
	}

	CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))zbx_openssl_locking_cb);

	/* do not register our own threadid_func() callback, use OpenSSL default one */
}

static void	zbx_openssl_thread_cleanup(void)
{
	int	i, num_locks;

	CRYPTO_set_locking_callback(NULL);

	num_locks = CRYPTO_num_locks();

	for (i = 0; i < num_locks; i++)
		tls_mutex_destroy(crypto_mutexes + i);

	zbx_free(crypto_mutexes);
}

static int	zbx_openssl_init_ssl(zbx_uint64_t opts, void *settings)
{
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
	ZBX_UNUSED(opts);
	ZBX_UNUSED(settings);

	SSL_load_error_strings();
	ERR_load_BIO_strings();
	SSL_library_init();
#endif
#ifdef _WINDOWS
	ZBX_UNUSED(opts);
	ZBX_UNUSED(settings);
#endif
	zbx_openssl_thread_setup();
	return 1;
}

static void	OPENSSL_cleanup(void)
{
	RAND_cleanup();
	ERR_free_strings();
	zbx_openssl_thread_cleanup();
}
#endif	/* OPENSSL_VERSION_NUMBER < 0x1010000fL */

#if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(LIBRESSL_VERSION_NUMBER)
/* OpenSSL 1.1.0 or newer, not LibreSSL */
static int	zbx_openssl_init_ssl(zbx_uint64_t opts, void *settings)
{
	return OPENSSL_init_ssl(opts, settings);
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(LIBRESSL_VERSION_NUMBER) */

static zbx_get_program_type_f		zbx_get_program_type_cb = NULL;

static ZBX_THREAD_LOCAL const char	*my_psk_identity	= NULL;
static ZBX_THREAD_LOCAL size_t		my_psk_identity_len	= 0;
static ZBX_THREAD_LOCAL char		*my_psk			= NULL;
static ZBX_THREAD_LOCAL size_t		my_psk_len		= 0;

/* Pointer to zbx_dc_get_psk_by_identity() initialized at runtime. This is a workaround for linking. */
/* Server and proxy link with src/libs/zbxdbcache/dbconfig.o where zbx_dc_get_psk_by_identity() resides */
/* but other components (e.g. agent) do not link dbconfig.o. */
static zbx_find_psk_in_cache_f	find_psk_in_cache_cb = NULL;

/* variable for passing information from callback functions if PSK was found among host PSKs or autoregistration PSK */
static unsigned int	psk_usage;

static zbx_tls_status_t	tls_status = ZBX_TLS_INIT_NONE;

static ZBX_THREAD_LOCAL const SSL_METHOD	*method			= NULL;
static ZBX_THREAD_LOCAL SSL_CTX			*ctx_cert		= NULL;
#ifdef HAVE_OPENSSL_WITH_PSK
static ZBX_THREAD_LOCAL SSL_CTX			*ctx_psk		= NULL;
static ZBX_THREAD_LOCAL SSL_CTX			*ctx_all		= NULL;
/* variables for passing required PSK identity and PSK info to client callback function */
static ZBX_THREAD_LOCAL const char		*psk_identity_for_cb	= NULL;
static ZBX_THREAD_LOCAL size_t			psk_identity_len_for_cb	= 0;
static ZBX_THREAD_LOCAL char			*psk_for_cb		= NULL;
static ZBX_THREAD_LOCAL size_t			psk_len_for_cb		= 0;
#endif
#ifdef HAVE_OPENSSL_WITH_PSK
/* variables for capturing PSK identity from server callback function */
static ZBX_THREAD_LOCAL int			incoming_connection_has_psk = 0;
static ZBX_THREAD_LOCAL char			incoming_connection_psk_id[PSK_MAX_IDENTITY_LEN + 1];
#endif
/* buffer for messages produced by zbx_openssl_info_cb() */
ZBX_THREAD_LOCAL char				info_buf[256];

/******************************************************************************
 *                                                                            *
 * Purpose: get state, alert, error information on TLS connection             *
 *                                                                            *
 * Comments:                                                                  *
 *     This is a callback function, its arguments are defined in OpenSSL.     *
 *                                                                            *
 ******************************************************************************/
static void	zbx_openssl_info_cb(const SSL *ssl, int where, int ret)
{
	/* There was an idea of using SSL_CB_LOOP and SSL_state_string_long() to write state changes into Zabbix log */
	/* if logging level is LOG_LEVEL_TRACE. Unfortunately if OpenSSL for security is compiled without SSLv3 */
	/* (i.e. OPENSSL_NO_SSL3 is defined) then SSL_state_string_long() does not provide descriptions of many */
	/* states anymore. The idea was dropped but the code is here for debugging hard problems. */
#if 0
	if (0 != (where & SSL_CB_LOOP))
	{
		zabbix_log(LOG_LEVEL_TRACE, "OpenSSL debug: state=0x%x \"%s\"", (unsigned int)SSL_state(ssl),
				SSL_state_string_long(ssl));
	}
#else
	ZBX_UNUSED(ssl);
#endif
	if (0 != (where & SSL_CB_ALERT))	/* alert sent or received */
	{
		const char	*handshake, *direction, *rw;

		if (0 != (where & SSL_CB_EXIT))
			handshake = " handshake";
		else
			handshake = "";

		if (0 != (where & SSL_ST_CONNECT))
			direction = " connect";
		else if (0 != (where & SSL_ST_ACCEPT))
			direction = " accept";
		else
			direction = "";

		if (0 != (where & SSL_CB_READ))
			rw = " read";
		else if (0 != (where & SSL_CB_WRITE))
			rw = " write";
		else
			rw = "";

		zbx_snprintf(info_buf, sizeof(info_buf), ": TLS%s%s%s %s alert \"%s\"", handshake, direction, rw,
				SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
	}
}

static void	tls_socket_event(SSL *ctx, ssize_t ssl_err, short *event)
{
	ZBX_UNUSED(ctx);
	switch (ssl_err)
	{
		case SSL_ERROR_WANT_WRITE:
			*event = POLLOUT;
			break;
		/*case SSL_ERROR_WANT_READ:*/
		default:
			*event = POLLIN;
			break;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: wait for socket to be available for read or write depending on    *
 *          unfinished operation                                              *
 *                                                                            *
 ******************************************************************************/
static int	tls_socket_wait(ZBX_SOCKET s, SSL *ctx, ssize_t ssl_err)
{
	zbx_pollfd_t	pd;
	int		ret;
	short		event;

	ZBX_UNUSED(ctx);

	pd.fd = s;

	tls_socket_event(ctx, ssl_err, &pd.events);

	event = pd.events;

	if (0 > (ret = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
	{
		if (SUCCEED != zbx_socket_had_nonblocking_error())
			return FAIL;

		return SUCCEED;
	}

	if (1 == ret && 0 == (pd.revents & event))
		return FAIL;

	return SUCCEED;
}

static int	tls_is_nonblocking_error(ssize_t err)
{
	if (SSL_ERROR_WANT_READ == err || SSL_ERROR_WANT_WRITE == err)
		return SUCCEED;

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: compose a TLS error message                                       *
 *                                                                            *
 ******************************************************************************/
static void	zbx_tls_error_msg(char **error, size_t *error_alloc, size_t *error_offset)
{
	unsigned long	error_code;
	const char	*file, *data;
	int		line, flags;
	char		err[1024];

	/* concatenate all error messages in the queue into one string */
#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER >=	3	/* OpenSSL 3.0.0 or newer */
	const char	*func;

	while (0 != (error_code = ERR_get_error_all(&file, &line, &func, &data, &flags)))
#else									/* OpenSSL 1.x.x or LibreSSL */
	while (0 != (error_code = ERR_get_error_line_data(&file, &line, &data, &flags)))
#endif
	{
		ERR_error_string_n(error_code, err, sizeof(err));

#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER >=	3	/* OpenSSL 3.0.0 or newer */
		zbx_snprintf_alloc(error, error_alloc, error_offset, " file %s line %d func %s: %s",
				file, line, func, err);
#else									/* OpenSSL 1.x.x or LibreSSL */
		zbx_snprintf_alloc(error, error_alloc, error_offset, " file %s line %d: %s", file, line, err);
#endif
		if (NULL != data && 0 != (flags & ERR_TXT_STRING))
			zbx_snprintf_alloc(error, error_alloc, error_offset, ": %s", data);
	}
}

#if defined(HAVE_OPENSSL_WITH_PSK)
/******************************************************************************
 *                                                                            *
 * Purpose:                                                                   *
 *     set pre-shared key for outgoing TLS connection upon OpenSSL request    *
 *                                                                            *
 * Parameters:                                                                *
 *     ssl              - [IN] not used                                       *
 *     hint             - [IN] not used                                       *
 *     identity         - [OUT] buffer to write PSK identity into             *
 *     max_identity_len - [IN] size of the 'identity' buffer                  *
 *     psk              - [OUT] buffer to write PSK into                      *
 *     max_psk_len      - [IN] size of the 'psk' buffer                       *
 *                                                                            *
 * Return value:                                                              *
 *     > 0 - length of PSK in bytes                                           *
 *       0 - an error occurred                                                *
 *                                                                            *
 * Comments:                                                                  *
 *     A callback function, its arguments are defined in OpenSSL.             *
 *     Used in all programs making outgoing TLS PSK connections.              *
 *                                                                            *
 *     As a client we use different PSKs depending on connection to be made.  *
 *     Apparently there is no simple way to specify which PSK should be set   *
 *     by this callback function. We use global variables to pass this info.  *
 *                                                                            *
 ******************************************************************************/
static unsigned int	zbx_psk_client_cb(SSL *ssl, const char *hint, char *identity,
		unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len)
{
	ZBX_UNUSED(ssl);
	ZBX_UNUSED(hint);

	zabbix_log(LOG_LEVEL_DEBUG, "%s() requested PSK identity \"%s\"", __func__, psk_identity_for_cb);

	if (max_identity_len < psk_identity_len_for_cb + 1)	/* 1 byte for terminating '\0' */
	{
		zabbix_log(LOG_LEVEL_WARNING, "requested PSK identity \"%s\" does not fit into %u-byte buffer",
				psk_identity_for_cb, max_identity_len);
		return 0;
	}

	if (max_psk_len < psk_len_for_cb)
	{
		zabbix_log(LOG_LEVEL_WARNING, "PSK associated with PSK identity \"%s\" does not fit into %u-byte"
				" buffer", psk_identity_for_cb, max_psk_len);
		return 0;
	}

	zbx_strlcpy(identity, psk_identity_for_cb, max_identity_len);
	memcpy(psk, psk_for_cb, psk_len_for_cb);

	return (unsigned int)psk_len_for_cb;
}

/******************************************************************************
 *                                                                            *
 * Purpose:                                                                   *
 *     set pre-shared key for incoming TLS connection upon OpenSSL request    *
 *                                                                            *
 * Parameters:                                                                *
 *     ssl              - [IN] not used                                       *
 *     identity         - [IN] PSK identity sent by client                    *
 *     psk              - [OUT] buffer to write PSK into                      *
 *     max_psk_len      - [IN] size of the 'psk' buffer                       *
 *                                                                            *
 * Return value:                                                              *
 *     > 0 - length of PSK in bytes                                           *
 *       0 - PSK identity not found                                           *
 *                                                                            *
 * Comments:                                                                  *
 *     A callback function, its arguments are defined in OpenSSL.             *
 *     Used in all programs accepting incoming TLS PSK connections.           *
 *                                                                            *
 ******************************************************************************/
static unsigned int	zbx_psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk,
		unsigned int max_psk_len)
{
	const char	*psk_loc;
	size_t		psk_len = 0;
	int		psk_bin_len;
	unsigned char	tls_psk_hex[HOST_TLS_PSK_LEN_MAX], psk_buf[HOST_TLS_PSK_LEN / 2];

	ZBX_UNUSED(ssl);

	zabbix_log(LOG_LEVEL_DEBUG, "%s() requested PSK identity \"%s\"", __func__, identity);

	incoming_connection_has_psk = 1;
	psk_usage = 0;

	if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_PROXY | ZBX_PROGRAM_TYPE_SERVER)))
	{
		/* call the function zbx_dc_get_psk_by_identity() by pointer */
		if (0 < find_psk_in_cache_cb((const unsigned char *)identity, tls_psk_hex, &psk_usage))
		{
			/* The PSK is in configuration cache. Convert PSK to binary form. */
			if (0 >= (psk_bin_len = zbx_hex2bin(tls_psk_hex, psk_buf, sizeof(psk_buf))))
			{
				/* this should have been prevented by validation in frontend or API */
				zabbix_log(LOG_LEVEL_WARNING, "cannot convert PSK to binary form for PSK identity"
						" \"%s\"", identity);
				goto fail;
			}

			psk_loc = (const char *)psk_buf;
			psk_len = (size_t)psk_bin_len;
		}

		if (0 != (zbx_get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY) &&
				0 < my_psk_identity_len &&
				0 == strcmp(my_psk_identity, identity))
		{
			/* the PSK is in proxy configuration file */
			psk_usage |= ZBX_PSK_FOR_PROXY;

			if (0 < psk_len && (psk_len != my_psk_len || 0 != memcmp(psk_loc, my_psk, psk_len)))
			{
				/* PSK was also found in configuration cache but with different value */
				zbx_psk_warn_misconfig(identity);
				psk_usage &= ~(unsigned int)ZBX_PSK_FOR_AUTOREG;
			}

			psk_loc = my_psk;	/* prefer PSK from proxy configuration file */
			psk_len = my_psk_len;
		}

		if (0 == psk_len)
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot find requested PSK identity \"%s\"", identity);
			goto fail;
		}
	}
	else if (0 != (zbx_get_program_type_cb() & ZBX_PROGRAM_TYPE_AGENTD))
	{
		if (0 < my_psk_identity_len)
		{
			if (0 == strcmp(my_psk_identity, identity))
			{
				psk_loc = my_psk;
				psk_len = my_psk_len;
			}
			else
			{
				zabbix_log(LOG_LEVEL_WARNING, "cannot find requested PSK identity \"%s\", available PSK"
						" identity \"%s\"", identity, my_psk_identity);
				goto fail;
			}
		}
	}

	if (0 < psk_len)
	{
		if ((size_t)max_psk_len < psk_len)
		{
			zabbix_log(LOG_LEVEL_WARNING, "PSK associated with PSK identity \"%s\" does not fit into"
					" %u-byte buffer", identity, max_psk_len);
			goto fail;
		}

		memcpy(psk, psk_loc, psk_len);
		zbx_strlcpy(incoming_connection_psk_id, identity, sizeof(incoming_connection_psk_id));

		return (unsigned int)psk_len;	/* success */
	}
fail:
	incoming_connection_psk_id[0] = '\0';
	return 0;	/* PSK not found */
}
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: write names of enabled OpenSSL ciphersuites into Zabbix log for   *
 *          debugging                                                         *
 *                                                                            *
 * Parameters:                                                                *
 *     title1  - [IN] name of the calling function                            *
 *     title2  - [IN] name of the group of ciphersuites                       *
 *     ciphers - [IN] stack of ciphersuites                                   *
 *                                                                            *
 ******************************************************************************/
static void	zbx_log_ciphersuites(const char *title1, const char *title2, SSL_CTX *ciphers)
{
	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		char			*msg = NULL;
		size_t			msg_alloc = 0, msg_offset = 0;
		int			i, num;
		STACK_OF(SSL_CIPHER)	*cipher_list;

		zbx_snprintf_alloc(&msg, &msg_alloc, &msg_offset, "%s() %s ciphersuites:", title1, title2);

		cipher_list = SSL_CTX_get_ciphers(ciphers);
		num = sk_SSL_CIPHER_num(cipher_list);

		for (i = 0; i < num; i++)
		{
			zbx_snprintf_alloc(&msg, &msg_alloc, &msg_offset, " %s",
					SSL_CIPHER_get_name(sk_SSL_CIPHER_value(cipher_list, i)));
		}

		zabbix_log(LOG_LEVEL_DEBUG, "%s", msg);
		zbx_free(msg);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose:                                                                   *
 *     Print distinguished name (i.e. issuer, subject) into buffer. Intended  *
 *     to use as an alternative to OpenSSL X509_NAME_oneline() and to meet    *
 *     application needs.                                                     *
 *                                                                            *
 * Parameters:                                                                *
 *     dn    - [IN] pointer to distinguished name                             *
 *     buf   - [OUT] output buffer                                            *
 *     size  - [IN] output buffer size                                        *
 *     error - [OUT] dynamically allocated memory with error message          *
 *                                                                            *
 * Return value:                                                              *
 *     SUCCEED - no errors, FAIL - an error occurred                          *
 *                                                                            *
 * Comments:                                                                  *
 *     Examples often use OpenSSL X509_NAME_oneline() to print certificate    *
 *     issuer and subject into memory buffers but it is a legacy function and *
 *     strongly discouraged in new applications. So, we have to use functions *
 *     writing into BIOs and then turn results into memory buffers.           *
 *                                                                            *
 ******************************************************************************/
static int	zbx_x509_dn_gets(X509_NAME *dn, char *buf, size_t size, char **error)
{
	BIO		*bio;
	const char	*data;
	size_t		len;
	int		ret = FAIL;

	if (NULL == (bio = BIO_new(BIO_s_mem())))
	{
		*error = zbx_strdup(*error, "cannot create BIO");
		goto out;
	}

	/* XN_FLAG_RFC2253 - RFC 2253 is outdated, it was replaced by RFC 4514 "Lightweight Directory Access Protocol */
	/* (LDAP): String Representation of Distinguished Names" */

	if (0 > X509_NAME_print_ex(bio, dn, 0, XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB))
	{
		*error = zbx_strdup(*error, "cannot print distinguished name");
		goto out;
	}

	if (size <= (len = (size_t)BIO_get_mem_data(bio, &data)))
	{
		*error = zbx_strdup(*error, "output buffer too small");
		goto out;
	}

	zbx_strlcpy(buf, data, len + 1);
	ret = SUCCEED;
out:
	if (NULL != bio)
	{
		/* ensure that associated memory buffer will be freed by BIO_vfree() */
		(void)BIO_set_close(bio, BIO_CLOSE);
		BIO_vfree(bio);
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: write peer certificate information into Zabbix log for debugging  *
 *                                                                            *
 * Parameters:                                                                *
 *     function_name - [IN] caller function name                              *
 *     tls_ctx       - [IN] TLS context                                       *
 *                                                                            *
 ******************************************************************************/
static void	zbx_log_peer_cert(const char *function_name, const zbx_tls_context_t *tls_ctx)
{
	char	*error = NULL;
	X509	*cert;
	char	issuer[HOST_TLS_ISSUER_LEN_MAX], subject[HOST_TLS_SUBJECT_LEN_MAX];

	if (SUCCEED != ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
		return;

	if (NULL == (cert = SSL_get_peer_certificate(tls_ctx->ctx)))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot obtain peer certificate", function_name);
	}
	else if (SUCCEED != zbx_x509_dn_gets(X509_get_issuer_name(cert), issuer, sizeof(issuer), &error))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot obtain peer certificate issuer: %s", function_name, error);
	}
	else if (SUCCEED != zbx_x509_dn_gets(X509_get_subject_name(cert), subject, sizeof(subject), &error))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot obtain peer certificate subject: %s", function_name, error);
	}
	else
	{
		zabbix_log(LOG_LEVEL_DEBUG, "%s() peer certificate issuer:\"%s\" subject:\"%s\"",
				function_name, issuer, subject);
	}

	if (NULL != cert)
		X509_free(cert);

	zbx_free(error);
}

/******************************************************************************
 *                                                                            *
 * Purpose:                                                                   *
 *     verify peer certificate issuer and subject of the given TLS context    *
 *                                                                            *
 * Parameters:                                                                *
 *     tls_ctx      - [IN] TLS context to verify                              *
 *     issuer       - [IN] required issuer (ignore if NULL or empty string)   *
 *     subject      - [IN] required subject (ignore if NULL or empty string)  *
 *     error        - [OUT] dynamically allocated memory with error message   *
 *                                                                            *
 * Return value:                                                              *
 *     SUCCEED or FAIL                                                        *
 *                                                                            *
 ******************************************************************************/
static int	zbx_verify_issuer_subject(const zbx_tls_context_t *tls_ctx, const char *issuer, const char *subject,
		char **error)
{
	char			tls_issuer[HOST_TLS_ISSUER_LEN_MAX], tls_subject[HOST_TLS_SUBJECT_LEN_MAX];
	int			issuer_mismatch = 0, subject_mismatch = 0;
	size_t			error_alloc = 0, error_offset = 0;
	X509			*cert;

	if ((NULL == issuer || '\0' == *issuer) && (NULL == subject || '\0' == *subject))
		return SUCCEED;

	tls_issuer[0] = '\0';
	tls_subject[0] = '\0';

	if (NULL == (cert = SSL_get_peer_certificate(tls_ctx->ctx)))
	{
		*error = zbx_strdup(*error, "cannot obtain peer certificate");
		return FAIL;
	}

	if (NULL != issuer && '\0' != *issuer)
	{
		if (SUCCEED != zbx_x509_dn_gets(X509_get_issuer_name(cert), tls_issuer, sizeof(tls_issuer), error))
		{
			X509_free(cert);
			return FAIL;
		}
	}

	if (NULL != subject && '\0' != *subject)
	{
		if (SUCCEED != zbx_x509_dn_gets(X509_get_subject_name(cert), tls_subject, sizeof(tls_subject), error))
		{
			X509_free(cert);
			return FAIL;
		}
	}

	X509_free(cert);

	/* simplified match, not compliant with RFC 4517, 4518 */

	if (NULL != issuer && '\0' != *issuer)
		issuer_mismatch = strcmp(tls_issuer, issuer);

	if (NULL != subject && '\0' != *subject)
		subject_mismatch = strcmp(tls_subject, subject);

	if (0 == issuer_mismatch && 0 == subject_mismatch)
		return SUCCEED;

	if (0 != issuer_mismatch)
	{
		zbx_snprintf_alloc(error, &error_alloc, &error_offset, "issuer: peer: \"%s\", required: \"%s\"",
				tls_issuer, issuer);
	}

	if (0 != subject_mismatch)
	{
		if (0 != issuer_mismatch)
			zbx_strcpy_alloc(error, &error_alloc, &error_offset, ", ");

		zbx_snprintf_alloc(error, &error_alloc, &error_offset, "subject: peer: \"%s\", required: \"%s\"",
				tls_subject, subject);
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize TLS library, log library version                       *
 *                                                                            *
 * Comments:                                                                  *
 *     Some crypto libraries require initialization. On Unix the              *
 *     initialization is done separately in each child process which uses     *
 *     crypto libraries. On MS Windows it is done in the first thread.        *
 *                                                                            *
 *     Flag 'init_status' is used to prevent library deinitialization on exit *
 *     if it was not yet initialized (can happen if termination signal is     *
 *     received) and library initialization in threads when it was done in    *
 *     parent process.                                                        *
 *                                                                            *
 ******************************************************************************/
static void	zbx_tls_library_init(zbx_tls_status_t status)
{
	/* skip initialization in threads if it was already done in parent process */
	if (ZBX_TLS_INIT_NONE != tls_status)
		return;

#if !defined(LIBRESSL_VERSION_NUMBER)	/* LibreSSL does not require initialization */
	if (1 != zbx_openssl_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot initialize OpenSSL library");
		exit(EXIT_FAILURE);
	}
#endif
	tls_status = status;

	zabbix_log(LOG_LEVEL_DEBUG, "OpenSSL library (version %s) initialized", OpenSSL_version(OPENSSL_VERSION));
}

/******************************************************************************
 *                                                                            *
 * Purpose: deinitialize TLS library                                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_library_deinit(zbx_tls_status_t status)
{
	if (status == tls_status)
	{
		tls_status = 0;
		OPENSSL_cleanup();
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize TLS library in a parent process                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_init_parent(zbx_get_program_type_f zbx_get_program_type_cb_arg)
{
	zbx_get_program_type_cb = zbx_get_program_type_cb_arg;

	zbx_tls_library_init(ZBX_TLS_INIT_THREADS);
}

static const char	*zbx_ctx_name(SSL_CTX *param)
{
	if (ctx_cert == param)
		return "certificate-based encryption";

#if defined(HAVE_OPENSSL_WITH_PSK)
	if (ctx_psk == param)
		return "PSK-based encryption";

	if (ctx_all == param)
		return "certificate and PSK-based encryption";
#endif
	THIS_SHOULD_NEVER_HAPPEN;
	return ZBX_NULL2STR(NULL);
}

static int	zbx_set_ecdhe_parameters(SSL_CTX *ctx)
{
	const char	*msg = "Perfect Forward Secrecy ECDHE ciphersuites will not be available for";
	long		res;
	int		ret = SUCCEED;
#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER >= 3	/* OpenSSL 3.0.0 or newer */
	int		grp_list[1] = { NID_X9_62_prime256v1 };	/* use curve secp256r1/prime256v1/NIST P-256 */

	if (1 != (res = SSL_CTX_set1_groups(ctx, grp_list, ARRSIZE(grp_list))))
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s() SSL_CTX_set1_groups() returned %ld. %s %s",
				__func__, res, msg, zbx_ctx_name(ctx));
		ret = FAIL;
	}
#else									/* OpenSSL 1.x.x or LibreSSL */
	EC_KEY	*ecdh;

	/* use curve secp256r1/prime256v1/NIST P-256 */
	if (NULL == (ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)))
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s() EC_KEY_new_by_curve_name() failed. %s %s",
				__func__, msg, zbx_ctx_name(ctx));
		return FAIL;
	}

	SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);

	if (1 != (res = SSL_CTX_set_tmp_ecdh(ctx, ecdh)))
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s() SSL_CTX_set_tmp_ecdh() returned %ld. %s %s",
				__func__, res, msg, zbx_ctx_name(ctx));
		ret = FAIL;
	}

	EC_KEY_free(ecdh);
#endif
	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: read available configuration parameters and initialize TLS        *
 *          library in a child process                                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_init_child(const zbx_config_tls_t *config_tls, zbx_get_program_type_f zbx_get_program_type_cb_arg,
		zbx_find_psk_in_cache_f zbx_find_psk_in_cache_cb_arg)
{
#define ZBX_CIPHERS_CERT_ECDHE		"EECDH+aRSA+AES128:"
#define ZBX_CIPHERS_CERT		"RSA+aRSA+AES128"

#if defined(HAVE_OPENSSL_WITH_PSK)
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
	/* TLS_AES_256_GCM_SHA384 is excluded from client ciphersuite list for PSK based connections. */
	/* By default, in TLS 1.3 only *-SHA256 ciphersuites work with PSK. */
#	define ZBX_CIPHERS_PSK_TLS13	"TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
#endif
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL	/* OpenSSL 1.1.0 or newer */
#	define ZBX_CIPHERS_PSK_ECDHE	"kECDHEPSK+AES128:"
#	define ZBX_CIPHERS_PSK		"kPSK+AES128"
#else
#	define ZBX_CIPHERS_PSK_ECDHE	""
#	define ZBX_CIPHERS_PSK		"PSK-AES128-CBC-SHA"
#endif
#endif
	char	*error = NULL;
	size_t	error_alloc = 0, error_offset = 0;
#ifndef _WINDOWS
	sigset_t	mask, orig_mask;
#endif
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_get_program_type_cb = zbx_get_program_type_cb_arg;
	find_psk_in_cache_cb = zbx_find_psk_in_cache_cb_arg;

#ifndef _WINDOWS
	/* Invalid TLS parameters will cause exit. Once one process exits the parent process will send SIGHUP to */
	/* child processes which may be on their way to exit on their own - do not interrupt them, block signal */
	/* SIGHUP and unblock it when TLS parameters are good and libraries are initialized. */
	sigemptyset(&mask);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGHUP);
	sigaddset(&mask, SIGUSR2);
	sigaddset(&mask, SIGQUIT);
	zbx_sigmask(SIG_BLOCK, &mask, &orig_mask);
#endif
	zbx_tls_library_init(ZBX_TLS_INIT_PROCESS);

	if (1 != RAND_status())		/* protect against not properly seeded PRNG */
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot initialize PRNG");
		zbx_tls_free();
		exit(EXIT_FAILURE);
	}

	/* set protocol version to TLS 1.2 */
	if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_SENDER | ZBX_PROGRAM_TYPE_GET)))
		method = TLS_client_method();
	else	/* ZBX_PROGRAM_TYPE_SERVER | ZBX_PROGRAM_TYPE_PROXY | ZBX_PROGRAM_TYPE_AGENTD */
		method = TLS_method();

	/* create context for certificate-only authentication if certificate is configured */
	if (NULL != config_tls->cert_file)
	{
		if (NULL == (ctx_cert = SSL_CTX_new(method)))
			goto out_method;

		if (1 != SSL_CTX_set_min_proto_version(ctx_cert, TLS1_2_VERSION))
			goto out_method;
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	/* Create context for PSK-only authentication. PSK can come from configuration file (in proxy, agentd) */
	/* and later from database (in server, proxy). */
	if (NULL != config_tls->psk_file || 0 != (zbx_get_program_type_cb() &
			(ZBX_PROGRAM_TYPE_SERVER | ZBX_PROGRAM_TYPE_PROXY)))
	{
		if (NULL == (ctx_psk = SSL_CTX_new(method)))
			goto out_method;

		if (1 != SSL_CTX_set_min_proto_version(ctx_psk, TLS1_2_VERSION))
			goto out_method;
	}

	/* Sometimes we need to be ready for both certificate and PSK whichever comes in. Set up a universal context */
	/* for certificate and PSK authentication to prepare for both. */
	if (NULL != ctx_cert && NULL != ctx_psk)
	{
		if (NULL == (ctx_all = SSL_CTX_new(method)))
			goto out_method;

		if (1 != SSL_CTX_set_min_proto_version(ctx_all, TLS1_2_VERSION))
			goto out_method;
	}
#endif
	/* 'TLSCAFile' parameter (in zabbix_server.conf, zabbix_proxy.conf, zabbix_agentd.conf) */
	if (NULL != config_tls->ca_file)
	{
#if defined(HAVE_OPENSSL_WITH_PSK)
		if (1 != SSL_CTX_load_verify_locations(ctx_cert, config_tls->ca_file, NULL) ||
				(NULL != ctx_all && 1 != SSL_CTX_load_verify_locations(ctx_all,
				config_tls->ca_file, NULL)))
#else
		if (1 != SSL_CTX_load_verify_locations(ctx_cert, config_tls->ca_file, NULL))
#endif
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot load CA certificate(s) from"
					" file \"%s\":", config_tls->ca_file);
			goto out;
		}

		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded CA certificate(s) from file \"%s\"", __func__,
				config_tls->ca_file);

		/* It is possible to limit the length of certificate chain being verified. For example: */
		/* SSL_CTX_set_verify_depth(ctx_cert, 2); */
		/* Currently use the default depth 100. */

		SSL_CTX_set_verify(ctx_cert, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);

#if defined(HAVE_OPENSSL_WITH_PSK)
		if (NULL != ctx_all)
			SSL_CTX_set_verify(ctx_all, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
#endif
	}

	/* 'TLSCRLFile' parameter (in zabbix_server.conf, zabbix_proxy.conf, zabbix_agentd.conf). */
	/* Load CRL (certificate revocation list) file. */
	if (NULL != config_tls->crl_file)
	{
		X509_STORE	*store_cert;
		X509_LOOKUP	*lookup_cert;
		int		count_cert;

		store_cert = SSL_CTX_get_cert_store(ctx_cert);

		if (NULL == (lookup_cert = X509_STORE_add_lookup(store_cert, X509_LOOKUP_file())))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "X509_STORE_add_lookup() #%d failed"
					" when loading CRL(s) from file \"%s\":", 1, config_tls->crl_file);
			goto out;
		}

		if (0 >= (count_cert = X509_load_crl_file(lookup_cert, config_tls->crl_file, X509_FILETYPE_PEM)))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot load CRL(s) from file \"%s\":",
					config_tls->crl_file);
			goto out;
		}

		if (1 != X509_STORE_set_flags(store_cert, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "X509_STORE_set_flags() #%d failed when"
					" loading CRL(s) from file \"%s\":", 1, config_tls->crl_file);
			goto out;
		}
#if defined(HAVE_OPENSSL_WITH_PSK)
		if (NULL != ctx_all)
		{
			X509_STORE	*store_all;
			X509_LOOKUP	*lookup_all;
			int		count_all;

			store_all = SSL_CTX_get_cert_store(ctx_all);

			if (NULL == (lookup_all = X509_STORE_add_lookup(store_all, X509_LOOKUP_file())))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "X509_STORE_add_lookup() #%d"
						" failed when loading CRL(s) from file \"%s\":", 2,
						config_tls->crl_file);
				goto out;
			}

			if (0 >= (count_all = X509_load_crl_file(lookup_all, config_tls->crl_file,
					X509_FILETYPE_PEM)))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot load CRL(s) from file"
						" \"%s\":", config_tls->crl_file);
				goto out;
			}

			if (count_cert != count_all)
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "number of CRL(s) loaded from"
						" file \"%s\" does not match: %d and %d", config_tls->crl_file,
						count_cert, count_all);
				goto out1;
			}

			if (1 != X509_STORE_set_flags(store_all, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "X509_STORE_set_flags() #%d"
						" failed when loading CRL(s) from file \"%s\":", 2,
						config_tls->crl_file);
				goto out;
			}
		}
#endif
		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded %d CRL(s) from file \"%s\"", __func__, count_cert,
				config_tls->crl_file);
	}

	/* 'TLSCertFile' parameter (in zabbix_server.conf, zabbix_proxy.conf, zabbix_agentd.conf). */
	/* Load certificate. */
	if (NULL != config_tls->cert_file)
	{
#if defined(HAVE_OPENSSL_WITH_PSK)
		if (1 != SSL_CTX_use_certificate_chain_file(ctx_cert, config_tls->cert_file) ||
				(NULL != ctx_all && 1 != SSL_CTX_use_certificate_chain_file(ctx_all,
				config_tls->cert_file)))
#else
		if (1 != SSL_CTX_use_certificate_chain_file(ctx_cert, config_tls->cert_file))
#endif
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot load certificate(s) from file"
					" \"%s\":", config_tls->cert_file);
			goto out;
		}

		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded certificate(s) from file \"%s\"", __func__,
				config_tls->cert_file);
	}

	/* 'TLSKeyFile' parameter (in zabbix_server.conf, zabbix_proxy.conf, zabbix_agentd.conf). */
	/* Load private key. */
	if (NULL != config_tls->key_file)
	{
#if defined(HAVE_OPENSSL_WITH_PSK)
		if (1 != SSL_CTX_use_PrivateKey_file(ctx_cert, config_tls->key_file, SSL_FILETYPE_PEM) ||
				(NULL != ctx_all && 1 != SSL_CTX_use_PrivateKey_file(ctx_all,
				config_tls->key_file, SSL_FILETYPE_PEM)))
#else
		if (1 != SSL_CTX_use_PrivateKey_file(ctx_cert, config_tls->key_file, SSL_FILETYPE_PEM))
#endif
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot load private key from file"
					" \"%s\":", config_tls->key_file);
			goto out;
		}

		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded private key from file \"%s\"", __func__,
				config_tls->key_file);

		if (1 != SSL_CTX_check_private_key(ctx_cert))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "certificate and private key do not"
					" match:");
			goto out;
		}
	}

	/* 'TLSPSKIdentity' and 'TLSPSKFile' parameters (in zabbix_proxy.conf, zabbix_agentd.conf). */
	/*  Load pre-shared key and identity to be used with the pre-shared key. */
	if (NULL != config_tls->psk_file)
	{
		my_psk_identity = config_tls->psk_identity;
		my_psk_identity_len = strlen(my_psk_identity);

		zbx_check_psk_identity_len(my_psk_identity_len);

		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded PSK identity \"%s\"", __func__, config_tls->psk_identity);

		zbx_read_psk_file(config_tls->psk_file, &my_psk, &my_psk_len);

		zabbix_log(LOG_LEVEL_DEBUG, "%s() loaded PSK from file \"%s\"", __func__,
				config_tls->psk_file);
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	/* set up PSK global variables for client callback if PSK comes only from configuration file or command line */

	if (NULL != ctx_psk && 0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_AGENTD | ZBX_PROGRAM_TYPE_SENDER |
			ZBX_PROGRAM_TYPE_GET)))
	{
		psk_identity_for_cb = my_psk_identity;
		psk_identity_len_for_cb = my_psk_identity_len;
		psk_for_cb = my_psk;
		psk_len_for_cb = my_psk_len;
	}
#endif
	if (NULL != ctx_cert)
	{
		const char	*ciphers;

		SSL_CTX_set_info_callback(ctx_cert, zbx_openssl_info_cb);

		/* use server ciphersuite preference, do not use RFC 4507 ticket extension */
		SSL_CTX_set_options(ctx_cert, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET);

		/* do not connect to unpatched servers */
		SSL_CTX_clear_options(ctx_cert, SSL_OP_LEGACY_SERVER_CONNECT);

		/* disable session caching */
		SSL_CTX_set_session_cache_mode(ctx_cert, SSL_SESS_CACHE_OFF);

		/* try to enable ECDH ciphersuites */
		if (SUCCEED == zbx_set_ecdhe_parameters(ctx_cert))
			ciphers = ZBX_CIPHERS_CERT_ECDHE ZBX_CIPHERS_CERT;
		else
			ciphers = ZBX_CIPHERS_CERT;

		/* override TLS 1.3 certificate ciphersuites with user-defined settings */
		if (NULL != config_tls->cipher_cert13 || NULL != config_tls->cipher_cmd13)
		{
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL && !defined(LIBRESSL_VERSION_NUMBER)	/* only OpenSSL 1.1.1 or newer */
			const char	*override_ciphers = config_tls->cipher_cert13; /* can be NULL */

			if (NULL != config_tls->cipher_cmd13 && ZBX_TCP_SEC_TLS_CERT ==
					config_tls->connect_mode)
			{
				override_ciphers = config_tls->cipher_cmd13;
			}

			if (NULL != override_ciphers && 1 != SSL_CTX_set_ciphersuites(ctx_cert, override_ciphers))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
						" certificate ciphersuites from \"TLSCipherCert13\" or"
						" \"--tls-cipher13\" parameter:");
				goto out;
			}
#else
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
					" certificate ciphersuites: compiled with OpenSSL version older than 1.1.1 or"
					" with LibreSSL. Consider not using parameters \"TLSCipherCert13\" or"
					" \"--tls-cipher13\"");
			goto out1;
#endif
		}

		/* override TLS 1.2 certificate ciphersuites with user-defined settings */
		if (NULL != config_tls->cipher_cert || NULL != config_tls->cipher_cmd)
		{
			const char	*override_ciphers = config_tls->cipher_cert;	/* can be NULL */

			if (NULL != config_tls->cipher_cmd && ZBX_TCP_SEC_TLS_CERT ==
					config_tls->connect_mode)
			{
				override_ciphers = config_tls->cipher_cmd;
			}

			if (NULL != override_ciphers && 1 != SSL_CTX_set_cipher_list(ctx_cert, override_ciphers))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.2"
						" certificate ciphersuites from \"TLSCipherCert\" or \"--tls-cipher\""
						" parameter:");
				goto out;
			}
		}
		else if (1 != SSL_CTX_set_cipher_list(ctx_cert, ciphers))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of certificate"
					" ciphersuites:");
			goto out;
		}

		zbx_log_ciphersuites(__func__, "certificate", ctx_cert);
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	if (NULL != ctx_psk)
	{
		const char	*ciphers;

		SSL_CTX_set_info_callback(ctx_psk, zbx_openssl_info_cb);

		if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_SERVER | ZBX_PROGRAM_TYPE_PROXY |
				ZBX_PROGRAM_TYPE_AGENTD | ZBX_PROGRAM_TYPE_SENDER | ZBX_PROGRAM_TYPE_GET)))
		{
			SSL_CTX_set_psk_client_callback(ctx_psk, zbx_psk_client_cb);
		}

		if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_SERVER | ZBX_PROGRAM_TYPE_PROXY |
				ZBX_PROGRAM_TYPE_AGENTD)))
		{
			SSL_CTX_set_psk_server_callback(ctx_psk, zbx_psk_server_cb);
		}

		SSL_CTX_set_options(ctx_psk, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET);
		SSL_CTX_clear_options(ctx_psk, SSL_OP_LEGACY_SERVER_CONNECT);
		SSL_CTX_set_session_cache_mode(ctx_psk, SSL_SESS_CACHE_OFF);

		if ('\0' != *ZBX_CIPHERS_PSK_ECDHE && SUCCEED == zbx_set_ecdhe_parameters(ctx_psk))
			ciphers = ZBX_CIPHERS_PSK_ECDHE ZBX_CIPHERS_PSK;
		else
			ciphers = ZBX_CIPHERS_PSK;

		/* override TLS 1.3 PSK ciphersuites with user-defined settings */
		if (NULL != config_tls->cipher_psk13 || NULL != config_tls->cipher_cmd13)
		{
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
			const char	*override_ciphers = config_tls->cipher_psk13; /* can be NULL */

			if (NULL != config_tls->cipher_cmd13 && ZBX_TCP_SEC_TLS_PSK ==
					config_tls->connect_mode)
			{
				override_ciphers = config_tls->cipher_cmd13;
			}

			if (NULL != override_ciphers && 1 != SSL_CTX_set_ciphersuites(ctx_psk, override_ciphers))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
						" PSK ciphersuites from \"TLSCipherPSK13\" or \"--tls-cipher13\""
						" parameter:");
				goto out;
			}
#else
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
					" PSK ciphersuites: compiled with OpenSSL version older than 1.1.1."
					" Consider not using parameters \"TLSCipherPSK13\" or \"--tls-cipher13\"");
			goto out1;
#endif
		}
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
		else if (1 != SSL_CTX_set_ciphersuites(ctx_psk, ZBX_CIPHERS_PSK_TLS13))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of PSK TLS 1.3"
					"  ciphersuites:");
			goto out;
		}
#endif
		/* override TLS 1.2 PSK ciphersuites with user-defined settings */
		if (NULL != config_tls->cipher_psk || NULL != config_tls->cipher_cmd)
		{
			const char	*override_ciphers = config_tls->cipher_psk; /* can be NULL */

			if (NULL != config_tls->cipher_cmd && ZBX_TCP_SEC_TLS_PSK ==
					config_tls->connect_mode)
			{
				override_ciphers = config_tls->cipher_cmd;
			}

			if (NULL != override_ciphers && 1 != SSL_CTX_set_cipher_list(ctx_psk, override_ciphers))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.2"
						" PSK ciphersuites from \"TLSCipherPSK\" or \"--tls-cipher\""
						" parameter:");
				goto out;
			}
		}
		else if (1 != SSL_CTX_set_cipher_list(ctx_psk, ciphers))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of PSK ciphersuites:");
			goto out;
		}

		zbx_log_ciphersuites(__func__, "PSK", ctx_psk);
	}

	if (NULL != ctx_all)
	{
		const char	*ciphers;

		SSL_CTX_set_info_callback(ctx_all, zbx_openssl_info_cb);

		if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_SERVER | ZBX_PROGRAM_TYPE_PROXY |
				ZBX_PROGRAM_TYPE_AGENTD)))
		{
			SSL_CTX_set_psk_server_callback(ctx_all, zbx_psk_server_cb);
		}

		SSL_CTX_set_options(ctx_all, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET);
		SSL_CTX_clear_options(ctx_all, SSL_OP_LEGACY_SERVER_CONNECT);
		SSL_CTX_set_session_cache_mode(ctx_all, SSL_SESS_CACHE_OFF);

		if (SUCCEED == zbx_set_ecdhe_parameters(ctx_all))
			ciphers = ZBX_CIPHERS_CERT_ECDHE ZBX_CIPHERS_CERT ":" ZBX_CIPHERS_PSK_ECDHE ZBX_CIPHERS_PSK;
		else
			ciphers = ZBX_CIPHERS_CERT ":" ZBX_CIPHERS_PSK;

		/* override TLS 1.3 ciphersuites with user-defined setting */
		if (NULL != config_tls->cipher_all13)
		{
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
			if (1 != SSL_CTX_set_ciphersuites(ctx_all, config_tls->cipher_all13))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
						" ciphersuites from \"TLSCipherAll13\" parameter:");
				goto out;
			}
#else
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.3"
					" ciphersuites: compiled with OpenSSL version older than 1.1.1."
					" Consider not using parameter \"TLSCipherAll13\"");
			goto out1;
#endif
		}

		/* override TLS 1.2 ciphersuites with user-defined setting */
		if (NULL != config_tls->cipher_all)
		{
			if (1 != SSL_CTX_set_cipher_list(ctx_all, config_tls->cipher_all))
			{
				zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of TLS 1.2"
						" ciphersuites from \"TLSCipherAll\" parameter:");
				goto out;
			}
		}
		else if (1 != SSL_CTX_set_cipher_list(ctx_all, ciphers))
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot set list of all ciphersuites:");
			goto out;
		}

		zbx_log_ciphersuites(__func__, "certificate and PSK", ctx_all);
	}

	if (NULL == ctx_psk)
	{
		/* cannot override TLS 1.3 PSK ciphersuites */
		if (NULL != config_tls->cipher_psk13)
		{
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherPSK13\" cannot"
					" be applied: the list of PSK ciphersuites is not used");
#else
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherPSK13\" cannot"
					" be applied: compiled with OpenSSL version older than 1.1.1");
#endif
			goto out1;
		}

		/* cannot override TLS 1.2 PSK ciphersuites */
		if (NULL != config_tls->cipher_psk)
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherPSK\" cannot"
					" be applied: the list of PSK ciphersuites is not used");
			goto out1;
		}
	}

	if (NULL == ctx_all)
	{
		/* cannot override TLS 1.3 ciphersuites */
		if (NULL != config_tls->cipher_all13)
		{
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer */
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherAll13\" cannot"
					" be applied: the combined list of certificate and PSK ciphersuites is"
					" not used. Most likely parameters \"TLSCipherCert13\" and/or \"TLSCipherPSK13\""
					" are sufficient");
#else
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherAll13\" cannot"
					" be applied: compiled with OpenSSL version older than 1.1.1");
#endif
			goto out1;
		}

		/* cannot override TLS 1.2 ciphersuites */
		if (NULL != config_tls->cipher_all)
		{
			zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "parameter \"TLSCipherAll\" cannot"
					" be applied: the combined list of certificate and PSK ciphersuites is"
					" not used. Most likely parameters \"TLSCipherCert\" and/or \"TLSCipherPSK\""
					" are sufficient");
			goto out1;
		}
	}
#else	/* HAVE_OPENSSL_WITH_PSK is not defined */
	/* cannot use TLSCipherPSK13, TLSCipherPSK, TLSCipherAll13 and TLSCipherAll13 parameters */
	/* if PSK is not supported by crypto library */
	if (NULL != config_tls->cipher_psk13 || NULL != config_tls->cipher_psk ||
			NULL != config_tls->cipher_all13 || NULL != config_tls->cipher_all)
	{
		zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "at least one of parameters TLSCipherPSK13,"
				" TLSCipherPSK, TLSCipherAll13 or TLSCipherAll is defined. These parameters must not"
				" be defined because the program is compiled with OpenSSL without PSK support or"
				" LibreSSL");
		goto out1;
	}
#endif /* defined(HAVE_OPENSSL_WITH_PSK) */
#ifndef _WINDOWS
	zbx_sigmask(SIG_SETMASK, &orig_mask, NULL);
#endif
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);

	return;

out_method:
	zbx_snprintf_alloc(&error, &error_alloc, &error_offset, "cannot initialize TLS method:");
out:
	zbx_tls_error_msg(&error, &error_alloc, &error_offset);
out1:
	zabbix_log(LOG_LEVEL_CRIT, "%s", error);
	zbx_free(error);
	zbx_tls_free();
	exit(EXIT_FAILURE);

#undef ZBX_CIPHERS_CERT_ECDHE
#undef ZBX_CIPHERS_CERT
#undef ZBX_CIPHERS_PSK_ECDHE
#undef ZBX_CIPHERS_PSK
#undef ZBX_CIPHERS_PSK_TLS13
}

/******************************************************************************
 *                                                                            *
 * Purpose: release TLS library resources allocated in zbx_tls_init_parent()  *
 *          and zbx_tls_init_child()                                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_free(void)
{
	if (NULL != ctx_cert)
		SSL_CTX_free(ctx_cert);

#if defined(HAVE_OPENSSL_WITH_PSK)
	if (NULL != ctx_psk)
		SSL_CTX_free(ctx_psk);

	if (NULL != ctx_all)
		SSL_CTX_free(ctx_all);
#endif
	if (NULL != my_psk)
	{
		zbx_guaranteed_memset(my_psk, 0, my_psk_len);
		my_psk_len = 0;
		zbx_free(my_psk);
	}

	zbx_tls_library_deinit(ZBX_TLS_INIT_PROCESS);
}

static int	openssl_get_error(const SSL *s, int res, const char *func, size_t *error_alloc, size_t *error_offset,
		char **error)
{
	int	result_code;

	result_code = SSL_get_error(s, res);

	switch (result_code)
	{
		case SSL_ERROR_NONE:		/* handshake successful */
			return SUCCEED;
		case SSL_ERROR_ZERO_RETURN:
			zbx_snprintf_alloc(error, error_alloc, error_offset,
					"%s() TLS connection has been closed during read", func);
			return FAIL;
		case SSL_ERROR_SYSCALL:
			if (0 == ERR_peek_error())
			{
				if (0 == res)
				{
					zbx_snprintf_alloc(error, error_alloc, error_offset,
							"%s() connection closed by peer", func);
				}
				else if (-1 == res)
				{
					zbx_snprintf_alloc(error, error_alloc, error_offset, "%s()"
							" I/O error: %s", func,
							zbx_strerror_from_system(zbx_socket_last_error()));
				}
				else
				{
					/* "man SSL_get_error" describes only res == 0 and res == -1 for */
					/* SSL_ERROR_SYSCALL case */
					zbx_snprintf_alloc(error, error_alloc, error_offset, "%s()"
							" returned undocumented code %d", func, res);
				}
			}
			else
			{
				zbx_snprintf_alloc(error, error_alloc, error_offset, "%s() set"
						" result code to SSL_ERROR_SYSCALL:", func);
				zbx_tls_error_msg(error, error_alloc, error_offset);
				zbx_snprintf_alloc(error, error_alloc, error_offset, "%s", info_buf);
			}
			return FAIL;
		case SSL_ERROR_SSL:
			zbx_snprintf_alloc(error, error_alloc, error_offset, "%s() set"
					" result code to SSL_ERROR_SSL:", func);
			zbx_tls_error_msg(error, error_alloc, error_offset);
			zbx_snprintf_alloc(error, error_alloc, error_offset, "%s", info_buf);
			return FAIL;
		default:
			zbx_snprintf_alloc(error, error_alloc, error_offset, "%s() set result code"
					" to %d", func, result_code);
			zbx_tls_error_msg(error, error_alloc, error_offset);
			zbx_snprintf_alloc(error, error_alloc, error_offset, "%s", info_buf);
			return FAIL;
	}
}

static const char	*tls_error_string(int err)
{
	switch (err)
	{
		case SSL_ERROR_WANT_READ:
			return "SSL_ERROR_WANT_READ";
		case SSL_ERROR_WANT_WRITE:
			return "SSL_ERROR_WANT_WRITE";
		default:
			return "unknown error";
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: establish a TLS connection over an established TCP connection     *
 *                                                                            *
 * Parameters:                                                                *
 *     s           - [IN] socket with opened connection                       *
 *     tls_connect - [IN] how to connect. Allowed values:                     *
 *                        ZBX_TCP_SEC_TLS_CERT, ZBX_TCP_SEC_TLS_PSK.          *
 *     tls_arg1    - [IN] required issuer of peer certificate (may be NULL    *
 *                        or empty string if not important) or PSK identity   *
 *                        to connect with depending on value of               *
 *                        'tls_connect'.                                      *
 *     tls_arg2    - [IN] required subject of peer certificate (may be NULL   *
 *                        or empty string if not important) or PSK            *
 *                        (in hex-string) to connect with depending on value  *
 *                        of 'tls_connect'.                                   *
 *     server_name - [IN] optional server name indication for TLS             *
 *     event       - [OUT] may be NULL for blocking TLS handshake, otherwise  *
 *                         informs caller to wait for POLLIN or POLLOUT and   *
 *                         retry function to complete async TLS handshake     *
 *     error       - [OUT] dynamically allocated memory with error message    *
 *                                                                            *
 * Return value:                                                              *
 *     SUCCEED - successful TLS handshake with a valid certificate or PSK     *
 *     FAIL - an error occurred or retry is needed if event is filled         *
 *                                                                            *
 ******************************************************************************/
int	zbx_tls_connect(zbx_socket_t *s, unsigned int tls_connect, const char *tls_arg1, const char *tls_arg2,
		const char *server_name, short *event, char **error)
{
	int		ret = FAIL, res;
	size_t		error_alloc = 0, error_offset = 0;
	unsigned char	initialized;

	if (NULL != event)
		*event = 0;

	if (NULL == s->tls_ctx)
	{
		s->tls_ctx = zbx_malloc(s->tls_ctx, sizeof(zbx_tls_context_t));
		s->tls_ctx->ctx = NULL;
		initialized = 0;
	}
	else
		initialized = 1;

	if (ZBX_TCP_SEC_TLS_CERT == tls_connect)
	{
		if (0 == initialized)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "In %s(): issuer:\"%s\" subject:\"%s\"", __func__,
					ZBX_NULL2EMPTY_STR(tls_arg1), ZBX_NULL2EMPTY_STR(tls_arg2));

			if (NULL == ctx_cert)
			{
				*error = zbx_strdup(*error, "cannot connect with TLS and certificate: no valid"
					" certificate loaded");
				goto out;
			}

			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_cert)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create connection"
						" context:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}
	}
	else if (ZBX_TCP_SEC_TLS_PSK == tls_connect)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "In %s(): psk_identity:\"%s\"", __func__, ZBX_NULL2EMPTY_STR(tls_arg1));

#if defined(HAVE_OPENSSL_WITH_PSK)
		if (0 == initialized)
		{
			if (NULL == ctx_psk)
			{
				*error = zbx_strdup(*error, "cannot connect with TLS and PSK: no valid PSK loaded");
				goto out;
			}

			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_psk)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create connection"
						" context:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}

		if (NULL == tls_arg2)	/* PSK is not set from DB */
		{
			/* Set up PSK global variables from a configuration file (always in agentd and a case when */
			/* active proxy connects to server). Here we set it only in case of active proxy */
			/* because for other programs it has already been set in zbx_tls_init_child(). */

			if (0 != (zbx_get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_ACTIVE))
			{
				psk_identity_for_cb = my_psk_identity;
				psk_identity_len_for_cb = my_psk_identity_len;
				psk_for_cb = my_psk;
				psk_len_for_cb = my_psk_len;
			}
		}
		else
		{
			/* PSK comes from a database (case for a server/proxy when it connects to an agent for */
			/* passive checks, for a server when it connects to a passive proxy) */

			if (0 == initialized)
			{
				if (0 >= (s->tls_ctx->psk_len = zbx_hex2bin((const unsigned char *)tls_arg2,
						(unsigned char *)s->tls_ctx->psk_buf, sizeof(s->tls_ctx->psk_buf))))
				{
					*error = zbx_strdup(*error, "invalid PSK");
					goto out;
				}

				s->tls_ctx->identity_len = (NULL == tls_arg1 ? 0 : strlen(tls_arg1));
			}

			/* some data reside in stack but it will be available at the time when a PSK client callback */
			/* function copies the data into buffers provided by OpenSSL within the callback */
			psk_identity_for_cb = tls_arg1;			/* string is on stack */
			/* NULL check to silence analyzer warning */
			psk_identity_len_for_cb = s->tls_ctx->identity_len;
			psk_for_cb = s->tls_ctx->psk_buf;				/* buffer is on stack */
			psk_len_for_cb = (size_t)s->tls_ctx->psk_len;
		}
#else
		*error = zbx_strdup(*error, "cannot connect with TLS and PSK: support for PSK was not compiled in");
		goto out;
#endif
	}
	else
	{
		*error = zbx_strdup(*error, "invalid connection parameters");
		THIS_SHOULD_NEVER_HAPPEN;
		goto out1;
	}

	if (0 == initialized)
	{
		if (NULL != server_name && ZBX_TCP_SEC_UNENCRYPTED != tls_connect && 1 != SSL_set_tlsext_host_name(
				s->tls_ctx->ctx, server_name))
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot set %s tls host name", server_name);
		}

		/* set our connected TCP socket to TLS context */
		if (1 != SSL_set_fd(s->tls_ctx->ctx, s->socket))
		{
			*error = zbx_strdup(*error, "cannot set socket for TLS context");
			goto out;
		}
	}

	/* TLS handshake */

	info_buf[0] = '\0';	/* empty buffer for zbx_openssl_info_cb() messages */

	while (-1 == (res = SSL_connect(s->tls_ctx->ctx)))
	{
		int	ssl_err;

		ssl_err = SSL_get_error(s->tls_ctx->ctx, res);

		if (SUCCEED != tls_is_nonblocking_error(ssl_err))
			break;

		if (NULL != event)
		{
			tls_socket_event(s->tls_ctx->ctx, ssl_err, event);

			zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s %s", __func__, tls_error_string(ssl_err),
					zbx_result_string(ret));
			return FAIL;
		}

		if (FAIL == tls_socket_wait(s->socket, s->tls_ctx->ctx, ssl_err))
		{
			*error = zbx_dsprintf(*error, "cannot wait for TLS handshake: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
			goto out;
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			*error = zbx_strdup(*error, "SSL_connect() timed out");
			goto out;
		}
	}

	if (1 != res)
	{
		if (ZBX_TCP_SEC_TLS_CERT == tls_connect)
		{
			long	verify_result;

			/* In case of certificate error SSL_get_verify_result() provides more helpful diagnostics */
			/* than other methods. Include it as first but continue with other diagnostics. */
			if (X509_V_OK != (verify_result = SSL_get_verify_result(s->tls_ctx->ctx)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s: ",
						X509_verify_cert_error_string(verify_result));
			}
		}

		if (FAIL == openssl_get_error(s->tls_ctx->ctx, res, "SSL_connect", &error_alloc, &error_offset, error))
			goto out;
	}

	if (ZBX_TCP_SEC_TLS_CERT == tls_connect)
	{
		long	verify_result;

		/* log peer certificate information for debugging */
		zbx_log_peer_cert(__func__, s->tls_ctx);

		/* perform basic verification of peer certificate */
		if (X509_V_OK != (verify_result = SSL_get_verify_result(s->tls_ctx->ctx)))
		{
			zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s",
					X509_verify_cert_error_string(verify_result));
			zbx_tls_close(s);
			goto out1;
		}

		/* if required verify peer certificate Issuer and Subject */
		if (SUCCEED != zbx_verify_issuer_subject(s->tls_ctx, tls_arg1, tls_arg2, error))
		{
			zbx_tls_close(s);
			goto out1;
		}
	}

	s->connection_type = tls_connect;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():SUCCEED (established %s %s)", __func__,
			SSL_get_version(s->tls_ctx->ctx), SSL_get_cipher(s->tls_ctx->ctx));

	return SUCCEED;

out:	/* an error occurred */
	if (NULL != s->tls_ctx->ctx)
		SSL_free(s->tls_ctx->ctx);

	zbx_free(s->tls_ctx);
out1:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s error:'%s'", __func__, zbx_result_string(ret),
			ZBX_NULL2EMPTY_STR(*error));
	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: establish a TLS connection over an accepted TCP connection        *
 *                                                                            *
 * Parameters:                                                                *
 *     s          - [IN] socket with opened connection                        *
 *     error      - [OUT] dynamically allocated memory with error message     *
 *     tls_accept - [IN] type of connection to accept. Can be be either       *
 *                       ZBX_TCP_SEC_TLS_CERT or ZBX_TCP_SEC_TLS_PSK, or      *
 *                       a bitwise 'OR' of both.                              *
 *                                                                            *
 * Return value:                                                              *
 *     SUCCEED - successful TLS handshake with a valid certificate or PSK     *
 *     FAIL - an error occurred                                               *
 *                                                                            *
 ******************************************************************************/
int	zbx_tls_accept(zbx_socket_t *s, unsigned int tls_accept, char **error)
{
	const char	*cipher_name;
	int		ret = FAIL, res;
	size_t		error_alloc = 0, error_offset = 0;
	long		verify_result;

#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer, or LibreSSL */
	const unsigned char	session_id_context[] = {'Z', 'b', 'x'};
#endif
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	s->tls_ctx = zbx_malloc(s->tls_ctx, sizeof(zbx_tls_context_t));
	s->tls_ctx->ctx = NULL;

#if defined(HAVE_OPENSSL_WITH_PSK)
	incoming_connection_has_psk = 0;	/* assume certificate-based connection by default */
#endif
	if ((ZBX_TCP_SEC_TLS_CERT | ZBX_TCP_SEC_TLS_PSK) == (tls_accept & (ZBX_TCP_SEC_TLS_CERT | ZBX_TCP_SEC_TLS_PSK)))
	{
#if defined(HAVE_OPENSSL_WITH_PSK)
		/* common case in trapper - be ready for all types of incoming connections but possible also in */
		/* agentd listener */

		if (NULL != ctx_all)
		{
			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_all)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create context to accept"
						" connection:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}
#else
		if (0 != (zbx_get_program_type_cb() & (ZBX_PROGRAM_TYPE_PROXY | ZBX_PROGRAM_TYPE_SERVER)))
		{
			/* server or proxy running with OpenSSL or LibreSSL without PSK support */
			if (NULL != ctx_cert)
			{
				if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_cert)))
				{
					zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create context"
							" to accept connection:");
					zbx_tls_error_msg(error, &error_alloc, &error_offset);
					goto out;
				}
			}
			else
			{
				*error = zbx_strdup(*error, "not ready for certificate-based incoming connection:"
						" certificate not loaded. PSK support not compiled in.");
				goto out;
			}
		}
#endif
		else if (0 != (zbx_get_program_type_cb() & ZBX_PROGRAM_TYPE_AGENTD))
		{
			THIS_SHOULD_NEVER_HAPPEN;
			goto out;
		}
#if defined(HAVE_OPENSSL_WITH_PSK)
		else if (NULL != ctx_psk)
		{
			/* Server or proxy with no certificate configured. PSK is always assumed to be configured on */
			/* server or proxy because PSK can come from database. */

			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_psk)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create context to accept"
						" connection:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}
		else
		{
			THIS_SHOULD_NEVER_HAPPEN;
			goto out;
		}
#endif
	}
	else if (0 != (tls_accept & ZBX_TCP_SEC_TLS_CERT))
	{
		if (NULL != ctx_cert)
		{
			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_cert)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create context to accept"
						" connection:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}
		else
		{
			*error = zbx_strdup(*error, "not ready for certificate-based incoming connection: certificate"
					" not loaded");
			goto out;
		}
	}
	else	/* PSK */
	{
#if defined(HAVE_OPENSSL_WITH_PSK)
		if (NULL != ctx_psk)
		{
			if (NULL == (s->tls_ctx->ctx = SSL_new(ctx_psk)))
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "cannot create context to accept"
						" connection:");
				zbx_tls_error_msg(error, &error_alloc, &error_offset);
				goto out;
			}
		}
		else
		{
			*error = zbx_strdup(*error, "not ready for PSK-based incoming connection: PSK not loaded");
			goto out;
		}
#else
		*error = zbx_strdup(*error, "support for PSK was not compiled in");
		goto out;
#endif
	}

#if OPENSSL_VERSION_NUMBER >= 0x1010100fL	/* OpenSSL 1.1.1 or newer, or LibreSSL */
	if (1 != SSL_set_session_id_context(s->tls_ctx->ctx, session_id_context, sizeof(session_id_context)))
	{
		*error = zbx_strdup(*error, "cannot set session_id_context");
		goto out;
	}
#endif
	if (1 != SSL_set_fd(s->tls_ctx->ctx, s->socket))
	{
		*error = zbx_strdup(*error, "cannot set socket for TLS context");
		goto out;
	}

	/* TLS handshake */

	info_buf[0] = '\0';	/* empty buffer for zbx_openssl_info_cb() messages */

	while (-1 == (res = SSL_accept(s->tls_ctx->ctx)))
	{
		int	ssl_err;

		ssl_err = SSL_get_error(s->tls_ctx->ctx, res);

		if (SSL_ERROR_WANT_READ != ssl_err && SSL_ERROR_WANT_WRITE != ssl_err)
			break;

		if (FAIL == tls_socket_wait(s->socket, s->tls_ctx->ctx, ssl_err))
		{
			*error = zbx_dsprintf(*error, "cannot wait for TLS handshake: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
			goto out;
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			*error = zbx_strdup(*error, "SSL_accept() timed out");
			goto out;
		}
	}

	if (1 != res)
	{
		int	result_code;

		/* In case of certificate error SSL_get_verify_result() provides more helpful diagnostics */
		/* than other methods. Include it as first but continue with other diagnostics. Should be */
		/* harmless in case of PSK. */

		if (X509_V_OK != (verify_result = SSL_get_verify_result(s->tls_ctx->ctx)))
		{
			zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s: ",
					X509_verify_cert_error_string(verify_result));
		}

		result_code = SSL_get_error(s->tls_ctx->ctx, res);

		if (0 == res)
		{
			zbx_snprintf_alloc(error, &error_alloc, &error_offset, "TLS connection has been closed during"
					" handshake:");
		}
		else
		{
			if (SSL_ERROR_SYSCALL == result_code && 0 != errno)
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "TLS handshake set result code"
						" to %d (SSL_ERROR_SYSCALL):%s:", result_code, zbx_strerror(errno));
			}
			else
			{
				zbx_snprintf_alloc(error, &error_alloc, &error_offset, "TLS handshake set result code"
						" to %d:", result_code);
			}
		}

		zbx_tls_error_msg(error, &error_alloc, &error_offset);
		zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s", info_buf);
		goto out;
	}

	/* Is this TLS connection using certificate or PSK? */

	cipher_name = SSL_get_cipher(s->tls_ctx->ctx);

#if defined(HAVE_OPENSSL_WITH_PSK)
	if (1 == incoming_connection_has_psk)
	{
		s->connection_type = ZBX_TCP_SEC_TLS_PSK;
	}
	else if (0 != strncmp("(NONE)", cipher_name, ZBX_CONST_STRLEN("(NONE)")))
#endif
	{
		s->connection_type = ZBX_TCP_SEC_TLS_CERT;

		/* log peer certificate information for debugging */
		zbx_log_peer_cert(__func__, s->tls_ctx);

		/* perform basic verification of peer certificate */
		if (X509_V_OK != (verify_result = SSL_get_verify_result(s->tls_ctx->ctx)))
		{
			zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s",
					X509_verify_cert_error_string(verify_result));
			zbx_tls_close(s);
			goto out1;
		}

		/* Issuer and Subject will be verified later, after receiving sender type and host name */
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	else
	{
		THIS_SHOULD_NEVER_HAPPEN;
		zbx_tls_close(s);
		return FAIL;
	}
#endif
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():SUCCEED (established %s %s)", __func__,
			SSL_get_version(s->tls_ctx->ctx), cipher_name);

	return SUCCEED;

out:	/* an error occurred */
	if (NULL != s->tls_ctx->ctx)
		SSL_free(s->tls_ctx->ctx);

	zbx_free(s->tls_ctx);
out1:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s error:'%s'", __func__, zbx_result_string(ret),
			ZBX_NULL2EMPTY_STR(*error));
	return ret;
}

/* SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE should not be returned here because we set */
/* SSL_MODE_AUTO_RETRY flag in zbx_tls_init_child() */

ssize_t	zbx_tls_write(zbx_socket_t *s, const char *buf, size_t len, short *event, char **error)
{
	ssize_t		offset = 0;
	int		n;

	info_buf[0] = '\0';	/* empty buffer for zbx_openssl_info_cb() messages */

	while (1)
	{
		if (0 == (n = SSL_write(s->tls_ctx->ctx, buf + offset, (int)(len - (size_t)offset))))
			break;

		if (0 > n)
		{
			ssize_t	err;

			err = (size_t)SSL_get_error(s->tls_ctx->ctx, n);
			if (SUCCEED != tls_is_nonblocking_error(err))
				break;

			if (NULL != event)
			{
				tls_socket_event(s->tls_ctx->ctx, err, event);
				return offset;
			}

			if (FAIL == tls_socket_wait(s->socket, s->tls_ctx->ctx, err))
			{
				*error = zbx_dsprintf(*error, "cannot wait socket: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return ZBX_PROTO_ERROR;
			}
		}
		else
		{
			offset += n;

			if (offset == (ssize_t)len)
				break;
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			*error = zbx_strdup(*error, "write timeout");
			return ZBX_PROTO_ERROR;
		}
	}

	if (0 >= n)
	{
		size_t	error_alloc = 0, error_offset = 0;

		if (SUCCEED == openssl_get_error(s->tls_ctx->ctx, n, "SSL_write", &error_alloc,
				&error_offset, error))
		{
			*error = zbx_strdup(*error, "SSL_write() unexpected result code");
		}

		return ZBX_PROTO_ERROR;
	}

	return offset;
}

ssize_t	zbx_tls_read(zbx_socket_t *s, char *buf, size_t len, short *events, char **error)
{
	int	n = 0;
	ssize_t	err;

	info_buf[0] = '\0';	/* empty buffer for zbx_openssl_info_cb() messages */

	while (1)
	{
		if (0 <= (n = SSL_read(s->tls_ctx->ctx, buf, (int)len)))
			break;

		err = (size_t)SSL_get_error(s->tls_ctx->ctx, n);
		if (SUCCEED != tls_is_nonblocking_error(err))
			break;

		if (NULL != events)
		{
			tls_socket_event(s->tls_ctx->ctx, err, events);
			return ZBX_PROTO_ERROR;
		}

		if (FAIL == tls_socket_wait(s->socket, s->tls_ctx->ctx, err))
		{
			*error = zbx_dsprintf(*error, "cannot wait socket: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
			return ZBX_PROTO_ERROR;
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			*error = zbx_strdup(*error, "read timeout");
			return ZBX_PROTO_ERROR;
		}
	}

	if (0 == n)
	{
		if (SSL_ERROR_ZERO_RETURN != SSL_get_error(s->tls_ctx->ctx, (int)n))
		{
			size_t	error_alloc = 0, error_offset = 0;

			if (SUCCEED == openssl_get_error(s->tls_ctx->ctx, n, "SSL_read", &error_alloc,
					&error_offset, error))
			{
				*error = zbx_strdup(*error, "SSL_read() unexpected result code");
			}

			return ZBX_PROTO_ERROR;
		}

		return n;
	}

	if (0 > n)
	{
		size_t	error_alloc = 0, error_offset = 0;

		if (SUCCEED == openssl_get_error(s->tls_ctx->ctx, n, "SSL_read", &error_alloc,
				&error_offset, error))
		{
			*error = zbx_strdup(*error, "SSL_read() unexpected result code");
		}

		return ZBX_PROTO_ERROR;
	}

	return n;
}

/******************************************************************************
 *                                                                            *
 * Purpose: close a TLS connection before closing a TCP socket                *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_close(zbx_socket_t *s)
{
	int	res;

	if (NULL == s->tls_ctx)
		return;

	zbx_socket_set_deadline(s, s->timeout);

	if (NULL != s->tls_ctx->ctx)
	{
		info_buf[0] = '\0';	/* empty buffer for zbx_openssl_info_cb() messages */

		/* After TLS shutdown the TCP connection will be closed. So, there is no need to do a bidirectional */
		/* TLS shutdown - unidirectional shutdown is ok. */
		if (0 == SSL_in_init(s->tls_ctx->ctx) &&
				0 == (SSL_get_shutdown(s->tls_ctx->ctx) & SSL_RECEIVED_SHUTDOWN))
		{
			while (0 > (res = SSL_shutdown(s->tls_ctx->ctx)))
			{
				int	err = SSL_get_error(s->tls_ctx->ctx, res);

				if (SUCCEED != tls_is_nonblocking_error(err))
				{
					int	result_code;
					char	*error = NULL;
					size_t	error_alloc = 0, error_offset = 0;

					result_code = SSL_get_error(s->tls_ctx->ctx, res);
					zbx_tls_error_msg(&error, &error_alloc, &error_offset);

					if (SSL_ERROR_SYSCALL == result_code && 0 != errno)
					{
						zabbix_log(LOG_LEVEL_WARNING, "SSL_shutdown() with %s set result code"
								" to %d (SSL_ERROR_SYSCALL):%s%s%s", s->peer,
								result_code, ZBX_NULL2EMPTY_STR(error), info_buf,
								zbx_strerror(errno));
					}
					else
					{
						zabbix_log(LOG_LEVEL_WARNING, "SSL_shutdown() with %s set result code"
								" to %d:%s%s", s->peer, result_code,
								ZBX_NULL2EMPTY_STR(error), info_buf);
					}
					zbx_free(error);
					break;
				}

				if (FAIL == tls_socket_wait(s->socket, s->tls_ctx->ctx, err))
				{
					zabbix_log(LOG_LEVEL_WARNING, "cannot wait socket: %s",
							zbx_strerror_from_system(zbx_socket_last_error()));
					break;
				}

				if (SUCCEED != zbx_socket_check_deadline(s))
				{
					zabbix_log(LOG_LEVEL_DEBUG, "timeout while closing tls connection");
					break;
				}
			}
		}

		SSL_free(s->tls_ctx->ctx);
	}

	zbx_free(s->tls_ctx);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get certificate attributes from the context of established        *
 *          connection                                                        *
 *                                                                            *
 ******************************************************************************/
int	zbx_tls_get_attr_cert(const zbx_socket_t *s, zbx_tls_conn_attr_t *attr)
{
	char	*error = NULL;
	X509	*peer_cert;

	if (NULL == (peer_cert = SSL_get_peer_certificate(s->tls_ctx->ctx)))
	{
		zabbix_log(LOG_LEVEL_WARNING, "no peer certificate, SSL_get_peer_certificate() returned NULL");
		return FAIL;
	}

	if (SUCCEED != zbx_x509_dn_gets(X509_get_issuer_name(peer_cert), attr->issuer, sizeof(attr->issuer), &error))
	{
		zabbix_log(LOG_LEVEL_WARNING, "error while getting issuer name: \"%s\"", error);
		zbx_free(error);
		X509_free(peer_cert);
		return FAIL;
	}

	if (SUCCEED != zbx_x509_dn_gets(X509_get_subject_name(peer_cert), attr->subject, sizeof(attr->subject), &error))
	{
		zabbix_log(LOG_LEVEL_WARNING, "error while getting subject name: \"%s\"", error);
		zbx_free(error);
		X509_free(peer_cert);
		return FAIL;
	}

	X509_free(peer_cert);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get PSK attributes from the context of established connection     *
 *                                                                            *
 * Comments:                                                                  *
 *     This function can be used only on server-side of TLS connection.       *
 *     GnuTLS makes it asymmetric - see documentation for                     *
 *     gnutls_psk_server_get_username() and gnutls_psk_client_get_hint()      *
 *     (the latter function is not used in Zabbix).                           *
 *     Implementation for OpenSSL is server-side only, too.                   *
 *                                                                            *
 ******************************************************************************/
#if defined(HAVE_OPENSSL_WITH_PSK)
int	zbx_tls_get_attr_psk(const zbx_socket_t *s, zbx_tls_conn_attr_t *attr)
{
	ZBX_UNUSED(s);

	/* SSL_get_psk_identity() is not used here. It works with TLS 1.2, */
	/* but returns NULL with TLS 1.3 in OpenSSL 1.1.1 */
	if ('\0' == incoming_connection_psk_id[0])
		return FAIL;

	attr->psk_identity = incoming_connection_psk_id;
	attr->psk_identity_len = strlen(attr->psk_identity);
	return SUCCEED;
}
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: get PSK attributes or certificate attributes from the context of  *
 *          established connection                                            *
 *                                                                            *
 * Comments:                                                                  *
 *     This function can be used only on server-side of TLS connection.       *
 *     GnuTLS makes it asymmetric - see documentation for                     *
 *     gnutls_psk_server_get_username() and gnutls_psk_client_get_hint()      *
 *     (the latter function is not used in Zabbix).                           *
 *     Implementation for OpenSSL is server-side only, too.                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_tls_get_attr(const zbx_socket_t *sock, zbx_tls_conn_attr_t *attr, char **error)
{
	if (ZBX_TCP_SEC_TLS_CERT == sock->connection_type)
	{
		if (SUCCEED != zbx_tls_get_attr_cert(sock, attr))
		{
			*error = zbx_strdup(*error, "internal error: cannot get connection attributes");
			THIS_SHOULD_NEVER_HAPPEN;
			return FAIL;
		}
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	else if (ZBX_TCP_SEC_TLS_PSK == sock->connection_type)
	{
		if (SUCCEED != zbx_tls_get_attr_psk(sock, attr))
		{
			*error = zbx_strdup(*error, "internal error: cannot get connection attributes");
			THIS_SHOULD_NEVER_HAPPEN;
			return FAIL;
		}
	}
#endif
	else if (ZBX_TCP_SEC_UNENCRYPTED != sock->connection_type)
	{
		*error = zbx_strdup(*error, "internal error: invalid connection type");
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	attr->connection_type = sock->connection_type;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: validate PSK attributes or certificate attributes from the        *
 *          context of established connection                                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_tls_validate_attr(const zbx_tls_conn_attr_t *attr, const char *tls_issuer, const char *tls_subject,
		const char *tls_psk_identity, const char **msg)
{
	if (ZBX_TCP_SEC_TLS_CERT == attr->connection_type)
	{
		/* simplified match, not compliant with RFC 4517, 4518 */
		if ('\0' != *tls_issuer && 0 != strcmp(tls_issuer, attr->issuer))
		{
			*msg = "certificate issuer does not match";
			return FAIL;
		}

		/* simplified match, not compliant with RFC 4517, 4518 */
		if ('\0' != *tls_subject && 0 != strcmp(tls_subject, attr->subject))
		{
			*msg = "certificate subject does not match";
			return FAIL;
		}
	}
#if defined(HAVE_OPENSSL_WITH_PSK)
	else if (ZBX_TCP_SEC_TLS_PSK == attr->connection_type)
	{
		if (NULL != tls_psk_identity)
		{
			if (strlen(tls_psk_identity) != attr->psk_identity_len ||
					0 != memcmp(tls_psk_identity, attr->psk_identity,
					attr->psk_identity_len))
			{
				*msg = "false PSK identity";
				return FAIL;
			}
		}
		else
		{
			*msg = "missing PSK";
			return FAIL;
		}
	}
#endif
	return SUCCEED;
}

#if defined(_WINDOWS)
/******************************************************************************
 *                                                                            *
 * Purpose: pass some TLS variables from one thread to other                  *
 *                                                                            *
 * Comments: used in Zabbix sender on MS Windows                              *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_pass_vars(ZBX_THREAD_SENDVAL_TLS_ARGS *args)
{
	args->ctx_cert = ctx_cert;
#if defined(HAVE_OPENSSL_WITH_PSK)
	args->ctx_psk = ctx_psk;
	args->psk_identity_for_cb = psk_identity_for_cb;
	args->psk_identity_len_for_cb = psk_identity_len_for_cb;
	args->psk_for_cb = psk_for_cb;
	args->psk_len_for_cb = psk_len_for_cb;
#endif
}

/******************************************************************************
 *                                                                            *
 * Purpose: pass some TLS variables from one thread to other                  *
 *                                                                            *
 * Comments: used in Zabbix sender on MS Windows                              *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_take_vars(ZBX_THREAD_SENDVAL_TLS_ARGS *args)
{
	ctx_cert = args->ctx_cert;
#if defined(HAVE_OPENSSL_WITH_PSK)
	ctx_psk = args->ctx_psk;
	psk_identity_for_cb = args->psk_identity_for_cb;
	psk_identity_len_for_cb = args->psk_identity_len_for_cb;
	psk_for_cb = args->psk_for_cb;
	psk_len_for_cb = args->psk_len_for_cb;
#endif
}
#endif

unsigned int	zbx_tls_get_psk_usage(void)
{
	return	psk_usage;
}

/******************************************************************************
 *                                                                            *
 * Purpose: TLS cleanup for using in signal handlers                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_tls_free_on_signal(void)
{
	if (NULL != my_psk)
		zbx_guaranteed_memset(my_psk, 0, my_psk_len);
}
