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: 552cc11b1f ("merged the modularization branch (credentials) back to trunk")
Fixes: 8c06e9c0ed ("added #define LDAP_DEPRECATED in order to use old ldap_init() function")
This commit is contained in:
Tobias Brunner
2026-07-24 08:47:37 +02:00
parent e64877b46c
commit e37aac7b4f
3 changed files with 115 additions and 25 deletions
+1 -1
View File
@@ -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
+113 -24
View File
@@ -15,12 +15,9 @@
* for more details.
*/
#ifndef LDAP_DEPRECATED
#define LDAP_DEPRECATED 1
#endif /* LDAP_DEPRECATED */
#include <ldap.h>
#define _GNU_SOURCE /* for asprintf() */
#include <errno.h>
#include <ldap.h>
#include <library.h>
#include <utils/debug.h>
@@ -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;
}
+1
View File
@@ -565,6 +565,7 @@ static char *whitelist[] = {
"Curl_client_write",
/* libldap */
"ldap_int_initialize",
"ldap_pvt_tls_set_option",
/* ClearSilver */
"nerr_init",
/* libgcrypt */