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

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
#include "tls.h"
#endif
#include "zbxlog.h"
#include "zbxcompress.h"
#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxip.h"
#include "zbxtime.h"
#include "zbxcrypto.h"

#ifdef _WINDOWS
#	ifndef _WIN32_WINNT_WIN7
#		define _WIN32_WINNT_WIN7		0x0601	/* allow compilation on older Windows systems */
#	endif
#	ifndef WSA_FLAG_NO_HANDLE_INHERIT
#		define WSA_FLAG_NO_HANDLE_INHERIT	0x80	/* allow compilation on older Windows systems */
#	endif
#endif

#ifndef ZBX_SOCKLEN_T
#	define ZBX_SOCKLEN_T socklen_t
#endif

#ifndef SOCK_CLOEXEC
#	define SOCK_CLOEXEC 0	/* SOCK_CLOEXEC is Linux-specific, available since 2.6.23 */
#endif

static int	socket_set_nonblocking(ZBX_SOCKET s);
static void	tcp_set_socket_strerror_from_getaddrinfo(const char *ip);
static ssize_t	tcp_read(zbx_socket_t *s, char *buffer, size_t size, short *events);

zbx_config_tls_t	*zbx_config_tls_new(void)
{
	zbx_config_tls_t	*config_tls;

	config_tls = (zbx_config_tls_t *)zbx_malloc(NULL, sizeof(zbx_config_tls_t));

	config_tls->connect_mode	= ZBX_TCP_SEC_UNENCRYPTED;
	config_tls->accept_modes	= ZBX_TCP_SEC_UNENCRYPTED;

	config_tls->connect		= NULL;
	config_tls->accept		= NULL;
	config_tls->ca_file		= NULL;
	config_tls->crl_file		= NULL;
	config_tls->server_cert_issuer	= NULL;
	config_tls->server_cert_subject	= NULL;
	config_tls->cert_file		= NULL;
	config_tls->key_file		= NULL;
	config_tls->psk_identity	= NULL;
	config_tls->psk_file		= NULL;
	config_tls->cipher_cert13	= NULL;
	config_tls->cipher_cert		= NULL;
	config_tls->cipher_psk13	= NULL;
	config_tls->cipher_psk		= NULL;
	config_tls->cipher_all13	= NULL;
	config_tls->cipher_all		= NULL;
	config_tls->cipher_cmd13	= NULL;
	config_tls->cipher_cmd		= NULL;
	config_tls->tls_listen		= NULL;
	config_tls->frontend_accept	= NULL;
	config_tls->frontend_cert_issuer	= NULL;
	config_tls->frontend_cert_subject	= NULL;
	config_tls->frontend_accept_modes	= ZBX_TCP_SEC_UNENCRYPTED;

	return config_tls;
}

void	zbx_config_tls_free(zbx_config_tls_t *config_tls)
{
	zbx_free(config_tls->connect);
	zbx_free(config_tls->accept);
	zbx_free(config_tls->ca_file);
	zbx_free(config_tls->crl_file);
	zbx_free(config_tls->server_cert_issuer);
	zbx_free(config_tls->server_cert_subject);
	zbx_free(config_tls->cert_file);
	zbx_free(config_tls->key_file);
	zbx_free(config_tls->psk_identity);
	zbx_free(config_tls->psk_file);
	zbx_free(config_tls->cipher_cert13);
	zbx_free(config_tls->cipher_cert);
	zbx_free(config_tls->cipher_psk13);
	zbx_free(config_tls->cipher_psk);
	zbx_free(config_tls->cipher_all13);
	zbx_free(config_tls->cipher_all);
	zbx_free(config_tls->cipher_cmd13);
	zbx_free(config_tls->cipher_cmd);
	zbx_free(config_tls->frontend_accept);
	zbx_free(config_tls->tls_listen);
	zbx_free(config_tls->frontend_cert_issuer);
	zbx_free(config_tls->frontend_cert_subject);

	zbx_free(config_tls);
}

/******************************************************************************
 *                                                                            *
 * Purpose: return string describing tcp error                                *
 *                                                                            *
 * Return value: pointer to the null terminated string                        *
 *                                                                            *
 ******************************************************************************/

#define ZBX_SOCKET_STRERROR_LEN	512

static ZBX_THREAD_LOCAL char	zbx_socket_strerror_message[ZBX_SOCKET_STRERROR_LEN];

const char	*zbx_socket_strerror(void)
{
	zbx_socket_strerror_message[ZBX_SOCKET_STRERROR_LEN - 1] = '\0';	/* force null termination */
	return zbx_socket_strerror_message;
}

__zbx_attr_format_printf(1, 2)
static void	zbx_set_socket_strerror(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);

	zbx_vsnprintf(zbx_socket_strerror_message, sizeof(zbx_socket_strerror_message), fmt, args);

	va_end(args);
}

/******************************************************************************
 *                                                                            *
 * Purpose: Get peer IP address info from a socket early while it is          *
 *          connected. Connection can be terminated due to various errors at  *
 *          any time and peer IP address will not be available anymore.       *
 *                                                                            *
 * Return value: SUCCEED or FAIL                                              *
 *                                                                            *
 ******************************************************************************/
static int	zbx_socket_peer_ip_save(zbx_socket_t *s)
{
	ZBX_SOCKADDR	sa;
	ZBX_SOCKLEN_T	sz = sizeof(sa);
	char		*error_message = NULL;

	if (ZBX_PROTO_ERROR == getpeername(s->socket, (struct sockaddr *)&sa, &sz))
	{
		error_message = zbx_strerror_from_system(zbx_socket_last_error());
		zbx_set_socket_strerror("connection rejected, getpeername() failed: %s", error_message);
		return FAIL;
	}

	/* store getpeername() result to have IP address in numerical form for security check */
	memcpy(&s->peer_info, &sa, (size_t)sz);

	/* store IP address as a text string for error reporting */

#ifdef HAVE_IPV6
	if (0 != zbx_getnameinfo((struct sockaddr *)&sa, s->peer, sizeof(s->peer), NULL, 0, NI_NUMERICHOST))
	{
		error_message = zbx_strerror_from_system(zbx_socket_last_error());
		zbx_set_socket_strerror("connection rejected, getnameinfo() failed: %s", error_message);
		return FAIL;
	}
#else
	zbx_strscpy(s->peer, inet_ntoa(sa.sin_addr));
#endif
	return SUCCEED;
}

#if !defined(_WINDOWS) && !defined(__MINGW32__)
/**
*******************************************************************************
 *                                                                            *
 * Purpose: retrieve host name by IP address                                  *
 *                                                                            *
 * Parameters: ip      - [IN] IP address to resolve                           *
 *             host    - [OUT] Buffer to store resolved host name.            *
 *                             On failure set to empty string.                *
 *             hostlen - [IN] size of host buffer                             *
 *                                                                            *
 ******************************************************************************/
void	zbx_gethost_by_ip(const char *ip, char *host, size_t hostlen)
{
	struct addrinfo	hints, *ai = NULL;

	assert(ip);

	memset(&hints, 0, sizeof(hints));
#ifdef HAVE_IPV6
	hints.ai_family = PF_UNSPEC;
#else
	hints.ai_family = AF_INET;
#endif

	if (0 != getaddrinfo(ip, NULL, &hints, &ai))
	{
		host[0] = '\0';
		goto out;
	}

	if (0 != getnameinfo(ai->ai_addr, ai->ai_addrlen, host, (socklen_t)hostlen, NULL, 0, NI_NAMEREQD))
	{
		host[0] = '\0';
		goto out;
	}
out:
	if (NULL != ai)
		freeaddrinfo(ai);
}

int	zbx_inet_pton(int af, const char *src, void *dst)
{
	if (AF_INET == af)
	{
		memset(dst, 0, sizeof(struct in_addr *));

		if (0 >= inet_pton(AF_INET, src, (struct in_addr *)dst))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot get ip from IPv4 address: %s", zbx_strerror(errno));
			return FAIL;
		}

		return SUCCEED;
	}
	else if (AF_INET6 == af)
	{
		memset(dst, 0, sizeof(struct in6_addr));

		if (0 >= inet_pton(AF_INET6, src, (struct in6_addr *)dst))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot get ip from IPv6 address: %s", zbx_strerror(errno));
			return FAIL;
		}

		return SUCCEED;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "Fail to parse IP: '%s'", src);

	return FAIL;
}

int	zbx_inet_ntop(struct sockaddr *ai_addr, char *ip, socklen_t len)
{
	if (AF_INET == ai_addr->sa_family)
	{
		const struct sockaddr_in	*sin = (const struct sockaddr_in *) (void *)ai_addr;

		if (NULL == inet_ntop(AF_INET, &sin->sin_addr, ip, len))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot get ip from IPv4 address: %s", zbx_strerror(errno));
			return FAIL;
		}

		return SUCCEED;
	}
	else if (AF_INET6 == ai_addr->sa_family)
	{
		const struct sockaddr_in6	*sin6 = (const struct sockaddr_in6 *) (void *)ai_addr;

		if (NULL == inet_ntop(AF_INET6, &sin6->sin6_addr, ip, len))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot get ip from IPv6 address: %s", zbx_strerror(errno));
			return FAIL;
		}

		return SUCCEED;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "unknown address family:%d", ai_addr->sa_family);
	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: retrieve IP address by host name                                  *
 *                                                                            *
 * Parameters: host  - [IN] hostname to resolve                               *
 *             ip    - [OUT] Buffer to store resolved IP address.             *
 *                           On failure set to empty string.                  *
 *             iplen - [IN] size of ip buffer                                 *
 *                                                                            *
 ******************************************************************************/
void	zbx_getip_by_host(const char *host, char *ip, size_t iplen)
{
	struct addrinfo	hints, *ai = NULL;

	assert(ip);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;

	if (0 != getaddrinfo(host, NULL, &hints, &ai))
	{
		ip[0] = '\0';
		goto out;
	}

	if (FAIL == zbx_inet_ntop(ai->ai_addr, ip, (socklen_t)iplen))
		ip[0] = '\0';
out:
	if (NULL != ai)
		freeaddrinfo(ai);
}

#endif	/* _WINDOWS */

#ifdef _WINDOWS
/******************************************************************************
 *                                                                            *
 * Purpose: check Windows version                                             *
 *                                                                            *
 * Parameters: major    - [IN] major windows version                          *
 *             minor    - [IN] minor windows version                          *
 *             servpack - [IN] service pack version                           *
 *                                                                            *
 * Return value: SUCCEED - Windows version matches input parameters           *
 *                         or greater                                         *
 *               FAIL    - Windows version is older                           *
 *                                                                            *
 * Comments: This is reimplementation of IsWindowsVersionOrGreater() from     *
 *           Version Helper API. We need it because the original function is  *
 *           only available in newer Windows toolchains (VS2013+)             *
 *                                                                            *
 ******************************************************************************/
static int zbx_is_win_ver_or_greater(zbx_uint32_t major, zbx_uint32_t minor, zbx_uint32_t servpack)
{
	OSVERSIONINFOEXW vi = { sizeof(vi), major, minor, 0, 0, { 0 }, servpack, 0 };

	/* no need to test for an error, check VersionHelpers.h and usage examples */

	return VerifyVersionInfoW(&vi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
			VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0,
			VER_MAJORVERSION, VER_GREATER_EQUAL),
			VER_MINORVERSION, VER_GREATER_EQUAL),
			VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)) ? SUCCEED : FAIL;
}
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: Initialize Windows Sockets APIs                                   *
 *                                                                            *
 * Parameters: error - [OUT] error message                                    *
 *                                                                            *
 * Return value: SUCCEED or FAIL - an error occurred                          *
 *                                                                            *
 ******************************************************************************/
#ifdef _WINDOWS
int	zbx_socket_start(char **error)
{
	WSADATA	sockInfo;
	int	ret;

	if (0 != (ret = WSAStartup(MAKEWORD(2, 2), &sockInfo)))
	{
		*error = zbx_dsprintf(*error, "Cannot initialize Winsock DLL: %s", zbx_strerror_from_system(ret));
		return FAIL;
	}

	return SUCCEED;
}
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: initialize socket                                                 *
 *                                                                            *
 ******************************************************************************/
void	zbx_socket_clean(zbx_socket_t *s)
{
	memset(s, 0, sizeof(zbx_socket_t));

	s->socket = ZBX_SOCKET_ERROR;
	s->buf_type = ZBX_BUF_TYPE_STAT;
}

