Blob Blame History Raw
From 0a0d0f66409eb83e06b7dc50543c2f6c15a36bc4 Mon Sep 17 00:00:00 2001
From: Alexey A Nikitin <nikitin@amazon.com>
Date: Mon, 29 Oct 2018 20:40:36 -0700
Subject: [PATCH] Make 'adcli info' DC location mechanism more compliant with
 [MS-ADTS] and [MS-NRPC]

AD specifications say that DC locator must attempt to find a suitable DC for the client. That means going through all of the DCs in SRV RRs one by one until one of them answers.

The problem with adcli's original behavior is that it queries only five DCs from SRV, ever. This becomes a problem if for any reason there is a large number of DCs in the domain from which the client cannot get a CLDAP response.
---
 library/addisco.c | 146 +++++++++++++++++++++++++++++-----------------
 1 file changed, 94 insertions(+), 52 deletions(-)

diff --git a/library/addisco.c b/library/addisco.c
index 8cc5bf0..6e73ead 100644
--- a/library/addisco.c
+++ b/library/addisco.c
@@ -41,8 +41,10 @@
 #include <string.h>
 #include <time.h>
 
-/* Number of servers to do discovery against */
-#define DISCO_COUNT 5
+/* Number of servers to do discovery against.
+ * For AD DS maximum number of DCs is 1200.
+ */
+#define DISCO_COUNT 1200
 
 /* The time period in which to do rapid requests */
 #define DISCO_FEVER  1
@@ -453,6 +455,51 @@ parse_disco (LDAP *ldap,
 	return usability;
 }
 
