/*
** Copyright (C) 2001-2025 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 "dns.h"
#include "ip_reverse.h"
#include "../sysinfo.h"

#include "zbxsysinfo.h"

#include "zbxtime.h"
#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxcomms.h"
#include "zbxalgo.h"

#if defined(_WINDOWS) || defined(__MINGW32__)
#	include <windns.h>
#	pragma comment(lib, "Dnsapi.lib") /* add the library for DnsQuery function */
#endif

#if defined(HAVE_RES_QUERY) || defined(_WINDOWS) || defined(__MINGW32__)

static const char	*decode_type(int q_type)
{
	static ZBX_THREAD_LOCAL char	buf[16];

	switch (q_type)
	{
		case T_A:
			return "A";	/* address */
		case T_AAAA:
			return "AAAA";	/* v6 address */
		case T_NS:
			return "NS";	/* name server */
		case T_MD:
			return "MD";	/* mail destination */		/* obsolete */
		case T_MF:
			return "MF";	/* mail forwarder */		/* obsolete */
		case T_CNAME:
			return "CNAME";	/* canonical name */
		case T_SOA:
			return "SOA";	/* start of authority */
		case T_MB:
			return "MB";	/* mailbox */			/* experimental */
		case T_MG:
			return "MG";	/* mail group member */		/* experimental */
		case T_MR:
			return "MR";	/* mail rename */		/* experimental */
		case T_NULL:
			return "NULL";	/* null */			/* obsolete */
		case T_WKS:
			return "WKS";	/* well-known service */	/* obsolete */
		case T_PTR:
			return "PTR";	/* domain name pointer */
		case T_HINFO:
			return "HINFO";	/* host information */
		case T_MINFO:
			return "MINFO";	/* mailbox information */	/* experimental */
		case T_MX:
			return "MX";	/* mail exchanger */
		case T_TXT:
			return "TXT";	/* text */
		case T_SRV:
			return "SRV";	/* service locator */
		default:
			zbx_snprintf(buf, sizeof(buf), "T_%d", q_type);
			return buf;
	}
}

#if !defined(_WINDOWS) && !defined(__MINGW32__)
static char	*get_name(unsigned char *msg, unsigned char *msg_end, unsigned char **msg_ptr)
{
	int		res;
	static char	buffer[MAX_STRING_LEN];

	if (-1 == (res = dn_expand(msg, msg_end, *msg_ptr, buffer, sizeof(buffer))))
		return NULL;

	*msg_ptr += res;

	return buffer;
}
#endif	/* !defined(_WINDOWS) && !defined(__MINGW32__)*/

/* Replace zbx_inet_ntop/zbx_inet_pton with inet_ntop/inet_pton in case of drop Windows XP/W2k3 support */
#if defined(_WINDOWS) || defined(__MINGW32__)
int	zbx_inet_pton(int af, const char *src, void *dst)
{
	struct sockaddr_storage	ss;
	int			size = sizeof(ss);
	char			src_copy[INET6_ADDRSTRLEN + 1];

	memset(&ss, '\0', sizeof(ss));
	ss.ss_family = af;
	zbx_strlcpy(src_copy, src, INET6_ADDRSTRLEN+1);
	src_copy[INET6_ADDRSTRLEN] = 0;

	if (0 == WSAStringToAddressA(src_copy, af, NULL, (struct sockaddr *)&ss, &size))
	{
		switch(af)
		{
			case AF_INET:
				*((struct in_addr *)dst) = ((struct sockaddr_in *)&ss)->sin_addr;
				return SUCCEED;
			case AF_INET6:
				*((struct in6_addr *)dst) = ((struct sockaddr_in6 *)&ss)->sin6_addr;
				return SUCCEED;
			default:
				return FAIL;
		}
		return SUCCEED;
	}

	return FAIL;
}

