/*
** 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 .
**/
#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
# 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