/******************************************************************************
 *                                                                            *
 * Purpose: free socket's dynamic buffer                                      *
 *                                                                            *
 ******************************************************************************/
static void	zbx_socket_free(zbx_socket_t *s)
{
	if (ZBX_BUF_TYPE_DYN == s->buf_type)
		zbx_free(s->buffer);
}

/******************************************************************************
 *                                                                            *
 * Purpose: detach receive buffer                                             *
 *                                                                            *
 * Return value: Detached dynamic buffer or copy of static buffer.            *
 *                                                                            *
 * Comments: The socket buffer is reset.                                      *
 *                                                                            *
 ******************************************************************************/
char	*zbx_socket_detach_buffer(zbx_socket_t *s)
{
	char	*out;

	if (ZBX_BUF_TYPE_DYN == s->buf_type)
	{
		out = s->buffer;
		s->buf_type = ZBX_BUF_TYPE_STAT;
		s->buffer = s->buf_stat;
	}
	else
		out = zbx_strdup(NULL, s->buf_stat);

	*s->buffer = '\0';

	return out;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create socket poll error message                                  *
 *                                                                            *
 ******************************************************************************/
char	*socket_poll_error(short revents)
{
	char	*str = NULL;
	size_t	str_alloc = 0, str_offset = 0;
	char	delim = '(';

	zbx_strcpy_alloc(&str, &str_alloc, &str_offset, "connection error ");

	if (0 != (revents & POLLERR))
	{
		zbx_snprintf_alloc(&str, &str_alloc, &str_offset, "%c%s", delim, "POLLERR");
		delim = ',';
	}

	if (0 != (revents & POLLHUP))
	{
		zbx_snprintf_alloc(&str, &str_alloc, &str_offset, "%c%s", delim, "POLLHUP");
		delim = ',';
	}

	if (0 != (revents & POLLNVAL))
		zbx_snprintf_alloc(&str, &str_alloc, &str_offset, "%c%s", delim, "POLLNVAL");

	zbx_chrcpy_alloc(&str, &str_alloc, &str_offset, ')');

	return str;
}

/******************************************************************************
 *                                                                            *
 * Purpose: wait for socket to become writable and without errors (connected) *
 *                                                                            *
 * Parameters: s       - [IN]                                                 *
 *             timeout - [IN] The maximum time to wait (in milliseconds) for  *
 *                            the socket to become writable. Range 0-1000,    *
 *                            negative value means infinite wait.             *
 *             error   - [OUT] error message                                  *
 *                                                                            *
 * Return value: SUCCEED - connected successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_socket_pollout(zbx_socket_t *s, int timeout, char **error)
{
	int		rc;
	zbx_pollfd_t	pd;

	pd.fd = s->socket;
	pd.events = POLLOUT;

	while (0 >= (rc = zbx_socket_poll(&pd, 1, timeout)))
	{
		if (-1 == rc && SUCCEED != zbx_socket_had_nonblocking_error())
		{
			if (NULL != error)
				*error = zbx_strdup(NULL, "cannot wait for connection");

			return FAIL;
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			if (NULL != error)
				*error = zbx_strdup(NULL, "connection timed out");

			return FAIL;
		}
	}

	if (POLLOUT != (pd.revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)))
	{
		if (NULL != error)
		{
			*error = socket_poll_error(pd.revents);
			zabbix_log(LOG_LEVEL_DEBUG, "poll(POLLOUT) failed with revents 0x%x", (unsigned)pd.revents);
		}

		return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: initiate connection to specified address                          *
 *                                                                            *
 * Parameters: s           - [IN/OUT]                                         *
 *             type        - [IN] TCP or UDP                                  *
 *             source_ip   - [IN]                                             *
 *             ip          - [IN]                                             *
 *             port        - [IN]                                             *
 *             timeout     - [IN]                                             *
 *                                                                            *
 * Return value: SUCCEED - connection initiated successfully                  *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_socket_connect(zbx_socket_t *s, int type, const char *source_ip, const char *ip, unsigned short port,
		int timeout)
{
	int		flags, ret = FAIL;
	char		service[8];
	struct addrinfo	*ai = NULL, hints, *ai_bind = NULL;
	void		(*func_socket_close)(zbx_socket_t *s);

	zbx_socket_clean(s);
	s->connection_type = ZBX_TCP_SEC_UNENCRYPTED;
	s->timeout = timeout;

	if (SUCCEED == zbx_is_ip4(ip))
		flags = AI_NUMERICHOST;
#ifdef HAVE_IPV6
	else if (SUCCEED == zbx_is_ip6(ip))
		flags = AI_NUMERICHOST;
#endif
	else
		flags = 0;

	zbx_snprintf(service, sizeof(service), "%hu", port);
	zbx_tcp_init_hints(&hints, type, flags);

	if (0 != getaddrinfo(ip, service, &hints, &ai))
	{
		tcp_set_socket_strerror_from_getaddrinfo(ip);
		goto out;
	}

	if (ZBX_SOCKET_ERROR == (s->socket = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, ai->ai_protocol)))
	{
		zbx_set_socket_strerror("cannot create socket [[%s]:%hu]: %s",
				ip, port, zbx_strerror_from_system(zbx_socket_last_error()));
		goto out;
	}

#if !defined(_WINDOWS) && !SOCK_CLOEXEC
	if (-1 == fcntl(s->socket, F_SETFD, FD_CLOEXEC))
	{
		zbx_set_socket_strerror("failed to set the FD_CLOEXEC file descriptor flag on socket [[%s]:%hu]: %s",
				ip, port, zbx_strerror_from_system(zbx_socket_last_error()));
	}
#endif
	func_socket_close = (SOCK_STREAM == type ? zbx_tcp_close : zbx_udp_close);

	if (NULL != source_ip)
	{
		zbx_tcp_init_hints(&hints, type, AI_NUMERICHOST);

		if (0 != getaddrinfo(source_ip, NULL, &hints, &ai_bind))
		{
			tcp_set_socket_strerror_from_getaddrinfo(source_ip);
			func_socket_close(s);
			goto out;
		}

		if (ZBX_PROTO_ERROR == zbx_bind(s->socket, ai_bind->ai_addr, ai_bind->ai_addrlen))
		{
			zbx_set_socket_strerror("bind() failed: %s", zbx_strerror_from_system(zbx_socket_last_error()));
			func_socket_close(s);
			goto out;
		}
	}

	if (SUCCEED != socket_set_nonblocking(s->socket))
	{
		zbx_set_socket_strerror("setting non-blocking mode for [[%s]:%hu] failed: %s",
				NULL != ip ? ip : "-", port,
				zbx_strerror_from_system(zbx_socket_last_error()));
		func_socket_close(s);
		goto out;
	}

	if (ZBX_PROTO_ERROR == connect(s->socket, ai->ai_addr, ai->ai_addrlen) &&
			SUCCEED != zbx_socket_had_nonblocking_error())
	{
		zbx_set_socket_strerror("cannot connect to address: %s",
				zbx_strerror_from_system(zbx_socket_last_error()));
		func_socket_close(s);
		goto out;
	}

	zbx_strlcpy(s->peer, ip, sizeof(s->peer));

	zbx_socket_set_deadline(s, timeout);

	ret = SUCCEED;
out:
	if (NULL != ai)
		freeaddrinfo(ai);

	if (NULL != ai_bind)
		freeaddrinfo(ai_bind);

	return ret;
}

int	zbx_socket_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)
{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	if (ZBX_TCP_SEC_TLS_PSK == tls_connect && '\0' == *tls_arg1)
	{
		*error = zbx_strdup(*error, "cannot connect with PSK: PSK not available");
		return FAIL;
	}

	return zbx_tls_connect(s, tls_connect, tls_arg1, tls_arg2, server_name, event, error);
#else
	ZBX_UNUSED(s);
	ZBX_UNUSED(tls_connect);
	ZBX_UNUSED(tls_arg1);
	ZBX_UNUSED(tls_arg2);
	ZBX_UNUSED(server_name);
	ZBX_UNUSED(event);
	ZBX_UNUSED(tls_connect);

	*error = zbx_strdup(*error, "support for TLS was not compiled in");
	return FAIL;
#endif
}

/*****************************************************************************
 *                                                                            *
 * Purpose: connect socket of specified type to external host                 *
 *                                                                            *
 * Parameters: s           - [IN/OUT]                                         *
 *             type        - [IN] TCP or UDP                                  *
 *             source_ip   - [IN]                                             *
 *             ip          - [IN]                                             *
 *             port        - [IN]                                             *
 *             timeout     - [IN]                                             *
 *             tls_connect - [IN] TLS mode (certificate, PSK or unencrypted)  *
 *             tls_arg1    - [IN] TLS argument (issuer or PSK identity)       *
 *             tls_arg2    - [IN] TLS argument (subject or PSK)               *
 *                                                                            *
 * Return value: SUCCEED - connected successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
static int	zbx_socket_create(zbx_socket_t *s, int type, const char *source_ip, const char *ip,
		unsigned short port, int timeout, unsigned int tls_connect, const char *tls_arg1, const char *tls_arg2)
{
	int		ret = FAIL;
	char		*error = NULL;
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	const char	*server_name = NULL;
#endif
	if (SUCCEED != zbx_socket_connect(s, type, source_ip, ip, port, timeout))
		goto out;

	if (SUCCEED != zbx_socket_pollout(s, ZBX_SOCKET_POLL_TIMEOUT, &error))
	{
		void		(*func_socket_close)(zbx_socket_t *s);

		func_socket_close = (SOCK_STREAM == type ? zbx_tcp_close : zbx_udp_close);
		func_socket_close(s);

		zbx_set_socket_strerror("cannot connect to [[%s]:%hu]: %s", ip, port, error);
		zbx_free(error);
		goto out;
	}

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	if (SUCCEED != zbx_is_ip(ip))
		server_name = ip;

	if (ZBX_TCP_SEC_TLS_CERT == tls_connect || ZBX_TCP_SEC_TLS_PSK == tls_connect)
	{
		if (SUCCEED != zbx_socket_tls_connect(s, tls_connect, tls_arg1, tls_arg2, server_name, NULL, &error))
		{
			zbx_tcp_close(s);
			zbx_set_socket_strerror("TCP successful, cannot establish TLS to [[%s]:%hu]: %s", ip, port,
					error);
			zbx_free(error);
			goto out;
		}
	}
#else
	ZBX_UNUSED(tls_arg1);
	ZBX_UNUSED(tls_arg2);
	ZBX_UNUSED(tls_connect);
#endif

	ret = SUCCEED;
out:
	return ret;
}

int	zbx_tcp_connect(zbx_socket_t *s, const char *source_ip, const char *ip, unsigned short port, int timeout,
		unsigned int tls_connect, const char *tls_arg1, const char *tls_arg2)
{
	if (ZBX_TCP_SEC_UNENCRYPTED != tls_connect && ZBX_TCP_SEC_TLS_CERT != tls_connect &&
			ZBX_TCP_SEC_TLS_PSK != tls_connect)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_socket_create(s, SOCK_STREAM, source_ip, ip, port, timeout, tls_connect, tls_arg1, tls_arg2);
}

/******************************************************************************
 *                                                                            *
 * Purpose: write data to TCP socket (with optional TLS support)              *
 *                                                                            *
 * Parameters: s    - [IN]                                                    *
 *             buf  - [IN] data buffer to send                                *
 *             len  - [IN] length of data to send                             *
 *             event- [OUT] event flag                                        *
 *                                                                            *
 * Return value: number of bytes written, or ZBX_PROTO_ERROR on failure       *
 *                                                                            *
 ******************************************************************************/
ssize_t	zbx_tcp_write(zbx_socket_t *s, const char *buf, size_t len, short *event)
{
	zbx_pollfd_t	pd;
	ssize_t		n, offset = 0;

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	if (NULL != s->tls_ctx)	/* TLS connection */
	{
		char	*error = NULL;

		if (ZBX_PROTO_ERROR == (n = zbx_tls_write(s, buf, len, event, &error)))
		{
			zbx_set_socket_strerror("%s", error);
			zbx_free(error);
		}

		return n;
	}
#endif

	if (0 < (n = ZBX_TCP_WRITE(s->socket, buf, len)) && (size_t)n == len)
		return n;

	pd.fd = s->socket;
	pd.events = POLLOUT;

	while (1)
	{
		if (0 > n)
		{
			int	rc;

			if (SUCCEED != zbx_socket_had_nonblocking_error())
			{
				zbx_set_socket_strerror("cannot write data: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return ZBX_PROTO_ERROR;
			}

			if (NULL != event)
			{
				*event = POLLOUT;
				return offset;
			}

			if (-1 == (rc = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
			{
				if (SUCCEED != zbx_socket_had_nonblocking_error())
				{
					zbx_set_socket_strerror("cannot wait for socket: %s",
							zbx_strerror_from_system(zbx_socket_last_error()));
					return ZBX_PROTO_ERROR;
				}
			}
			else if (0 != rc && 0 == (pd.revents & POLLOUT))
			{
				char	*errmsg;

				errmsg = socket_poll_error(pd.revents);
				zbx_set_socket_strerror("%s", errmsg);
				zbx_free(errmsg);

				zabbix_log(LOG_LEVEL_DEBUG, "poll(POLLOUT) failed with revents 0x%x",
						(unsigned)pd.revents);

				return ZBX_PROTO_ERROR;
			}
		}
		else
		{
			offset += n;

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

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			zbx_set_socket_strerror("write timeout");
			return ZBX_PROTO_ERROR;
		}

		n = ZBX_TCP_WRITE(s->socket, buf + offset, (len - (size_t)offset));
	}

	return offset;
}

#define ZBX_TCP_HEADER_DATA	"ZBXD"
#define ZBX_TCP_HEADER_LEN	ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA)

/******************************************************************************
 *                                                                            *
 * Purpose: initialize TCP send context for sending data over socket          *
 *                                                                            *
 * Parameters: data     - [IN] data to send                                   *
 *             len      - [IN] length of data to send                         *
 *             reserved - [IN] Original size of the uncompressed data.        *
 *                             If zero, compression will be applied when      *
 *                             requested by flags.                            *
 *             flags    - [IN] protocol and compression flags                 *
 *             context  - [OUT] send context                                  *
 *                                                                            *
 * Return value: SUCCEED - context initialized successfully                   *
 *               FAIL    - an error occurred                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_send_context_init(const char *data, size_t len, size_t reserved, unsigned char flags,
		zbx_tcp_send_context_t *context)
{
	const zbx_uint64_t	max_uint32 = ~(zbx_uint32_t)0;

	context->compressed_data = NULL;
	context->written = 0;
	context->written_header = 0;
	context->header_len = 0;

	context->data = data;
	context->send_len = len;

	if (0 == (flags & ZBX_TCP_PROTOCOL))
		return SUCCEED;

	if (ZBX_MAX_RECV_LARGE_DATA_SIZE < len)
	{
		zbx_set_socket_strerror("cannot send data: message size " ZBX_FS_UI64 " exceeds the maximum"
				" size " ZBX_FS_UI64 " bytes.", (zbx_uint64_t)len,
				(zbx_uint64_t)ZBX_MAX_RECV_LARGE_DATA_SIZE);
		return FAIL;
	}

	if (ZBX_MAX_RECV_LARGE_DATA_SIZE < reserved)
	{
		zbx_set_socket_strerror("cannot send data: uncompressed message size " ZBX_FS_UI64
				" exceeds the maximum size " ZBX_FS_UI64 " bytes.", (zbx_uint64_t)reserved,
				(zbx_uint64_t)ZBX_MAX_RECV_LARGE_DATA_SIZE);
		return FAIL;
	}

	if (0 != (flags & ZBX_TCP_COMPRESS))
	{
		/* compress if not compressed yet */
		if (0 == reserved)
		{
			if (SUCCEED != zbx_compress(data, len, &context->compressed_data, &context->send_len))
			{
				zbx_set_socket_strerror("cannot compress data: %s", zbx_compress_strerror());

				return FAIL;
			}

			context->data = context->compressed_data;
			reserved = len;
		}
	}

	memcpy(context->header_buf, ZBX_TCP_HEADER_DATA, ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA));
	context->header_len = ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA);

	if (max_uint32 <= len || max_uint32 <= reserved)
		flags |= ZBX_TCP_LARGE;

	context->header_buf[context->header_len++] = flags;

	if (0 != (flags & ZBX_TCP_LARGE))
	{
		zbx_uint64_t	len64_le;

		len64_le = zbx_htole_uint64((zbx_uint64_t)context->send_len);
		memcpy(context->header_buf + context->header_len, &len64_le, sizeof(len64_le));
		context->header_len += sizeof(len64_le);

		len64_le = zbx_htole_uint64((zbx_uint64_t)reserved);
		memcpy(context->header_buf + context->header_len, &len64_le, sizeof(len64_le));
		context->header_len += sizeof(len64_le);
	}
	else
	{
		zbx_uint32_t	len32_le;

		len32_le = zbx_htole_uint32((zbx_uint32_t)context->send_len);
		memcpy(context->header_buf + context->header_len, &len32_le, sizeof(len32_le));
		context->header_len += sizeof(len32_le);

		len32_le = zbx_htole_uint32((zbx_uint32_t)reserved);
		memcpy(context->header_buf + context->header_len, &len32_le, sizeof(len32_le));
		context->header_len += sizeof(len32_le);
	}

	return SUCCEED;
}

void	zbx_tcp_send_context_clear(zbx_tcp_send_context_t *state)
{
	zbx_free(state->compressed_data);
}

/******************************************************************************
 *                                                                            *
 * Purpose: send data                                                         *
 *                                                                            *
 * Parameters: s       - [IN/OUT]                                             *
 *             context - [IN/OUT]                                             *
 *             event   - [OUT] event flag (optional)                          *
 *                                                                            *
 *                                                                            *
 * Return value: SUCCEED - success                                            *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 * Comments:                                                                  *
 *     RFC 5246 "The Transport Layer Security (TLS) Protocol. Version 1.2"    *
 *     says: "The record layer fragments information blocks into TLSPlaintext *
 *     records carrying data in chunks of 2^14 bytes or less.".               *
 *                                                                            *
 *     This function combines sending of Zabbix protocol header (5 bytes),    *
 *     data length (8 bytes or 16 bytes for large packet) and at least part   *
 *     of the message into one block of up to 16384 bytes for efficiency.     *
 *     The same is applied for sending unencrypted messages.                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_send_context(zbx_socket_t *s, zbx_tcp_send_context_t *context, short *event)
{
#define ZBX_TLS_MAX_REC_LEN	16384
	ssize_t	bytes_sent, send_bytes;

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

	if (context->header_len > (size_t)context->written_header)
	{
		ssize_t	data_len;
		char	buf[ZBX_TLS_MAX_REC_LEN];	/* Buffer is allocated on stack with a hope that it   */
							/* will be short-lived in CPU cache. Static buffer is */
							/* not used on purpose.                               */
		ssize_t	remaining_header_len = (ssize_t)context->header_len - context->written_header;

		memcpy(buf, context->header_buf + context->written_header, (size_t)remaining_header_len);

		data_len = MIN((ssize_t)context->send_len, ZBX_TLS_MAX_REC_LEN - remaining_header_len);
		memcpy(buf + remaining_header_len, context->data, (size_t)data_len);

		send_bytes = remaining_header_len + data_len;

		if (ZBX_PROTO_ERROR == (bytes_sent = zbx_tcp_write(s, buf, (size_t)send_bytes, event)))
			return FAIL;

		if (bytes_sent > remaining_header_len)
		{
			context->written += bytes_sent - remaining_header_len;
			context->written_header += remaining_header_len;
		}
		else
			context->written_header += bytes_sent;

		if (NULL != event && 0 != *event)
			return FAIL;
	}

	while (context->written < (ssize_t)context->send_len)
	{
		if (ZBX_TCP_SEC_UNENCRYPTED == s->connection_type)
			send_bytes = (ssize_t)context->send_len - context->written;
		else
			send_bytes = MIN(ZBX_TLS_MAX_REC_LEN, (ssize_t)context->send_len - context->written);

		if (ZBX_PROTO_ERROR == (bytes_sent = zbx_tcp_write(s, context->data + context->written,
				(size_t)send_bytes, event)))
		{
			return FAIL;
		}

		context->written += bytes_sent;

		if (NULL != event && 0 != *event)
			return FAIL;
	}

	return SUCCEED;
}

int	zbx_tcp_send_ext(zbx_socket_t *s, const char *data, size_t len, size_t reserved, unsigned char flags,
		int timeout)
{
	int			ret;
	zbx_tcp_send_context_t	context;

	if (0 != timeout)
		zbx_socket_set_deadline(s, timeout);

	if (SUCCEED == (ret = zbx_tcp_send_context_init(data, len, reserved, flags, &context)))
	{
		ret = zbx_tcp_send_context(s, &context, NULL);
		zbx_tcp_send_context_clear(&context);
	}

	if (0 != timeout)
		zbx_socket_set_deadline(s, 0);

	return ret;

#undef ZBX_TLS_MAX_REC_LEN
}

/******************************************************************************
 *                                                                            *
 * Purpose: close open TCP socket                                             *
 *                                                                            *
 ******************************************************************************/
void	zbx_tcp_close(zbx_socket_t *s)
{
	zbx_tcp_unaccept(s);

	zbx_socket_free(s);
	zbx_socket_close(s->socket);
}

/******************************************************************************
 *                                                                            *
 * Parameters: addr          - [IN] IP address or hostname                    *
 *             family        - [OUT] address family                           *
 *             error         - [OUT]                                          *
 *             max_error_len - [IN]                                           *
 *                                                                            *
 * Return value: SUCCEED - success                                            *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
#ifdef HAVE_IPV6
int	zbx_get_address_family(const char *addr, int *family, char *error, int max_error_len)
{
	struct addrinfo	hints, *ai = NULL;
	int		err, res = FAIL;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = 0;
	hints.ai_socktype = SOCK_STREAM;

	if (0 != (err = getaddrinfo(addr, NULL, &hints, &ai)))
	{
		zbx_snprintf(error, max_error_len, "%s: [%d] %s", addr, err, gai_strerror(err));
		goto out;
	}

	if (PF_INET != ai->ai_family && PF_INET6 != ai->ai_family)
	{
		zbx_snprintf(error, max_error_len, "%s: unsupported address family", addr);
		goto out;
	}

	*family = (int)ai->ai_family;

	res = SUCCEED;
out:
	if (NULL != ai)
		freeaddrinfo(ai);

	return res;
}
#endif	/* HAVE_IPV6 */

static void	tcp_set_socket_strerror_from_getaddrinfo(const char *ip)
{
#if defined(_WINDOWS)
		zbx_set_socket_strerror("getaddrinfo() failed for '%s': %s",
				ip, zbx_strerror_from_system(WSAGetLastError()));
#else
#if defined(HAVE_HSTRERROR)
		zbx_set_socket_strerror("getaddrinfo() failed for '%s': [%d] %s",
				ip, h_errno, hstrerror(h_errno));
#else
		zbx_set_socket_strerror("getaddrinfo() failed for '%s': [%d]",
				ip, h_errno);
#endif
#endif
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize hints for getaddrinfo() call                           *
 *                                                                            *
 ******************************************************************************/
void	zbx_tcp_init_hints(struct addrinfo *hints, int socktype, int flags)
{
	memset(hints, 0, sizeof(struct addrinfo));

#if defined(HAVE_IPV6)
	hints->ai_family = PF_UNSPEC;
#else
	hints->ai_family =  PF_INET;
#endif
	hints->ai_socktype = socktype;
	hints->ai_flags = flags;
}

/******************************************************************************
 *                                                                            *
 * Purpose: set non-blocking socket operation                                 *
 *                                                                            *
 ******************************************************************************/
static int	socket_set_nonblocking(ZBX_SOCKET s)
{
#if defined(_WINDOWS)
	u_long	value = 1;

	if (0 != ioctlsocket(s, FIONBIO, (unsigned long*)&value))
		return FAIL;
#else
	if (-1 == fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK))
		return FAIL;
#endif
	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if the last socket error was because of non-blocking socket *
 *                                                                            *
 ******************************************************************************/
int	zbx_socket_had_nonblocking_error(void)
{
#ifndef _WINDOWS
	switch (errno)
	{
		case EINTR:
		case EAGAIN:
		case EINPROGRESS:
			return SUCCEED;
		default:
			return FAIL;
	}
#else
	switch (WSAGetLastError())
	{
		case 0:
		case WSAEINPROGRESS:
		case WSAEWOULDBLOCK:
			return SUCCEED;
		default:
			return FAIL;
	}
#endif
}

#if defined(_WINDOWS)

/******************************************************************************
 *                                                                            *
 * Purpose: poll() function emulation for windows                             *
 *                                                                            *
 * Comments: WSAPoll() does not fully behave like poll() and also is not      *
 *           supported on older (xp64/server2003) systems                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_socket_poll(zbx_pollfd_t* fds, unsigned long fds_num, int timeout)
{
	fd_set		fds_read;
	fd_set		fds_write;
	fd_set		fds_err;
	int		ret;
	unsigned long	i;
	struct timeval	tv;

	FD_ZERO(&fds_read);
	FD_ZERO(&fds_write);
	FD_ZERO(&fds_err);

	for (i = 0; i < fds_num; i++)
	{
		fds[i].revents = 0;

		if (fds[i].events & (POLLRDNORM | POLLIN))
			FD_SET(fds[i].fd, &fds_read);

		if (fds[i].events & (POLLWRNORM | POLLOUT))
			FD_SET(fds[i].fd, &fds_write);

		FD_SET(fds[i].fd, &fds_err);
	}

	tv.tv_sec = timeout / 1000;
	tv.tv_usec = (timeout % 1000) * 1000;

	if (0 >= (ret = select(0, &fds_read, &fds_write, &fds_err, &tv)))
		return ret;

	ret = 0;

	for (i = 0; i < fds_num; i++)
	{
		if (FD_ISSET(fds[i].fd, &fds_read))
			fds[i].revents |= (fds[i].events & (POLLRDNORM | POLLIN));

		if (FD_ISSET(fds[i].fd, &fds_write))
			fds[i].revents |= (fds[i].events & (POLLWRNORM | POLLOUT));

		if (FD_ISSET(fds[i].fd, &fds_err))
			fds[i].revents = POLLERR;

		if (0 != fds[i].revents)
			ret++;
	}

	return ret;
}

#endif

/******************************************************************************
 *                                                                            *
 * Purpose: inspect data in socket buffer without reading it                  *
 *                                                                            *
 ******************************************************************************/
static ssize_t	tcp_peek(zbx_socket_t *s, char *buffer, size_t size)
{
	ssize_t		n;
	zbx_pollfd_t	pd;

	if (0 <= (n = ZBX_TCP_RECV(s->socket, buffer, size, MSG_PEEK)))
		return n;

	if (SUCCEED != zbx_socket_had_nonblocking_error())
		return FAIL;

	pd.fd = s->socket;
	pd.events = POLLIN;

	while (1)
	{
		int	rc;

		if (-1 == (rc = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
		{
			if (SUCCEED != zbx_socket_had_nonblocking_error())
				return FAIL;
		}

		if (0 >= rc)
		{
			if (SUCCEED != zbx_socket_check_deadline(s))
				return TIMEOUT_ERROR;

			continue;
		}

		if (0 == (pd.revents & POLLIN))
			return FAIL;

		if (0 <= (n = ZBX_TCP_RECV(s->socket, buffer, size, MSG_PEEK)))
			break;

		if (SUCCEED != zbx_socket_had_nonblocking_error())
			return FAIL;
	}

	return n;
}

/******************************************************************************
 *                                                                            *
 * Purpose: read data from socket                                             *
 *                                                                            *
 ******************************************************************************/
static ssize_t	tcp_read(zbx_socket_t *s, char *buffer, size_t size, short *events)
{
	ssize_t		n;
	zbx_pollfd_t	pd;

	if (0 <= (n = ZBX_TCP_READ(s->socket, buffer, size)))
		return n;

	if (SUCCEED != zbx_socket_had_nonblocking_error())
	{
		zbx_set_socket_strerror("cannot read from socket: %s",
				zbx_strerror_from_system(zbx_socket_last_error()));
		return ZBX_PROTO_ERROR;
	}

	if (NULL != events)
	{
		*events = POLLIN;
		return ZBX_PROTO_ERROR;
	}

	pd.fd = s->socket;
	pd.events = POLLIN;

	while (1)
	{
		int	rc;

		if (-1 == (rc = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
		{
			if (SUCCEED != zbx_socket_had_nonblocking_error())
			{
				zbx_set_socket_strerror("cannot wait for socket: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return ZBX_PROTO_ERROR;
			}
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			zbx_set_socket_strerror("read timeout");
			return ZBX_PROTO_ERROR;
		}

		if (0 >= rc)
			continue;

		if (0 == (pd.revents & POLLIN))
		{
			char	*errmsg;

			errmsg = socket_poll_error(pd.revents);
			zbx_set_socket_strerror("%s", errmsg);
			zbx_free(errmsg);

			zabbix_log(LOG_LEVEL_DEBUG, "poll(POLLIN) failed with revents 0x%x", (unsigned)pd.revents);

			return ZBX_PROTO_ERROR;
		}

		if (0 <= (n = ZBX_TCP_READ(s->socket, buffer, size)))
			break;

		if (SUCCEED != zbx_socket_had_nonblocking_error())
		{
			zbx_set_socket_strerror("cannot read from socket: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
			return ZBX_PROTO_ERROR;
		}
	}

	return n;
}

static int	tcp_err_in_use(void)
{
#if defined(_WINDOWS)
	return WSAEADDRINUSE == zbx_socket_last_error() ? SUCCEED : FAIL;
#else
	return EADDRINUSE == zbx_socket_last_error() ? SUCCEED : FAIL;
#endif
}

/******************************************************************************
 *                                                                            *
 * Purpose: creates socket for listening                                      *
 *                                                                            *
 * Parameters:                                                                *
 *     s                           - [OUT]                                    *
 *     listen_ip                   - [IN] IP address or comma-separated list  *
 *                                        of addresses to listen on           *
 *     listen_port                 - [IN] TCP port number (1024 - 32767)      *
 *     timeout                     - [IN] socket timeout in seconds (1 - 30)  *
 *     config_tcp_max_backlog_size - [IN] maximum number of pending           *
 *                                        connections in TCP queue            *
 *                                                                            *
 * Return value: SUCCEED - success                                            *
 *               FAIL - error occurred                                        *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_listen(zbx_socket_t *s, const char *listen_ip, unsigned short listen_port, int timeout,
		int config_tcp_max_backlog_size)
{
	struct addrinfo	hints, *ai = NULL, *current_ai;
	char		port[8], *ip, *ips, *delim;
	int		i, err, on = 1, ret = FAIL;

#if defined(_WINDOWS)
	/* WSASocket() option to prevent inheritance is available on */
	/* Windows Server 2008 R2 SP1 or newer and on Windows 7 SP1 or newer */
	static ZBX_THREAD_LOCAL int	no_inherit_wsapi = -1;

	if (-1 == no_inherit_wsapi)
	{
		/* Both Windows 7 and Windows 2008 R2 are 0x0601 */
		no_inherit_wsapi = zbx_is_win_ver_or_greater((_WIN32_WINNT_WIN7 >> 8) & 0xff,
				_WIN32_WINNT_WIN7 & 0xff, 1) == SUCCEED;
	}
#endif

	zbx_socket_clean(s);
	s->timeout = timeout;

	zbx_tcp_init_hints(&hints, SOCK_STREAM, AI_NUMERICHOST | AI_PASSIVE);
	zbx_snprintf(port, sizeof(port), "%hu", listen_port);

	ip = ips = (NULL == listen_ip ? NULL : strdup(listen_ip));

	while (1)
	{
		delim = (NULL == ip ? NULL : strchr(ip, ','));
		if (NULL != delim)
			*delim = '\0';

		if (0 != (err = getaddrinfo(ip, port, &hints, &ai)))
		{
			zbx_set_socket_strerror("cannot resolve address [[%s]:%s]: [%d] %s",
					NULL != ip ? ip : "-", port, err, gai_strerror(err));
			goto out;
		}

		for (current_ai = ai; NULL != current_ai; current_ai = current_ai->ai_next)
		{
			if (ZBX_SOCKET_COUNT == s->num_socks)
			{
				zbx_set_socket_strerror("not enough space for socket [[%s]:%s]",
						NULL != ip ? ip : "-", port);
				goto out;
			}

			if (PF_INET != current_ai->ai_family && PF_INET6 != current_ai->ai_family)
				continue;

#if defined(_WINDOWS)
			/* WSA_FLAG_NO_HANDLE_INHERIT prevents socket inheritance if we call CreateProcess() */
			/* later on. If it's not available we still try to avoid inheritance by calling  */
			/* SetHandleInformation() below. WSA_FLAG_OVERLAPPED is not mandatory but strongly */
			/* recommended for every socket */
			s->sockets[s->num_socks] = WSASocket(current_ai->ai_family, current_ai->ai_socktype,
					current_ai->ai_protocol, NULL, 0,
					(0 != no_inherit_wsapi ? WSA_FLAG_NO_HANDLE_INHERIT : 0) |
					WSA_FLAG_OVERLAPPED);
			if (ZBX_SOCKET_ERROR == s->sockets[s->num_socks])
			{
				zbx_set_socket_strerror("WSASocket() for [[%s]:%s] failed: %s",
						NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));

				if (WSAEAFNOSUPPORT == zbx_socket_last_error())
					continue;

				goto out;
			}

			/* If WSA_FLAG_NO_HANDLE_INHERIT not available, prevent listening socket from */
			/* inheritance with the old API. Disabling handle inheritance in WSASocket() instead of */
			/* SetHandleInformation() is preferred because it provides atomicity and gets the job done */
			/* on systems with non-IFS LSPs installed. So there is a chance that the socket will be still */
			/* inherited on Windows XP with 3rd party firewall/antivirus installed */
			if (0 == no_inherit_wsapi && 0 == SetHandleInformation((HANDLE)s->sockets[s->num_socks],
					HANDLE_FLAG_INHERIT, 0))
			{
				zabbix_log(LOG_LEVEL_WARNING, "SetHandleInformation() failed: %s",
						zbx_strerror_from_system(GetLastError()));
			}

			/* prevent other processes from binding to the same port */
			/* SO_EXCLUSIVEADDRUSE is mutually exclusive with SO_REUSEADDR */
			/* on Windows SO_REUSEADDR has different semantics than on Unix */
			/* https://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx */
			if (ZBX_PROTO_ERROR == setsockopt(s->sockets[s->num_socks], SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
					(void *)&on, sizeof(on)))
			{
				zbx_set_socket_strerror("setsockopt() with %s for [[%s]:%s] failed: %s",
						"SO_EXCLUSIVEADDRUSE", NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
			}

#else
			if (ZBX_SOCKET_ERROR == (s->sockets[s->num_socks] =
					socket(current_ai->ai_family, current_ai->ai_socktype | SOCK_CLOEXEC,
					current_ai->ai_protocol)))
			{
				zbx_set_socket_strerror("socket() for [[%s]:%s] failed: %s",
						NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));

				if (EAFNOSUPPORT == zbx_socket_last_error())
					continue;

				goto out;
			}

#	if !SOCK_CLOEXEC
			if (-1 == fcntl(s->sockets[s->num_socks], F_SETFD, FD_CLOEXEC))
			{
				zbx_set_socket_strerror("failed to set the FD_CLOEXEC file descriptor flag on "
						"socket [[%s]:%s]: %s", NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
			}
#	endif

			/* enable address reuse */
			/* this is to immediately use the address even if it is in TIME_WAIT state */
			/* http://www-128.ibm.com/developerworks/linux/library/l-sockpit/index.html */
			if (ZBX_PROTO_ERROR == setsockopt(s->sockets[s->num_socks], SOL_SOCKET, SO_REUSEADDR,
					(void *)&on, sizeof(on)))
			{
				zbx_set_socket_strerror("setsockopt() with %s for [[%s]:%s] failed: %s",
						"SO_REUSEADDR", NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
			}
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
			if (PF_INET6 == current_ai->ai_family &&
					ZBX_PROTO_ERROR == setsockopt(s->sockets[s->num_socks], IPPROTO_IPV6,
					IPV6_V6ONLY, (void *)&on, sizeof(on)))
			{
				zbx_set_socket_strerror("setsockopt() with %s for [[%s]:%s] failed: %s",
						"IPV6_V6ONLY", NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
			}
#endif
			if (ZBX_PROTO_ERROR == zbx_bind(s->sockets[s->num_socks], current_ai->ai_addr,
								current_ai->ai_addrlen))
			{
				zbx_set_socket_strerror("bind() for [[%s]:%s] failed: %s",
						NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
				zbx_socket_close(s->sockets[s->num_socks]);

				if (SUCCEED == tcp_err_in_use())
					continue;
				else
					goto out;
			}

			if (ZBX_PROTO_ERROR == listen(s->sockets[s->num_socks], config_tcp_max_backlog_size))
			{
				zbx_set_socket_strerror("listen() for [[%s]:%s] failed: %s",
						NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
				zbx_socket_close(s->sockets[s->num_socks]);
				goto out;
			}

			if (SUCCEED != socket_set_nonblocking(s->sockets[s->num_socks]))
			{
				zbx_set_socket_strerror("setting non-blocking mode for [[%s]:%s] failed: %s",
						NULL != ip ? ip : "-", port,
						zbx_strerror_from_system(zbx_socket_last_error()));
				zbx_socket_close(s->sockets[s->num_socks]);
				goto out;
			}

			s->num_socks++;
		}

		if (NULL != ai)
		{
			freeaddrinfo(ai);
			ai = NULL;
		}

		if (NULL == ip || NULL == delim)
			break;

		*delim = ',';
		ip = delim + 1;
	}

	if (0 == s->num_socks)
	{
		zbx_set_socket_strerror("zbx_tcp_listen(): "
				"failed to create listening socket for [[%s]:%hu]: %s",
				NULL != listen_ip ? listen_ip : "-", listen_port,
				zbx_strerror_from_system(zbx_socket_last_error()));
		goto out;
	}

	ret = SUCCEED;
out:
	if (NULL != ips)
		zbx_free(ips);

	if (NULL != ai)
		freeaddrinfo(ai);

	if (SUCCEED != ret)
	{
		for (i = 0; i < s->num_socks; i++)
			zbx_socket_close(s->sockets[i]);
	}

	return ret;
}

void	zbx_tcp_unlisten(zbx_socket_t *s)
{
	int	i;

	for (i = 0; i < s->num_socks; i++)
		zbx_socket_close(s->sockets[i]);

	zbx_socket_clean(s);
}

/******************************************************************************
 *                                                                            *
 * Purpose: permits incoming connection attempt on socket                     *
 *                                                                            *
 * Parameters:                                                                *
 *     s                      - [IN/OUT]                                      *
 *     tls_accept             - [IN] TLS configuration                        *
 *     poll_timeout           - [IN] milliseconds to wait for connection      *
 *                                   0 - don't wait, -1 - wait forever        *
 *     tls_listen             - [IN] allow unencrypted inbound                *
 *     unencrypted_allowed_ip - [IN]                                          *
 *                                                                            *
 * Return value: SUCCEED       - success                                      *
 *               FAIL          - an error occurred                            *
 *               TIMEOUT_ERROR - no connections for the timeout period        *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_accept(zbx_socket_t *s, unsigned int tls_accept, int poll_timeout, char *tls_listen,
		const char *unencrypted_allowed_ip)
{
	ZBX_SOCKADDR	serv_addr;
	ZBX_SOCKET	accepted_socket;
	ZBX_SOCKLEN_T	nlen;
	int		i, ret = FAIL;
	ssize_t		res;
	char		buf;	/* 1 byte buffer */
	zbx_pollfd_t	*pds;

	zbx_tcp_unaccept(s);

	pds = (zbx_pollfd_t *)zbx_malloc(NULL, sizeof(zbx_pollfd_t) * (size_t)s->num_socks);

	for (i = 0; i < s->num_socks; i++)
	{
		pds[i].fd = s->sockets[i];
		pds[i].events = POLLIN;
	}

	if (ZBX_PROTO_ERROR == (ret = zbx_socket_poll(pds, (unsigned long)s->num_socks, poll_timeout * 1000)))
	{
		if (SUCCEED == zbx_socket_had_nonblocking_error())
			ret = TIMEOUT_ERROR;
		else
			zbx_set_socket_strerror("poll() failed: %s", zbx_strerror_from_system(zbx_socket_last_error()));

		goto out;
	}

	if (0 == ret)
	{
		ret = TIMEOUT_ERROR;
		goto out;
	}

	for (i = 0; i < s->num_socks; i++)
	{
		if (0 != (pds[i].revents & POLLIN))
			break;
	}

	if (i == s->num_socks)
	{
		zbx_set_socket_strerror("incoming connection has failed");
		goto out;
	}

	/* Since this socket was returned by poll, we know we have */
	/* a connection waiting and that this accept() will not block. */
	nlen = sizeof(serv_addr);
	if (ZBX_SOCKET_ERROR == (accepted_socket = (ZBX_SOCKET)accept(s->sockets[i], (struct sockaddr *)&serv_addr,
			&nlen)))
	{
		if (SUCCEED == zbx_socket_had_nonblocking_error())
			ret = TIMEOUT_ERROR;
		else
		{
			zbx_set_socket_strerror("accept() failed: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
		}

		goto out;
	}

	s->socket_orig = s->socket;	/* remember main socket */
	s->socket = accepted_socket;	/* replace socket to accepted */
	s->accepted = 1;

	if (SUCCEED != socket_set_nonblocking(accepted_socket))
	{
		zbx_set_socket_strerror("failed to set socket non-blocking mode: %s",
				zbx_strerror_from_system(zbx_socket_last_error()));
		zbx_tcp_unaccept(s);
		goto out;
	}

	if (SUCCEED != zbx_socket_peer_ip_save(s))
	{
		/* cannot get peer IP address */
		zbx_tcp_unaccept(s);
		goto out;
	}

	zbx_socket_set_deadline(s, s->timeout);

	if (FAIL == (res = tcp_peek(s, &buf, 1)) || TIMEOUT_ERROR == res)
	{
		zbx_set_socket_strerror("from %s: reading first byte from connection failed: %s", s->peer,
				zbx_strerror_from_system(zbx_socket_last_error()));
		zbx_tcp_unaccept(s);
		goto out;
	}

	/* if the 1st byte is 0x16 then assume it's a TLS connection */
	if (1 == res && '\x16' == buf)
	{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
		if (0 != (tls_accept & (ZBX_TCP_SEC_TLS_CERT | ZBX_TCP_SEC_TLS_PSK)))
		{
			char	*error = NULL;

			if (SUCCEED != zbx_tls_accept(s, tls_accept, &error))
			{
				zbx_set_socket_strerror("from %s: %s", s->peer, error);
				zbx_tcp_unaccept(s);
				zbx_free(error);
				goto out;
			}
		}
		else
		{
			zbx_set_socket_strerror("from %s: TLS connections are not allowed", s->peer);
			zbx_tcp_unaccept(s);
			goto out;
		}
#else
		zbx_set_socket_strerror("from %s: support for TLS was not compiled in", s->peer);
		zbx_tcp_unaccept(s);
		goto out;
#endif
	}
	else
	{
		if (0 == (tls_accept & ZBX_TCP_SEC_UNENCRYPTED))
		{
			zbx_set_socket_strerror("from %s: unencrypted connections are not allowed", s->peer);
			zbx_tcp_unaccept(s);
			goto out;
		}

		s->connection_type = ZBX_TCP_SEC_UNENCRYPTED;
	}

	s->max_len_limit = 0;

	if (ZBX_TCP_SEC_UNENCRYPTED == s->connection_type && NULL != tls_listen)
	{
		if (NULL == unencrypted_allowed_ip || SUCCEED != zbx_tcp_check_allowed_peers(s, unencrypted_allowed_ip))
		{
			zbx_set_socket_strerror("from %s: unencrypted connections are not allowed", s->peer);
			zbx_tcp_unaccept(s);
			goto out;
		}
		else
		{
			/* stats request allowed exception when unencrypted connection not allowed */
			s->max_len_limit = ZBX_MAX_RECV_2KB_DATA_SIZE;
		}
	}

	zbx_socket_set_deadline(s, 0);

	ret = SUCCEED;
out:
	zbx_free(pds);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: close accepted connection                                         *
 *                                                                            *
 ******************************************************************************/
void	zbx_tcp_unaccept(zbx_socket_t *s)
{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	zbx_tls_close(s);
#endif
	if (!s->accepted) return;

	shutdown(s->socket, 2);

	zbx_socket_free(s);
	zbx_socket_close(s->socket);

	s->socket = s->socket_orig;	/* restore main socket */
	s->socket_orig = ZBX_SOCKET_ERROR;
	s->accepted = 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: finds next line in socket data buffer                             *
 *                                                                            *
 * Parameters: s - [IN]                                                       *
 *                                                                            *
 * Return value: A pointer to the next line or NULL if the socket data buffer *
 *               contains no more lines.                                      *
 *                                                                            *
 ******************************************************************************/
static const char	*zbx_socket_find_line(zbx_socket_t *s)
{
	char	*ptr, *line = NULL;

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

	/* check if the buffer contains the next line */
	if ((size_t)(s->next_line - s->buffer) <= s->read_bytes && NULL != (ptr = strchr(s->next_line, '\n')))
	{
		line = s->next_line;
		s->next_line = ptr + 1;

		if (ptr > line && '\r' == *(ptr - 1))
			ptr--;

		*ptr = '\0';
	}

	return line;
}

/******************************************************************************
 *                                                                            *
 * Purpose: reads next line from socket                                       *
 *                                                                            *
 * Parameters: s - [IN]                                                       *
 *                                                                            *
 * Return value: A pointer to the line in socket buffer or NULL if there are  *
 *               no more lines (socket was closed or an error occurred).      *
 *                                                                            *
 * Comments: Lines larger than 64KB are truncated.                            *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_tcp_recv_line(zbx_socket_t *s)
{
#define ZBX_TCP_LINE_LEN	(64 * ZBX_KIBIBYTE)

	char		buffer[ZBX_STAT_BUF_LEN], *ptr = NULL;
	const char	*line;
	ssize_t		nbytes;
	size_t		alloc = 0, offset = 0, line_length, left;

	/* check if the buffer already contains the next line */
	if (NULL != (line = zbx_socket_find_line(s)))
		return line;

	/* Find the size of leftover data from the last read line operation and copy */
	/* the leftover data to the static buffer and reset the dynamic buffer.      */
	/* Because we are reading data in ZBX_STAT_BUF_LEN chunks the leftover       */
	/* data will always fit the static buffer.                                   */
	if (NULL != s->next_line)
	{
		left = (size_t)(s->read_bytes - (size_t)(s->next_line - s->buffer));
		memmove(s->buf_stat, s->next_line, left);
	}
	else
		left = 0;

	s->read_bytes = left;
	s->next_line = s->buf_stat;

	zbx_socket_free(s);
	s->buf_type = ZBX_BUF_TYPE_STAT;
	s->buffer = s->buf_stat;

	/* read more data into static buffer */
	if (ZBX_PROTO_ERROR == (nbytes = tcp_read(s, s->buf_stat + left, ZBX_STAT_BUF_LEN - left - 1, NULL)))
		goto out;

	s->buf_stat[left + (size_t)nbytes] = '\0';

	if (0 == nbytes)
	{
		/* Socket was closed before newline was found. If we have data in buffer  */
		/* return it with success. Otherwise return failure.                      */
		line = 0 != s->read_bytes ? s->next_line : NULL;
		s->next_line += s->read_bytes;

		goto out;
	}

	s->read_bytes += (size_t)nbytes;

	/* check if the static buffer now contains the next line */
	if (NULL != (line = zbx_socket_find_line(s)))
		goto out;

	/* copy the static buffer data into dynamic buffer */
	s->buf_type = ZBX_BUF_TYPE_DYN;
	s->buffer = NULL;
	zbx_strncpy_alloc(&s->buffer, &alloc, &offset, s->buf_stat, s->read_bytes);
	line_length = s->read_bytes;

	/* Read data into dynamic buffer until newline has been found. */
	/* Lines larger than ZBX_TCP_LINE_LEN bytes will be truncated. */
	do
	{
		if (ZBX_PROTO_ERROR == (nbytes = tcp_read(s, buffer, ZBX_STAT_BUF_LEN - 1, NULL)))
			goto out;

		if (0 == nbytes)
		{
			/* socket was closed before newline was found, just return the data we have */
			line = 0 != s->read_bytes ? s->buffer : NULL;
			s->next_line = s->buffer + s->read_bytes;

			goto out;
		}

		buffer[nbytes] = '\0';
		ptr = strchr(buffer, '\n');

		if (s->read_bytes + (size_t)nbytes < ZBX_TCP_LINE_LEN && s->read_bytes == line_length)
		{
			zbx_strncpy_alloc(&s->buffer, &alloc, &offset, buffer, (size_t)nbytes);
			s->read_bytes += (size_t)nbytes;
		}
		else
		{
			if (0 != (left = (NULL == ptr ? ZBX_TCP_LINE_LEN - s->read_bytes :
					MIN(ZBX_TCP_LINE_LEN - s->read_bytes, (size_t)(ptr - buffer)))))
			{
				/* fill the string to the defined limit */
				zbx_strncpy_alloc(&s->buffer, &alloc, &offset, buffer, left);
				s->read_bytes += left;
			}

			/* if the line exceeds the defined limit then truncate it by skipping data until the newline */
			if (NULL != ptr)
			{
				zbx_strncpy_alloc(&s->buffer, &alloc, &offset, ptr, (size_t)(nbytes - (ptr - buffer)));
				s->read_bytes += (size_t)(nbytes - (ptr - buffer));
			}
		}

		line_length += (size_t)nbytes;

	}
	while (NULL == ptr);

	s->next_line = s->buffer;
	line = zbx_socket_find_line(s);
out:
	return line;
}

ssize_t	zbx_tcp_read(zbx_socket_t *s, char *buf, size_t len, short *events)
{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	ssize_t	res;

	if (NULL != s->tls_ctx)	/* TLS connection */
	{
		char	*error = NULL;

		if (ZBX_PROTO_ERROR == (res = zbx_tls_read(s, buf, len, events, &error)))
		{
			if (NULL == events || 0 == *events)
			{
				zbx_set_socket_strerror("%s", error);
				zbx_free(error);
			}
		}

		return res;
	}
#endif
	return tcp_read(s, buf, len, events);
}


/******************************************************************************
 *                                                                            *
 * Purpose: read TLS close_notify alert from TCP socket                       *
 *                                                                            *
 * Parameters: s       - [IN/OUT]                                             *
 *             timeout - [IN] maximum time to wait for close_notify (sec)     *
 *                             (range 0-1000)                                 *
 *             events  - [OUT] event flag (optional)                          *
 *                                                                            *
 * Return value: number of bytes read (0 if not TLS)                          *
 *               ZBX_PROTO_ERROR - an error occurred                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_read_close_notify(zbx_socket_t *s, int timeout, short *events)
{
	int	ret;
	char	buf[ZBX_STAT_BUF_LEN];

	if (SUCCEED != zbx_tls_used(s))
		return 0;

	if (0 != timeout)
		zbx_socket_set_deadline(s, timeout);

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

	ret = zbx_tcp_read(s, buf, sizeof(buf), events);

	if (0 != timeout)
		zbx_socket_set_deadline(s, 0);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: sets deadline for socket operations                               *
 *                                                                            *
 ******************************************************************************/
void	zbx_socket_set_deadline(zbx_socket_t *s, int timeout)
{
	if (0 == timeout)
	{
		s->deadline.sec = 0;
		s->deadline.ns = 0;
		return;
	}

	zbx_ts_get_deadline(&s->deadline, timeout);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if deadline has not been reached                            *
 *                                                                            *
 ******************************************************************************/
int	zbx_socket_check_deadline(zbx_socket_t *s)
{
	if (0 == s->deadline.sec)
		return SUCCEED;

	return zbx_ts_check_deadline(&s->deadline);
}

#define ZBX_TCP_EXPECT_HEADER		1
#define ZBX_TCP_EXPECT_VERSION		2
#define ZBX_TCP_EXPECT_VERSION_VALIDATE	3
#define ZBX_TCP_EXPECT_LENGTH		4
#define ZBX_TCP_EXPECT_SIZE		5

void	zbx_tcp_recv_context_init(zbx_socket_t *s, zbx_tcp_recv_context_t *tcp_recv_context, unsigned char flags)
{
	tcp_recv_context->buf_dyn_bytes = 0;
	tcp_recv_context->buf_stat_bytes = 0;
	tcp_recv_context->offset = 0;
	tcp_recv_context->expected_len = 16 * ZBX_MEBIBYTE;
	tcp_recv_context->reserved = 0;
	tcp_recv_context->expect = ZBX_TCP_EXPECT_HEADER;
#if defined(_WINDOWS)
	tcp_recv_context->max_len = ZBX_MAX_RECV_DATA_SIZE;
#else
	if (0 != s->max_len_limit)
	{
		tcp_recv_context->max_len = s->max_len_limit;
	}
	else if (0 != (flags & ZBX_TCP_LARGE))
	{
		tcp_recv_context->max_len = ZBX_MAX_RECV_LARGE_DATA_SIZE;
	}
	else
	{
		tcp_recv_context->max_len = ZBX_MAX_RECV_DATA_SIZE;
	}
#endif
	zbx_socket_free(s);
	tcp_recv_context->allocated = 0;

	s->buf_type = ZBX_BUF_TYPE_STAT;
	s->buffer = s->buf_stat;
}

ssize_t	zbx_tcp_recv_context(zbx_socket_t *s, zbx_tcp_recv_context_t *context, unsigned char flags, short *events)
{
	ssize_t	nbytes;

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

	while (0 != (nbytes = zbx_tcp_read(s, s->buf_stat + context->buf_stat_bytes,
			sizeof(s->buf_stat) - context->buf_stat_bytes, events)))
	{
		if (ZBX_PROTO_ERROR == nbytes)
			goto out;

		if (ZBX_BUF_TYPE_STAT == s->buf_type)
			context->buf_stat_bytes += (size_t)nbytes;
		else
		{
			if (context->buf_dyn_bytes + (size_t)nbytes <= context->expected_len)
				memcpy(s->buffer + context->buf_dyn_bytes, s->buf_stat, (size_t)nbytes);
			context->buf_dyn_bytes += (size_t)nbytes;
		}

		if (context->buf_stat_bytes + context->buf_dyn_bytes >= context->expected_len)
			break;

		if (ZBX_TCP_EXPECT_HEADER == context->expect)
		{
			if (ZBX_TCP_HEADER_LEN > context->buf_stat_bytes)
			{
				if (0 == strncmp(s->buf_stat, ZBX_TCP_HEADER_DATA, context->buf_stat_bytes))
					continue;

				break;
			}
			else
			{
				if (0 != strncmp(s->buf_stat, ZBX_TCP_HEADER_DATA, ZBX_TCP_HEADER_LEN))
				{
					/* invalid header, abort receiving */
					break;
				}

				context->expect = ZBX_TCP_EXPECT_VERSION;
				context->offset += ZBX_TCP_HEADER_LEN;
			}
		}

		if (ZBX_TCP_EXPECT_VERSION == context->expect)
		{
			if (context->offset + 1 > context->buf_stat_bytes)
				continue;

			context->expect = ZBX_TCP_EXPECT_VERSION_VALIDATE;
			context->protocol_version = s->buf_stat[ZBX_TCP_HEADER_LEN];

			if (0 == (context->protocol_version & ZBX_TCP_PROTOCOL) ||
					context->protocol_version > (ZBX_TCP_PROTOCOL | ZBX_TCP_COMPRESS | flags))
			{
				/* invalid protocol version, abort receiving */
				break;
			}
			s->protocol = context->protocol_version;
			context->expect = ZBX_TCP_EXPECT_LENGTH;
			context->offset++;
		}

		if (ZBX_TCP_EXPECT_LENGTH == context->expect)
		{
			if (0 != (context->protocol_version & ZBX_TCP_LARGE))
			{
				zbx_uint64_t	len64_le;

				if (context->offset + 2 * sizeof(len64_le) > context->buf_stat_bytes)
					continue;

				memcpy(&len64_le, s->buf_stat + context->offset, sizeof(len64_le));
				context->offset += sizeof(len64_le);
				context->expected_len = zbx_letoh_uint64(len64_le);

				memcpy(&len64_le, s->buf_stat + context->offset, sizeof(len64_le));
				context->offset += sizeof(len64_le);
				context->reserved = zbx_letoh_uint64(len64_le);
			}
			else
			{
				zbx_uint32_t	len32_le;

				if (context->offset + 2 * sizeof(len32_le) > context->buf_stat_bytes)
					continue;

				memcpy(&len32_le, s->buf_stat + context->offset, sizeof(len32_le));
				context->offset += sizeof(len32_le);
				context->expected_len = zbx_letoh_uint32(len32_le);

				memcpy(&len32_le, s->buf_stat + context->offset, sizeof(len32_le));
				context->offset += sizeof(len32_le);
				context->reserved = zbx_letoh_uint32(len32_le);
			}

			if (context->max_len < context->expected_len)
			{
				zbx_set_socket_strerror("message exceeds the maximum size");
				zabbix_log(LOG_LEVEL_WARNING, "Message size " ZBX_FS_UI64 " from %s exceeds the "
						"maximum size " ZBX_FS_UI64 " bytes. Message ignored.",
						context->expected_len, s->peer, context->max_len);
				nbytes = ZBX_PROTO_ERROR;
				goto out;
			}

			/* compressed protocol stores uncompressed packet size in the reserved data */
			if (context->max_len < context->reserved)
			{
				zbx_set_socket_strerror("uncompressed message size exceeds the maximum size");
				zabbix_log(LOG_LEVEL_WARNING, "Uncompressed message size " ZBX_FS_UI64 " from %s"
						" exceeds the maximum size " ZBX_FS_UI64 " bytes. Message ignored.",
						context->reserved, s->peer, context->max_len);
				nbytes = ZBX_PROTO_ERROR;
				goto out;
			}

			if (sizeof(s->buf_stat) > context->expected_len)
			{
				context->buf_stat_bytes -= context->offset;
				memmove(s->buf_stat, s->buf_stat + context->offset, context->buf_stat_bytes);
			}
			else
			{
				char	*buffer;

				if (NULL == (buffer = (char *)malloc(context->expected_len + 1)))
				{
					zbx_set_socket_strerror("cannot allocate memory: out of memory");
					zabbix_log(LOG_LEVEL_WARNING, "Message size " ZBX_FS_UI64
							" from %s exceeds the available memory size."
							" Message ignored.", context->expected_len, s->peer);
					nbytes = ZBX_PROTO_ERROR;
					goto out;
				}

				s->buf_type = ZBX_BUF_TYPE_DYN;
				s->buffer = buffer;
				context->buf_dyn_bytes = context->buf_stat_bytes - context->offset;
				context->buf_stat_bytes = 0;
				memcpy(s->buffer, s->buf_stat + context->offset, context->buf_dyn_bytes);
			}

			context->expect = ZBX_TCP_EXPECT_SIZE;

			if (context->buf_stat_bytes + context->buf_dyn_bytes >= context->expected_len)
				break;
		}
	}

	if (ZBX_TCP_EXPECT_SIZE == context->expect)
	{
		if (context->buf_stat_bytes + context->buf_dyn_bytes == context->expected_len)
		{
			if (0 != (context->protocol_version & ZBX_TCP_COMPRESS))
			{
				char	*out;
				size_t	out_size = context->reserved;
				if (NULL == (out = (char *)malloc(context->reserved + 1)))
				{
					zbx_set_socket_strerror("cannot allocate memory to uncompress data:"
							" out of memory");
					zabbix_log(LOG_LEVEL_WARNING, "Uncompressed message size " ZBX_FS_UI64
							" from %s exceeds the available memory size."
							" Message ignored.", context->reserved, s->peer);
					nbytes = ZBX_PROTO_ERROR;
					goto out;
				}

				if (FAIL == zbx_uncompress(s->buffer, context->buf_stat_bytes + context->buf_dyn_bytes,
						out, &out_size))
				{
					zbx_free(out);
					zbx_set_socket_strerror("cannot uncompress data: %s", zbx_compress_strerror());
					nbytes = ZBX_PROTO_ERROR;
					goto out;
				}

				if (out_size != context->reserved)
				{
					zbx_free(out);
					zbx_set_socket_strerror("size of uncompressed data is less than expected");
					nbytes = ZBX_PROTO_ERROR;
					goto out;
				}

				if (ZBX_BUF_TYPE_DYN == s->buf_type)
					zbx_free(s->buffer);

				s->buf_type = ZBX_BUF_TYPE_DYN;
				s->buffer = out;
				s->read_bytes = context->reserved;

				zabbix_log(LOG_LEVEL_TRACE, "%s(): received " ZBX_FS_SIZE_T " bytes with"
						" compression ratio %.1f", __func__,
						(zbx_fs_size_t)(context->buf_stat_bytes + context->buf_dyn_bytes),
						(double)context->reserved / (double)(context->buf_stat_bytes +
						context->buf_dyn_bytes));
			}
			else
				s->read_bytes = context->buf_stat_bytes + context->buf_dyn_bytes;

			s->buffer[s->read_bytes] = '\0';
		}
		else
		{
			zbx_set_socket_strerror("message length does not match expected length");
			if (context->buf_stat_bytes + context->buf_dyn_bytes < context->expected_len)
			{
				zabbix_log(LOG_LEVEL_WARNING, "Message from %s is shorter than expected " ZBX_FS_UI64
						" bytes. Message ignored.", s->peer,
						(zbx_uint64_t)context->expected_len);
			}
			else
			{
				zabbix_log(LOG_LEVEL_WARNING, "Message from %s is longer than expected " ZBX_FS_UI64
						" bytes. Message ignored.", s->peer,
						(zbx_uint64_t)context->expected_len);
			}

			nbytes = ZBX_PROTO_ERROR;
		}
	}
	else if (ZBX_TCP_EXPECT_LENGTH == context->expect)
	{
		zbx_set_socket_strerror("message is missing data length");
		zabbix_log(LOG_LEVEL_WARNING, "Message from %s is missing data length. Message ignored.", s->peer);
		nbytes = ZBX_PROTO_ERROR;
	}
	else if (ZBX_TCP_EXPECT_VERSION == context->expect)
	{
		zbx_set_socket_strerror("message is missing protocol version");
		zabbix_log(LOG_LEVEL_WARNING, "Message from %s is missing protocol version. Message ignored.",
				s->peer);
		nbytes = ZBX_PROTO_ERROR;
	}
	else if (ZBX_TCP_EXPECT_VERSION_VALIDATE == context->expect)
	{
		zbx_set_socket_strerror("message is using unsupported protocol version");
		zabbix_log(LOG_LEVEL_WARNING, "Message from %s is using unsupported protocol version \"%d\"."
				" Message ignored.", s->peer, context->protocol_version);
		nbytes = ZBX_PROTO_ERROR;
	}
	else if (0 != context->buf_stat_bytes)
	{
		zbx_set_socket_strerror("message is missing header");
		zabbix_log(LOG_LEVEL_WARNING, "Message from %s is missing header. Message ignored.", s->peer);
		nbytes = ZBX_PROTO_ERROR;
	}
	else
	{
		s->read_bytes = 0;
		s->buffer[s->read_bytes] = '\0';
	}
out:
	return (ZBX_PROTO_ERROR == nbytes ? FAIL : (ssize_t)(s->read_bytes + context->offset));

#undef ZBX_TCP_EXPECT_HEADER
#undef ZBX_TCP_EXPECT_LENGTH
#undef ZBX_TCP_EXPECT_SIZE
}

/******************************************************************************
 *                                                                            *
 * Purpose: receive data                                                      *
 *                                                                            *
 * Return value: number of bytes received - success,                          *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
ssize_t	zbx_tcp_recv_ext(zbx_socket_t *s, int timeout, unsigned char flags)
{
	zbx_tcp_recv_context_t	tcp_recv_context;
	ssize_t			nbytes;

	if (0 != timeout)
		zbx_socket_set_deadline(s, timeout);

	zbx_tcp_recv_context_init(s, &tcp_recv_context, flags);

	nbytes = zbx_tcp_recv_context(s, &tcp_recv_context, flags, NULL);

	if (0 != timeout)
		zbx_socket_set_deadline(s, 0);

	return nbytes;
}

/******************************************************************************
 *                                                                            *
 * Purpose: receive data till connection is closed                            *
 *                                                                            *
 * Return value: number of bytes received - success,                          *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
ssize_t	zbx_tcp_recv_raw_ext(zbx_socket_t *s, int timeout)
{
	zbx_tcp_recv_context_t	tcp_recv_context;
	ssize_t			nbytes;

	if (0 != timeout)
		zbx_socket_set_deadline(s, timeout);

	zbx_tcp_recv_context_init(s, &tcp_recv_context, 0);

	nbytes = zbx_tcp_recv_context_raw(s, &tcp_recv_context, NULL, 0);

	if (0 != timeout)
		zbx_socket_set_deadline(s, 0);

	return (ZBX_PROTO_ERROR == nbytes ? FAIL : nbytes);
}

/******************************************************************************
 *                                                                            *
 * Purpose: receive raw data till socket is full or once                      *
 *                                                                            *
 * Parameters: s       - [IN/OUT]                                             *
 *             context - [IN/OUT] state of socket descriptor                  *
 *             events  - [OUT] socket state                                   *
 *             once    - [IN] read socket once                                *
 *                                                                            *
 * Return value: number of bytes in socket context - success                  *
 *               ZBX_PROTO_ERROR - an error occurred                          *
 *                                                                            *
 * Comments: context must be initialized                                      *
 *                                                                            *
 ******************************************************************************/
ssize_t	zbx_tcp_recv_context_raw(zbx_socket_t *s, zbx_tcp_recv_context_t *context, short *events, int once)
{
	ssize_t	nbytes;

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

	while (0 != (nbytes = zbx_tcp_read(s, s->buf_stat + context->buf_stat_bytes,
			sizeof(s->buf_stat) - context->buf_stat_bytes, events)))
	{
		if (ZBX_PROTO_ERROR == nbytes)
			goto out;

		if (ZBX_BUF_TYPE_STAT == s->buf_type)
			context->buf_stat_bytes += (size_t)nbytes;
		else
		{
			if (context->buf_dyn_bytes + (size_t)nbytes >= context->allocated)
			{
				while (context->buf_dyn_bytes + (size_t)nbytes >= context->allocated)
					context->allocated *= 2;

				s->buffer = (char *)zbx_realloc(s->buffer, context->allocated);
			}

			memcpy(s->buffer + context->buf_dyn_bytes, s->buf_stat, (size_t)nbytes);
			context->buf_dyn_bytes += (size_t)nbytes;
		}

		if (context->buf_stat_bytes + context->buf_dyn_bytes >= context->expected_len)
			break;

		if (sizeof(s->buf_stat) == context->buf_stat_bytes)
		{
			s->buf_type = ZBX_BUF_TYPE_DYN;
			context->allocated = 8 * ZBX_STAT_BUF_LEN;
			s->buffer = (char *)zbx_malloc(NULL, context->allocated);
			context->buf_dyn_bytes = sizeof(s->buf_stat);
			context->buf_stat_bytes = 0;
			memcpy(s->buffer, s->buf_stat, sizeof(s->buf_stat));
		}

		if (0 != once)
			break;
	}

	if (context->buf_stat_bytes + context->buf_dyn_bytes >= context->expected_len)
	{
		zabbix_log(LOG_LEVEL_WARNING, "Message from %s is longer than " ZBX_FS_UI64 " bytes allowed for"
				" plain text. Message ignored.", s->peer, context->expected_len);
		nbytes = ZBX_PROTO_ERROR;
		goto out;
	}

	s->read_bytes = context->buf_stat_bytes + context->buf_dyn_bytes;
	s->buffer[s->read_bytes] = '\0';
out:
	return (ZBX_PROTO_ERROR == nbytes ? ZBX_PROTO_ERROR : (ssize_t)(s->read_bytes));
}

/******************************************************************************
 *                                                                            *
 * Purpose: receive next line from socket                                     *
 *                                                                            *
 * Parameters: s       - [IN/OUT]                                             *
 *             context - [IN/OUT] state of socket descriptor                  *
 *             events  - [OUT] socket state                                   *
 *                                                                            *
 * Return value: pointer to line - success,                                   *
 *               NULL - an error occurred                                     *
 *                                                                            *
 * Comments: context must be initialized                                      *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_tcp_recv_context_line(zbx_socket_t *s, zbx_tcp_recv_context_t *context, short *events)
{
	const char	*line;

	/* check if the buffer already contains the next line */
	if (NULL != (line = zbx_socket_find_line(s)))
		return line;

	if (NULL != s->next_line)
	{
		if (ZBX_BUF_TYPE_STAT == s->buf_type)
			context->buf_stat_bytes -= (size_t)(s->next_line - s->buffer);
		else
			context->buf_dyn_bytes -= (size_t)(s->next_line - s->buffer);

		memmove(s->buffer, s->next_line, ZBX_BUF_TYPE_STAT == s->buf_type ?
				context->buf_stat_bytes : context->buf_dyn_bytes);

		s->read_bytes = context->buf_stat_bytes + context->buf_dyn_bytes;
		s->next_line = NULL;
	}

	do
	{
		ssize_t	nbytes, nbytes_prev = (ssize_t)(context->buf_stat_bytes + context->buf_dyn_bytes);

		if (ZBX_PROTO_ERROR == (nbytes = zbx_tcp_recv_context_raw(s, context, events, 1)))
			goto out;

		if (nbytes == nbytes_prev)
		{
			/* socket was closed before newline was found, just return the data we have */
			line = 0 != s->read_bytes ? s->buffer : NULL;
			s->next_line = s->buffer + s->read_bytes;

			goto out;
		}
	}
	while (NULL == strchr(s->buffer, '\n'));

	s->next_line = s->buffer;
	line = zbx_socket_find_line(s);
out:
	return line;
}

static int	subnet_match(int af, unsigned int prefix_size, const void *address1, const void *address2)
{
	unsigned char	netmask[16] = {0};
	int		i, j, bytes;

	if (af == AF_INET)
	{
		if (prefix_size > ZBX_IPV4_MAX_CIDR_PREFIX)
			return FAIL;
		bytes = 4;
	}
	else
	{
		if (prefix_size > ZBX_IPV6_MAX_CIDR_PREFIX)
			return FAIL;
		bytes = 16;
	}

	/* CIDR notation to subnet mask */
	for (i = (int)prefix_size, j = 0; i > 0 && j < bytes; i -= 8, j++)
		netmask[j] = (unsigned char)(i >= 8 ? 0xFF : ~((1 << (8 - i)) - 1));

	/* The result of the bitwise AND operation of IP address and the subnet mask is the network prefix. */
	/* All hosts on a subnetwork have the same network prefix. */
	for (i = 0; i < bytes; i++)
	{
		if ((((const unsigned char *)address1)[i] & netmask[i]) !=
				(((const unsigned char *)address2)[i] & netmask[i]))
		{
			return FAIL;
		}
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if address belongs to given subnet                          *
 *                                                                            *
 * Parameters: prefix_size - [IN] subnet prefix size                          *
 *             current_ai  - [IN] subnet                                      *
 *             name        - [IN] address                                     *
 *             ipv6v4_mode - [IN] compare IPv6 IPv4-mapped address with       *
 *                                IPv4 addresses only                         *
 *                                                                            *
 * Return value: SUCCEED - address belongs to subnet                          *
 *               FAIL - otherwise                                             *
 *                                                                            *
 ******************************************************************************/
#ifndef HAVE_IPV6
int	zbx_ip_cmp(unsigned int prefix_size, const struct addrinfo *current_ai, const ZBX_SOCKADDR *name,
		int ipv6v4_mode)
{
	const struct sockaddr_in	*name4 = (const struct sockaddr_in *)name,
					*ai_addr4 = (const struct sockaddr_in *)current_ai->ai_addr;

	ZBX_UNUSED(ipv6v4_mode);

	return subnet_match(current_ai->ai_family, prefix_size, &name4->sin_addr.s_addr, &ai_addr4->sin_addr.s_addr);
}
#else
int	zbx_ip_cmp(unsigned int prefix_size, const struct addrinfo *current_ai, const ZBX_SOCKADDR *name,
		int ipv6v4_mode)
{
	/* Network Byte Order is ensured */
	/* IPv4-compatible, the first 96 bits are zeros */
	const unsigned char	ipv4_compat_mask[12] = {0};
	/* IPv4-mapped, the first 80 bits are zeros, 16 next - ones */
	const unsigned char	ipv4_mapped_mask[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255};

	const struct sockaddr_in	*name4 = (const struct sockaddr_in *)name,
					*ai_addr4 = (const struct sockaddr_in *)current_ai->ai_addr;
	const struct sockaddr_in6	*name6 = (const struct sockaddr_in6 *)name,
					*ai_addr6 = (const struct sockaddr_in6 *)current_ai->ai_addr;

#ifdef HAVE_SOCKADDR_STORAGE_SS_FAMILY
	if (current_ai->ai_family == name->ss_family)
#else
	if (current_ai->ai_family == name->__ss_family)
#endif
	{
		switch (current_ai->ai_family)
		{
			case AF_INET:
				if (SUCCEED == subnet_match(current_ai->ai_family, prefix_size, &name4->sin_addr.s_addr,
						&ai_addr4->sin_addr.s_addr))
				{
					return SUCCEED;
				}
				break;
			case AF_INET6:
				if ((0 == ipv6v4_mode || 0 != memcmp(name6->sin6_addr.s6_addr, ipv4_mapped_mask, 12)) &&
						SUCCEED == subnet_match(current_ai->ai_family, prefix_size,
						name6->sin6_addr.s6_addr,
						ai_addr6->sin6_addr.s6_addr))
				{
					return SUCCEED;
				}
				break;
		}
	}
	else
	{
		unsigned char	ipv6_compat_address[16], ipv6_mapped_address[16];

		if (AF_INET == current_ai->ai_family)
		{
			/* incoming AF_INET6, must see whether it is compatible or mapped */

			if (((0 == memcmp(name6->sin6_addr.s6_addr, ipv4_mapped_mask, 12)) ||
					(0 == ipv6v4_mode && 0 == memcmp(name6->sin6_addr.s6_addr,
					ipv4_compat_mask, 12))) && SUCCEED == subnet_match(AF_INET, prefix_size,
					&name6->sin6_addr.s6_addr[12], &ai_addr4->sin_addr.s_addr))
			{
				return SUCCEED;
			}
		}
		else if (AF_INET6 == current_ai->ai_family && 0 == ipv6v4_mode)
		{
			/* incoming AF_INET, must see whether the given is compatible or mapped */

			memcpy(ipv6_compat_address, ipv4_compat_mask, sizeof(ipv4_compat_mask));
			memcpy(&ipv6_compat_address[sizeof(ipv4_compat_mask)], &name4->sin_addr.s_addr, 4);

			memcpy(ipv6_mapped_address, ipv4_mapped_mask, sizeof(ipv4_mapped_mask));
			memcpy(&ipv6_mapped_address[sizeof(ipv4_mapped_mask)], &name4->sin_addr.s_addr, 4);

			if (SUCCEED == subnet_match(AF_INET6, prefix_size,
					&ai_addr6->sin6_addr.s6_addr, ipv6_compat_address) ||
					SUCCEED == subnet_match(AF_INET6, prefix_size,
					&ai_addr6->sin6_addr.s6_addr, ipv6_mapped_address))
			{
				return SUCCEED;
			}
		}
	}
	return FAIL;
}
#endif

int	validate_cidr(const char *ip, const char *cidr, void *value)
{
	if (SUCCEED == zbx_is_ip4(ip))
		return zbx_is_uint_range(cidr, value, 0, ZBX_IPV4_MAX_CIDR_PREFIX);
#ifdef HAVE_IPV6
	if (SUCCEED == zbx_is_ip6(ip))
		return zbx_is_uint_range(cidr, value, 0, ZBX_IPV6_MAX_CIDR_PREFIX);
#endif
	return FAIL;
}

int	zbx_validate_peer_list(const char *peer_list, char **error)
{
	char	*start, *end, *cidr_sep;
	char	tmp[MAX_STRING_LEN];

	zbx_strscpy(tmp, peer_list);

	for (start = tmp; '\0' != *start;)
	{
		if (NULL != (end = strchr(start, ',')))
			*end = '\0';

		if (NULL != (cidr_sep = strchr(start, '/')))
		{
			*cidr_sep = '\0';

			if (FAIL == validate_cidr(start, cidr_sep + 1, NULL))
			{
				*cidr_sep = '/';
				*error = zbx_dsprintf(NULL, "\"%s\"", start);
				return FAIL;
			}
		}
		else if (FAIL == zbx_is_supported_ip(start) && FAIL == zbx_validate_hostname(start))
		{
			*error = zbx_dsprintf(NULL, "\"%s\"", start);
			return FAIL;
		}

		if (NULL != end)
			start = end + 1;
		else
			break;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if connection initiator is in list of peers                 *
 *                                                                            *
 * Parameters: peer_info - [IN]                                               *
 *             peer_list - [IN] Comma-delimited list of allowed peers.        *
 *                              NULL not allowed. Empty string results in     *
 *                              return value FAIL.                            *
 *                                                                            *
 * Return value: SUCCEED - connection allowed                                 *
 *               FAIL - connection is not allowed                             *
 *                                                                            *
 * Comments: standard, compatible and IPv4-mapped addresses are treated       *
 *           the same: 127.0.0.1 == ::127.0.0.1 == ::ffff:127.0.0.1           *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_check_allowed_peers_info(const ZBX_SOCKADDR *peer_info, const char *peer_list)
{
	char	*start = NULL, *end = NULL, *cidr_sep, tmp[MAX_STRING_LEN];
	int	prefix_size;

	/* examine list of allowed peers which may include DNS names, IPv4/6 addresses and addresses in CIDR notation */

	zbx_strscpy(tmp, peer_list);

	for (start = tmp; '\0' != *start;)
	{
		struct addrinfo	hints, *ai = NULL, *current_ai;

		prefix_size = -1;

		if (NULL != (end = strchr(start, ',')))
			*end = '\0';

		if (NULL != (cidr_sep = strchr(start, '/')))
		{
			*cidr_sep = '\0';

			/* validate_cidr() may overwrite 'prefix_size' */
			if (SUCCEED != validate_cidr(start, cidr_sep + 1, &prefix_size))
				*cidr_sep = '/';	/* CIDR is only supported for IP */
		}

		memset(&hints, 0, sizeof(hints));
		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_protocol = IPPROTO_TCP;

		if (0 == getaddrinfo(start, NULL, &hints, &ai))
		{
			if (NULL != end)
				*end = ',';

			for (current_ai = ai; NULL != current_ai; current_ai = current_ai->ai_next)
			{
				int	prefix_size_current = prefix_size;

				if (-1 == prefix_size_current)
				{
					prefix_size_current = (current_ai->ai_family == AF_INET ?
							ZBX_IPV4_MAX_CIDR_PREFIX : ZBX_IPV6_MAX_CIDR_PREFIX);
				}

				if (SUCCEED == zbx_ip_cmp((unsigned int)prefix_size_current, current_ai, peer_info, 0))
				{
					freeaddrinfo(ai);
					return SUCCEED;
				}
			}
			freeaddrinfo(ai);
		}

		if (NULL != end)
			start = end + 1;
		else
			break;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if connection initiator is in list of peers                 *
 *                                                                            *
 * Parameters: s         - [IN]                                               *
 *             peer_list - [IN] Comma-delimited list of allowed peers.        *
 *                              NULL not allowed. Empty string results in     *
 *                              return value FAIL.                            *
 *                                                                            *
 * Return value: SUCCEED - connection allowed                                 *
 *               FAIL - connection is not allowed                             *
 *                                                                            *
 * Comments: standard, compatible and IPv4-mapped addresses are treated       *
 *           the same: 127.0.0.1 == ::127.0.0.1 == ::ffff:127.0.0.1           *
 *                                                                            *
 ******************************************************************************/
int	zbx_tcp_check_allowed_peers(const zbx_socket_t *s, const char *peer_list)
{
	if (SUCCEED == zbx_tcp_check_allowed_peers_info(&s->peer_info, peer_list))
		return SUCCEED;

	zbx_set_socket_strerror("connection from \"%s\" rejected, allowed hosts: \"%s\"", s->peer, peer_list);

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: translate connection type code to name                            *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_tcp_connection_type_name(unsigned int type)
{
	switch (type)
	{
		case ZBX_TCP_SEC_UNENCRYPTED:
			return "unencrypted";
		case ZBX_TCP_SEC_TLS_CERT:
			return "TLS with certificate";
		case ZBX_TCP_SEC_TLS_PSK:
			return "TLS with PSK";
		default:
			return "unknown";
	}
}

int	zbx_udp_connect(zbx_socket_t *s, const char *source_ip, const char *ip, unsigned short port, int timeout)
{
	return zbx_socket_create(s, SOCK_DGRAM, source_ip, ip, port, timeout, ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL);
}

/******************************************************************************
 *                                                                            *
 * Purpose: send data over UDP socket                                         *
 *                                                                            *
 * Parameters: s        - [IN/OUT]                                            *
 *             data     - [IN] data to send                                   *
 *             data_len - [IN] length of data to send                         *
 *             timeout  - [IN] maximum time to wait for send (seconds)        *
 *                               range: 0 - INT_MAX                           *
 *                                                                            *
 * Return value: SUCCEED - data sent successfully                             *
 *               FAIL    - an error occurred                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_udp_send(zbx_socket_t *s, const char *data, size_t data_len, int timeout)
{
	ssize_t		offset = 0, n;
	zbx_pollfd_t	pd;

	zbx_socket_set_deadline(s, timeout);

	pd.fd = s->socket;
	pd.events = POLLOUT;

	while (offset < (ssize_t)data_len)
	{
		if (ZBX_PROTO_ERROR == (n = zbx_sendto(s->socket, data + offset, data_len - (size_t)offset, 0, NULL,
				0)))
		{
			int	rc;

			if (SUCCEED != zbx_socket_had_nonblocking_error())
			{
				zbx_set_socket_strerror("sendto() failed: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return FAIL;
			}

			if (-1 == (rc = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
			{
				if (SUCCEED == zbx_socket_had_nonblocking_error())
					continue;

				zbx_set_socket_strerror("cannot wait for socket: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return FAIL;
			}

			if (0 != rc && 0 == (pd.revents & POLLOUT))
			{
				char	*errmsg;

				errmsg = socket_poll_error(pd.revents);
				zbx_set_socket_strerror("%s", errmsg);
				zbx_free(errmsg);

				zabbix_log(LOG_LEVEL_DEBUG, "poll(POLLOUT) failed with revents 0x%x",
						(unsigned)pd.revents);

				return FAIL;
			}
		}
		else
			offset += n;

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			zbx_set_socket_strerror("send timeout");
			return FAIL;
		}
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: receive data from UDP socket                                      *
 *                                                                            *
 * Parameters: s       - [IN/OUT]                                             *
 *             timeout - [IN] maximum time to wait for data (seconds)         *
 *                            range: 0 - INT_MAX                              *
 *                                                                            *
 * Return value: SUCCEED - data received successfully                         *
 *               FAIL    - an error occurred                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_udp_recv(zbx_socket_t *s, int timeout)
{
	char	buffer[65508];	/* maximum payload for UDP over IPv4 is 65507 bytes */

	ssize_t		n;
	zbx_pollfd_t	pd;

	zbx_socket_set_deadline(s, timeout);

	pd.fd = s->socket;
	pd.events = POLLIN;

	zbx_socket_free(s);

	while (0 >= (n = recvfrom(s->socket, buffer, sizeof(buffer) - 1, 0, NULL, NULL)))
	{
		int	rc;

		if (0 == n)
		{
			zbx_set_socket_strerror("connection shutdown");
			return FAIL;
		}

		if (SUCCEED != zbx_socket_had_nonblocking_error())
		{
			zbx_set_socket_strerror("recvfrom() failed: %s",
					zbx_strerror_from_system(zbx_socket_last_error()));
			return FAIL;
		}

		if (-1 == (rc = zbx_socket_poll(&pd, 1, ZBX_SOCKET_POLL_TIMEOUT)))
		{
			if (SUCCEED != zbx_socket_had_nonblocking_error())
			{
				zbx_set_socket_strerror("cannot wait for socket: %s",
						zbx_strerror_from_system(zbx_socket_last_error()));
				return FAIL;
			}
		}

		if (SUCCEED != zbx_socket_check_deadline(s))
		{
			zbx_set_socket_strerror("recv timeout");
			return FAIL;
		}

		if (0 >= rc)
			continue;

		if (0 == (pd.revents & POLLIN))
		{
			char	*errmsg;

			errmsg = socket_poll_error(pd.revents);
			zbx_set_socket_strerror("%s", errmsg);
			zbx_free(errmsg);

			zabbix_log(LOG_LEVEL_DEBUG, "poll(POLLIN) failed with revents 0x%x",
					(unsigned)pd.revents);

			return FAIL;
		}
	}

	if (sizeof(s->buf_stat) > (size_t)n)
	{
		s->buf_type = ZBX_BUF_TYPE_STAT;
		s->buffer = s->buf_stat;
	}
	else
	{
		s->buf_type = ZBX_BUF_TYPE_DYN;
		s->buffer = (char *)zbx_malloc(s->buffer, (size_t)n + 1);
	}

	memcpy(s->buffer, buffer, (size_t)n);
	s->buffer[n] = '\0';
	s->read_bytes = (size_t)n;

	return SUCCEED;
}

void	zbx_udp_close(zbx_socket_t *s)
{
	zbx_socket_free(s);
	zbx_socket_close(s->socket);
}

int	zbx_tls_used(const zbx_socket_t *s)
{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	if (NULL != s->tls_ctx)
		return SUCCEED;
#else
	ZBX_UNUSED(s);
#endif
	return FAIL;
}
