File: System\DirectoryServices\Protocols\ldap\LdapConnection.Linux.cs
Web Access
Project: src\src\libraries\System.DirectoryServices.Protocols\src\System.DirectoryServices.Protocols.csproj (System.DirectoryServices.Protocols)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.DirectoryServices.Protocols
{
    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()
        {
            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);
 
            string scheme;
            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]);
                    if (!servers[i].Contains(':'))
                    {
                        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)
        {
            int error;
 
            if (LocalAppContextSwitches.UseBasicAuthFallback)
            {
                if (tempCredential == null && (AuthType == AuthType.External || AuthType == AuthType.Kerberos))
                {
                    error = BindSasl();
                }
                else
                {
                    error = LdapPal.BindToDirectory(_ldapHandle, cred.user, cred.password);
                }
            }
            else
            {
                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;
        }
 
        private int BindSasl()
        {
            SaslDefaultCredentials defaults = GetSaslDefaults();
            IntPtr ptrToDefaults = Marshal.AllocHGlobal(Marshal.SizeOf(defaults));
            Marshal.StructureToPtr(defaults, ptrToDefaults, false);
            try
            {
                // Bump up the protocol version because ldap_sasl_interactive_bind requires LDAP V3 else it returns LDAP_NOT_SUPPORTED and this ends up throwing LdapException: The feature is not supported.
                SessionOptions.ProtocolVersion = 3;
                return Interop.Ldap.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.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.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.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;
        }
    }
}