const char *zbx_inet_ntop(int af, const void *src, char *dst, size_t size)
{
	struct sockaddr_storage ss;
	unsigned long s = size;

	memset(&ss, '\0', sizeof(ss));
	ss.ss_family = af;

	switch(af)
	{
		case AF_INET:
			((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src;
			break;
		case AF_INET6:
			((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src;
			break;
		default:
			return NULL;
	}

	return (0 == WSAAddressToStringA((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s))? dst : NULL;
}
#endif
#endif	/* defined(HAVE_RES_QUERY) || defined(_WINDOWS) || defined(__MINGW32__) */

#define DNS_QUERY_LONG	0
#define DNS_QUERY_SHORT	1
#define DNS_QUERY_PERF	2

static int	dns_query(AGENT_REQUEST *request, AGENT_RESULT *result, int short_answer)
{
#if defined(HAVE_RES_QUERY) || defined(_WINDOWS) || defined(__MINGW32__)

	size_t			offset = 0;
	int			res, type, retrans, retry, use_tcp, i, ret = SYSINFO_RET_FAIL, ip_type = AF_INET;
	char			*ip, zone[MAX_STRING_LEN], buffer[MAX_STRING_LEN], *zone_str, *param,
				tmp[MAX_STRING_LEN];
	double			check_time = zbx_time();
	struct in_addr		inaddr;
	struct in6_addr		in6addr;
#if !defined(_WINDOWS) && !defined(__MINGW32__)
#if defined(HAVE_RES_NINIT) && !defined(_AIX)
	/* It seems that on some AIX systems with no updates installed res_ninit() can */
	/* corrupt stack (see ZBX-14559). Use res_init() on AIX. */
	struct __res_state	res_state_local;
#else	/* thread-unsafe resolver API */
	int			saved_retrans, saved_retry, saved_nscount = 0;
	unsigned long		saved_options;
	struct sockaddr_in	saved_ns;
#	if defined(HAVE_RES_U_EXT)		/* thread-unsafe resolver API /Linux/ */
	int			save_nssocks, saved_nscount6;
#	endif
#endif

#if defined(HAVE_RES_EXT_EXT)		/* AIX */
	union res_sockaddr_union	saved_ns6;
#elif defined(HAVE_RES_U_EXT_EXT)	/* BSD */
	struct sockaddr_in6		saved_ns6;
#else
	struct sockaddr_in6		*saved_ns6;
#endif
	struct sockaddr_in6	sockaddrin6;
	struct addrinfo		hint, *hres = NULL;
#endif
	typedef struct
	{
		const char	*name;
		int		type;
	}
	resolv_querytype_t;

	static const resolv_querytype_t	qt[] =
	{
		{"ANY",		T_ANY},
		{"A",		T_A},
		{"AAAA",	T_AAAA},
		{"NS",		T_NS},
		{"MD",		T_MD},
		{"MF",		T_MF},
		{"CNAME",	T_CNAME},
		{"SOA",		T_SOA},
		{"MB",		T_MB},
		{"MG",		T_MG},
		{"MR",		T_MR},
		{"NULL",	T_NULL},
#if !defined(_WINDOWS) && !defined(__MINGW32__)
		{"WKS",		T_WKS},
#endif
		{"PTR",		T_PTR},
		{"HINFO",	T_HINFO},
		{"MINFO",	T_MINFO},
		{"MX",		T_MX},
		{"TXT",		T_TXT},
		{"SRV",		T_SRV},
		{0}
	};

#if defined(_WINDOWS) || defined(__MINGW32__)
	PDNS_RECORD	pQueryResults, pDnsRecord;
	wchar_t		*wzone;
	char		tmp2[MAX_STRING_LEN];
	DWORD		options;
#else
	char		*name;
	unsigned char	*msg_end, *msg_ptr, *p;
	int		num_answers, num_query, q_type, q_class, q_len, value, c, n;
	struct servent	*s;
	HEADER		*hp;
	struct protoent	*pr;
#if PACKETSZ > 1024
	unsigned char	buf[PACKETSZ];
#else
	unsigned char	buf[1024];
#endif

	typedef union
	{
		HEADER		h;
#if defined(NS_PACKETSZ)
		unsigned char	buffer[NS_PACKETSZ];
#elif defined(PACKETSZ)
		unsigned char	buffer[PACKETSZ];
#else
		unsigned char	buffer[512];
#endif
	}
	answer_t;

	answer_t	answer;
#endif	/* defined(_WINDOWS) || defined(__MINGW32__) */
	zbx_vector_str_t	answers;

	*buffer = '\0';

	if (6 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	ip = get_rparam(request, 0);
	zone_str = get_rparam(request, 1);

#if !defined(_WINDOWS) && !defined(__MINGW32__)
	memset(&hint, '\0', sizeof(hint));
	hint.ai_family = PF_UNSPEC;
	hint.ai_flags = AI_NUMERICHOST;

	if (NULL != ip && '\0' != *ip && 0 == getaddrinfo(ip, NULL, &hint, &hres) && AF_INET6 == hres->ai_family)
		ip_type = hres->ai_family;

	if (NULL != hres)
		freeaddrinfo(hres);
#endif
	if (NULL == zone_str || '\0' == *zone_str)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Second parameter cannot be empty."));
		return SYSINFO_RET_FAIL;
	}
	else
	{
		zbx_strscpy(zone, zone_str);
	}

	param = get_rparam(request, 2);

	if (NULL == param || '\0' == *param)
		type = T_SOA;
	else
	{
		for (i = 0; NULL != qt[i].name; i++)
		{
			if (0 == strcasecmp(qt[i].name, param))
			{
				type = qt[i].type;
				break;
			}
		}

		if (NULL == qt[i].name)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
			return SYSINFO_RET_FAIL;
		}
	}

	param = get_rparam(request, 3);

	if (NULL == param || '\0' == *param)
		retrans = 1;
	else if (SUCCEED != zbx_is_uint31(param, &retrans) || 0 == retrans)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter."));
		return SYSINFO_RET_FAIL;
	}

	param = get_rparam(request, 4);

	if (NULL == param || '\0' == *param)
		retry = 2;
	else if (SUCCEED != zbx_is_uint31(param, &retry) || 0 == retry)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		return SYSINFO_RET_FAIL;
	}

	param = get_rparam(request, 5);

	if (NULL == param || '\0' == *param || 0 == strcmp(param, "udp"))
		use_tcp = 0;
	else if (0 == strcmp(param, "tcp"))
		use_tcp = 1;
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid sixth parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (T_PTR == type)
	{
		char	*reversed_zone = NULL, *error = NULL;

		if (FAIL == zbx_ip_reverse(zone, &reversed_zone, &error))
		{
			SET_MSG_RESULT(result, error);

			return SYSINFO_RET_FAIL;
		}
		zbx_strscpy(zone, reversed_zone);
		zbx_free(reversed_zone);
	}

#if defined(_WINDOWS) || defined(__MINGW32__)
	options = DNS_QUERY_STANDARD | DNS_QUERY_BYPASS_CACHE;
	if (0 != use_tcp)
		options |= DNS_QUERY_USE_TCP_ONLY;

	wzone = zbx_utf8_to_unicode(zone);
	res = DnsQuery(wzone, type, options, NULL, &pQueryResults, NULL);
	zbx_free(wzone);

	if (DNS_QUERY_SHORT == short_answer)
	{
		SET_UI64_RESULT(result, DNS_RCODE_NOERROR != res ? 0 : 1);
		ret = SYSINFO_RET_OK;
		goto clean_dns;
	}
	else if (DNS_QUERY_PERF == short_answer)
	{
		if (ERROR_TIMEOUT == res)
		{
			SET_DBL_RESULT(result, 0.0);
			ret = SYSINFO_RET_OK;
			goto clean_dns;
		}
		else
		{
			check_time = zbx_time() - check_time;

			if (zbx_get_float_epsilon() > check_time)
				check_time = zbx_get_float_epsilon();

			SET_DBL_RESULT(result, check_time);
			ret = SYSINFO_RET_OK;
			goto clean_dns;
		}
	}

	if (DNS_RCODE_NOERROR != res)
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot perform DNS query: [%d]", res));
		return SYSINFO_RET_FAIL;
	}

	pDnsRecord = pQueryResults;
	zbx_vector_str_create(&answers);

	while (NULL != pDnsRecord)
	{
		if (DnsSectionAnswer != pDnsRecord->Flags.S.Section)
		{
			pDnsRecord = pDnsRecord->pNext;
			continue;
		}

		if (NULL == pDnsRecord->pName)
			goto clean;

		offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%-20s",
				zbx_unicode_to_utf8_static(pDnsRecord->pName, tmp, sizeof(tmp)));
		offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %-8s",
				decode_type(pDnsRecord->wType));

		switch (pDnsRecord->wType)
		{
			case T_A:
				inaddr.s_addr = pDnsRecord->Data.A.IpAddress;
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						inet_ntoa(inaddr));
				break;
			case T_AAAA:
				memcpy(&in6addr.s6_addr, &(pDnsRecord->Data.AAAA.Ip6Address), sizeof(in6addr.s6_addr));
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_inet_ntop(AF_INET6, &in6addr, tmp, sizeof(tmp)));
				break;
			case T_NS:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.NS.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_MD:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MD.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_MF:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MF.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_CNAME:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.CNAME.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_SOA:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset,
						" %s %s %lu %lu %lu %lu %lu",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.SOA.pNamePrimaryServer, tmp,
						sizeof(tmp)),
						zbx_unicode_to_utf8_static(pDnsRecord->Data.SOA.pNameAdministrator,
						tmp2, sizeof(tmp2)),
						pDnsRecord->Data.SOA.dwSerialNo,
						pDnsRecord->Data.SOA.dwRefresh,
						pDnsRecord->Data.SOA.dwRetry,
						pDnsRecord->Data.SOA.dwExpire,
						pDnsRecord->Data.SOA.dwDefaultTtl);
				break;
			case T_MB:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MB.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_MG:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MG.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_MR:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MR.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_NULL:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " len:%lu",
						pDnsRecord->Data.Null.dwByteCount);
				break;
			case T_PTR:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.PTR.pNameHost, tmp,
						sizeof(tmp)));
				break;
			case T_HINFO:
				for (i = 0; i < (int)(pDnsRecord->Data.HINFO.dwStringCount); i++)
					offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " \"%s\"",
							zbx_unicode_to_utf8_static(
							pDnsRecord->Data.HINFO.pStringArray[i], tmp, sizeof(tmp)));
				break;
			case T_MINFO:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s %s",
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MINFO.pNameMailbox, tmp,
						sizeof(tmp)),
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MINFO.pNameErrorsMailbox,
						tmp2, sizeof(tmp2)));
				break;
			case T_MX:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %hu %s",
						pDnsRecord->Data.MX.wPreference,
						zbx_unicode_to_utf8_static(pDnsRecord->Data.MX.pNameExchange, tmp,
						sizeof(tmp)));
				break;
			case T_TXT:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " \"");

				for (i = 0; i < (int)(pDnsRecord->Data.TXT.dwStringCount); i++)
					offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%s ",
							zbx_unicode_to_utf8_static(pDnsRecord->Data.TXT.pStringArray[i],
							tmp, sizeof(tmp)));

				if (0 < i)
					offset -= 1;	/* remove the trailing space */

				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "\"");

				break;
			case T_SRV:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %hu %hu %hu %s",
						pDnsRecord->Data.SRV.wPriority,
						pDnsRecord->Data.SRV.wWeight,
						pDnsRecord->Data.SRV.wPort,
						zbx_unicode_to_utf8_static(pDnsRecord->Data.SRV.pNameTarget, tmp,
						sizeof(tmp)));
				break;
			default:
				break;
		}

		zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "\n");

		pDnsRecord = pDnsRecord->pNext;
		zbx_vector_str_append(&answers, zbx_strdup(NULL, buffer));
		offset = 0;
		*buffer = '\0';
	}