+static int
+ldap_disco_poller (LDAP **ldap,
+                   LDAPMessage **message,
+                   adcli_disco **results,
+                   const char **addrs)
+{
+	int found = ADCLI_DISCO_UNUSABLE;
+	int close_ldap;
+	int parsed;
+	int ret = 0;
+	struct timeval tvpoll = { 0, 0 };
+
+	switch (ldap_result (*ldap, LDAP_RES_ANY, 1, &tvpoll, message)) {
+		case LDAP_RES_SEARCH_ENTRY:
+		case LDAP_RES_SEARCH_RESULT:
+			parsed = parse_disco (*ldap, *addrs, *message, results);
+			if (parsed > found)
+				found = parsed;
+			ldap_msgfree (*message);
+			close_ldap = 1;
+			break;
+		case -1:
+			ldap_get_option (*ldap, LDAP_OPT_RESULT_CODE, &ret);
+			close_ldap = 1;
+			break;
+		default:
+			ldap_msgfree (*message);
+			close_ldap = 0;
+			break;
+	}
+
+	if (ret != LDAP_SUCCESS) {
+		_adcli_ldap_handle_failure (*ldap, ADCLI_ERR_CONFIG,
+		                            "Couldn't perform discovery search");
+	}
+
+	/* Done with this connection */
+	if (close_ldap) {
+		ldap_unbind_ext_s (*ldap, NULL, NULL);
+		*ldap = NULL;
+	}
+
+	return found;
+}
+
 static int
 ldap_disco (const char *domain,
             srvinfo *srv,
@@ -477,6 +524,7 @@ ldap_disco (const char *domain,
 	int num, i;
 	int ret;
 	int have_any = 0;
+	struct timeval interval;
 
 	if (domain) {
 		value = _adcli_ldap_escape_filter (domain);
@@ -540,7 +588,6 @@ ldap_disco (const char *domain,
 				version = LDAP_VERSION3;
 				ldap_set_option (ldap[num], LDAP_OPT_PROTOCOL_VERSION, &version);
 				ldap_set_option (ldap[num], LDAP_OPT_REFERRALS , 0);
-				_adcli_info ("Sending netlogon pings to domain controller: %s", url);
 				addrs[num] = srv->hostname;
 				have_any = 1;
 				num++;
@@ -555,70 +602,65 @@ ldap_disco (const char *domain,
 		freeaddrinfo (res);
 	}
 
-	/* Wait for the first response. Poor mans fd watch */
-	for (started = now = time (NULL);
-	     have_any && found != ADCLI_DISCO_USABLE && now < started + DISCO_TIME;
-	     now = time (NULL)) {
+	/* Initial send and short time wait */
+	interval.tv_sec = 0;
+	for (i = 0; ADCLI_DISCO_UNUSABLE == found && i < num; ++i) {
+		int parsed;
+
+		if (NULL == ldap[i])
+			continue;
 
-		struct timeval tvpoll = { 0, 0 };
-		struct timeval interval;
+		have_any = 1;
+		_adcli_info ("Sending NetLogon ping to domain controller: %s", addrs[i]);
 
-		/* If in the initial period, send feverishly */
-		if (now < started + DISCO_FEVER) {
-			interval.tv_sec = 0;
-			interval.tv_usec = 100 * 1000;
+		ret = ldap_search_ext (ldap[i], "", LDAP_SCOPE_BASE,
+		                       filter, attrs, 0, NULL, NULL, NULL,
+		                       -1, &msgidp);
+
+		if (ret != LDAP_SUCCESS) {
+			_adcli_ldap_handle_failure (ldap[i], ADCLI_ERR_CONFIG,
+			                            "Couldn't perform discovery search");
+			ldap_unbind_ext_s (ldap[i], NULL, NULL);
+			ldap[i] = NULL;
+		}
+
+		/* From https://msdn.microsoft.com/en-us/library/ff718294.aspx first
+		 * five DCs are given 0.4 seconds timeout, next five are given 0.2
+		 * seconds, and the rest are given 0.1 seconds
+		 */
+		if (i < 5) {
+			interval.tv_usec = 400000;
+		} else if (i < 10) {
+			interval.tv_usec = 200000;
 		} else {
-			interval.tv_sec = 1;
-			interval.tv_usec = 0;
+			interval.tv_usec = 100000;
 		}
+		select (0, NULL, NULL, NULL, &interval);
+
+		parsed = ldap_disco_poller (&(ldap[i]), &message, results, &(addrs[i]));
+		if (parsed > found)
+			found = parsed;
+	}
+
+	/* Wait some more until LDAP timeout (DISCO_TIME) */
+	for (started = now = time (NULL);
+	     have_any && ADCLI_DISCO_UNUSABLE == found && now < started + DISCO_TIME;
+	     now = time (NULL)) {
 
 		select (0, NULL, NULL, NULL, &interval);
 
 		have_any = 0;
-		for (i = 0; found != ADCLI_DISCO_USABLE && i < num; i++) {
-			int close_ldap;
+		for (i = 0; ADCLI_DISCO_UNUSABLE == found && i < num; ++i) {
 			int parsed;
 
 			if (ldap[i] == NULL)
 				continue;
 
-			ret = 0;
 			have_any = 1;
-			switch (ldap_result (ldap[i], LDAP_RES_ANY, 1, &tvpoll, &message)) {
-			case LDAP_RES_SEARCH_ENTRY:
-			case LDAP_RES_SEARCH_RESULT:
-				parsed = parse_disco (ldap[i], addrs[i], message, results);
-				if (parsed > found)
-					found = parsed;
-				ldap_msgfree (message);
-				close_ldap = 1;
-				break;
-			case 0:
-				ret = ldap_search_ext (ldap[i], "", LDAP_SCOPE_BASE,
-				                       filter, attrs, 0, NULL, NULL, NULL,
-				                       -1, &msgidp);
-				close_ldap = (ret != 0);
-				break;
-			case -1:
-				ldap_get_option (ldap[i], LDAP_OPT_RESULT_CODE, &ret);
-				close_ldap = 1;
-				break;
-			default:
-				ldap_msgfree (message);
-				close_ldap = 0;
-				break;
-			}
-
-			if (ret != LDAP_SUCCESS) {
-				_adcli_ldap_handle_failure (ldap[i], ADCLI_ERR_CONFIG,
-				                            "Couldn't perform discovery search");
-			}
 
-			/* Done with this connection */
-			if (close_ldap) {
-				ldap_unbind_ext_s (ldap[i], NULL, NULL);
-				ldap[i] = NULL;
-			}
+			parsed = ldap_disco_poller (&(ldap[i]), &message, results, &(addrs[i]));
+			if (parsed > found)
+				found = parsed;
 		}
 	}
 
-- 
2.21.0