Skip to content

Commit

Permalink
Adding the support of reusing machine-wide credentials in the case wh…
Browse files Browse the repository at this point in the history
…ere 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 <[email protected]>

* Addressing feedback and adding ldap4net to TPN

Co-authored-by: Alexander Chermyanin <[email protected]>
  • Loading branch information
joperezr and flamencist authored May 15, 2020
1 parent c2291eb commit adab8f2
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 5 deletions.
13 changes: 13 additions & 0 deletions THIRD-PARTY-NOTICES.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 6 additions & 1 deletion src/libraries/Common/src/Interop/Interop.Ldap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -94,7 +96,10 @@ internal enum LdapOption
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
Expand Down
58 changes: 56 additions & 2 deletions src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,60 @@
using System.Runtime.InteropServices;
using System.DirectoryServices.Protocols;

namespace System.DirectoryServices.Protocols
{
/// <summary>
/// Structure that will get passed into the Sasl interactive callback in case
/// the authentication process emits challenges to validate information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct SaslDefaultCredentials
{
public string mech;
public string realm;
public string authcid;
public string passwd;
public string authzid;
}

/// <summary>
/// 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.
/// </summary>
[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)]
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,60 @@ internal static int SearchDirectory(ConnectionHandle ldapHandle, string dn, int
internal static string PtrToString(IntPtr requestName) => Marshal.PtrToStringAnsi(requestName);

internal static IntPtr StringToPtr(string s) => Marshal.StringToHGlobalAnsi(s);

/// <summary>
/// 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.
/// </summary>
/// <param name="ldapHandle">The connection handle to the LDAP server.</param>
/// <param name="flags">Flags that control the interaction used to retrieve any necessary Sasl authentication parameters</param>
/// <param name="defaultsPtr">Pointer to the defaults structure that was sent to sasl_interactive_bind</param>
/// <param name="interactPtr">Pointer to the challenge we need to resolve</param>
/// <returns></returns>
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<SaslInteractiveChallenge>(currentInteractPtr);
SaslDefaultCredentials defaults = Marshal.PtrToStructure<SaslDefaultCredentials>(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<SaslInteractiveChallenge>());
interactChallenge = Marshal.PtrToStructure<SaslInteractiveChallenge>(currentInteractPtr);
}

return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;

namespace System.DirectoryServices.Protocols
{
Expand All @@ -24,9 +25,9 @@ private int InternalConnectToServer()
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
{
Expand All @@ -35,5 +36,43 @@ private int InternalBind(NetworkCredential tempCredential, SEC_WINNT_AUTH_IDENTI

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;
}
}
}

0 comments on commit adab8f2

Please sign in to comment.