diff options
Diffstat (limited to 'resolve_host.c')
-rw-r--r-- | resolve_host.c | 256 |
1 files changed, 228 insertions, 28 deletions
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; |