From 728dd396f81a4a9e48c4828586574c3743db0d5c Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 25 Sep 2020 13:42:46 -0700 Subject: [PATCH] nsswitch: Add an async DNS kerberos locator plugin. Used in production on a large customer site. Signed-off-by: Jeremy Allison Reviewed-by: Ralph Boehme --- nsswitch/krb5_plugin/async_dns_krb5_locator.c | 445 ++++++++++++++++++ nsswitch/wscript_build | 17 + 2 files changed, 462 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..959d730a954 --- /dev/null +++ b/nsswitch/krb5_plugin/async_dns_krb5_locator.c @@ -0,0 +1,445 @@ +/* + 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 . +*/ + +#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 +#endif + +#include +#include + +#ifndef KRB5_PLUGIN_NO_HANDLE +#define KRB5_PLUGIN_NO_HANDLE KRB5_KDC_UNREACH /* Heimdal */ +#endif + +struct singleton_realm_kdc_list_cache { + char *realm; + struct samba_sockaddr *kdc_list; + size_t 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 samba_sockaddr *kdcs, + size_t num_kdcs, + const char *service, + int socktype, + int (*cbfunc)(void *, int, struct sockaddr *), + void *cbdata) +{ + int ret = 0; + size_t i; + + for (i = 0; i < num_kdcs; i++) { + struct sockaddr *sa = NULL; + + if (kdcs[i].u.ss.ss_family == AF_INET) { + struct sockaddr_in *sin = &kdcs[i].u.in; + sin->sin_family = AF_INET; + sin->sin_port = htons(88); + sa = &kdcs[i].u.sa; + } +#if defined(HAVE_IPV6) + if (kdcs[i].u.ss.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = &kdcs[i].u.in6; + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(88); + sa = &kdcs[i].u.sa; + } +#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[%zu] %s\n", + (unsigned int)getpid(), + i, + print_sockaddr(addr, + sizeof(addr), + &kdcs[i].u.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; + + /* + * We have a new lookup to do. As it's a singleton + * cache make sure we have no old cache. + */ + TALLOC_FREE(scache); + + scache = talloc_zero(NULL, + struct singleton_realm_kdc_list_cache); + if (scache == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + scache->realm = talloc_strdup(scache, realm); + if (scache->realm == NULL) { + TALLOC_FREE(scache); + return KRB5_PLUGIN_NO_HANDLE; + } + + status = get_kdc_list(scache, + realm, + NULL, + &scache->kdc_list, + &scache->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 + TALLOC_FREE(scache); + return KRB5_PLUGIN_NO_HANDLE; + } + if (scache->num_kdcs == 0) { + TALLOC_FREE(scache); + return KRB5_PLUGIN_NO_HANDLE; + } + } +#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 %zu 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 b754c168425..e612377962c 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',