From adab8f20bb14677e5395e2c6a13c4ecda228adc8 Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Thu, 14 May 2020 18:02:11 -0700 Subject: [PATCH] Adding the support of reusing machine-wide credentials in the case where the machine is already domain-joined to the LDAP Server. (#36405) * Adding the support of reusing machine-wide credentials in the case where the machine is already domain-joined to the LDAP Server. Co-authored-by: Alexander Chermyanin * Addressing feedback and adding ldap4net to TPN Co-authored-by: Alexander Chermyanin --- THIRD-PARTY-NOTICES.TXT | 13 +++++ src/libraries/Common/src/Interop/Interop.Ldap.cs | 7 ++- .../src/Interop/Linux/OpenLdap/Interop.Ldap.cs | 58 +++++++++++++++++++++- .../Protocols/Interop/LdapPal.Linux.cs | 55 ++++++++++++++++++++ .../Protocols/ldap/LdapConnection.Linux.cs | 43 +++++++++++++++- 5 files changed, 171 insertions(+), 5 deletions(-) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 33b2268..db93040 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -820,3 +820,16 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +License notice for ldap4net +--------------------------- + +The MIT License (MIT) + +Copyright (c) 2018 Alexander Chermyanin + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/libraries/Common/src/Interop/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Interop.Ldap.cs index 121fae5..1c312dc 100644 --- a/src/libraries/Common/src/Interop/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Interop.Ldap.cs @@ -9,6 +9,8 @@ internal static partial class Interop public const int SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; public const int SEC_WINNT_AUTH_IDENTITY_VERSION = 0x200; public const string MICROSOFT_KERBEROS_NAME_W = "Kerberos"; + public const uint LDAP_SASL_QUIET = 2; + public const string KerberosDefaultMechanism = "GSSAPI"; } namespace System.DirectoryServices.Protocols @@ -94,7 +96,10 @@ namespace System.DirectoryServices.Protocols LDAP_OPT_SASL_METHOD = 0x97, LDAP_OPT_AREC_EXCLUSIVE = 0x98, // Not Supported in Linux LDAP_OPT_SECURITY_CONTEXT = 0x99, - LDAP_OPT_ROOTDSE_CACHE = 0x9a // Not Supported in Linux + LDAP_OPT_ROOTDSE_CACHE = 0x9a, // Not Supported in Linux + LDAP_OPT_X_SASL_REALM = 0x6101, + LDAP_OPT_X_SASL_AUTHCID = 0x6102, + LDAP_OPT_X_SASL_AUTHZID = 0x6103 } internal enum ResultAll 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 7925e24..02fea97 100644 --- a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs @@ -6,6 +6,60 @@ using System; using System.Runtime.InteropServices; using System.DirectoryServices.Protocols; +namespace System.DirectoryServices.Protocols +{ + /// + /// Structure that will get passed into the Sasl interactive callback in case + /// the authentication process emits challenges to validate information. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct SaslDefaultCredentials + { + public string mech; + public string realm; + public string authcid; + public string passwd; + public string authzid; + } + + /// + /// Structure that will represent a Sasl Interactive challenge during a + /// Sasl interactive bind, which will contain the challenge and it is also + /// where we will have to resolve the result. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class SaslInteractiveChallenge + { + public ulong saslChallengeType; + public string challenge; + public string prompt; + public string defresult; + public IntPtr result; + public uint len; + } + + internal enum SaslChallengeType + { + SASL_CB_LIST_END = 0, + SASL_CB_GETOPT = 1, + SASL_CB_LOG = 2, + SASL_CB_GETPATH = 3, + SASL_CB_VERIFYFILE = 4, + SASL_CB_GETCONFPATH = 5, + SASL_CB_USER = 0x4001, + SASL_CB_AUTHNAME = 0x4002, + SASL_CB_LANGUAGE = 0x4003, + SASL_CB_PASS = 0x4004, + SASL_CB_ECHOPROMPT = 0x4005, + SASL_CB_NOECHOPROMPT = 0x4006, + SASL_CB_CNONCE = 0x4007, + SASL_CB_GETREALM = 0x4008, + SASL_CB_PROXY_POLICY = 0x8001, + } +} + +internal delegate int LDAP_SASL_INTERACT_PROC(IntPtr ld, uint flags, IntPtr defaults, IntPtr interact); + internal static partial class Interop { [DllImport(Libraries.OpenLdap, EntryPoint = "ldap_initialize", CharSet = CharSet.Ansi, SetLastError = true)] @@ -74,8 +128,8 @@ internal static partial class Interop [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, SetLastError = true)] - public static extern int ldap_sasl_bind_s([In] ConnectionHandle ld, string dn, string mechanism, berval cred, IntPtr servercontrol, IntPtr clientcontrol, ref berval 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); 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 09ae9c5..9089e89 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 @@ -110,5 +110,60 @@ namespace System.DirectoryServices.Protocols internal static string PtrToString(IntPtr requestName) => Marshal.PtrToStringAnsi(requestName); internal static IntPtr StringToPtr(string s) => Marshal.StringToHGlobalAnsi(s); + + /// + /// Function that will be sent to the Sasl interactive bind procedure which will resolve all Sasl challenges + /// that get passed in by using the defaults that we get passed in. + /// + /// The connection handle to the LDAP server. + /// Flags that control the interaction used to retrieve any necessary Sasl authentication parameters + /// Pointer to the defaults structure that was sent to sasl_interactive_bind + /// Pointer to the challenge we need to resolve + /// + internal static int SaslInteractionProcedure(IntPtr ldapHandle, uint flags, IntPtr defaultsPtr, IntPtr interactPtr) + { + if (ldapHandle == IntPtr.Zero) + { + return -9; // Parameter Error + } + // Convert pointers into managed structures. + IntPtr currentInteractPtr = interactPtr; + SaslInteractiveChallenge interactChallenge = Marshal.PtrToStructure(currentInteractPtr); + SaslDefaultCredentials defaults = Marshal.PtrToStructure(defaultsPtr); + + // loop through all of the challenges that were sent through the interactChallenge. + while (interactChallenge.saslChallengeType != (int)SaslChallengeType.SASL_CB_LIST_END) + { + // use defaults to fix the challenge type + switch (interactChallenge.saslChallengeType) + { + case (int)SaslChallengeType.SASL_CB_GETREALM: + interactChallenge.defresult = defaults.realm; + break; + case (int)SaslChallengeType.SASL_CB_AUTHNAME: + interactChallenge.defresult = defaults.authcid; + break; + case (int)SaslChallengeType.SASL_CB_PASS: + interactChallenge.defresult = defaults.passwd; + break; + case (int)SaslChallengeType.SASL_CB_USER: + interactChallenge.defresult = defaults.authzid; + break; + } + + if (!string.IsNullOrEmpty(interactChallenge.defresult)) + { + interactChallenge.result = Marshal.StringToHGlobalAnsi(interactChallenge.defresult); + interactChallenge.len = interactChallenge != null ? (uint)interactChallenge.defresult.Length : 0; + } + + // Move to next interact challenge + Marshal.StructureToPtr(interactChallenge, currentInteractPtr, false); + currentInteractPtr = IntPtr.Add(currentInteractPtr, Marshal.SizeOf()); + interactChallenge = Marshal.PtrToStructure(currentInteractPtr); + } + + return 0; + } } } 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 405d4f9..6383c1c 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 @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Net; +using System.Runtime.InteropServices; namespace System.DirectoryServices.Protocols { @@ -24,9 +25,9 @@ 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) + if (tempCredential == null && (AuthType == AuthType.External || AuthType == AuthType.Kerberos)) { - error = Interop.ldap_simple_bind(_ldapHandle, null, null); + error = BindSasl(); } else { @@ -35,5 +36,43 @@ namespace System.DirectoryServices.Protocols return error; } + + private int BindSasl() + { + SaslDefaultCredentials defaults = GetSaslDefaults(); + IntPtr ptrToDefaults = Marshal.AllocHGlobal(Marshal.SizeOf(defaults)); + Marshal.StructureToPtr(defaults, ptrToDefaults, false); + try + { + return Interop.ldap_sasl_interactive_bind(_ldapHandle, null, Interop.KerberosDefaultMechanism, IntPtr.Zero, IntPtr.Zero, Interop.LDAP_SASL_QUIET, LdapPal.SaslInteractionProcedure, ptrToDefaults); + } + finally + { + GC.KeepAlive(defaults); //Making sure we keep it in scope as we will still use ptrToDefaults + Marshal.FreeHGlobal(ptrToDefaults); + } + } + + private SaslDefaultCredentials GetSaslDefaults() + { + var defaults = new SaslDefaultCredentials { mech = Interop.KerberosDefaultMechanism }; + IntPtr outValue = IntPtr.Zero; + int error = Interop.ldap_get_option_ptr(_ldapHandle, LdapOption.LDAP_OPT_X_SASL_REALM, ref outValue); + if (error == 0 && outValue != IntPtr.Zero) + { + defaults.realm = Marshal.PtrToStringAnsi(outValue); + } + error = Interop.ldap_get_option_ptr(_ldapHandle, LdapOption.LDAP_OPT_X_SASL_AUTHCID, ref outValue); + if (error == 0 && outValue != IntPtr.Zero) + { + defaults.authcid = Marshal.PtrToStringAnsi(outValue); + } + error = Interop.ldap_get_option_ptr(_ldapHandle, LdapOption.LDAP_OPT_X_SASL_AUTHZID, ref outValue); + if (error == 0 && outValue != IntPtr.Zero) + { + defaults.authzid = Marshal.PtrToStringAnsi(outValue); + } + return defaults; + } } } -- 2.7.4