#else	/* !defined(_WINDOWS) && !defined(__MINGW32__) */

#if defined(HAVE_RES_NINIT) && !defined(_AIX) && (defined(HAVE_RES_U_EXT) || defined(HAVE_RES_U_EXT_EXT))

#ifdef HAVE_RES_NDESTROY
#	define zbx_free_res(ptr) res_ndestroy(ptr);
#else
#	define zbx_free_res(ptr) res_nclose(ptr);
#endif

#else
#	define zbx_free_res(ptr)
#endif

#if defined(HAVE_RES_NINIT) && !defined(_AIX)
	memset(&res_state_local, 0, sizeof(res_state_local));
	if (-1 == res_ninit(&res_state_local))	/* initialize always, settings might have changed */
#else
	if (-1 == res_init())	/* initialize always, settings might have changed */
#endif
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot initialize DNS subsystem: %s", zbx_strerror(errno)));
		return SYSINFO_RET_FAIL;
	}

#if defined(HAVE_RES_NINIT) && !defined(_AIX)
	if (-1 == (res = res_nmkquery(&res_state_local, QUERY, zone, C_IN, type, NULL, 0, NULL, buf, sizeof(buf))))
#else
	if (-1 == (res = res_mkquery(QUERY, zone, C_IN, type, NULL, 0, NULL, buf, sizeof(buf))))
