Trying to perform an LDAP request using Negotiate or Kerberos authentication with the System.DirectoryServices.Protocols package on macOS throws LdapException: The feature is not supported.
using System;
using System.DirectoryServices.Protocols;
using System.Linq;
using var ldapConnection = new LdapConnection(new LdapDirectoryIdentifier(Environment.GetEnvironmentVariable("LDAPHOST")), null, AuthType.Kerberos);
ldapConnection.SessionOptions.ReferralChasing = ReferralChasingOptions.None;
var searchRequest = new SearchRequest(Environment.GetEnvironmentVariable("LDAPBASE"), "(&(objectCategory=person)(objectClass=user)(c=ZZ))", SearchScope.Subtree, "mail");
var response = (SearchResponse)ldapConnection.SendRequest(searchRequest);
foreach (SearchResultEntry entry in response.Entries)
{
Console.WriteLine($"mail: {entry.Attributes["mail"]?.GetValues(typeof(string))?.OfType<string>().FirstOrDefault()}");
}
Console.WriteLine($"Found {response.Entries.Count} entries");
Export the LDAPHOST environment variable to your actual domain controller (e.g. dc.contoso.com) and LDAPBASE to your actual search base (e.g. DC=contoso,DC=com) then run the ldap console app with the dotnet run command.
export LDAPHOST="dc.contoso.com"
export LDAPBASE="DC=contoso,DC=com"
dotnet run
Expected behavior
The program runs and prints Found 0 entries (because no user actually matches the c=ZZ filter criteria).
Actual behavior
The program throws System.DirectoryServices.Protocols.LdapException: The feature is not supported.
Stack trace:
at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
at System.DirectoryServices.Protocols.LdapConnection.SendRequestHelper(DirectoryRequest request, Int32& messageID)
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)
at Program.<Main>$(String[] args) in Program.cs:line 8
Regression?
Probably not a regression.
Known Workarounds
It's possible to get the code working by explicitly settings the LDAP protocol to version 3 by either adding ldapConnection.SessionOptions.ProtocolVersion = 3; just after creating the LdapConnection object or by creating an ~/.ldaprc file with this content, as documented in ldap.conf(5):
VERSION 3
Configuration
The bug probably happens on Linux too since it shares a lot of the implementation, but I have not tested.
dotnet --info
.NET SDK:
Version: 8.0.403
Commit: c64aa40a71
Workload version: 8.0.400-manifests.51490631
MSBuild version: 17.11.9+a69bbaaf5
Runtime Environment:
OS Name: Mac OS X
OS Version: 13.6
OS Platform: Darwin
RID: osx-arm64
Base Path: /usr/local/share/dotnet/sdk/8.0.403/
.NET workloads installed:
Configured to use loose manifests when installing new manifests.
There are no installed workloads to display.
Host:
Version: 8.0.10
Architecture: arm64
Commit: 81cabf2857
.NET SDKs installed:
6.0.425 [/usr/local/share/dotnet/sdk]
8.0.403 [/usr/local/share/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.33 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.10 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.33 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.10 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
x64 [/usr/local/share/dotnet/x64]
registered at [/etc/dotnet/install_location_x64]
Environment variables:
Not set
global.json file:
Not found
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download
Other information
The workaround looks simple but it took me many hours reading the OpenLDAP source code the to figure it out!
/* do a quick !LDAPv3 check... ldap_sasl_bind will do the rest. */
if (ld->ld_version < LDAP_VERSION3) {
ld->ld_errno = LDAP_NOT_SUPPORTED;
return ld->ld_errno;
}
Since the System.DirectoryServices.Protocols package description says a managed implementation of Lightweight Directory Access Protocol (LDAP) version 3 I think it should use the LDAPV3 protocol by default and thus completely prevent this exception.
I'll submit a pull request shortly to address this issue.
Description
Trying to perform an LDAP request using Negotiate or Kerberos authentication with the System.DirectoryServices.Protocols package on macOS throws
LdapException: The feature is not supported
.Reproduction Steps
ldap.csproj
Program.cs
Export the
LDAPHOST
environment variable to your actual domain controller (e.g.dc.contoso.com
) andLDAPBASE
to your actual search base (e.g.DC=contoso,DC=com
) then run theldap
console app with thedotnet run
command.Expected behavior
The program runs and prints
Found 0 entries
(because no user actually matches thec=ZZ
filter criteria).Actual behavior
The program throws
System.DirectoryServices.Protocols.LdapException: The feature is not supported.
Stack trace:
Regression?
Probably not a regression.
Known Workarounds
It's possible to get the code working by explicitly settings the LDAP protocol to version 3 by either adding
ldapConnection.SessionOptions.ProtocolVersion = 3;
just after creating theLdapConnection
object or by creating an~/.ldaprc
file with this content, as documented in ldap.conf(5):Configuration
The bug probably happens on Linux too since it shares a lot of the implementation, but I have not tested.
Other information
The workaround looks simple but it took me many hours reading the OpenLDAP source code the to figure it out!
I had to understand that the default LDAP version is V2 but that the command line tools such as
ldapsearch
works fine because the LDAP version is explicitly set to V3 when the-P
option (protocol version) is not specified. The key part was understanding that the LdapException was coming from the protocol version check in theldap_int_sasl_bind
method.Since the System.DirectoryServices.Protocols package description says a managed implementation of Lightweight Directory Access Protocol (LDAP) version 3 I think it should use the LDAPV3 protocol by default and thus completely prevent this exception.
I'll submit a pull request shortly to address this issue.