ipa-getkeytab: add option to discover servers using DNS SRV

The basic flow is:

- If server is provided by the user then use it
- If server the magic value '_srv', check for _ldap._tcp SRV records for
  the domain in /etc/ipa/default.conf
- If no servers are found use the server from default.conf

https://pagure.io/freeipa/issue/8478

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Rob Crittenden 2021-07-15 15:11:28 -04:00
parent 1457bc602b
commit ae05970ea7
4 changed files with 236 additions and 1 deletions

View File

@ -58,6 +58,7 @@ ipa_getkeytab_LDADD = \
$(SASL_LIBS) \ $(SASL_LIBS) \
$(POPT_LIBS) \ $(POPT_LIBS) \
$(LIBINTL_LIBS) \ $(LIBINTL_LIBS) \
$(RESOLV_LIBS) \
$(INI_LIBS) \ $(INI_LIBS) \
$(NULL) $(NULL)

View File

@ -34,9 +34,11 @@
#include <time.h> #include <time.h>
#include <krb5.h> #include <krb5.h>
#include <ldap.h> #include <ldap.h>
#include <resolv.h>
#include <sasl/sasl.h> #include <sasl/sasl.h>
#include <popt.h> #include <popt.h>
#include <ini_configobj.h> #include <ini_configobj.h>
#include <openssl/rand.h>
#include "config.h" #include "config.h"
@ -46,6 +48,174 @@
#include "ipa_ldap.h" #include "ipa_ldap.h"
struct srvrec {
char *host;
uint16_t port;
int priority, weight;
struct srvrec *next;
};
static int
srvrec_priority_sort(const void *a, const void *b)
{
const struct srvrec *sa, *sb;
sa = a;
sb = b;
return sa->priority - sb->priority;
}
static int
srvrec_sort_weight(const void *a, const void *b)
{
const struct srvrec *sa, *sb;
sa = a;
sb = b;
return sa->weight - sb->weight;
}
/* Return a uniform random number between 0 and range */
static double
rand_inclusive(double range)
{
long long r;
if (range == 0) {
return 0;
}
if (RAND_bytes((unsigned char *) &r, sizeof(r)) == -1) {
return 0;
}
if (r < 0) {
r = -r;
}
return ((double)r / (double)LLONG_MAX) * range;
}
static void
sort_prio_weight(struct srvrec *res, int len)
{
int i, j;
double tweight;
struct srvrec tmp;
double r;
qsort(res, len, sizeof(res[0]), srvrec_sort_weight);
for (i = 0; i < len - 1; i++) {
tweight = 0;
for (j = i; j < len; j++) {
/* Give records with 0 weight a small chance */
tweight += res[j].weight ? res[j].weight : 0.01;
}
r = rand_inclusive(tweight);
tweight = 0;
for (j = i; j < len; j++) {
tweight += res[j].weight ? res[j].weight : 0.01;
if (tweight >= r) {
break;
}
}
if (j >= len) {
continue;
}
memcpy(&tmp, &res[i], sizeof(tmp));
memcpy(&res[i], &res[j], sizeof(tmp));
memcpy(&res[j], &tmp, sizeof(tmp));
}
}
/* The caller is responsible for freeing the results */
static int
query_srv(const char *name, const char *domain, struct srvrec **results)
{
int i, j, len;
unsigned char *answer = NULL;
size_t answer_len = NS_MAXMSG;
struct srvrec *res = NULL;
ns_msg msg;
ns_rr rr;
int rv = -1;
*results = NULL;
if ((name == NULL) || (strlen(name) == 0) ||
(domain == NULL) || (strlen(domain) == 0)) {
return -1;
}
res_init();
answer = malloc(answer_len + 1);
if (answer == NULL) {
return -1;
}
memset(answer, 0, answer_len + 1);
i = res_querydomain(name, domain, C_IN, T_SRV, answer, answer_len);
if (i == -1) {
goto error;
}
answer_len = i;
memset(&msg, 0, sizeof(msg));
if (ns_initparse(answer, answer_len, &msg) != 0) {
goto error;
}
memset(&rr, 0, sizeof(rr));
for (i = 0; ns_parserr(&msg, ns_s_an, i, &rr) == 0; i++) {
continue;
}
if (i == 0) {
goto error;
}
len = i;
res = malloc(sizeof(*res) * i);
if (res == NULL) {
goto error;
}
memset(res, 0, sizeof(*res) * i);
for (i = 0, j = 0; i < len; i++) {
if (ns_parserr(&msg, ns_s_an, i, &rr) != 0) {
continue;
}
if (rr.rdlength < 6) {
continue;
}
res[j].host = malloc(rr.rdlength - 6 + 1);
if (res[j].host == NULL) {
goto error;
}
res[j].priority = ntohs(*(uint16_t *)rr.rdata);
res[j].weight = ntohs(*(uint16_t *)(rr.rdata + 2));
res[j].port = ntohs(*(uint16_t *)(rr.rdata + 4));
memcpy(res[j].host, rr.rdata + 6, rr.rdlength - 6);
if (ns_name_ntop(rr.rdata + 6, res[j].host, rr.rdlength - 6) == -1) {
continue;
}
res[j].host[rr.rdlength - 6] = '\0';
j++;
}
len = j;
qsort(res, len, sizeof(res[0]), srvrec_priority_sort);
i = 0;
while (i < len) {
j = i + 1;
while (j < len && (res[j].priority == res[i].priority)) {
j++;
}
sort_prio_weight(res + i, j - i);
i = j;
}
/* Fixup the linked-list pointers */
for (i = 0; i < len - 1; i++) {
res[i].next = &res[i + 1];
}
*results = res;
rv = 0;
error:
free(answer);
return rv;
}
static int check_sasl_mech(const char *mech) static int check_sasl_mech(const char *mech)
{ {
int i; int i;
@ -619,6 +789,7 @@ static char *ask_password(krb5_context krbctx, char *prompt1, char *prompt2,
struct ipa_config { struct ipa_config {
const char *server_name; const char *server_name;
const char *domain;
}; };
static int config_from_file(struct ini_cfgobj *cfgctx) static int config_from_file(struct ini_cfgobj *cfgctx)
@ -688,6 +859,11 @@ int read_ipa_config(struct ipa_config **ipacfg)
if (ret == 0 && obj != NULL) { if (ret == 0 && obj != NULL) {
(*ipacfg)->server_name = ini_get_string_config_value(obj, &ret); (*ipacfg)->server_name = ini_get_string_config_value(obj, &ret);
} }
ret = ini_get_config_valueobj("global", "domain", cfgctx,
INI_GET_LAST_VALUE, &obj);
if (ret == 0 && obj != NULL) {
(*ipacfg)->domain = ini_get_string_config_value(obj, &ret);
}
return 0; return 0;
} }
@ -754,6 +930,7 @@ int main(int argc, const char *argv[])
static const char *sasl_mech = NULL; static const char *sasl_mech = NULL;
static const char *ca_cert_file = NULL; static const char *ca_cert_file = NULL;
int quiet = 0; int quiet = 0;
int verbose = 0;
int askpass = 0; int askpass = 0;
int askbindpw = 0; int askbindpw = 0;
int permitted_enctypes = 0; int permitted_enctypes = 0;
@ -761,6 +938,8 @@ int main(int argc, const char *argv[])
struct poptOption options[] = { struct poptOption options[] = {
{ "quiet", 'q', POPT_ARG_NONE, &quiet, 0, { "quiet", 'q', POPT_ARG_NONE, &quiet, 0,
_("Print as little as possible"), _("Output only on errors")}, _("Print as little as possible"), _("Output only on errors")},
{ "verbose", 'v', POPT_ARG_NONE, &verbose, 0,
_("Print debugging information"), _("Output debug info")},
{ "server", 's', POPT_ARG_STRING, &server, 0, { "server", 's', POPT_ARG_STRING, &server, 0,
_("Contact this specific KDC Server"), _("Contact this specific KDC Server"),
_("Server Name") }, _("Server Name") },
@ -906,6 +1085,41 @@ int main(int argc, const char *argv[])
exit(2); exit(2);
} }
if (server && (strcasecmp(server, "_srv_") == 0)) {
struct srvrec *srvrecs, *srv;
struct ipa_config *ipacfg = NULL;
ret = read_ipa_config(&ipacfg);
if (ret == 0 && ipacfg->domain && verbose) {
fprintf(stderr, _("DNS discovery for domain %s\n"), ipacfg->domain);
}
if (query_srv("_ldap._tcp", ipacfg->domain, &srvrecs) == 0) {
for (srv = srvrecs; (srv != NULL); srv = srv->next) {
if (verbose) {
fprintf(stderr, _("Discovered server %s\n"), srv->host);
}
}
for (srv = srvrecs; (srv != NULL); srv = srv->next) {
server = strdup(srv->host);
if (verbose) {
fprintf(stderr, _("Using discovered server %s\n"), server);
}
break;
}
for (srv = srvrecs; (srv != NULL); srv = srv->next) {
free(srv->host);
}
} else {
if (verbose) {
fprintf(stderr, _("DNS Discovery failed\n"));
}
}
if (strcasecmp(server, "_srv_") == 0) {
/* Discovery failed, fall through to option methods */
server = NULL;
}
}
if (!server && !ldap_uri) { if (!server && !ldap_uri) {
struct ipa_config *ipacfg = NULL; struct ipa_config *ipacfg = NULL;
@ -915,10 +1129,17 @@ int main(int argc, const char *argv[])
ipacfg->server_name = NULL; ipacfg->server_name = NULL;
} }
free(ipacfg); free(ipacfg);
if (verbose && server) {
fprintf(stderr, _("Using server from config %s\n"), server);
}
if (!server) { if (!server) {
fprintf(stderr, _("Server name not provided and unavailable\n")); fprintf(stderr, _("Server name not provided and unavailable\n"));
exit(2); exit(2);
} }
} else {
if (verbose) {
fprintf(stderr, _("Using provided server %s\n"), server);
}
} }
if (server) { if (server) {
ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri); ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri);

