runtime: LDAP referral chasing cannot be disabled on Linux

I have an LDAP server running on a separate machine.

I can run the following code successfully when using the dotnet 5.0.100 SDK on Windows 10, and I can perform the corresponding query successfully using ldapsearch on Ubuntu 20.04.1 LTS. However, running the following code on Ubuntu 20.04.1 LTS or in a Docker container built with the dotnet 5.0 SDK image that also has libldap-common installed fails as described below.

I am attempting to use System.DirectoryServices.Protocol to perform an LDAP query that I was able to perform successfully previously when I was using the Novell library. Specifically, I am creating the connection and binding using

_ldapConnection = new LdapConnection(
                new LdapDirectoryIdentifier(host, port, false, false),
                new NetworkCredential(userName: "CN=Administrator,CN=Users,DC=inc,DC=company,DC=com", password: "mypassword"),
                AuthType.Basic
            );
_ldapConnection.Bind();

And then doing the following query

string searchBase = "DC=inc,DC=company,DC=com";
            string filter = "(&(objectClass=person)(objectClass=user))";
            var search = new SearchRequest(searchBase, filter, SearchScope.Subtree, null);
            var response = (System.DirectoryServices.Protocols.SearchResponse)_ldapConnection.SendRequest(search, System.TimeSpan.FromMinutes(1));

However, doing so results in the following error

Unhandled exception. System.DirectoryServices.Protocols.DirectoryOperationException: An unspecified operation error occurred.
   at System.DirectoryServices.Protocols.LdapConnection.ConstructResponseAsync(Int32 messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeOut, Boolean exceptionOnTimeOut, Boolean sync)
   at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)

I unpacked every property in the exception itself, and in the corresponding response attached to the exception. This is what I found:

Data: System.Collections.ListDictionaryInternal
Help Link: 
HResult: -2146233088
InnerException: 
Message: An unspecified operation error occurred.
Source: System.DirectoryServices.Protocols
StackTrace:    at System.DirectoryServices.Protocols.LdapConnection.ConstructResponseAsync(Int32 messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeOut, Boolean exceptionOnTimeOut, Boolean sync)
   at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
   at Keep_LdapCore.LdapService.LogUsers() in /workspaces/Keep-LdapCore/LdapService.cs:line 35
TargetSite: Void MoveNext()
Response.Controls: System.DirectoryServices.Protocols.DirectoryControl[]
Response.Error Message: Referral:
ldap://ForestDnsZones.inc.company.com/DC=ForestDnsZones,DC=inc,DC=company,DC=com
ldap://DomainDnsZones.inc.company.com/DC=DomainDnsZones,DC=inc,DC=company,DC=com
ldap://inc.company.com/CN=Configuration,DC=inc,DC=company,DC=com
Response.MatchedDN: 
Response.Referral: System.Uri[]
Response.RequestId: 
Response.Result Code: -1

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (11 by maintainers)

Most upvoted comments

Hi, I am also running into this issue and I am able to reproduce with ldapsearch. I am using Microsoft Active Directory as the LDAP server (which has the behaviour described).

I have changed the query so it returns one match and one property only so the ldapsearch output is shorter.

On any other DN other than the base DN, the result looks like this:

$ ldapsearch -o ldif-wrap=no -H ldap://contoso.com -b 'CN=Users,DC=contoso,DC=com' '(sAMAccountName=Administrator)' 'name'
SASL/GSS-SPNEGO authentication started
SASL username: johndoe@contoso.com
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <CN=Users,DC=contoso,DC=com> with scope subtree
# filter: (sAMAccountName=Administrator)
# requesting: name
#

# Administrator, Users, contoso.com
dn: CN=Administrator,CN=Users,DC=contoso,DC=com
name: Administrator

# search result
search: 3
result: 0 Success

# numResponses: 2
# numEntries: 1

But on the root DN, the result looks like this:

$ ldapsearch -o ldif-wrap=no -H ldap://contoso.com -b 'DC=contoso,DC=com' '(sAMAccountName=Administrator)' 'name'
SASL/GSS-SPNEGO authentication started
SASL username: johndoe@contoso.com
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <DC=contoso,DC=com> with scope subtree
# filter: (sAMAccountName=Administrator)
# requesting: name
#

# Administrator, Users, contoso.com
dn: CN=Administrator,CN=Users,DC=contoso,DC=com
name: Administrator

# search reference
ref: ldap://ForestDnsZones.contoso.com/DC=ForestDnsZones,DC=contoso,DC=com

# search reference
ref: ldap://DomainDnsZones.contoso.com/DC=DomainDnsZones,DC=contoso,DC=com

# search reference
ref: ldap://contoso.com/CN=Configuration,DC=contoso,DC=com

# search result
search: 3
result: 0 Success

# numResponses: 5
# numEntries: 1
# numReferences: 3

The difference between the two is that referrals are returned. They can be also seen in the first comment in this issue after the line Response.Error Message: Referral: in the exception details, ForestDnsZones and DomainDnsZones are usually Active Directory specific.

By default, ldapsearch does not follow referrals, the option -C enables referral following:

$ ldapsearch -C -o ldif-wrap=no -H ldap://contoso.com -b 'DC=contoso,DC=com' '(sAMAccountName=Administrator)' 'name'
SASL/GSS-SPNEGO authentication started
SASL username: johndoe@contoso.com
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <DC=contoso,DC=com> with scope subtree
# filter: (sAMAccountName=Administrator)
# requesting: name
#

# Administrator, Users, contoso.com
dn: CN=Administrator,CN=Users,DC=contoso,DC=com
name: Administrator

# search reference
ref: ldap://ForestDnsZones.contoso.com/DC=ForestDnsZones,DC=contoso,DC=com

# search reference
ref: ldap://contoso.com/CN=Configuration,DC=contoso,DC=com

# search reference
ref: ldap://DomainDnsZones.contoso.com/DC=DomainDnsZones,DC=contoso,DC=com

# search reference
ref: ldap://contoso.com/CN=Schema,CN=Configuration,DC=contoso,DC=com

# search result
search: 3
result: 1 Operations error
text: 000004DC: LdapErr: DSID-0C090A7D, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v3839

# numResponses: 6
# numEntries: 1
# numReferences: 4

So I can get the operation error using ldapsearch. (The option is in fact obsolete, see here, it’s not documented)

Trying to understand what is happening, I turned on debug logging for ldapsearch (-d 1), indicated a SASL mechanism so it’s easier to see in the debug log (-Y GSSAPI), redirected the debug log to stdout and filtered the output with grep -B 10 -A 5 ldap_pvt_connect. I get the following output:

