From 94482bbb689f0625488acaf45d506637b7a27834 Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Tue, 14 Sep 2021 17:44:55 +0000 Subject: [PATCH] Merged PR 17575: [Release 6.0] Fix StartTLS in DirectoryService.Protocols in 6.0 and backport changes from 5.0 release. Supersedes https://dev.azure.com/dnceng/internal/_git/dotnet-runtime/pullrequest/17428 for release/6.0 branch. --- .../Interop/Linux/OpenLdap/Interop.Ldap.cs | 3 +- .../System.DirectoryServices.Protocols.csproj | 4 +++ .../Protocols/Interop/LdapPal.Linux.cs | 26 +++++++++++++- .../Protocols/Interop/SafeHandles.Linux.cs | 7 ++++ .../Protocols/ldap/LdapConnection.Linux.cs | 34 ++++++++++++++++--- .../Protocols/ldap/LdapSessionOptions.cs | 5 ++- .../Protocols/ldap/LocalAppContextSwitches.cs | 18 ++++++++++ 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LocalAppContextSwitches.cs 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 f38bb375b347e5..7fbaf9cc32eab8 100644 --- a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs @@ -136,8 +136,9 @@ static Ldap() [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); + // Note that ldap_start_tls_s has a different signature across Windows LDAP and OpenLDAP [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_start_tls_s", CharSet = CharSet.Ansi)] - public static extern int ldap_start_tls(ConnectionHandle ldapHandle, ref int ServerReturnValue, ref IntPtr Message, IntPtr ServerControls, IntPtr ClientControls); + public static extern int ldap_start_tls(ConnectionHandle ldapHandle, IntPtr serverControls, IntPtr clientControls); [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_parse_result", CharSet = CharSet.Ansi)] public static extern int ldap_parse_result([In] ConnectionHandle ldapHandle, [In] IntPtr result, ref int serverError, ref IntPtr dn, ref IntPtr message, ref IntPtr referral, ref IntPtr control, byte freeIt); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj index d317292d1144a0..d9839c121271df 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj +++ b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj @@ -67,7 +67,11 @@ + + + Common\System\LocalAppContextSwitches.Common.cs + Common\Interop\Linux\OpenLdap\Interop.Ldap.cs 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 701fae26108282..d67782f40ad36b 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 @@ -123,7 +123,31 @@ internal static int BindToDirectory(ConnectionHandle ld, string who, string pass } } - 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); + internal static int StartTls(ConnectionHandle ldapHandle, ref int serverReturnValue, ref IntPtr message, IntPtr serverControls, IntPtr clientControls) + { + // Windows and Linux have different signatures for ldap_start_tls_s. + // On Linux, we don't have a serverReturnValue or the message/result parameter. + // + // So in the PAL here, just emulate. + + int error = Interop.Ldap.ldap_start_tls(ldapHandle, serverControls, clientControls); + + // On Windows, serverReturnValue only has meaning if the result code is LDAP_OTHER. + // If OpenLDAP returns that, we don't have a better code, so assign that through. + // If we get any other error, assign serverReturnValue to 0 since it shouldn't be read. + if (error == (int)ResultCode.Other) + { + serverReturnValue = error; + } + else + { + serverReturnValue = 0; + } + + // We don't have a referrer/message/result value, so just set it to NULL. + message = IntPtr.Zero; + return error; + } // openldap doesn't have a ldap_stop_tls function. Returning true as no-op for Linux. internal static byte StopTls(ConnectionHandle ldapHandle) => 1; diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs index 2383e816e1ddb6..a7e43947e20314 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs @@ -16,6 +16,13 @@ public ConnectionHandle() _needDispose = true; } + internal ConnectionHandle(string uri) + :base(true) + { + Interop.Ldap.ldap_initialize(out handle, uri); + _needDispose = true; + } + internal ConnectionHandle(IntPtr value, bool disposeHandle) : base(true) { _needDispose = disposeHandle; 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 02c8f0c239587b..6adf1ebac982cd 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 @@ -20,7 +20,7 @@ private void InternalInitConnectionHandle(string hostname) throw new NullReferenceException(); } - _ldapHandle = new ConnectionHandle(); + _ldapHandle = new ConnectionHandle($"ldap://{hostname}:{((LdapDirectoryIdentifier)_directoryIdentifier).PortNumber}"); } private int InternalConnectToServer() @@ -79,13 +79,39 @@ private int InternalConnectToServer() private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTITY_EX cred, BindMethod method) { int error; - if (tempCredential == null && (AuthType == AuthType.External || AuthType == AuthType.Kerberos)) + + if (LocalAppContextSwitches.UseBasicAuthFallback) { - error = BindSasl(); + if (tempCredential == null && (AuthType == AuthType.External || AuthType == AuthType.Kerberos)) + { + error = BindSasl(); + } + else + { + error = LdapPal.BindToDirectory(_ldapHandle, cred.user, cred.password); + } } else { - error = LdapPal.BindToDirectory(_ldapHandle, cred.user, cred.password); + if (method == BindMethod.LDAP_AUTH_NEGOTIATE) + { + if (tempCredential == null) + { + error = BindSasl(); + } + else + { + // Explicit credentials were provided. If we call ldap_bind_s it will + // return LDAP_NOT_SUPPORTED, so just skip the P/Invoke. + error = (int)LdapError.NotSupported; + } + } + else + { + // Basic and Anonymous are handled elsewhere. + Debug.Assert(AuthType != AuthType.Anonymous && AuthType != AuthType.Basic); + error = (int)LdapError.AuthUnknown; + } } return error; 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 5b28020eed2fd2..9a200fc5ba218b 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 @@ -641,11 +641,14 @@ public unsafe void StartTransportLayerSecurity(DirectoryControlCollection contro response.ResponseName = "1.3.6.1.4.1.1466.20037"; throw new TlsOperationException(response); } - else if (LdapErrorMappings.IsLdapError(error)) + + if (LdapErrorMappings.IsLdapError(error)) { string errorMessage = LdapErrorMappings.MapResultCode(error); throw new LdapException(error, errorMessage); } + + throw new LdapException(error); } } finally diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LocalAppContextSwitches.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LocalAppContextSwitches.cs new file mode 100644 index 00000000000000..59ddfdf7f10ed3 --- /dev/null +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LocalAppContextSwitches.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class LocalAppContextSwitches + { + private static int s_useBasicAuthFallback; + + public static bool UseBasicAuthFallback + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue("System.DirectoryServices.Protocols.UseBasicAuthFallback", ref s_useBasicAuthFallback); + } + } +}