#endif
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot create DNS query: %s", zbx_strerror(errno)));

		zbx_free_res(&res_state_local);

		return SYSINFO_RET_FAIL;
	}

	if (NULL != ip && '\0' != *ip && AF_INET == ip_type)
	{
		if (0 == inet_aton(ip, &inaddr))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid IP address."));

			zbx_free_res(&res_state_local);

			return SYSINFO_RET_FAIL;
		}

#if defined(HAVE_RES_NINIT) && !defined(_AIX)
		res_state_local.nsaddr_list[0].sin_addr = inaddr;
		res_state_local.nsaddr_list[0].sin_family = AF_INET;
		res_state_local.nsaddr_list[0].sin_port = htons(ZBX_DEFAULT_DNS_PORT);
		res_state_local.nscount = 1;
#else	/* thread-unsafe resolver API */
		memcpy(&saved_ns, &(_res.nsaddr_list[0]), sizeof(struct sockaddr_in));
		saved_nscount = _res.nscount;

		_res.nsaddr_list[0].sin_addr = inaddr;
		_res.nsaddr_list[0].sin_family = AF_INET;
		_res.nsaddr_list[0].sin_port = htons(ZBX_DEFAULT_DNS_PORT);
		_res.nscount = 1;
#endif
	}
	else if (NULL != ip && '\0' != *ip && AF_INET6 == ip_type)
	{
		if (0 == inet_pton(ip_type, ip, &in6addr))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid IPv6 address."));

			zbx_free_res(&res_state_local);

			return SYSINFO_RET_FAIL;
		}

		memset(&sockaddrin6, '\0', sizeof(sockaddrin6));
#if defined(HAVE_RES_SIN6_LEN)
		sockaddrin6.sin6_len = sizeof(sockaddrin6);
#endif
		sockaddrin6.sin6_family = AF_INET6;
		sockaddrin6.sin6_addr = in6addr;
		sockaddrin6.sin6_port = htons(ZBX_DEFAULT_DNS_PORT);
#if defined(HAVE_RES_NINIT) && !defined(_AIX) && (defined(HAVE_RES_U_EXT) || defined(HAVE_RES_U_EXT_EXT))
		memset(&res_state_local.nsaddr_list[0], '\0', sizeof(res_state_local.nsaddr_list[0]));
#		ifdef HAVE_RES_U_EXT			/* Linux */
		saved_ns6 = res_state_local._u._ext.nsaddrs[0];
		res_state_local._u._ext.nsaddrs[0] = &sockaddrin6;
		res_state_local._u._ext.nssocks[0] = -1;
		res_state_local._u._ext.nscount6 = 1;	/* CentOS */
#		elif HAVE_RES_U_EXT_EXT			/* BSD */
		if (NULL != res_state_local._u._ext.ext)
			memcpy(res_state_local._u._ext.ext, &sockaddrin6, sizeof(sockaddrin6));

		res_state_local.nsaddr_list[0].sin_port = htons(ZBX_DEFAULT_DNS_PORT);
#		endif
		res_state_local.nscount = 1;
#else
		memcpy(&saved_ns, &(_res.nsaddr_list[0]), sizeof(struct sockaddr_in));
		saved_nscount = _res.nscount;
#		if defined(HAVE_RES_U_EXT) || defined(HAVE_RES_U_EXT_EXT) || defined(HAVE_RES_EXT_EXT)
		memset(&_res.nsaddr_list[0], '\0', sizeof(_res.nsaddr_list[0]));
		_res.nscount = 1;
#		endif
#		if defined(HAVE_RES_U_EXT)		/* thread-unsafe resolver API /Linux/ */
		saved_nscount6 = _res._u._ext.nscount6;
		saved_ns6 = _res._u._ext.nsaddrs[0];
		save_nssocks = _res._u._ext.nssocks[0];
		_res._u._ext.nsaddrs[0] = &sockaddrin6;
		_res._u._ext.nssocks[0] = -1;
		_res._u._ext.nscount6 = 1;
#		elif defined(HAVE_RES_U_EXT_EXT)	/* thread-unsafe resolver API /BSD/ */
		memcpy(&saved_ns6, _res._u._ext.ext, sizeof(saved_ns6));
		_res.nsaddr_list[0].sin_port = htons(ZBX_DEFAULT_DNS_PORT);

		if (NULL != _res._u._ext.ext)
			memcpy(_res._u._ext.ext, &sockaddrin6, sizeof(sockaddrin6));
#		elif defined(HAVE_RES_EXT_EXT)		/* thread-unsafe resolver API /AIX/ */
		memcpy(&saved_ns6, &(_res._ext.ext.nsaddrs[0]), sizeof(saved_ns6));
		memcpy(&_res._ext.ext.nsaddrs[0], &sockaddrin6, sizeof(sockaddrin6));
