/* * resolving DNS hostname routine * * 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 * 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #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 rc; /* 10 for max width of decimal scopeid */ char tmpbuf[NI_MAXHOST + 1 + 10 + 1]; const char *ipaddr; size_t len; 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) return EX_USAGE; addr = addrlist; while (addr) { /* skip non-TCP entries */ if (addr->ai_socktype != SOCK_STREAM || addr->ai_protocol != IPPROTO_TCP) { addr = addr->ai_next; continue; } switch (addr->ai_addr->sa_family) { case AF_INET6: count_v6++; if (count_v6 + count_v4 > MAX_ADDRESSES) { addr = addr->ai_next; continue; } 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; } 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) *addrstr = '\0'; else strlcat(addrstr, ",", MAX_ADDR_LIST_LEN); strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN); 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; }