$ ldapsearch -C -d 1 -o ldif-wrap=no -Y GSSAPI -H ldap://contoso.com -b 'DC=contoso,DC=com' -s subtree '(sAMAccountName=Administrator)' 'name' 2>&1 | grep -B 10 -A 5 ldap_pvt_connect
ldap_create
ldap_url_parse_ext(ldap://contoso.com:389/??base)
ldap_sasl_interactive_bind: user selected: GSSAPI
ldap_int_sasl_bind: GSSAPI
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP contoso.com:389
ldap_new_socket: 3
ldap_prepare_socket: 3
ldap_connect_to_host: Trying 10.10.10.42:389
ldap_pvt_connect: fd: 3 tm: -1 async: 0
attempting to connect:
connect success
ldap_int_sasl_open: host=dc1.contoso.com
SASL/GSSAPI authentication started
ldap_sasl_bind
--
ber_scanf fmt ({it) ber:
ber_scanf fmt ({me) ber:
ldap_chase_v3referral: msgid 4, url "ldap://ForestDnsZones.contoso.com/DC=ForestDnsZones,DC=contoso,DC=com"
ldap_send_server_request
ldap_new_connection 0 1 1
ldap_int_open_connection
ldap_connect_to_host: TCP ForestDnsZones.contoso.com:389
ldap_new_socket: 5
ldap_prepare_socket: 5
ldap_connect_to_host: Trying 10.10.10.42:389
ldap_pvt_connect: fd: 5 tm: -1 async: 0
attempting to connect:
connect success
anonymous rebind via ldap_sasl_bind("")
ldap_sasl_bind
ldap_send_initial_request
--
ber_scanf fmt ({it) ber:
ber_scanf fmt ({me) ber:
ldap_chase_v3referral: msgid 4, url "ldap://DomainDnsZones.contoso.com/DC=DomainDnsZones,DC=contoso,DC=com"
ldap_send_server_request
ldap_new_connection 0 1 1
ldap_int_open_connection
ldap_connect_to_host: TCP DomainDnsZones.contoso.com:389
ldap_new_socket: 6
ldap_prepare_socket: 6
ldap_connect_to_host: Trying 10.10.10.42:389
ldap_pvt_connect: fd: 6 tm: -1 async: 0
attempting to connect:
connect success
anonymous rebind via ldap_sasl_bind("")
ldap_sasl_bind
ldap_send_initial_request

OpenLDAP seems to follow referrals unauthenticated by default and Active Directory seems to accept the anonymous bind done in this case but gives an error on the first request done on the connection (the errors are further in the output but mixed together).

Checking in the OpenLDAP documentation, referrals can be turned off or a rebind callback can be given and neither of the two options work on LdapConnection because of differences between Windows and OpenLDAP.

For the first way, referrals are turned off with LDAP_OPT_OFF (and on with LDAP_OPT_ON) with the option LDAP_OPT_REFERRALS (see here for the definition). It is is used in LdapConnection.SessionOptions.ReferralChasing which is a property with the enum type ReferralChasingOptions which matches the values in Windows (see here) but there are extra values that OpenLDAP does not support. In fact, as LDAP_OPT_OFF is defined as ((void *) 0) in OpenLDAP, the define itself must be passed to ldap_set_option as the “pointer” to the option, not a pointer to the value like in Windows, so setting the property always turns on referrals. (The OpenLDAP code only checks for zero / LDAP_OPT_OFF for boolean options (see example here), so LDAP_OPT_ON can be any non-zero pointer)

For the second way, callbacks are set with LdapConnection.SessionOptions.ReferralCallback and this property contains three delegates which match the ones in the structure LDAP_REFERRAL_CALLBACK (see here) and the structure is passed to ldap_set_option with LDAP_OPT_REFERRAL_CALLBACK to set them on a LDAP connection object. But OpenLDAP does not support this structure and option, it only has one callback which is set with ldap_set_rebind_proc and is for rebinds only, there is no multiple callbacks, so setting the property causes a LDAP exception when checking the error code of ldap_set_option.

Yup, there is a command called ldapsearch which is includded when you install the ldap-utils package. This one uses libldap underneath in a very similar fashion as we do except that it won’t depend on a previous bind since you will pass credentials directly into the command, whereas with us we get a handle that is bounded and then perform the search on it.

In this case I don’t think that libldap is the problem, @NadimNadimNadimNadimNadim’s search has a correct filter and query, so assuming that the starting DN exists on the directory then query should in theory work. In fact, the search on their code looks very similar to the ones we use in our tests:

https://github.com/dotnet/runtime/blob/db3649e67ca6b56eb0e604f7c2e3a2479b02d950/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs#L549-L551

That is why I’m thinking that the problem here is that the bind was unsuccessful, and why I asked to try with a different username to see if that solves the issue.

It varies (something we are trying to do better at) but you can always ping the folks listed at https://github.com/dotnet/runtime/blob/master/docs/area-owners.md

In this case I happen to know that it’s @joperezr for Linux stuff - hopefully he can help here.