Skip to content

Cannot Bind LdapConnection to multiple servers on Linux #79653

@iinuwa

Description

@iinuwa

Description

If you try to use the LdapDirectoryIdentifier(string[] servers, ...) overloads with multiple hostnames on Linux, the program will abort completely due to a null reference in native code (no managed exception thrown).

The problem is the way that the initial LDAP handle is created from the constructor. In the Windows code, the hostnames string is built with the concatenated servers when the handle is created. In Linux, we cannot do the same thing because we require the scheme to be known, which is only possible by setting SessionOptions, which is only available after the constructor exits.

The Linux code currently puts in a dummy value based on the hostname, but this is unnecessary as the ldap_initialize(LDAP **ldp, char* uri) method can take a NULL uri. Also, the current code wastefully builds this hostname string twice on Linux.

There is a patch below; I'll submit a a follow up PR.

Reproduction Steps

using System.DirectoryServices.Protocols;
using System.Net;

Console.WriteLine("Single server");
var identifier = new LdapDirectoryIdentifier("localhost", 1389);
var connection = Bind(identifier);
Console.WriteLine("Bound to single server");
connection.Dispose();

Console.WriteLine("Multiple servers");
var identifier2 = new LdapDirectoryIdentifier(new [] { "localhost", "localhost" }, 1389, false, false);
var connection2 = Bind(identifier2); // OpenLDAP aborts on invalid LDAP handle
Console.WriteLine("Bound to multiple servers");
connection.Dispose();

LdapConnection Bind(LdapDirectoryIdentifier identifier)
{
    var connection = new LdapConnection(identifier, new NetworkCredential("cn=admin,dc=example,dc=org", "password"))
    {
      AuthType = AuthType.Basic,
    };
    connection.SessionOptions.ProtocolVersion = 3;
    connection.Bind();
    return connection;
}
podman run --publish 1389:389 --publish 1636:636 --name ldap --hostname ldap.local --detach --rm --env LDAP_TLS_VERIFY_CLIENT=never --env LDAP_ADMIN_PASSWORD=password osixia/openldap --loglevel debug
dotnet run

Expected behavior

Connections can be bound to multiple servers

Actual behavior

Single server
Bound to single server
Multiple servers
ldapTest: ../../../../libraries/libldap/sasl.c:151: ldap_sasl_bind: Assertion `ld != NULL' failed.

Regression?

I'm not sure how far this goes back, but it's at least since .NET 6 and 7.

Known Workarounds

None.

Configuration

.NET 6
Linux (Ubuntu 22.04)
x64

This is a Linux issue.

Other information

Diff that verifies broken behavior:

diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
index c32078fe4f1..b31cf294f4d 100644
--- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
@@ -670,6 +670,30 @@ public void TestSortedSearch()
             }
         }

+        [ConditionalFact(nameof(IsLdapConfigurationExist))]
+        public void TestMultipleServerBind()
+        {
+            LdapDirectoryIdentifier directoryIdentifier = string.IsNullOrEmpty(LdapConfiguration.Configuration.Port) ?
+                                        new LdapDirectoryIdentifier(new string[] { LdapConfiguration.Configuration.ServerName, LdapConfiguration.Configuration.ServerName }, true, false) :
+                                        new LdapDirectoryIdentifier(new string[] { LdapConfiguration.Configuration.ServerName, LdapConfiguration.Configuration.ServerName },
+                                                                    int.Parse(LdapConfiguration.Configuration.Port, NumberStyles.None, CultureInfo.InvariantCulture),
+                                                                    true, false);
+            NetworkCredential credential = new NetworkCredential(LdapConfiguration.Configuration.UserName, LdapConfiguration.Configuration.Password);
+
+            using LdapConnection connection = new LdapConnection(directoryIdentifier, credential)
+            {
+                AuthType = AuthType.Basic
+            };
+
+            // Set server protocol before bind; OpenLDAP servers default
+            // to LDAP v2, which we do not support, and will return LDAP_PROTOCOL_ERROR
+            connection.SessionOptions.ProtocolVersion = 3;
+            connection.SessionOptions.SecureSocketLayer = LdapConfiguration.Configuration.UseTls;
+            connection.Bind();
+
+            connection.Timeout = new TimeSpan(0, 3, 0);
+        }
+

Patch that fixes it:

diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
index 49dfae1059f..0ce464ce208 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
@@ -13,14 +13,14 @@ public partial class LdapConnection
         // Linux doesn't support setting FQDN so we mark the flag as if it is already set so we don't make a call to set it again.
         private bool _setFQDNDone = true;
 
-        private void InternalInitConnectionHandle(string hostname)
+        private void InternalInitConnectionHandle()
         {
             if ((LdapDirectoryIdentifier)_directoryIdentifier == null)
             {
                 throw new NullReferenceException();
             }
 
-            _ldapHandle = new ConnectionHandle($"ldap://{hostname}:{((LdapDirectoryIdentifier)_directoryIdentifier).PortNumber}");
+            _ldapHandle = new ConnectionHandle();
         }
 
         private int InternalConnectToServer()
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
index 829a3abdc25..4bba6e282f7 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Windows.cs
@@ -4,6 +4,7 @@
 using System.Diagnostics;
 using System.Net;
 using System.Runtime.CompilerServices;
+using System.Text;
 
 namespace System.DirectoryServices.Protocols
 {
@@ -11,8 +12,31 @@ public partial class LdapConnection
     {
         private bool _setFQDNDone;
 
-        private void InternalInitConnectionHandle(string hostname)
+        private void InternalInitConnectionHandle()
         {
+            string hostname = null;
+            string[] servers = ((LdapDirectoryIdentifier)_directoryIdentifier)?.Servers;
+            if (servers != null && servers.Length != 0)
+            {
+                var temp = new StringBuilder(200);
+                for (int i = 0; i < servers.Length; i++)
+                {
+                    if (servers[i] != null)
+                    {
+                        temp.Append(servers[i]);
+                        if (i < servers.Length - 1)
+                        {
+                            temp.Append(' ');
+                        }
+                    }
+                }
+
+                if (temp.Length != 0)
+                {
+                    hostname = temp.ToString();
+                }
+            }
+
             LdapDirectoryIdentifier directoryIdentifier = _directoryIdentifier as LdapDirectoryIdentifier;
 
             // User wants to setup a connectionless session with server.
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs
index e6a9ea69b7e..6eb03213d48 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs
@@ -173,30 +173,7 @@ internal bool NeedDispose
 
         internal void Init()
         {
-            string hostname = null;
-            string[] servers = ((LdapDirectoryIdentifier)_directoryIdentifier)?.Servers;
-            if (servers != null && servers.Length != 0)
-            {
-                var temp = new StringBuilder(200);
-                for (int i = 0; i < servers.Length; i++)
-                {
-                    if (servers[i] != null)
-                    {
-                        temp.Append(servers[i]);
-                        if (i < servers.Length - 1)
-                        {
-                            temp.Append(' ');
-                        }
-                    }
-                }
-
-                if (temp.Length != 0)
-                {
-                    hostname = temp.ToString();
-                }
-            }
-
-            InternalInitConnectionHandle(hostname);
+            InternalInitConnectionHandle();
 
             // Create a WeakReference object with the target of ldapHandle and put it into our handle table.
             lock (s_objectLock)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions