summaryrefslogtreecommitdiff
path: root/resolve_host.c
diff options
context:
space:
mode:
Diffstat (limited to 'resolve_host.c')
-rw-r--r--resolve_host.c256
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;