7e1b55
From 42206df69adc9c1eefa3ee576891b2ae3ac269e0 Mon Sep 17 00:00:00 2001
7e1b55
From: Rob Crittenden <rcritten@redhat.com>
7e1b55
Date: Thu, 15 Jul 2021 15:11:28 -0400
7e1b55
Subject: [PATCH] ipa-getkeytab: add option to discover servers using DNS SRV
7e1b55
7e1b55
The basic flow is:
7e1b55
7e1b55
- If server is provided by the user then use it
7e1b55
- If server the magic value '_srv', check for _ldap._tcp SRV records for
7e1b55
  the domain in /etc/ipa/default.conf
7e1b55
- If no servers are found use the server from default.conf
7e1b55
7e1b55
https://pagure.io/freeipa/issue/8478
7e1b55
7e1b55
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
7e1b55
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
7e1b55
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
7e1b55
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
7e1b55
---
7e1b55
 client/Makefile.am         |   1 +
7e1b55
 client/ipa-getkeytab.c     | 221 +++++++++++++++++++++++++++++++++++++
7e1b55
 client/man/ipa-getkeytab.1 |   5 +-
7e1b55
 configure.ac               |  10 ++
7e1b55
 4 files changed, 236 insertions(+), 1 deletion(-)
7e1b55
7e1b55
diff --git a/client/Makefile.am b/client/Makefile.am
7e1b55
index 0031c04a5..72f4cb3dc 100644
7e1b55
--- a/client/Makefile.am
7e1b55
+++ b/client/Makefile.am
7e1b55
@@ -66,6 +66,7 @@ ipa_getkeytab_LDADD = 		\
7e1b55
 	$(SASL_LIBS)		\
7e1b55
 	$(POPT_LIBS)		\
7e1b55
 	$(LIBINTL_LIBS)         \
7e1b55
+	$(RESOLV_LIBS)		\
7e1b55
 	$(INI_LIBS)		\
7e1b55
 	$(NULL)
7e1b55
 
7e1b55
diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
7e1b55
index 04786be9e..d3673eb05 100644
7e1b55
--- a/client/ipa-getkeytab.c
7e1b55
+++ b/client/ipa-getkeytab.c
7e1b55
@@ -34,9 +34,11 @@
7e1b55
 #include <time.h>
7e1b55
 #include <krb5.h>
7e1b55
 #include <ldap.h>
7e1b55
+#include <resolv.h>
7e1b55
 #include <sasl/sasl.h>
7e1b55
 #include <popt.h>
7e1b55
 #include <ini_configobj.h>
7e1b55
+#include <openssl/rand.h>
7e1b55
 
7e1b55
 #include "config.h"
7e1b55
 
7e1b55
@@ -46,6 +48,174 @@
7e1b55
 #include "ipa_ldap.h"
7e1b55
 
7e1b55
 
