blob: d51ae8319a01d96e4d237dface6cd60796e48fae [file] [log] [blame]
From 5a38f9f94514669cfcd2d814bbc31fde3aed683c Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra@samba.org>
Date: Fri, 28 Aug 2020 15:14:31 -0700
Subject: [PATCH] Add MIT krb5 locator plugin that uses Samba async SRV DNS
lookups.
Signed-off-by: Jeremy Allison <jra@samba.org>
---
nsswitch/krb5_plugin/async_dns_krb5_locator.c | 458 ++++++++++++++++++
nsswitch/wscript_build | 17 +
2 files changed, 475 insertions(+)
create mode 100644 nsswitch/krb5_plugin/async_dns_krb5_locator.c
diff --git a/nsswitch/krb5_plugin/async_dns_krb5_locator.c b/nsswitch/krb5_plugin/async_dns_krb5_locator.c
new file mode 100644
index 00000000000..66aac7a2933
--- /dev/null
+++ b/nsswitch/krb5_plugin/async_dns_krb5_locator.c
@@ -0,0 +1,458 @@
+/*
+ Unix SMB/CIFS implementation.
+ Async DNS kerberos locator plugin
+ Copyright (C) Guenther Deschner 2007-2008
+ Copyright (C) Jeremy Allison 2020.
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "../../source3/include/includes.h"
+#include "../../source3/libsmb/namequery.h"
+
+#ifndef DEBUG_KRB5
+#undef DEBUG_KRB5
+#endif
+
+/* Uncomment to debug. */
+/* #define DEBUG_KRB5 1 */
+
+#if defined(HAVE_KRB5) && defined(HAVE_KRB5_LOCATE_PLUGIN_H)
+
+#ifdef HAVE_COM_ERR_H
+#include <com_err.h>
+#endif
+
+#include <krb5.h>
+#include <krb5/locate_plugin.h>
+
+#ifndef KRB5_PLUGIN_NO_HANDLE
+#define KRB5_PLUGIN_NO_HANDLE KRB5_KDC_UNREACH /* Heimdal */
+#endif
+
+struct singleton_realm_kdc_list_cache {
+ char *realm;
+ struct ip_service *kdc_list;
+ int num_kdcs;
+};
+
+static struct singleton_realm_kdc_list_cache *scache;
+
+static const char *get_service_from_locate_service_type(enum locate_service_type svc)
+{
+ switch (svc) {
+ case locate_service_kdc:
+ case locate_service_master_kdc:
+ return "88";
+ case locate_service_kadmin:
+ case locate_service_krb524:
+ /* not supported */
+ return NULL;
+ case locate_service_kpasswd:
+ return "464";
+ default:
+ break;
+ }
+ return NULL;
+
+}
+
+#ifdef DEBUG_KRB5
+static const char *locate_service_type_name(enum locate_service_type svc)
+{
+ switch (svc) {
+ case locate_service_kdc:
+ return "locate_service_kdc";
+ case locate_service_master_kdc:
+ return "locate_service_master_kdc";
+ case locate_service_kadmin:
+ return "locate_service_kadmin";
+ case locate_service_krb524:
+ return "locate_service_krb524";
+ case locate_service_kpasswd:
+ return "locate_service_kpasswd";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static const char *socktype_name(int socktype)
+{
+ switch (socktype) {
+ case SOCK_STREAM:
+ return "SOCK_STREAM";
+ case SOCK_DGRAM:
+ return "SOCK_DGRAM";
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+static const char *family_name(int family)
+{
+ switch (family) {
+ case AF_UNSPEC:
+ return "AF_UNSPEC";
+ case AF_INET:
+ return "AF_INET";
+#if defined(HAVE_IPV6)
+ case AF_INET6:
+ return "AF_INET6";
+#endif
+ default:
+ break;
+ }
+ return "unknown";
+}
+#endif
+
+/**
+ * Check input parameters, return KRB5_PLUGIN_NO_HANDLE for unsupported ones
+ *
+ * @param svc
+ * @param realm string
+ * @param socktype integer
+ * @param family integer
+ *
+ * @return integer.
+ */
+
+static int smb_krb5_adns_locator_lookup_sanity_check(
+ enum locate_service_type svc,
+ const char *realm,
+ int socktype,
+ int family)
+{
+ if (!realm || strlen(realm) == 0) {
+ return EINVAL;
+ }
+
+ switch (svc) {
+ case locate_service_kdc:
+ case locate_service_master_kdc:
+ break;
+ case locate_service_kadmin:
+ case locate_service_krb524:
+ case locate_service_kpasswd:
+ return KRB5_PLUGIN_NO_HANDLE;
+ default:
+ return EINVAL;
+ }
+
+ switch (family) {
+ case AF_UNSPEC:
+ case AF_INET:
+#if defined(HAVE_IPV6)
+ case AF_INET6:
+#endif
+ break;
+ default:
+ return EINVAL;
+ }
+
+ switch (socktype) {
+ case SOCK_STREAM:
+ case SOCK_DGRAM:
+ case 0: /* Heimdal uses that */
+ break;
+ default:
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Call back into the MIT libraries with each address
+ * we found. Assume AD-DC's always support both UDP and
+ * TCP port 88 for KDC service.
+ */
+
+static krb5_error_code smb_krb5_adns_locator_call_cbfunc(
+ struct ip_service *kdcs,
+ int num_kdcs,
+ const char *service,
+ int socktype,
+ int (*cbfunc)(void *, int, struct sockaddr *),
+ void *cbdata)
+{
+ int ret = 0;
+ int i;
+ struct sockaddr *sa = NULL;
+
+ for (i = 0; i < num_kdcs; i++) {
+ if (kdcs[i].ss.ss_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)&kdcs[i].ss;
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(88);
+ sa = (struct sockaddr *)sin;
+ }
+#if defined(HAVE_IPV6)
+ if (kdcs[i].ss.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&kdcs[i].ss;
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(88);
+ sa = (struct sockaddr *)sin6;
+ }
+#else
+ else {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+#endif
+
+#ifdef DEBUG_KRB5
+ {
+ char addr[INET6_ADDRSTRLEN];
+ fprintf(stderr, "[%5u]: "
+ "smb_krb5_adns_locator_call_cbfunc: "
+ "IP[%d] %s\n",
+ (unsigned int)getpid(),
+ i,
+ print_sockaddr(addr,
+ sizeof(addr),
+ &kdcs[i].ss));
+ }
+#endif
+
+ /* Assume all AD-DC's do both UDP and TCP on port 88. */
+ ret = cbfunc(cbdata, socktype, sa);
+ if (ret) {
+#ifdef DEBUG_KRB5
+ fprintf(stderr, "[%5u]: "
+ "smb_krb5_adns_locator_call_cbfunc: "
+ "failed to call callback: %s (%d)\n",
+ (unsigned int)getpid(),
+ error_message(ret),
+ ret);
+#endif
+ break;
+ }
+ }
+ return ret;
+}
+
+/**
+ * PUBLIC INTERFACE: locate init
+ *
+ * @param context krb5_context
+ * @param privata_data pointer to private data pointer
+ *
+ * @return krb5_error_code.
+ */
+
+static krb5_error_code smb_krb5_adns_locator_init(krb5_context context,
+ void **private_data)
+{
+ static bool loaded_config;
+ if (!loaded_config) {
+ lp_load_global(get_dyn_CONFIGFILE());
+ loaded_config = true;
+ }
+#ifdef DEBUG_KRB5
+ fprintf(stderr,"[%5u]: smb_krb5_adns_locator_init\n",
+ (unsigned int)getpid());
+#endif
+ return 0;
+}
+
+/**
+ * PUBLIC INTERFACE: close locate
+ *
+ * @param private_data pointer to private data
+ *
+ * @return void.
+ */
+
+static void smb_krb5_adns_locator_close(void *private_data)
+{
+#ifdef DEBUG_KRB5
+ fprintf(stderr,"[%5u]: smb_krb5_adns_locator_close\n",
+ (unsigned int)getpid());
+#endif
+ return;
+}
+
+/**
+ * PUBLIC INTERFACE: locate lookup
+ *
+ * @param private_data pointer to private data
+ * @param svc enum locate_service_type.
+ * @param realm string
+ * @param socktype integer
+ * @param family integer
+ * @param cbfunc callback function to send back entries
+ * @param cbdata void pointer to cbdata
+ *
+ * @return krb5_error_code.
+ */
+
+static krb5_error_code smb_krb5_adns_locator_lookup(void *private_data,
+ enum locate_service_type svc,
+ const char *realm,
+ int socktype,
+ int family,
+ int (*cbfunc)(void *, int, struct sockaddr *),
+ void *cbdata)
+{
+ krb5_error_code ret;
+ const char *service = get_service_from_locate_service_type(svc);
+
+#ifdef DEBUG_KRB5
+ fprintf(stderr,"[%5u]: smb_krb5_adns_locator_lookup: called for '%s' "
+ "svc: '%s' (%d) "
+ "socktype: '%s' (%d), family: '%s' (%d)\n",
+ (unsigned int)getpid(),
+ realm,
+ locate_service_type_name(svc),
+ svc,
+ socktype_name(socktype),
+ socktype,
+ family_name(family),
+ family);
+#endif
+ ret = smb_krb5_adns_locator_lookup_sanity_check(svc,
+ realm,
+ socktype,
+ family);
+ if (ret) {
+#ifdef DEBUG_KRB5
+ fprintf(stderr, "[%5u]: smb_krb5_adns_locator_lookup: "
+ "returning ret: %s (%d)\n",
+ (unsigned int)getpid(),
+ error_message(ret),
+ ret);
+#endif
+ return ret;
+ }
+
+ /*
+ * If is a subsequent lookup for the same realm
+ * and we have a cache for this already, don't re-do
+ * the DNS SRV -> A/AAAA lookups.
+ *
+ * kinit does this a lot, it looks for UDP then TCP.
+ */
+
+ if ((scache == NULL) || strcmp(realm, scache->realm) != 0) {
+ /* Cache is NULL or a different realm lookup. */
+ NTSTATUS status;
+ struct ip_service *kdcs = NULL;
+ int num_kdcs = 0;
+
+ /*
+ * We have a new lookup to do. As it's a singleton
+ * cache make sure we have no old cache.
+ */
+ TALLOC_FREE(scache);
+
+ status = get_kdc_list(realm, NULL, &kdcs, &num_kdcs);
+ if (!NT_STATUS_IS_OK(status)) {
+#ifdef DEBUG_KRB5
+ fprintf(stderr, "[%5u]: "
+ "smb_krb5_adns_locator_lookup: "
+ "get_kdc_list() for realm %s failed "
+ "with %s\n",
+ (unsigned int)getpid(),
+ realm,
+ nt_errstr(status));
+#endif
+ SAFE_FREE(kdcs);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ if (num_kdcs <= 0) {
+ SAFE_FREE(kdcs);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ /*
+ * We looked up the realm successfully.
+ * free up any previous scache and store it.
+ */
+
+ scache = talloc_zero(NULL,
+ struct singleton_realm_kdc_list_cache);
+ if (scache == NULL) {
+ SAFE_FREE(kdcs);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ scache->realm = talloc_strdup(scache, realm);
+ if (scache->realm == NULL) {
+ TALLOC_FREE(scache);
+ SAFE_FREE(kdcs);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ scache->kdc_list = talloc_memdup(scache,
+ kdcs,
+ sizeof(struct ip_service) * num_kdcs);
+ if (scache->kdc_list == NULL) {
+ TALLOC_FREE(scache);
+ SAFE_FREE(kdcs);
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ SAFE_FREE(kdcs);
+ scache->num_kdcs = num_kdcs;
+ }
+#ifdef DEBUG_KRB5
+ else {
+ fprintf(stderr, "[%5u]: "
+ "smb_krb5_adns_locator_lookup: "
+ "returning cached data for realm %s\n",
+ (unsigned int)getpid(),
+ realm);
+ }
+#endif
+ /*
+ * If we get here we know scache contains the right
+ * realm and non-null address list.
+ */
+
+#ifdef DEBUG_KRB5
+ fprintf(stderr, "[%5u]: smb_krb5_adns_locator_lookup: "
+ "got %d IP addresses for realm %s\n",
+ (unsigned int)getpid(),
+ scache->num_kdcs,
+ scache->realm);
+#endif
+
+ /*
+ * Don't free kdc list on success, we're
+ * always returning from the cache.
+ */
+ return smb_krb5_adns_locator_call_cbfunc(scache->kdc_list,
+ scache->num_kdcs,
+ service,
+ socktype,
+ cbfunc,
+ cbdata);
+}
+
+#ifdef HEIMDAL_KRB5_LOCATE_PLUGIN_H
+#define SMB_KRB5_LOCATOR_SYMBOL_NAME resolve /* Heimdal */
+#else
+#define SMB_KRB5_LOCATOR_SYMBOL_NAME service_locator /* MIT */
+#endif
+
+const krb5plugin_service_locate_ftable SMB_KRB5_LOCATOR_SYMBOL_NAME = {
+ .minor_version = 0,
+ .init = smb_krb5_adns_locator_init,
+ .fini = smb_krb5_adns_locator_close,
+#ifdef KRB5_PLUGIN_LOCATE_VERSION_2
+ .old_lookup = smb_krb5_adns_locator_lookup,
+#else
+ .lookup = smb_krb5_adns_locator_lookup,
+#endif
+};
+
+#endif
diff --git a/nsswitch/wscript_build b/nsswitch/wscript_build
index 861ed2f23bf..b7710e5200d 100644
--- a/nsswitch/wscript_build
+++ b/nsswitch/wscript_build
@@ -112,6 +112,23 @@ if bld.CONFIG_SET('HAVE_KRB5_LOCATE_PLUGIN_H'):
realname='winbind_krb5_locator.so',
install_path='${MODULESDIR}/krb5')
+if bld.CONFIG_SET('HAVE_KRB5_LOCATE_PLUGIN_H'):
+ bld.SAMBA_LIBRARY('async_dns_krb5_locator',
+ source='krb5_plugin/async_dns_krb5_locator.c',
+ deps='''
+ talloc
+ addns
+ samba_intl
+ libsmb
+ smbconf
+ KRBCLIENT
+ smbd_base
+ krb5
+ com_err
+ ''',
+ realname='async_dns_krb5_locator.so',
+ install_path='${MODULESDIR}/krb5')
+
if bld.CONFIG_SET('HAVE_KRB5_LOCALAUTH_PLUGIN_H'):
bld.SAMBA_LIBRARY('winbind_krb5_localauth',
source='krb5_plugin/winbind_krb5_localauth.c',
--
2.20.1