From e37aac7b4f88dde71b71093601647ef4ac094be3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 2 Jul 2026 16:36:52 +0200 Subject: [PATCH] ldap: Replace deprecated function calls and support LDAPS The use of the deprecated `ldap_init()` meant that LDAPS, although announced by the plugin, was not actually supported. The plugin just always used a plaintext connection. Now we use the current API and get support for LDAPS (requires a bit of an awkward URI construction). Based on the URI's scheme we also set an option to enforce a certificate check. The new NEWCTX option creates a connection-specific TLS context. Without that we get a global default context once bind is called that is not freed until the daemon exits (it leaks in LD and also seems unsafe in regards to multiple threads fetching CRLs via LDAP). Fixes: 552cc11b1f01 ("merged the modularization branch (credentials) back to trunk") Fixes: 8c06e9c0edc5 ("added #define LDAP_DEPRECATED in order to use old ldap_init() function") --- configure.ac | 2 +- src/libstrongswan/plugins/ldap/ldap_fetcher.c | 137 +++++++++++++++--- src/libstrongswan/utils/leak_detective.c | 1 + 3 files changed, 115 insertions(+), 25 deletions(-) diff --git a/configure.ac b/configure.ac index a64c3c58b..e81ed1e8f 100644 --- a/configure.ac +++ b/configure.ac @@ -1027,7 +1027,7 @@ if test x$gmp = xtrue; then fi if test x$ldap = xtrue; then - AC_CHECK_LIB([ldap],[ldap_init],[LIBS="$LIBS"],[AC_MSG_ERROR([LDAP library ldap not found])],[]) + AC_CHECK_LIB([ldap],[ldap_initialize],[LIBS="$LIBS"],[AC_MSG_ERROR([LDAP library ldap not found])],[]) AC_CHECK_LIB([lber],[ber_free],[LIBS="$LIBS"],[AC_MSG_ERROR([LDAP library lber not found])],[]) AC_CHECK_HEADER([ldap.h],,[AC_MSG_ERROR([LDAP header ldap.h not found!])]) fi diff --git a/src/libstrongswan/plugins/ldap/ldap_fetcher.c b/src/libstrongswan/plugins/ldap/ldap_fetcher.c index 74f73e56b..530313f48 100644 --- a/src/libstrongswan/plugins/ldap/ldap_fetcher.c +++ b/src/libstrongswan/plugins/ldap/ldap_fetcher.c @@ -15,12 +15,9 @@ * for more details. */ -#ifndef LDAP_DEPRECATED -#define LDAP_DEPRECATED 1 -#endif /* LDAP_DEPRECATED */ -#include +#define _GNU_SOURCE /* for asprintf() */ -#include +#include #include #include @@ -53,6 +50,7 @@ static bool parse(LDAP *ldap, LDAPMessage *result, chunk_t *response) { LDAPMessage *entry = ldap_first_entry(ldap, result); bool success = FALSE; + int res = 0; if (entry) { @@ -80,15 +78,17 @@ static bool parse(LDAP *ldap, LDAPMessage *result, chunk_t *response) } else { + ldap_get_option(ldap, LDAP_OPT_RESULT_CODE, &res); DBG1(DBG_LIB, "getting LDAP values failed: %s", - ldap_err2string(ldap_result2error(ldap, entry, 0))); + ldap_err2string(res)); } ldap_memfree(attr); } else { + ldap_get_option(ldap, LDAP_OPT_RESULT_CODE, &res); DBG1(DBG_LIB, "finding LDAP attributes failed: %s", - ldap_err2string(ldap_result2error(ldap, entry, 0))); + ldap_err2string(res)); } ber_free(ber, 0); } @@ -99,18 +99,72 @@ static bool parse(LDAP *ldap, LDAPMessage *result, chunk_t *response) return success; } +/** + * Set an LDAP option and report the error if necessary + */ +static bool set_ldap_option(LDAP *ldap, int option, const void *value, + const char *name) +{ + int res; + + res = ldap_set_option(ldap, option, value); + if (res != LDAP_OPT_SUCCESS) + { + DBG1(DBG_LIB, "setting LDAP option '%s' failed: %s", name, + ldap_err2string(res)); + return FALSE; + } + return TRUE; +} + +/** + * Generate an URL accepted by ldap_initialize(), only consisting of scheme, + * host and port. + */ +static char *build_ldap_url(LDAPURLDesc *lurl) +{ + char *url = NULL; + bool ipv6; + + if (!lurl->lud_host || !*lurl->lud_host) + { + DBG1(DBG_LIB, "LDAP URL without host not supported"); + return NULL; + } + + ipv6 = strchr(lurl->lud_host, ':') != NULL; + if (lurl->lud_port) + { + if (asprintf(&url, ipv6 ? "%s://[%s]:%d" : "%s://%s:%d", + lurl->lud_scheme, lurl->lud_host, lurl->lud_port) < 0) + { + return NULL; + } + } + else + { + if (asprintf(&url, ipv6 ? "%s://[%s]" : "%s://%s", lurl->lud_scheme, + lurl->lud_host) < 0) + { + return NULL; + } + } + return url; +} METHOD(fetcher_t, fetch, status_t, private_ldap_fetcher_t *this, char *url, void *userdata) { - LDAP *ldap; + LDAP *ldap = NULL; LDAPURLDesc *lurl; + char *ldap_url; LDAPMessage *msg; - int res; - int ldap_version = LDAP_VERSION3; - struct timeval timeout; - status_t status = FAILED; chunk_t *result = userdata; + struct timeval timeout; + int ldap_version = LDAP_VERSION3; + int res; + struct berval cred = {0, ""}; + status_t status = FAILED; if (!strpfx(url, "ldap")) { @@ -120,29 +174,65 @@ METHOD(fetcher_t, fetch, status_t, { return NOT_SUPPORTED; } - ldap = ldap_init(lurl->lud_host, lurl->lud_port); - if (ldap == NULL) + if (!streq(lurl->lud_scheme, "ldap") && !streq(lurl->lud_scheme, "ldaps")) + { + ldap_free_urldesc(lurl); + return NOT_SUPPORTED; + } + /* ldap_initialize() only expects "scheme://host:port", so we have to + * generate an appropriate string */ + ldap_url = build_ldap_url(lurl); + if (!ldap_url) { - DBG1(DBG_LIB, "LDAP initialization failed: %s", strerror(errno)); ldap_free_urldesc(lurl); return FAILED; } + res = ldap_initialize(&ldap, ldap_url); + free(ldap_url); + if (res != LDAP_SUCCESS) + { + DBG1(DBG_LIB, "LDAP initialization failed: %s", ldap_err2string(res)); + ldap_free_urldesc(lurl); + return FAILED; + } + + if (streq(lurl->lud_scheme, "ldaps")) + { + int require_cert = LDAP_OPT_X_TLS_DEMAND; + + if (!set_ldap_option(ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert, + "TLS require cert") || + !set_ldap_option(ldap, LDAP_OPT_X_TLS_NEWCTX, LDAP_OPT_ON, + "TLS context")) + { + ldap_unbind_ext_s(ldap, NULL, NULL); + ldap_free_urldesc(lurl); + return FAILED; + } + } timeout.tv_sec = this->timeout; timeout.tv_usec = 0; - ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version); - ldap_set_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &timeout); - ldap_set_option(ldap, LDAP_OPT_TIMEOUT, &timeout); + if (!set_ldap_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version, + "protocol version") || + !set_ldap_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &timeout, + "network timeout") || + !set_ldap_option(ldap, LDAP_OPT_TIMEOUT, &timeout, "timeout")) + { + ldap_unbind_ext_s(ldap, NULL, NULL); + ldap_free_urldesc(lurl); + return FAILED; + } DBG2(DBG_LIB, "sending LDAP request to '%s'...", url); - res = ldap_simple_bind_s(ldap, NULL, NULL); + res = ldap_sasl_bind_s(ldap, NULL, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); if (res == LDAP_SUCCESS) { - res = ldap_search_st(ldap, lurl->lud_dn, lurl->lud_scope, - lurl->lud_filter, lurl->lud_attrs, - 0, &timeout, &msg); + res = ldap_search_ext_s(ldap, lurl->lud_dn, lurl->lud_scope, + lurl->lud_filter, lurl->lud_attrs, + 0, NULL, NULL, &timeout, LDAP_NO_LIMIT, &msg); if (res == LDAP_SUCCESS) { @@ -162,7 +252,7 @@ METHOD(fetcher_t, fetch, status_t, DBG1(DBG_LIB, "LDAP bind to '%s' failed: %s", url, ldap_err2string(res)); } - ldap_unbind_s(ldap); + ldap_unbind_ext_s(ldap, NULL, NULL); ldap_free_urldesc(lurl); return status; } @@ -213,4 +303,3 @@ ldap_fetcher_t *ldap_fetcher_create() return &this->public; } - diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 616613a72..e9dc49cc6 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -565,6 +565,7 @@ static char *whitelist[] = { "Curl_client_write", /* libldap */ "ldap_int_initialize", + "ldap_pvt_tls_set_option", /* ClearSilver */ "nerr_init", /* libgcrypt */