View File

@ -78,7 +78,10 @@ arcfour\-hmac
\fB\-s ipaserver\fR \fB\-s ipaserver\fR
The IPA server to retrieve the keytab from (FQDN). If this option is not The IPA server to retrieve the keytab from (FQDN). If this option is not
provided the server name is read from the IPA configuration file provided the server name is read from the IPA configuration file
(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. (/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. If the
value is _srv_ then DNS discovery will be used to determine a server.
If this discovery fails then it will fall back to using the configuration
file.
.TP .TP
\fB\-q\fR \fB\-q\fR
Quiet mode. Only errors are displayed. Quiet mode. Only errors are displayed.

View File

@ -108,6 +108,16 @@ LDAP_CFLAGS=""
AC_SUBST(LDAP_LIBS) AC_SUBST(LDAP_LIBS)
AC_SUBST(LDAP_CFLAGS) AC_SUBST(LDAP_CFLAGS)
dnl ---------------------------------------------------------------------------
dnl - Check for resolv library
dnl ---------------------------------------------------------------------------
SAVE_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
AC_CHECK_LIB(resolv,main,RESOLV_LIBS=-lresolv)
AC_CHECK_HEADERS(resolv.h)
AC_SUBST(RESOLV_LIBS)
dnl --------------------------------------------------------------------------- dnl ---------------------------------------------------------------------------
dnl - Check for OpenSSL Crypto library dnl - Check for OpenSSL Crypto library
dnl --------------------------------------------------------------------------- dnl ---------------------------------------------------------------------------