7e1b55
+struct srvrec {
7e1b55
+    char *host;
7e1b55
+    uint16_t port;
7e1b55
+    int priority, weight;
7e1b55
+    struct srvrec *next;
7e1b55
+};
7e1b55
+
7e1b55
+static int
7e1b55
+srvrec_priority_sort(const void *a, const void *b)
7e1b55
+{
7e1b55
+	const struct srvrec *sa, *sb;
7e1b55
+
7e1b55
+	sa = a;
7e1b55
+	sb = b;
7e1b55
+	return sa->priority - sb->priority;
7e1b55
+}
7e1b55
+
7e1b55
+static int
7e1b55
+srvrec_sort_weight(const void *a, const void *b)
7e1b55
+{
7e1b55
+	const struct srvrec *sa, *sb;
7e1b55
+
7e1b55
+	sa = a;
7e1b55
+	sb = b;
7e1b55
+	return sa->weight - sb->weight;
7e1b55
+}
7e1b55
+
7e1b55
+/* Return a uniform random number between 0 and range */
7e1b55
+static double
7e1b55
+rand_inclusive(double range)
7e1b55
+{
7e1b55
+	long long r;
7e1b55
+
7e1b55
+	if (range == 0) {
7e1b55
+		return 0;
7e1b55
+	}
7e1b55
+
7e1b55
+	if (RAND_bytes((unsigned char *) &r, sizeof(r)) == -1) {
7e1b55
+		return 0;
7e1b55
+	}
7e1b55
+	if (r < 0) {
7e1b55
+		r = -r;
7e1b55
+	}
7e1b55
+	return ((double)r / (double)LLONG_MAX) * range;
7e1b55
+}
7e1b55
+
7e1b55
+static void
7e1b55
+sort_prio_weight(struct srvrec *res, int len)
7e1b55
+{
7e1b55
+	int i, j;
7e1b55
+	double tweight;
7e1b55
+	struct srvrec tmp;
7e1b55
+	double r;
7e1b55
+
7e1b55
+	qsort(res, len, sizeof(res[0]), srvrec_sort_weight);
7e1b55
+	for (i = 0; i < len - 1; i++) {
7e1b55
+		tweight = 0;
7e1b55
+		for (j = i; j < len; j++) {
7e1b55
+            /* Give records with 0 weight a small chance */
7e1b55
+			tweight += res[j].weight ? res[j].weight : 0.01;
7e1b55
+		}
7e1b55
+		r = rand_inclusive(tweight);
7e1b55
+		tweight = 0;
7e1b55
+		for (j = i; j < len; j++) {
7e1b55
+			tweight += res[j].weight ? res[j].weight : 0.01;
7e1b55
+			if (tweight >= r) {
7e1b55
+				break;
7e1b55
+			}
7e1b55
+		}
7e1b55
+		if (j >= len) {
7e1b55
+			continue;
7e1b55
+		}
7e1b55
+		memcpy(&tmp, &res[i], sizeof(tmp));
7e1b55
+		memcpy(&res[i], &res[j], sizeof(tmp));
7e1b55
+		memcpy(&res[j], &tmp, sizeof(tmp));
7e1b55
+	}
7e1b55
+}
7e1b55
+
7e1b55
+/* The caller is responsible for freeing the results */
7e1b55
+static int
7e1b55
+query_srv(const char *name, const char *domain, struct srvrec **results)
7e1b55
+{
7e1b55
+	int i, j, len;
7e1b55
+	unsigned char *answer = NULL;
7e1b55
+	size_t answer_len = NS_MAXMSG;
7e1b55
+	struct srvrec *res = NULL;
7e1b55
+	ns_msg msg;
7e1b55
+	ns_rr rr;
7e1b55
+	int rv = -1;
7e1b55
+
7e1b55
+	*results = NULL;
7e1b55
+	if ((name == NULL) || (strlen(name) == 0) ||
7e1b55
+	    (domain == NULL) || (strlen(domain) == 0)) {
7e1b55
+		return -1;
7e1b55
+	}
7e1b55
+
7e1b55
+	res_init();
7e1b55
+	answer = malloc(answer_len + 1);
7e1b55
+	if (answer == NULL) {
7e1b55
+		return -1;
7e1b55
+	}
7e1b55
+	memset(answer, 0, answer_len + 1);
7e1b55
+	i = res_querydomain(name, domain, C_IN, T_SRV, answer, answer_len);
7e1b55
+	if (i == -1) {
7e1b55
+		goto error;
7e1b55
+	}
7e1b55
+	answer_len = i;
7e1b55
+	memset(&msg, 0, sizeof(msg));
7e1b55
+	if (ns_initparse(answer, answer_len, &msg) != 0) {
7e1b55
+		goto error;
7e1b55
+	}
7e1b55
+	memset(&rr, 0, sizeof(rr));
7e1b55
+	for (i = 0; ns_parserr(&msg, ns_s_an, i, &rr) == 0; i++) {
7e1b55
+		continue;
7e1b55
+	}
7e1b55
+	if (i == 0) {
7e1b55
+		goto error;
7e1b55
+	}
7e1b55
+	len = i;
7e1b55
+	res = malloc(sizeof(*res) * i);
7e1b55
+	if (res == NULL) {
7e1b55
+		goto error;
7e1b55
+	}
7e1b55
+	memset(res, 0, sizeof(*res) * i);
7e1b55
+	for (i = 0, j = 0; i < len; i++) {
7e1b55
+		if (ns_parserr(&msg, ns_s_an, i, &rr) != 0) {
7e1b55
+			continue;
7e1b55
+		}
7e1b55
+		if (rr.rdlength < 6) {
7e1b55
+			continue;
7e1b55
+		}
7e1b55
+		res[j].host = malloc(rr.rdlength - 6 + 1);
7e1b55
+		if (res[j].host == NULL) {
7e1b55
+			goto error;
7e1b55
+		}
7e1b55
+		res[j].priority = ntohs(*(uint16_t *)rr.rdata);
7e1b55
+		res[j].weight = ntohs(*(uint16_t *)(rr.rdata + 2));
7e1b55
+		res[j].port = ntohs(*(uint16_t *)(rr.rdata + 4));
7e1b55
+		memcpy(res[j].host, rr.rdata + 6, rr.rdlength - 6);
7e1b55
+		if (ns_name_ntop(rr.rdata + 6, res[j].host, rr.rdlength - 6) == -1) {
7e1b55
+			continue;
7e1b55
+		}
7e1b55
+		res[j].host[rr.rdlength - 6] = '\0';
7e1b55
+		j++;
7e1b55
+	}
7e1b55
+	len = j;
7e1b55
+	qsort(res, len, sizeof(res[0]), srvrec_priority_sort);
7e1b55
+	i = 0;
7e1b55
+	while (i < len) {
7e1b55
+		j = i + 1;
7e1b55
+		while (j < len && (res[j].priority == res[i].priority)) {
7e1b55
+			j++;
7e1b55
+		}
7e1b55
+		sort_prio_weight(res + i, j - i);
7e1b55
+		i = j;
7e1b55
+	}
7e1b55
+	/* Fixup the linked-list pointers */
7e1b55
+	for (i = 0; i < len - 1; i++) {
7e1b55
+		res[i].next = &res[i + 1];
7e1b55
+	}
7e1b55
+	*results = res;
7e1b55
+	rv = 0;
7e1b55
+
7e1b55
+error:
7e1b55
+	free(answer);
7e1b55
+	return rv;
7e1b55
+}
7e1b55
+
7e1b55
 static int check_sasl_mech(const char *mech)