#		endif /* #if defined(HAVE_RES_U_EXT) */
#endif /* #if defined(HAVE_RES_NINIT) && !defined(_AIX) && (defined(HAVE_RES_U_EXT) || defined(HAVE_RES_U_EXT_EXT)) */
	}

#if defined(HAVE_RES_NINIT) && !defined(_AIX) && (defined(HAVE_RES_U_EXT) || defined(HAVE_RES_U_EXT_EXT))
	if (0 != use_tcp)
		res_state_local.options |= RES_USEVC;

	res_state_local.retrans = retrans;
	res_state_local.retry = retry;
	memset(&answer.buffer, 0, sizeof(answer.buffer));
	res = res_nsend(&res_state_local, buf, res, answer.buffer, sizeof(answer.buffer));

#	ifdef HAVE_RES_U_EXT	/* Linux */
	if (NULL != ip && '\0' != *ip && AF_INET6 == ip_type)
		res_state_local._u._ext.nsaddrs[0] = saved_ns6;
#	endif
	zbx_free_res(&res_state_local);

#else	/* thread-unsafe resolver API */
	saved_options = _res.options;
	saved_retrans = _res.retrans;
	saved_retry = _res.retry;

	if (0 != use_tcp)
		_res.options |= RES_USEVC;

	_res.retrans = retrans;
	_res.retry = retry;

	res = res_send(buf, res, answer.buffer, sizeof(answer.buffer));

	_res.options = saved_options;
	_res.retrans = saved_retrans;
	_res.retry = saved_retry;

	if (NULL != ip && '\0' != *ip)
	{
		if (AF_INET6 == ip_type)
		{
#			if defined(HAVE_RES_U_EXT)		/* Linux */
			_res._u._ext.nsaddrs[0] = saved_ns6;
			_res._u._ext.nssocks[0] = save_nssocks;
			_res._u._ext.nscount6 = saved_nscount6;
#			elif defined(HAVE_RES_U_EXT_EXT)	/* BSD */
			if (NULL != _res._u._ext.ext)
				memcpy(_res._u._ext.ext, &saved_ns6, sizeof(saved_ns6));
#			elif defined(HAVE_RES_EXT_EXT)		/* AIX */
			memcpy(&_res._ext.ext.nsaddrs[0], &saved_ns6, sizeof(saved_ns6));
#			endif
		}

		memcpy(&(_res.nsaddr_list[0]), &saved_ns, sizeof(struct sockaddr_in));
		_res.nscount = saved_nscount;
	}
