diff --git a/src/libraries/Common/src/Interop/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Interop.Ldap.cs
index 4ff3fd17eed9b2..84d78f6002e510 100644
--- a/src/libraries/Common/src/Interop/Interop.Ldap.cs
+++ b/src/libraries/Common/src/Interop/Interop.Ldap.cs
@@ -99,6 +99,7 @@ internal enum LdapOption
LDAP_OPT_SECURITY_CONTEXT = 0x99,
LDAP_OPT_ROOTDSE_CACHE = 0x9a, // Not Supported in Linux
LDAP_OPT_DEBUG_LEVEL = 0x5001,
+ LDAP_OPT_URI = 0x5006, // Not Supported in Windows
LDAP_OPT_X_SASL_REALM = 0x6101,
LDAP_OPT_X_SASL_AUTHCID = 0x6102,
LDAP_OPT_X_SASL_AUTHZID = 0x6103
diff --git a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
index 392add014ed6ad..37b79557c50a64 100644
--- a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
+++ b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
@@ -61,6 +61,8 @@ internal enum SaslChallengeType
internal static partial class Interop
{
+ public const string LDAP_SASL_SIMPLE = null;
+
internal static partial class Ldap
{
static Ldap()
@@ -75,10 +77,7 @@ static Ldap()
}
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_initialize", CharSet = CharSet.Ansi, SetLastError = true)]
- public static extern int ldap_initialize(out IntPtr ld, string hostname);
-
- [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_init", CharSet = CharSet.Ansi, SetLastError = true)]
- public static extern IntPtr ldap_init(string hostName, int portNumber);
+ public static extern int ldap_initialize(out IntPtr ld, string uri);
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_unbind_ext_s", CharSet = CharSet.Ansi)]
public static extern int ldap_unbind_ext_s(IntPtr ld, ref IntPtr serverctrls, ref IntPtr clientctrls);
@@ -125,6 +124,9 @@ static Ldap()
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_set_option", CharSet = CharSet.Ansi)]
public static extern int ldap_set_option_ptr([In] ConnectionHandle ldapHandle, [In] LdapOption option, ref IntPtr inValue);
+ [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_set_option", CharSet = CharSet.Ansi)]
+ public static extern int ldap_set_option_string([In] ConnectionHandle ldapHandle, [In] LdapOption option, string inValue);
+
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_set_option", CharSet = CharSet.Ansi)]
public static extern int ldap_set_option_referral([In] ConnectionHandle ldapHandle, [In] LdapOption option, ref LdapReferralCallback outValue);
@@ -143,15 +145,12 @@ static Ldap()
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_parse_reference", CharSet = CharSet.Ansi)]
public static extern int ldap_parse_reference([In] ConnectionHandle ldapHandle, [In] IntPtr result, ref IntPtr referrals, IntPtr ServerControls, byte freeIt);
+ [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_sasl_bind_s", CharSet = CharSet.Ansi)]
+ internal static extern int ldap_sasl_bind([In] ConnectionHandle ld, string dn, string mechanism, berval cred, IntPtr serverctrls, IntPtr clientctrls, IntPtr servercredp);
+
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_sasl_interactive_bind_s", CharSet = CharSet.Ansi)]
internal static extern int ldap_sasl_interactive_bind([In] ConnectionHandle ld, string dn, string mechanism, IntPtr serverctrls, IntPtr clientctrls, uint flags, [MarshalAs(UnmanagedType.FunctionPtr)] LDAP_SASL_INTERACT_PROC proc, IntPtr defaults);
- [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_simple_bind_s", CharSet = CharSet.Ansi, SetLastError = true)]
- public static extern int ldap_simple_bind([In] ConnectionHandle ld, string who, string passwd);
-
- [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_bind_s", CharSet = CharSet.Ansi, SetLastError = true)]
- public static extern int ldap_bind_s([In] ConnectionHandle ld, string who, string passwd, int method);
-
[DllImport(Libraries.OpenLdap, EntryPoint = "ldap_err2string", CharSet = CharSet.Ansi)]
public static extern IntPtr ldap_err2string(int err);
diff --git a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
index a80e41b7daf34e..24e41ecd4e7ea5 100644
--- a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
+++ b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml
@@ -28,6 +28,34 @@ and to test and view status
docker exec -it slapd01 slapcat
+SLAPD OPENLDAP SERVER WITH TLS
+==============================
+
+The osixia/openldap container image automatically creates a TLS lisener with a self-signed certificate. This can be used to test TLS.
+
+Start the container, with TLS on port 1636, without client certificate verification:
+
+ docker 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
+
+Extract the CA certificate and write to a temporary file:
+
+ docker exec ldap cat /container/service/slapd/assets/certs/ca.crt > /tmp/ca.crt
+
+Set the LDAP client CA certificate path in `/etc/ldap/ldap.conf` so OpenLDAP trusts the self-signed certificate:
+
+ # /etc/ldap/ldap.conf
+ #...
+ TLS_CACERT /tmp/ca.crt
+
+Finally, map the `ldap.local` hostname manually set above to the loopback address:
+
+ # /etc/hosts
+ 127.0.0.1 ldap.local
+
+To test and view the status:
+
+ ldapsearch -H ldaps://ldap.local:1636 -b dc=example,dc=org -x -D cn=admin,dc=example,dc=org -w password
+
ACTIVE DIRECTORY
================
@@ -83,5 +111,14 @@ Note:
%TESTPASSWORD%
ServerBind,None
+
+ ldap.local
+ DC=example,DC=org
+ 1636
+ cn=admin,dc=example,dc=org
+ password
+ ServerBind,None
+ true
+
\ No newline at end of file
diff --git a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
index 32d3f9b277016b..a9156b8efd5bbe 100644
--- a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
+++ b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs
@@ -10,7 +10,7 @@ namespace System.DirectoryServices.Tests
{
internal class LdapConfiguration
{
- private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at)
+ private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls)
{
ServerName = serverName;
SearchDn = searchDn;
@@ -18,6 +18,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
Password = password;
Port = port;
AuthenticationTypes = at;
+ UseTls = useTls;
}
private static LdapConfiguration s_ldapConfiguration = GetConfiguration("LDAP.Configuration.xml");
@@ -30,6 +31,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s
internal string Port { get; set; }
internal string SearchDn { get; set; }
internal AuthenticationTypes AuthenticationTypes { get; set; }
+ internal bool UseTls { get; set; }
internal string LdapPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{SearchDn}" : $"LDAP://{ServerName}:{Port}/{SearchDn}";
internal string RootDSEPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/rootDSE" : $"LDAP://{ServerName}:{Port}/rootDSE";
internal string UserNameWithNoDomain
@@ -104,6 +106,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
string user = "";
string password = "";
AuthenticationTypes at = AuthenticationTypes.None;
+ bool useTls = false;
XElement child = connection.Element("ServerName");
if (child != null)
@@ -132,6 +135,12 @@ internal static LdapConfiguration GetConfiguration(string configFile)
password = val;
}
+ child = connection.Element("UseTls");
+ if (child != null)
+ {
+ useTls = bool.Parse(child.Value);
+ }
+
child = connection.Element("AuthenticationTypes");
if (child != null)
{
@@ -161,7 +170,7 @@ internal static LdapConfiguration GetConfiguration(string configFile)
at |= AuthenticationTypes.Signing;
}
- ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at);
+ ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls);
}
}
catch (Exception ex)
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
index 87219ce1ff2ff0..31750f15897f8d 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
@@ -92,12 +92,32 @@ internal static int SearchDirectory(ConnectionHandle ldapHandle, string dn, int
internal static int SetPtrOption(ConnectionHandle ldapHandle, LdapOption option, ref IntPtr inValue) => Interop.Ldap.ldap_set_option_ptr(ldapHandle, option, ref inValue);
+ internal static int SetStringOption(ConnectionHandle ldapHandle, LdapOption option, string inValue) => Interop.Ldap.ldap_set_option_string(ldapHandle, option, inValue);
+
internal static int SetReferralOption(ConnectionHandle ldapHandle, LdapOption option, ref LdapReferralCallback outValue) => Interop.Ldap.ldap_set_option_referral(ldapHandle, option, ref outValue);
// This option is not supported in Linux, so it would most likely throw.
internal static int SetServerCertOption(ConnectionHandle ldapHandle, LdapOption option, VERIFYSERVERCERT outValue) => Interop.Ldap.ldap_set_option_servercert(ldapHandle, option, outValue);
- internal static int BindToDirectory(ConnectionHandle ld, string who, string passwd) => Interop.Ldap.ldap_simple_bind(ld, who, passwd);
+ internal static int BindToDirectory(ConnectionHandle ld, string who, string passwd)
+ {
+ IntPtr passwordPtr = IntPtr.Zero;
+ try
+ {
+ passwordPtr = LdapPal.StringToPtr(passwd);
+ berval passwordBerval = new berval
+ {
+ bv_len = passwd.Length,
+ bv_val = passwordPtr,
+ };
+
+ return Interop.Ldap.ldap_sasl_bind(ld, who, Interop.LDAP_SASL_SIMPLE, passwordBerval, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(passwordPtr);
+ }
+ }
internal static int StartTls(ConnectionHandle ldapHandle, ref int ServerReturnValue, ref IntPtr Message, IntPtr ServerControls, IntPtr ClientControls) => Interop.Ldap.ldap_start_tls(ldapHandle, ref ServerReturnValue, ref Message, ServerControls, ClientControls);
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 fa512418f6185b..02c8f0c239587b 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
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Net;
+using System.Text;
using System.Runtime.InteropServices;
namespace System.DirectoryServices.Protocols
@@ -12,13 +13,67 @@ 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) => _ldapHandle = new ConnectionHandle(Interop.Ldap.ldap_init(hostname, ((LdapDirectoryIdentifier)_directoryIdentifier).PortNumber), _needDispose);
+ private void InternalInitConnectionHandle(string hostname)
+ {
+ if ((LdapDirectoryIdentifier)_directoryIdentifier == null)
+ {
+ throw new NullReferenceException();
+ }
+
+ _ldapHandle = new ConnectionHandle();
+ }
private int InternalConnectToServer()
{
+ // In Linux you don't have to call Connect after calling init. You
+ // directly call bind. However, we set the URI for the connection
+ // here instead of during initialization because we need access to
+ // the SessionOptions property to properly define it, which is not
+ // available during init.
Debug.Assert(!_ldapHandle.IsInvalid);
- // In Linux you don't have to call Connect after calling init. You directly call bind.
- return 0;
+
+ string scheme = null;
+ LdapDirectoryIdentifier directoryIdentifier = (LdapDirectoryIdentifier)_directoryIdentifier;
+ if (directoryIdentifier.Connectionless)
+ {
+ scheme = "cldap://";
+ }
+ else if (SessionOptions.SecureSocketLayer)
+ {
+ scheme = "ldaps://";
+ }
+ else
+ {
+ scheme = "ldap://";
+ }
+
+ string uris = null;
+ string[] servers = directoryIdentifier.Servers;
+ if (servers != null && servers.Length != 0)
+ {
+ StringBuilder temp = new StringBuilder(200);
+ for (int i = 0; i < servers.Length; i++)
+ {
+ if (i != 0)
+ {
+ temp.Append(' ');
+ }
+ temp.Append(scheme);
+ temp.Append(servers[i]);
+ temp.Append(':');
+ temp.Append(directoryIdentifier.PortNumber);
+ }
+ if (temp.Length != 0)
+ {
+ uris = temp.ToString();
+ }
+ }
+ else
+ {
+ uris = $"{scheme}:{directoryIdentifier.PortNumber}";
+ }
+
+ return LdapPal.SetStringOption(_ldapHandle, LdapOption.LDAP_OPT_URI, uris);
}
private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTITY_EX cred, BindMethod method)
@@ -30,7 +85,7 @@ private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTI
}
else
{
- error = Interop.Ldap.ldap_simple_bind(_ldapHandle, cred.user, cred.password);
+ error = LdapPal.BindToDirectory(_ldapHandle, cred.user, cred.password);
}
return error;
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
index 052ef46be40bcc..b7bcca29c0ac26 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs
@@ -9,11 +9,12 @@ public partial class LdapSessionOptions
{
private static void PALCertFreeCRLContext(IntPtr certPtr) { /* No op */ }
- [SupportedOSPlatform("windows")]
- public bool SecureSocketLayer
+ public bool SecureSocketLayer { get; set; }
+
+ public int ProtocolVersion
{
- get => throw new PlatformNotSupportedException();
- set => throw new PlatformNotSupportedException();
+ get => GetPtrValueHelper(LdapOption.LDAP_OPT_VERSION).ToInt32();
+ set => SetPtrValueHelper(LdapOption.LDAP_OPT_VERSION, new IntPtr(value));
}
}
}
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs
index 50362992724b5c..c587f4218a4ca6 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs
@@ -23,5 +23,11 @@ public bool SecureSocketLayer
SetIntValueHelper(LdapOption.LDAP_OPT_SSL, temp);
}
}
+
+ public int ProtocolVersion
+ {
+ get => GetIntValueHelper(LdapOption.LDAP_OPT_VERSION);
+ set => SetIntValueHelper(LdapOption.LDAP_OPT_VERSION, value);
+ }
}
}
diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs
index 34884b97972b0f..869c86a8900ace 100644
--- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs
@@ -168,12 +168,6 @@ public int ReferralHopLimit
}
}
- public int ProtocolVersion
- {
- get => GetIntValueHelper(LdapOption.LDAP_OPT_VERSION);
- set => SetIntValueHelper(LdapOption.LDAP_OPT_VERSION, value);
- }
-
public string HostName
{
get => GetStringValueHelper(LdapOption.LDAP_OPT_HOST_NAME, false);
@@ -787,6 +781,33 @@ private void SetIntValueHelper(LdapOption option, int value)
ErrorChecking.CheckAndSetLdapError(error);
}
+ private IntPtr GetPtrValueHelper(LdapOption option)
+ {
+ if (_connection._disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ IntPtr outValue = new IntPtr(0);
+ int error = LdapPal.GetPtrOption(_connection._ldapHandle, option, ref outValue);
+ ErrorChecking.CheckAndSetLdapError(error);
+
+ return outValue;
+ }
+
+ private void SetPtrValueHelper(LdapOption option, IntPtr value)
+ {
+ if (_connection._disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ IntPtr temp = value;
+ int error = LdapPal.SetPtrOption(_connection._ldapHandle, option, ref temp);
+
+ ErrorChecking.CheckAndSetLdapError(error);
+ }
+
private string GetStringValueHelper(LdapOption option, bool releasePtr)
{
if (_connection._disposed)
diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
index 00d56e12bc8d19..f13a864d000187 100644
--- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
+++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs
@@ -630,6 +630,7 @@ private LdapConnection GetConnection()
// 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);