Merged PR 18242: [Release 6.0 RC2] Fix StartTLS in DirectoryService.Protocols in...
authorMatt Mitchell <mmitche@microsoft.com>
Tue, 21 Sep 2021 14:28:00 +0000 (14:28 +0000)
committerMatt Mitchell <mmitche@microsoft.com>
Tue, 21 Sep 2021 14:28:00 +0000 (14:28 +0000)
Supersedes https://dev.azure.com/dnceng/internal/_git/dotnet-runtime/pullrequest/17428 for release/6.0 branch.

Cherry picked from !17575

src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj
src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs
src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs
src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.Linux.cs
src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.cs
src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LocalAppContextSwitches.cs [new file with mode: 0644]

index f38bb37..7fbaf9c 100644 (file)
@@ -136,8 +136,9 @@ internal static partial class Interop
         [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);
index d317292..d9839c1 100644 (file)
     <Compile Include="System\DirectoryServices\Protocols\Interop\BerPal.Linux.cs" />
     <Compile Include="System\DirectoryServices\Protocols\ldap\LdapConnection.Linux.cs" />
     <Compile Include="System\DirectoryServices\Protocols\ldap\LdapSessionOptions.Linux.cs" />
+    <Compile Include="System\DirectoryServices\Protocols\ldap\LocalAppContextSwitches.cs" />
     <Compile Include="System\DirectoryServices\Protocols\Interop\SafeHandles.Linux.cs" />
+    <Compile Include="$(CommonPath)System\LocalAppContextSwitches.Common.cs">
+      <Link>Common\System\LocalAppContextSwitches.Common.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Linux\OpenLdap\Interop.Ldap.cs">
       <Link>Common\Interop\Linux\OpenLdap\Interop.Ldap.cs</Link>
     </Compile>
index 701fae2..d67782f 100644 (file)
@@ -123,7 +123,31 @@ namespace System.DirectoryServices.Protocols
             }
         }
 
-        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;
index 2383e81..a7e4394 100644 (file)
@@ -16,6 +16,13 @@ namespace System.DirectoryServices.Protocols
             _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;
index 02c8f0c..6adf1eb 100644 (file)
@@ -20,7 +20,7 @@ namespace System.DirectoryServices.Protocols
                 throw new NullReferenceException();
             }
 
-            _ldapHandle = new ConnectionHandle();
+            _ldapHandle = new ConnectionHandle($"ldap://{hostname}:{((LdapDirectoryIdentifier)_directoryIdentifier).PortNumber}");
         }
 
         private int InternalConnectToServer()
@@ -79,13 +79,39 @@ namespace System.DirectoryServices.Protocols
         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;
index 5b28020..9a200fc 100644 (file)
@@ -641,11 +641,14 @@ namespace System.DirectoryServices.Protocols
                         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 (file)
index 0000000..59ddfdf
--- /dev/null
@@ -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);
+        }
+    }
+}