7e1b55
 {
7e1b55
     int i;
7e1b55
@@ -619,6 +789,7 @@ static char *ask_password(krb5_context krbctx, char *prompt1, char *prompt2,
7e1b55
 
7e1b55
 struct ipa_config {
7e1b55
     const char *server_name;
7e1b55
+    const char *domain;
7e1b55
 };
7e1b55
 
7e1b55
 static int config_from_file(struct ini_cfgobj *cfgctx)
7e1b55
@@ -688,6 +859,11 @@ int read_ipa_config(struct ipa_config **ipacfg)
7e1b55
     if (ret == 0 && obj != NULL) {
7e1b55
         (*ipacfg)->server_name = ini_get_string_config_value(obj, &ret;;
7e1b55
     }
7e1b55
+    ret = ini_get_config_valueobj("global", "domain", cfgctx,
7e1b55
+                                  INI_GET_LAST_VALUE, &obj);
7e1b55
+    if (ret == 0 && obj != NULL) {
7e1b55
+        (*ipacfg)->domain = ini_get_string_config_value(obj, &ret;;
7e1b55
+    }
7e1b55
 
7e1b55
     return 0;
7e1b55
 }
7e1b55
@@ -754,6 +930,7 @@ int main(int argc, const char *argv[])
7e1b55
 	static const char *sasl_mech = NULL;
7e1b55
 	static const char *ca_cert_file = NULL;
7e1b55
 	int quiet = 0;
7e1b55
+	int verbose = 0;
7e1b55
 	int askpass = 0;
7e1b55
 	int askbindpw = 0;
7e1b55
 	int permitted_enctypes = 0;
7e1b55
@@ -761,6 +938,8 @@ int main(int argc, const char *argv[])
7e1b55
         struct poptOption options[] = {
7e1b55
             { "quiet", 'q', POPT_ARG_NONE, &quiet, 0,
7e1b55
               _("Print as little as possible"), _("Output only on errors")},
7e1b55
+            { "verbose", 'v', POPT_ARG_NONE, &verbose, 0,
7e1b55
+              _("Print debugging information"), _("Output debug info")},
7e1b55
             { "server", 's', POPT_ARG_STRING, &server, 0,
7e1b55
               _("Contact this specific KDC Server"),
7e1b55
               _("Server Name") },
7e1b55
@@ -906,6 +1085,41 @@ int main(int argc, const char *argv[])
7e1b55
         exit(2);
7e1b55
     }
7e1b55
 
7e1b55
+    if (server && (strcasecmp(server, "_srv_") == 0)) {
7e1b55
+        struct srvrec *srvrecs, *srv;
7e1b55
+        struct ipa_config *ipacfg = NULL;
7e1b55
+
7e1b55
+        ret = read_ipa_config(&ipacfg);
7e1b55
+        if (ret == 0 && ipacfg->domain && verbose) {
7e1b55
+            fprintf(stderr, _("DNS discovery for domain %s\n"), ipacfg->domain);
7e1b55
+        }
7e1b55
+        if (query_srv("_ldap._tcp", ipacfg->domain, &srvrecs) == 0) {
7e1b55
+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
7e1b55
+				if (verbose) {
7e1b55
+            	    fprintf(stderr, _("Discovered server %s\n"), srv->host);
7e1b55
+                }
7e1b55
+            }
7e1b55
+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
7e1b55
+                server = strdup(srv->host);
7e1b55
+				if (verbose) {
7e1b55
+            		fprintf(stderr, _("Using discovered server %s\n"), server);
7e1b55
+				}
7e1b55
+                break;
7e1b55
+            }
7e1b55
+            for (srv = srvrecs; (srv != NULL); srv = srv->next) {
7e1b55
+				free(srv->host);
7e1b55
+            }
7e1b55
+        } else {
7e1b55
+			if (verbose) {
7e1b55
+           		fprintf(stderr, _("DNS Discovery failed\n"));
7e1b55
+			}
7e1b55
+        }
7e1b55
+        if (strcasecmp(server, "_srv_") == 0) {
7e1b55
+            /* Discovery failed, fall through to option methods */
7e1b55
+            server = NULL;
7e1b55
+        }
7e1b55
+    }
7e1b55
+
7e1b55
     if (!server && !ldap_uri) {
7e1b55
         struct ipa_config *ipacfg = NULL;
7e1b55
 
7e1b55
@@ -915,10 +1129,17 @@ int main(int argc, const char *argv[])
7e1b55
             ipacfg->server_name = NULL;
7e1b55
         }
7e1b55
         free(ipacfg);
7e1b55
+		if (verbose && server) {
7e1b55
+            fprintf(stderr, _("Using server from config %s\n"), server);
7e1b55
+        }
7e1b55
         if (!server) {
7e1b55
             fprintf(stderr, _("Server name not provided and unavailable\n"));
7e1b55
             exit(2);
7e1b55
         }
7e1b55
+    } else {
7e1b55
+        if (verbose) {
7e1b55
+            fprintf(stderr, _("Using provided server %s\n"), server);
7e1b55
+        }
7e1b55
     }
