diff options
-rw-r--r-- | Makefile.am | 15 | ||||
-rw-r--r-- | cldap_ping.c | 345 | ||||
-rw-r--r-- | cldap_ping.h | 14 | ||||
-rw-r--r-- | mount.cifs.c | 5 | ||||
-rw-r--r-- | resolve_host.c | 256 | ||||
-rw-r--r-- | resolve_host.h | 6 |
6 files changed, 604 insertions, 37 deletions
diff --git a/Makefile.am b/Makefile.am index 1a22266..7877823 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,8 +3,8 @@ ACLOCAL_AMFLAGS = -I aclocal root_exec_sbindir = $(ROOTSBINDIR) root_exec_sbin_PROGRAMS = mount.cifs -mount_cifs_SOURCES = mount.cifs.c mtab.c resolve_host.c util.c -mount_cifs_LDADD = $(LIBCAP) $(CAPNG_LDADD) $(RT_LDADD) +mount_cifs_SOURCES = mount.cifs.c mtab.c $(resolve_hosts_SOURCES) util.c +mount_cifs_LDADD = $(LIBCAP) $(CAPNG_LDADD) $(RT_LDADD) $(resolve_hosts_LDADD) include_HEADERS = cifsidmap.h rst_man_pages = mount.cifs.8 @@ -28,6 +28,9 @@ bin_PROGRAMS = bin_SCRIPTS = sbin_PROGRAMS = +resolve_hosts_SOURCES = data_blob.c asn1.c cldap_ping.c resolve_host.c +resolve_hosts_LDADD = -ltalloc -lresolv + if CONFIG_CIFSUPCALL sbin_PROGRAMS += cifs.upcall cifs_upcall_SOURCES = cifs.upcall.c data_blob.c asn1.c spnego.c @@ -43,8 +46,8 @@ endif if CONFIG_CIFSCREDS bin_PROGRAMS += cifscreds -cifscreds_SOURCES = cifscreds.c cifskey.c resolve_host.c util.c -cifscreds_LDADD = -lkeyutils +cifscreds_SOURCES = cifscreds.c cifskey.c $(resolve_hosts_SOURCES) util.c +cifscreds_LDADD = -lkeyutils $(resolve_hosts_LDADD) rst_man_pages += cifscreds.1 @@ -105,8 +108,8 @@ endif if CONFIG_PAM pam_PROGRAMS = pam_cifscreds.so rst_man_pages += pam_cifscreds.8 -pam_cifscreds.so: pam_cifscreds.c cifskey.c resolve_host.c util.c - $(CC) $(DEFS) $(CFLAGS) $(AM_CFLAGS) $(LDFLAGS) -shared -fpic -o $@ $+ -lpam -lkeyutils +pam_cifscreds.so: pam_cifscreds.c cifskey.c $(resolve_hosts_SOURCES) util.c + $(CC) $(DEFS) $(CFLAGS) $(AM_CFLAGS) $(LDFLAGS) -shared -fpic -o $@ $+ -lpam -lkeyutils $(resolve_hosts_LDADD) endif diff --git a/cldap_ping.c b/cldap_ping.c new file mode 100644 index 0000000..9183b27 --- /dev/null +++ b/cldap_ping.c @@ -0,0 +1,345 @@ +/* + * CLDAP Ping to find closest ClientSiteName + * + * Copyright (C) 2024 David Voit (david.voit@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <talloc.h> +#include <string.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <resolv.h> +#include <stdbool.h> +#include "data_blob.h" +#include "asn1.h" +#include "cldap_ping.h" + +#define LDAP_DNS_DOMAIN "DnsDomain" +#define LDAP_DNS_DOMAIN_LEN strlen(LDAP_DNS_DOMAIN) +#define LDAP_NT_VERSION "NtVer" +#define LDAP_NT_VERSION_LEN strlen(LDAP_NT_VERSION) +#define LDAP_ATTRIBUTE_NETLOGON "NetLogon" +#define LDAP_ATTRIBUTE_NETLOGON_LEN strlen(LDAP_ATTRIBUTE_NETLOGON) + + +// Parse a ASN.1 BER tag size-field, returns start of payload of tag +char *parse_ber_size(char *buf, size_t *tag_size) { + size_t size = *buf & 0xff; + char *ret = (buf + 1); + if (size >= 0x81) { + switch (size) { + case 0x81: + size = *ret & 0xff; + ret += 1; + break; + case 0x82: + size = (*ret << 8) | (*(ret + 1) & 0xff); + ret += 2; + break; + case 0x83: + size = (*ret << 16) | (*(ret + 1) << 8) | (*(ret + 2) & 0xff); + ret += 3; + break; + case 0x84: + size = (*ret << 24) | (*(ret + 1) << 16) | (*(ret + 2) << 8) | (*(ret + 3) & 0xff); + ret += 4; + break; + default: + return NULL; + } + } + + *tag_size = size; + return ret; +} + +// simple wrapper over dn_expand which also calculates the new offset for the next compressed dn +int read_dns_string(char *buf, size_t buf_size, char *dest, size_t dest_size, size_t *offset) { + int compressed_length = dn_expand((u_char *)buf, (u_char *)buf+buf_size, (u_char *)buf + *offset, dest, (int)dest_size); + if (compressed_length < 0) { + return -1; + } + + *offset = *offset+compressed_length; + + return 0; +} + +// LDAP request for: (&(DnsDomain=DOMAIN_HERE)(NtVer=\\06\\00\\00\\00)) +ASN1_DATA *generate_cldap_query(char *domain) { + ASN1_DATA *data; + TALLOC_CTX *mem_ctx = talloc_init("cldap"); + + data = asn1_init(mem_ctx); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + + // Message id + asn1_push_tag(data, ASN1_INTEGER); + asn1_write_uint8(data, 1); + asn1_pop_tag(data); + + // SearchRequest + asn1_push_tag(data, ASN1_APPLICATION(3)); + + // empty baseObject + asn1_push_tag(data, ASN1_OCTET_STRING); + asn1_pop_tag(data); + + // scope 0 = baseObject + asn1_push_tag(data, ASN1_ENUMERATED); + asn1_write_uint8(data, 0); + asn1_pop_tag(data); + + // derefAliasses 0=neverDerefAlias + asn1_push_tag(data, ASN1_ENUMERATED); + asn1_write_uint8(data, 0); + asn1_pop_tag(data); + + // sizeLimit + asn1_push_tag(data, ASN1_INTEGER); + asn1_write_uint8(data, 0); + asn1_pop_tag(data); + + // timeLimit + asn1_push_tag(data, ASN1_INTEGER); + asn1_write_uint8(data, 0); + asn1_pop_tag(data); + + // typesOnly + asn1_push_tag(data, ASN1_BOOLEAN); + asn1_write_uint8(data, 0); + asn1_pop_tag(data); + + // AND + asn1_push_tag(data, ASN1_CONTEXT(0)); + // equalityMatch + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, LDAP_DNS_DOMAIN, LDAP_DNS_DOMAIN_LEN); + asn1_write_OctetString(data, domain, strlen(domain)); + asn1_pop_tag(data); + + // equalityMatch + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, LDAP_NT_VERSION, LDAP_NT_VERSION_LEN); + // Bitmask NETLOGON_NT_VERSION_5 & NETLOGON_NT_VERSION_5EX -> To get NETLOGON_SAM_LOGON_RESPONSE_EX as response + asn1_write_OctetString(data, "\x06\x00\x00\x00", 4); + asn1_pop_tag(data); + + // End AND + asn1_pop_tag(data); + + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OctetString(data, LDAP_ATTRIBUTE_NETLOGON, LDAP_ATTRIBUTE_NETLOGON_LEN); + asn1_pop_tag(data); + + // End SearchRequest + asn1_pop_tag(data); + // End Sequence + asn1_pop_tag(data); + + return data; +} + +// Input is a cldap response, output is a pointer to the NETLOGON_SAM_LOGON_RESPONSE_EX payload +ssize_t extract_netlogon_section(char *buffer, size_t buffer_size, char **netlogon_payload) { + size_t ber_size; + size_t netlogon_payload_size; + // Not enough space to read initial sequence - not an correct cldap response + if (buffer_size < 7) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + // Sequence tag + if (*buffer != 0x30) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *message_id_tag = parse_ber_size(buffer + 1, &ber_size); + + if (ber_size > buffer_size) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + if (*message_id_tag != 0x02) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *message_id = parse_ber_size(message_id_tag + 1, &ber_size); + + if (ber_size != 1 || *message_id != 1) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + // SearchResultEntry + if (*(message_id+1) != 0x64) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *object_name_tag = parse_ber_size(message_id+2, &ber_size); + if (object_name_tag == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *object_name = parse_ber_size(object_name_tag+1, &ber_size); + if (object_name == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + if (*object_name_tag != 4 || ber_size != 0) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *partial_attribute_list_tag = parse_ber_size(object_name+1, &ber_size); + if (partial_attribute_list_tag == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + if (*partial_attribute_list_tag != 0x30) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + + char *partial_attribute_tag = parse_ber_size(partial_attribute_list_tag+1, &ber_size); + if (partial_attribute_tag == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *attribute_name = parse_ber_size(partial_attribute_tag+1, &ber_size); + if (attribute_name == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + if (ber_size != LDAP_ATTRIBUTE_NETLOGON_LEN) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + if (strncasecmp(LDAP_ATTRIBUTE_NETLOGON, attribute_name, LDAP_ATTRIBUTE_NETLOGON_LEN) != 0) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + // SET + if (*(attribute_name+LDAP_ATTRIBUTE_NETLOGON_LEN) != 0x31) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + char *start_of_data = parse_ber_size(attribute_name+LDAP_ATTRIBUTE_NETLOGON_LEN+1, &ber_size); + if (start_of_data == NULL) { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + // octat-string of NetLogon data + if (*start_of_data != '\x04') { + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + *netlogon_payload = parse_ber_size(start_of_data + 1, &netlogon_payload_size); + + if (*netlogon_payload == NULL) { + *netlogon_payload = NULL; + return CLDAP_PING_PARSE_ERROR_LDAP; + } + + return (ssize_t)netlogon_payload_size; +} + +int netlogon_get_client_site(char *netlogon_response, size_t netlogon_size, char *sitename) { + // 24 mandatory bytes + if (netlogon_size < 25) { + return CLDAP_PING_PARSE_ERROR_NETLOGON; + } + + // LOGON_SAM_PAUSE_RESPONSE_EX -> Netlogon service is not in-sync try next dc instead + if (*netlogon_response == 0x18 && *(netlogon_response + 1) == 0x00) { + return CLDAP_PING_TRYNEXT; + } + + // NETLOGON_SAM_LOGON_RESPONSE_EX Opcode: 0x17 + if (*netlogon_response != 0x17 || *(netlogon_response + 1) != 0x00) { + return CLDAP_PING_PARSE_ERROR_NETLOGON; + } + + // skip over sbz, ds_flags and domain_guid + // and start directly at variable string portion of NETLOGON_SAM_LOGON_RESPONSE_EX + size_t offset = 24; + + for (int i=0; i < 8; i++) { + // iterate over DnsForestName, DnsDomainName, NetbiosDomainName, NetbiosComputerName, UserName, DcSiteName + // to finally get to our desired ClientSiteName field + if (read_dns_string(netlogon_response, netlogon_size, sitename, MAXCDNAME, &offset) < 0) { + return CLDAP_PING_PARSE_ERROR_NETLOGON; + } + } + + return 0; +} + +int cldap_ping(char *domain, sa_family_t family, void *addr, char *site_name) { + char buffer[8196]; + ssize_t response_size; + char *netlogon_response; + ssize_t netlogon_size; + struct sockaddr_storage socketaddr; + size_t addr_size; + int sock = socket(family, SOCK_DGRAM, 0); + if (sock < 0) { + return CLDAP_PING_NETWORK_ERROR; + } + + ASN1_DATA *data = generate_cldap_query(domain); + + if (family == AF_INET6) { + addr_size = sizeof(struct sockaddr_in6); + bzero((void *) &socketaddr, addr_size); + socketaddr.ss_family = AF_INET6; + ((struct sockaddr_in6 *)&socketaddr)->sin6_addr = *((struct in6_addr*)addr); + ((struct sockaddr_in6 *)&socketaddr)->sin6_port = htons(389); + } else { + addr_size = sizeof(struct sockaddr_in); + bzero((void *) &socketaddr, addr_size); + socketaddr.ss_family = AF_INET; + ((struct sockaddr_in *)&socketaddr)->sin_addr = *((struct in_addr*)addr); + ((struct sockaddr_in *)&socketaddr)->sin_port = htons(389); + } + + struct timeval timeout = {.tv_sec = 2, .tv_usec = 0}; + if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + return CLDAP_PING_NETWORK_ERROR; + } + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + return CLDAP_PING_NETWORK_ERROR; + } + + if (sendto(sock, data->data, data->length, 0, (struct sockaddr *)&socketaddr, addr_size) < 0) { + close(sock); + return CLDAP_PING_TRYNEXT; + } + + asn1_free(data); + response_size = recv(sock, buffer, sizeof(buffer), 0); + close(sock); + + if (response_size < 0) { + return CLDAP_PING_TRYNEXT; + } + + netlogon_size = extract_netlogon_section(buffer, response_size, &netlogon_response); + if (netlogon_size < 0) { + return (int)netlogon_size; + } + + return netlogon_get_client_site(netlogon_response, netlogon_size, site_name); +} diff --git a/cldap_ping.h b/cldap_ping.h new file mode 100644 index 0000000..9a23e72 --- /dev/null +++ b/cldap_ping.h @@ -0,0 +1,14 @@ +#ifndef _CLDAP_PING_H_ +#define _CLDAP_PING_H_ + +#define CLDAP_PING_NETWORK_ERROR -1 +#define CLDAP_PING_TRYNEXT -2 +#define CLDAP_PING_PARSE_ERROR_LDAP -3 +#define CLDAP_PING_PARSE_ERROR_NETLOGON -4 + +// returns CLDAP_PING_TRYNEXT if you should use another dc +// any other error code < 0 is a fatal error +// site_name must be of MAXCDNAME size! +int cldap_ping(char *domain, sa_family_t family, void *addr, char *site_name); + +#endif /* _CLDAP_PING_H_ */ diff --git a/mount.cifs.c b/mount.cifs.c index 2278995..3b7a6b3 100644 --- a/mount.cifs.c +++ b/mount.cifs.c @@ -1889,8 +1889,11 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info, if (rc) goto assemble_exit; - if (parsed_info->addrlist[0] == '\0') + if (parsed_info->addrlist[0] == '\0') { rc = resolve_host(parsed_info->host, parsed_info->addrlist); + if (rc == 0 && parsed_info->verboseflag) + fprintf(stderr, "Host \"%s\" resolved to the following IP addresses: %s\n", parsed_info->host, parsed_info->addrlist); + } switch (rc) { case EX_USAGE: diff --git a/resolve_host.c b/resolve_host.c index 17cbd10..fc682e5 100644 --- a/resolve_host.c +++ b/resolve_host.c @@ -3,6 +3,7 @@ * * Copyright (C) 2010 Jeff Layton (jlayton@samba.org) * Copyright (C) 2010 Igor Druzhinin (jaxbrigs@gmail.com) + * Copyright (C) 2024 David Voit (david.voit@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,15 +28,16 @@ #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> +#include <resolv.h> #include "mount.h" #include "util.h" +#include "cldap_ping.h" #include "resolve_host.h" /* * resolve hostname to comma-separated list of address(es) */ -int resolve_host(const char *host, char *addrstr) -{ +int resolve_host(const char *host, char *addrstr) { int rc; /* 10 for max width of decimal scopeid */ char tmpbuf[NI_MAXHOST + 1 + 10 + 1]; @@ -44,6 +46,7 @@ int resolve_host(const char *host, char *addrstr) struct addrinfo *addrlist, *addr; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; + size_t count_v4 = 0, count_v6 = 0; rc = getaddrinfo(host, NULL, NULL, &addrlist); if (rc != 0) @@ -59,34 +62,45 @@ int resolve_host(const char *host, char *addrstr) } switch (addr->ai_addr->sa_family) { - case AF_INET6: - sin6 = (struct sockaddr_in6 *)addr->ai_addr; - ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf, - sizeof(tmpbuf)); - if (!ipaddr) { - rc = EX_SYSERR; - goto resolve_host_out; - } + case AF_INET6: + count_v6++; + if (count_v6 + count_v4 > MAX_ADDRESSES) { + addr = addr->ai_next; + continue; + } - if (sin6->sin6_scope_id) { - len = strnlen(tmpbuf, sizeof(tmpbuf)); - snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u", - sin6->sin6_scope_id); - } - break; - case AF_INET: - sin = (struct sockaddr_in *)addr->ai_addr; - ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf, - sizeof(tmpbuf)); - if (!ipaddr) { - rc = EX_SYSERR; - goto resolve_host_out; - } + sin6 = (struct sockaddr_in6 *) addr->ai_addr; + ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } - break; - default: - addr = addr->ai_next; - continue; + + if (sin6->sin6_scope_id) { + len = strnlen(tmpbuf, sizeof(tmpbuf)); + snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u", + sin6->sin6_scope_id); + } + break; + case AF_INET: + count_v4++; + if (count_v6 + count_v4 > MAX_ADDRESSES) { + addr = addr->ai_next; + continue; + } + sin = (struct sockaddr_in *) addr->ai_addr; + ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } + break; + default: + addr = addr->ai_next; + continue; } if (addr == addrlist) @@ -98,6 +112,192 @@ int resolve_host(const char *host, char *addrstr) addr = addr->ai_next; } + + // Is this a DFS domain where we need to do a cldap ping to find the closest node? + if (count_v4 > 1 || count_v6 > 1) { + int res; + ns_msg global_domain_handle; + unsigned char global_domain_lookup[4096]; + ns_msg site_domain_handle; + unsigned char site_domain_lookup[4096]; + char dname[MAXCDNAME]; + int srv_cnt; + + res = res_init(); + if (res != 0) + goto resolve_host_out; + + res = snprintf(dname, MAXCDNAME, "_ldap._tcp.dc._msdcs.%s", host); + if (res < 0) + goto resolve_host_out; + + res = res_query(dname, C_IN, ns_t_srv, global_domain_lookup, sizeof(global_domain_lookup)); + if (res < 0) + goto resolve_host_out; + + // res is also the size of the response_buffer + res = ns_initparse(global_domain_lookup, res, &global_domain_handle); + if (res < 0) + goto resolve_host_out; + + srv_cnt = ns_msg_count (global_domain_handle, ns_s_an); + + // No or just one DC we are done + if (srv_cnt < 2) + goto resolve_host_out; + + char site_name[MAXCDNAME]; + // We assume that AD always sends the ip addresses in the addtional data block + for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) { + ns_rr rr; + res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr); + if (res < 0) + goto resolve_host_out; + + switch (ns_rr_type(rr)) { + case ns_t_aaaa: + if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) + continue; + res = cldap_ping((char *) host, AF_INET6, (void *)ns_rr_rdata(rr), site_name); + break; + case ns_t_a: + if (ns_rr_rdlen(rr) != NS_INADDRSZ) + continue; + res = cldap_ping((char *) host, AF_INET, (void *)ns_rr_rdata(rr), site_name); + break; + default: + continue; + } + + if (res == CLDAP_PING_TRYNEXT) { + continue; + } + + if (res < 0) { + goto resolve_host_out; + } + + if (site_name[0] == '\0') { + goto resolve_host_out; + } else { + // site found - leave loop + break; + } + } + + res = snprintf(dname, MAXCDNAME, "_ldap._tcp.%s._sites.dc._msdcs.%s", site_name, host); + if (res < 0) { + goto resolve_host_out; + } + + res = res_query(dname, C_IN, ns_t_srv, site_domain_lookup, sizeof(site_domain_lookup)); + if (res < 0) + goto resolve_host_out; + + // res is also the size of the response_buffer + res = ns_initparse(site_domain_lookup, res, &site_domain_handle); + if (res < 0) + goto resolve_host_out; + + int number_addresses = 0; + for (int i = 0; i < ns_msg_count(site_domain_handle, ns_s_ar); i++) { + if (i > MAX_ADDRESSES) + break; + + ns_rr rr; + res = ns_parserr(&site_domain_handle, ns_s_ar, i, &rr); + if (res < 0) + goto resolve_host_out; + + switch (ns_rr_type(rr)) { + case ns_t_aaaa: + if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) + continue; + ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } + break; + case ns_t_a: + if (ns_rr_rdlen(rr) != NS_INADDRSZ) + continue; + ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } + break; + default: + continue; + } + + number_addresses++; + + if (i == 0) + *addrstr = '\0'; + else + strlcat(addrstr, ",", MAX_ADDR_LIST_LEN); + + strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN); + } + + // Preferred site ips is now the first entry in addrstr, fill up with other sites till MAX_ADDRESS + for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) { + if (number_addresses > MAX_ADDRESSES) + break; + + ns_rr rr; + res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr); + if (res < 0) + goto resolve_host_out; + + switch (ns_rr_type(rr)) { + case ns_t_aaaa: + if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) + continue; + ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } + break; + case ns_t_a: + if (ns_rr_rdlen(rr) != NS_INADDRSZ) + continue; + ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf, + sizeof(tmpbuf)); + if (!ipaddr) { + rc = EX_SYSERR; + goto resolve_host_out; + } + break; + default: + continue; + } + + char *found = strstr(addrstr, tmpbuf); + + if (found) { + // We only have a real match if the substring is between ',' or it's the last/first entry in the list + char previous_seperator = found > addrstr ? *(found-1) : '\0'; + char next_seperator = *(found+strlen(tmpbuf)); + + if ((next_seperator == ',' || next_seperator == '\0') + && (previous_seperator == ',' || previous_seperator == '\0')) { + continue; + } + } + + number_addresses++; + strlcat(addrstr, ",", MAX_ADDR_LIST_LEN); + strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN); + } + } + resolve_host_out: freeaddrinfo(addrlist); return rc; diff --git a/resolve_host.h b/resolve_host.h index b949245..f2b19e6 100644 --- a/resolve_host.h +++ b/resolve_host.h @@ -26,8 +26,10 @@ /* currently maximum length of IPv6 address string */ #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN -/* limit list of addresses to 16 max-size addrs */ -#define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16) +#define MAX_ADDRESSES 16 + +/* limit list of addresses to MAX_ADDRESSES max-size addrs */ +#define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * MAX_ADDRESSES) extern int resolve_host(const char *host, char *addrstr); |