#endif
	hp = (HEADER *)answer.buffer;

	int	dns_is_down = -1 == res || NOERROR != hp->rcode || 0 == ntohs(hp->ancount);

	if (DNS_QUERY_SHORT == short_answer)
	{
		SET_UI64_RESULT(result, dns_is_down ? 0 : 1);

		return SYSINFO_RET_OK;
	}
	else if (DNS_QUERY_PERF == short_answer)
	{
		/* -1 for res is returned also for REFUSED and SERVFAIL for res_send()          */
		/* for NXDOMAIN - res is 0 and hp->rcode is set                                 */
		/* for REFUXED and SERVFAIL res is -1, with hp->rcode set for particular error  */
		/* for missing connection - res is -1, but hp->rcode it is uninitialized        */
		/* So, the only to detect the missing connection is to set hp->rcode to 0 first,*/
		/* and then to check if res is -1 with rcode beting set to 0.                   */
		if (-1 == res && 0 == hp->rcode)
		{
			SET_DBL_RESULT(result, 0.0);

			return SYSINFO_RET_OK;
		}
		else
		{
			check_time = zbx_time() - check_time;

			if (zbx_get_float_epsilon() > check_time)
				check_time = zbx_get_float_epsilon();
			SET_DBL_RESULT(result, check_time);
		}

		return SYSINFO_RET_OK;
	}

	if (1 == dns_is_down)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot perform DNS query."));

		return SYSINFO_RET_FAIL;
	}

	msg_end = answer.buffer + res;

	num_answers = ntohs(answer.h.ancount);
	num_query = ntohs(answer.h.qdcount);

	msg_ptr = answer.buffer + HFIXEDSZ;
	zbx_vector_str_create(&answers);

	/* skipping query records */
	for (; 0 < num_query && msg_ptr < msg_end; num_query--)
		msg_ptr += dn_skipname(msg_ptr, msg_end) + QFIXEDSZ;

	for (; 0 < num_answers && msg_ptr < msg_end; num_answers--)
	{
		if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL,
					"Cannot decode DNS response: cannot expand domain name."));
			ret = SYSINFO_RET_FAIL;
			goto clean;
		}

		offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%-20s", name);

		GETSHORT(q_type, msg_ptr);
		GETSHORT(q_class, msg_ptr);
		msg_ptr += INT32SZ;		/* skipping TTL */
		GETSHORT(q_len, msg_ptr);
		offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %-8s", decode_type(q_type));

		if (msg_ptr + q_len > msg_end)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response: record overflow."));
			ret = SYSINFO_RET_FAIL;
			goto clean;
		}

		switch (q_type)
		{
			case T_A:
				switch (q_class)
				{
					case C_IN:
					case C_HS:
						memcpy(&inaddr, msg_ptr, INADDRSZ);
						offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
								inet_ntoa(inaddr));
						break;
					default:
						;
				}

				msg_ptr += q_len;

				break;
			case T_AAAA:
				switch (q_class)
				{
					case C_IN:
					case C_HS:
						memcpy(&in6addr, msg_ptr, IN6ADDRSZ);
						offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
								inet_ntop(AF_INET6, &in6addr, tmp, sizeof(tmp)));
						break;
					default:
						;
				}

				msg_ptr += q_len;

				break;
			case T_NS:
			case T_CNAME:
			case T_MB:
			case T_MD:
			case T_MF:
			case T_MG:
			case T_MR:
			case T_PTR:
				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
				{
#define ERR_MSG_PREFIX	"Cannot decode DNS response: cannot expand "
					const char	*err_msg = NULL;

					switch (q_type)
					{
						case T_NS:
							err_msg = ERR_MSG_PREFIX "name server name.";
							break;
						case T_CNAME:
							err_msg = ERR_MSG_PREFIX "canonical name.";
							break;
						case T_MB:
							err_msg = ERR_MSG_PREFIX "mailbox name.";
							break;
						case T_MD:
							err_msg = ERR_MSG_PREFIX "mail destination name.";
							break;
						case T_MF:
							err_msg = ERR_MSG_PREFIX "mail forwarder name.";
							break;
						case T_MG:
							err_msg = ERR_MSG_PREFIX "mail group name.";
							break;
						case T_MR:
							err_msg = ERR_MSG_PREFIX "renamed mailbox name.";
							break;
						case T_PTR:
							err_msg = ERR_MSG_PREFIX "PTR name.";
							break;
					}

					SET_MSG_RESULT(result, zbx_strdup(NULL, err_msg));
					return SYSINFO_RET_FAIL;
#undef ERR_MSG_PREFIX
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);
				break;
			case T_MX:
				GETSHORT(value, msg_ptr);	/* preference */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))	/* exchange */
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand mail exchange name."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				break;
			case T_SOA:
				/* source host */
				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand source nameserver name."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				/* administrator */
				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand administrator mailbox name."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				GETLONG(value, msg_ptr);	/* serial number */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %u",
						(zbx_uint32_t)value);

				GETLONG(value, msg_ptr);	/* refresh time */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				GETLONG(value, msg_ptr);	/* retry time */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				GETLONG(value, msg_ptr);	/* expire time */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				GETLONG(value, msg_ptr);	/* minimum TTL */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				break;
			case T_NULL:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " len:%d", q_len);
				msg_ptr += q_len;
				break;
			case T_WKS:
				if (INT32SZ + 1 > q_len)
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" malformed WKS resource record."));
					return SYSINFO_RET_FAIL;
				}

				p = msg_ptr + q_len;

				memcpy(&inaddr, msg_ptr, INADDRSZ);
				msg_ptr += INT32SZ;

				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
						inet_ntoa(inaddr));

				if (NULL != (pr = getprotobynumber(*msg_ptr)))
				{
					offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s",
							pr->p_name);
				}
				else
				{
					offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d",
							(int)*msg_ptr);
				}

				msg_ptr++;
				n = 0;

				while (msg_ptr < p)
				{
					c = *msg_ptr++;

					do
					{
						if (0 != (c & 0200))
						{
							s = getservbyport((int)htons(n), pr ? pr->p_name : NULL);

							if (NULL != s)
							{
								offset += zbx_snprintf(buffer + offset,
										sizeof(buffer) - offset, " %s",
										s->s_name);
							}
							else
							{
								offset += zbx_snprintf(buffer + offset,
										sizeof(buffer) - offset, " #%d", n);
							}
						}

						c <<= 1;
					}
					while (0 != (++n & 07));
				}

				break;
			case T_HINFO:
				p = msg_ptr + q_len;
				c = *msg_ptr++;

				if (0 != c)
				{
					offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " \"%.*s\"",
							c, msg_ptr);
					msg_ptr += c;
				}

				if (msg_ptr < p)
				{
					c = *msg_ptr++;

					if (0 != c)
					{
						offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset,
								" \"%.*s\"", c, msg_ptr);
						msg_ptr += c;
					}
				}

				break;
			case T_MINFO:
				/* mailbox responsible for mailing lists */
				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand mailbox responsible for mailing lists."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				/* mailbox for error messages */
				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand mailbox for error messages."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				break;
			case T_TXT:
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " \"");
				p = msg_ptr + q_len;

				while (msg_ptr < p)
				{
					for (c = *msg_ptr++; 0 < c && msg_ptr < p; c--)
					{
						offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%c",
								*msg_ptr++);
					}
				}

				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "\"");

				break;
			case T_SRV:
				GETSHORT(value, msg_ptr);       /* priority */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				GETSHORT(value, msg_ptr);       /* weight */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				GETSHORT(value, msg_ptr);       /* port */
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %d", value);

				if (NULL == (name = get_name(answer.buffer, msg_end, &msg_ptr)))	/* target */
				{
					SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot decode DNS response:"
							" cannot expand service target hostname."));
					return SYSINFO_RET_FAIL;
				}
				offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", name);

				break;
			default:
				msg_ptr += q_len;
				break;
		}

		zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "\n");

		zbx_vector_str_append(&answers, zbx_strdup(NULL, buffer));
		offset = 0;
		*buffer = '\0';
	}