7e1b55
     if (server) {
7e1b55
         ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri);
7e1b55
diff --git a/client/man/ipa-getkeytab.1 b/client/man/ipa-getkeytab.1
7e1b55
index b57c5489c..07d2d73b3 100644
7e1b55
--- a/client/man/ipa-getkeytab.1
7e1b55
+++ b/client/man/ipa-getkeytab.1
7e1b55
@@ -78,7 +78,10 @@ arcfour\-hmac
7e1b55
 \fB\-s ipaserver\fR
7e1b55
 The IPA server to retrieve the keytab from (FQDN). If this option is not
7e1b55
 provided the server name is read from the IPA configuration file
7e1b55
-(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR.
7e1b55
+(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. If the
7e1b55
+value is _srv_ then DNS discovery will be used to determine a server.
7e1b55
+If this discovery fails then it will fall back to using the configuration
7e1b55
+file.
7e1b55
 .TP
7e1b55
 \fB\-q\fR
7e1b55
 Quiet mode. Only errors are displayed.
7e1b55
diff --git a/configure.ac b/configure.ac
7e1b55
index dc79d5dce..9d7a33825 100644
7e1b55
--- a/configure.ac
7e1b55
+++ b/configure.ac
7e1b55
@@ -108,6 +108,16 @@ LDAP_CFLAGS=""
7e1b55
 AC_SUBST(LDAP_LIBS)
7e1b55
 AC_SUBST(LDAP_CFLAGS)
7e1b55
 
7e1b55
+dnl ---------------------------------------------------------------------------
7e1b55
+dnl - Check for resolv library
7e1b55
+dnl ---------------------------------------------------------------------------
7e1b55
+
7e1b55
+SAVE_CPPFLAGS=$CPPFLAGS
7e1b55
+CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
7e1b55
+AC_CHECK_LIB(resolv,main,RESOLV_LIBS=-lresolv)
7e1b55
+AC_CHECK_HEADERS(resolv.h)
7e1b55
+AC_SUBST(RESOLV_LIBS)
7e1b55
+
7e1b55
 dnl ---------------------------------------------------------------------------
7e1b55
 dnl - Check for OpenSSL Crypto library
7e1b55
 dnl ---------------------------------------------------------------------------
7e1b55
-- 
7e1b55
2.31.1
7e1b55