#endif	/* defined(_WINDOWS) || defined(__MINGW32__) */

	zbx_vector_str_sort(&answers, ZBX_DEFAULT_STR_COMPARE_FUNC);

	for (i = 0; i < answers.values_num; i++)
		offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%s", answers.values[i]);

	if (0 != offset)
		buffer[--offset] = '\0';

	SET_TEXT_RESULT(result, zbx_strdup(NULL, buffer));
	ret = SYSINFO_RET_OK;

clean:
	zbx_vector_str_clear_ext(&answers, zbx_str_free);
	zbx_vector_str_destroy(&answers);
#if defined(_WINDOWS) || defined(__MINGW32__)
clean_dns:
	if (DNS_RCODE_NOERROR == res)
		DnsRecordListFree(pQueryResults, DnsFreeRecordList);
#endif

	return ret;

#else	/* all HAVE_RES_QUERY and _WINDOWS and __MINGW32__not defined */

	return SYSINFO_RET_FAIL;

#endif	/* defined(HAVE_RES_QUERY) || defined(_WINDOWS) || defined(__MINGW32__)*/
}

static int	dns_query_short(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	return dns_query(request, result, DNS_QUERY_SHORT);
}

static int	dns_query_long(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	return dns_query(request, result, DNS_QUERY_LONG);
}

static int	dns_query_perf(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	return dns_query(request, result, DNS_QUERY_PERF);
}

static int	dns_query_is_tcp(AGENT_REQUEST *request)
{
	char	*param;

	if (NULL != (param = get_rparam(request, 5)) && 0 == strcmp(param, "tcp"))
		return SUCCEED;

	return FAIL;
}

int	net_dns(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#if !defined(_WINDOWS) && !defined(__MINGW32__)
	if (SUCCEED == dns_query_is_tcp(request))
		return zbx_execute_threaded_metric(dns_query_short, request, result);
#endif
	return dns_query_short(request, result);
}

int	net_dns_record(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#if !defined(_WINDOWS) && !defined(__MINGW32__)
	if (SUCCEED == dns_query_is_tcp(request))
		return zbx_execute_threaded_metric(dns_query_long, request, result);
#endif
	return dns_query_long(request, result);
}

int	net_dns_perf(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#if !defined(_WINDOWS) && !defined(__MINGW32__)
	if (SUCCEED == dns_query_is_tcp(request))
		return zbx_execute_threaded_metric(dns_query_perf, request, result);
#endif
	return dns_query_perf(request, result);
}
#undef DNS_QUERY_LONG
#undef DNS_QUERY_SHORT
#undef DNS_QUERY_PERF