File: System\DirectoryServices\Protocols\ldap\LdapConnection.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.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
 
namespace System.DirectoryServices.Protocols
{
    internal delegate DirectoryResponse GetLdapResponseCallback(int messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeout, bool exceptionOnTimeOut);
 
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal unsafe delegate Interop.BOOL QUERYCLIENTCERT(IntPtr Connection, IntPtr trusted_CAs, IntPtr* certificateHandle);
 
    public partial class LdapConnection : DirectoryConnection, IDisposable
    {
        internal enum LdapResult
        {
            LDAP_RES_SEARCH_RESULT = 0x65,
            LDAP_RES_SEARCH_ENTRY = 0x64,
            LDAP_RES_MODIFY = 0x67,
            LDAP_RES_ADD = 0x69,
            LDAP_RES_DELETE = 0x6b,
            LDAP_RES_MODRDN = 0x6d,
            LDAP_RES_COMPARE = 0x6f,
            LDAP_RES_REFERRAL = 0x73,
            LDAP_RES_EXTENDED = 0x78
        }
 
        private const int LDAP_MOD_BVALUES = 0x80;
 
        internal static readonly object s_objectLock = new object();
        internal static readonly Hashtable s_handleTable = new Hashtable();
        private static readonly Hashtable s_asyncResultTable = Hashtable.Synchronized(new Hashtable());
        private static readonly ManualResetEvent s_waitHandle = new ManualResetEvent(false);
        private static readonly LdapPartialResultsProcessor s_partialResultsProcessor = new LdapPartialResultsProcessor(s_waitHandle);
 
        private AuthType _connectionAuthType = AuthType.Negotiate;
        internal bool _needDispose = true;
        internal ConnectionHandle _ldapHandle;
        internal bool _disposed;
        private bool _bounded;
        private bool _needRebind;
        private bool _connected;
        internal QUERYCLIENTCERT _clientCertificateRoutine;
 
        public LdapConnection(string server) : this(new LdapDirectoryIdentifier(server))
        {
        }
 
        public LdapConnection(LdapDirectoryIdentifier identifier) : this(identifier, null, AuthType.Negotiate)
        {
        }
 
        public LdapConnection(LdapDirectoryIdentifier identifier, NetworkCredential credential) : this(identifier, credential, AuthType.Negotiate)
        {
        }
 
        public unsafe LdapConnection(LdapDirectoryIdentifier identifier, NetworkCredential credential, AuthType authType)
        {
            _directoryIdentifier = identifier;
            _directoryCredential = (credential != null) ? new NetworkCredential(credential.UserName, credential.Password, credential.Domain) : null;
 
            _connectionAuthType = authType;
 
            if (authType < AuthType.Anonymous || authType > AuthType.Kerberos)
            {
                throw new InvalidEnumArgumentException(nameof(authType), (int)authType, typeof(AuthType));
            }
 
            // Throw if user wants to do anonymous bind but specifies credentials.
            if (AuthType == AuthType.Anonymous && (_directoryCredential != null && (!string.IsNullOrEmpty(_directoryCredential.Password) || !string.IsNullOrEmpty(_directoryCredential.UserName))))
            {
                throw new ArgumentException(SR.InvalidAuthCredential);
            }
 
            Init();
            SessionOptions = new LdapSessionOptions(this);
            _clientCertificateRoutine = new QUERYCLIENTCERT(ProcessClientCertificate);
        }
 
        internal unsafe LdapConnection(LdapDirectoryIdentifier identifier, NetworkCredential credential, AuthType authType, IntPtr handle)
        {
            _directoryIdentifier = identifier;
            _needDispose = false;
            _ldapHandle = new ConnectionHandle(handle, _needDispose);
            _directoryCredential = credential;
            _connectionAuthType = authType;
            SessionOptions = new LdapSessionOptions(this);
            _clientCertificateRoutine = new QUERYCLIENTCERT(ProcessClientCertificate);
        }
 
        ~LdapConnection() => Dispose(false);
 
        public override TimeSpan Timeout
        {
            get => _connectionTimeOut;
            set
            {
                if (value < TimeSpan.Zero)
                {
                    throw new ArgumentException(SR.NoNegativeTimeLimit, nameof(value));
                }
 
                // Prevent integer overflow.
                if (value.TotalSeconds > int.MaxValue)
                {
                    throw new ArgumentException(SR.TimespanExceedMax, nameof(value));
                }
 
                _connectionTimeOut = value;
            }
        }
 
        public AuthType AuthType
        {
            get => _connectionAuthType;
            set
            {
                if (value < AuthType.Anonymous || value > AuthType.Kerberos)
                {
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(AuthType));
                }
 
                // If the change is made after we have bound to the server and value is really
                // changed, set the flag to indicate the need to do rebind.
                if (_bounded && (value != _connectionAuthType))
                {
                    _needRebind = true;
                }
 
                _connectionAuthType = value;
            }
        }
 
        public LdapSessionOptions SessionOptions { get; }
 
        public override NetworkCredential Credential
        {
            set
            {
                if (_bounded && !SameCredential(_directoryCredential, value))
                {
                    _needRebind = true;
                }
 
                _directoryCredential = (value != null) ? new NetworkCredential(value.UserName, value.Password, value.Domain) : null;
            }
        }
 
        public bool AutoBind { get; set; } = true;
 
        internal bool NeedDispose
        {
            get => _needDispose;
            set
            {
                if (_ldapHandle != null)
                {
                    _ldapHandle._needDispose = value;
                }
 
                _needDispose = value;
            }
        }
 
        internal void Init()
        {
            InternalInitConnectionHandle();
 
            // Create a WeakReference object with the target of ldapHandle and put it into our handle table.
            lock (s_objectLock)
            {
                if (s_handleTable[_ldapHandle.DangerousGetHandle()] != null)
                {
                    s_handleTable.Remove(_ldapHandle.DangerousGetHandle());
                }
 
                s_handleTable.Add(_ldapHandle.DangerousGetHandle(), new WeakReference(this));
            }
        }
 
        public override DirectoryResponse SendRequest(DirectoryRequest request)
        {
            // No request specific timeout is specified, use the connection timeout.
            return SendRequest(request, _connectionTimeOut);
        }
 
        public DirectoryResponse SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            ArgumentNullException.ThrowIfNull(request);
 
            if (request is DsmlAuthRequest)
            {
                throw new NotSupportedException(SR.DsmlAuthRequestNotSupported);
            }
 
            int messageID = 0;
            int error = SendRequestHelper(request, ref messageID);
 
            LdapOperation operation = LdapOperation.LdapSearch;
            if (request is DeleteRequest)
            {
                operation = LdapOperation.LdapDelete;
            }
            else if (request is AddRequest)
            {
                operation = LdapOperation.LdapAdd;
            }
            else if (request is ModifyRequest)
            {
                operation = LdapOperation.LdapModify;
            }
            else if (request is SearchRequest)
            {
                operation = LdapOperation.LdapSearch;
            }
            else if (request is ModifyDNRequest)
            {
                operation = LdapOperation.LdapModifyDn;
            }
            else if (request is CompareRequest)
            {
                operation = LdapOperation.LdapCompare;
            }
            else if (request is ExtendedRequest)
            {
                operation = LdapOperation.LdapExtendedRequest;
            }
 
            if (error == 0 && messageID != -1)
            {
                ValueTask<DirectoryResponse> vt = ConstructResponseAsync(messageID, operation, ResultAll.LDAP_MSG_ALL, requestTimeout, true, sync: true);
                Debug.Assert(vt.IsCompleted);
                return vt.GetAwaiter().GetResult();
            }
            else
            {
                if (error == 0)
                {
                    // Success code but message is -1, unexpected.
                    error = LdapPal.GetLastErrorFromConnection(_ldapHandle);
                }
 
                throw ConstructException(error, operation);
            }
        }
 
        public IAsyncResult BeginSendRequest(DirectoryRequest request, PartialResultProcessing partialMode, AsyncCallback callback, object state)
        {
            return BeginSendRequest(request, _connectionTimeOut, partialMode, callback, state);
        }
 
        public IAsyncResult BeginSendRequest(DirectoryRequest request, TimeSpan requestTimeout, PartialResultProcessing partialMode, AsyncCallback callback, object state)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            ArgumentNullException.ThrowIfNull(request);
 
            if (partialMode < PartialResultProcessing.NoPartialResultSupport || partialMode > PartialResultProcessing.ReturnPartialResultsAndNotifyCallback)
            {
                throw new InvalidEnumArgumentException(nameof(partialMode), (int)partialMode, typeof(PartialResultProcessing));
            }
 
            if (partialMode != PartialResultProcessing.NoPartialResultSupport && !(request is SearchRequest))
            {
                throw new NotSupportedException(SR.PartialResultsNotSupported);
            }
 
            if (partialMode == PartialResultProcessing.ReturnPartialResultsAndNotifyCallback && callback == null)
            {
                throw new ArgumentException(SR.CallBackIsNull, nameof(callback));
            }
 
            int messageID = 0;
            int error = SendRequestHelper(request, ref messageID);
 
            LdapOperation operation = LdapOperation.LdapSearch;
            if (request is DeleteRequest)
            {
                operation = LdapOperation.LdapDelete;
            }
            else if (request is AddRequest)
            {
                operation = LdapOperation.LdapAdd;
            }
            else if (request is ModifyRequest)
            {
                operation = LdapOperation.LdapModify;
            }
            else if (request is SearchRequest)
            {
                operation = LdapOperation.LdapSearch;
            }
            else if (request is ModifyDNRequest)
            {
                operation = LdapOperation.LdapModifyDn;
            }
            else if (request is CompareRequest)
            {
                operation = LdapOperation.LdapCompare;
            }
            else if (request is ExtendedRequest)
            {
                operation = LdapOperation.LdapExtendedRequest;
            }
 
            if (error == 0 && messageID != -1)
            {
                if (partialMode == PartialResultProcessing.NoPartialResultSupport)
                {
                    var requestState = new LdapRequestState();
                    var asyncResult = new LdapAsyncResult(callback, state, false);
 
                    requestState._ldapAsync = asyncResult;
                    asyncResult._resultObject = requestState;
 
                    s_asyncResultTable.Add(asyncResult, messageID);
 
                    _ = ResponseCallback(ConstructResponseAsync(messageID, operation, ResultAll.LDAP_MSG_ALL, requestTimeout, true, sync: false), requestState);
 
                    static async Task ResponseCallback(ValueTask<DirectoryResponse> vt, LdapRequestState requestState)
                    {
                        try
                        {
                            DirectoryResponse response = await vt.ConfigureAwait(false);
                            requestState._response = response;
                        }
                        catch (Exception e)
                        {
                            requestState._exception = e;
                            requestState._response = null;
                        }
 
                        // Signal waitable object, indicate operation completed and fire callback.
                        requestState._ldapAsync._manualResetEvent.Set();
                        requestState._ldapAsync._completed = true;
 
                        if (requestState._ldapAsync._callback != null && !requestState._abortCalled)
                        {
                            requestState._ldapAsync._callback(requestState._ldapAsync);
                        }
                    }
 
                    return asyncResult;
                }
                else
                {
                    // the user registers to retrieve partial results
                    bool partialCallback = partialMode == PartialResultProcessing.ReturnPartialResultsAndNotifyCallback;
 
                    var asyncResult = new LdapPartialAsyncResult(messageID, callback, state, true, this, partialCallback, requestTimeout);
                    s_partialResultsProcessor.Add(asyncResult);
 
                    return asyncResult;
                }
            }
 
            if (error == 0)
            {
                // Success code but message is -1, unexpected.
                error = LdapPal.GetLastErrorFromConnection(_ldapHandle);
            }
 
            throw ConstructException(error, operation);
        }
 
        public void Abort(IAsyncResult asyncResult)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            ArgumentNullException.ThrowIfNull(asyncResult);
 
            if (!(asyncResult is LdapAsyncResult))
            {
                throw new ArgumentException(SR.Format(SR.NotReturnedAsyncResult, nameof(asyncResult)));
            }
 
            int messageId;
 
            LdapAsyncResult result = (LdapAsyncResult)asyncResult;
            if (!result._partialResults)
            {
                if (!s_asyncResultTable.Contains(asyncResult))
                {
                    throw new ArgumentException(SR.InvalidAsyncResult);
                }
 
                messageId = (int)(s_asyncResultTable[asyncResult]);
 
                // remove the asyncResult from our connection table
                s_asyncResultTable.Remove(asyncResult);
            }
            else
            {
                s_partialResultsProcessor.Remove((LdapPartialAsyncResult)asyncResult);
                messageId = ((LdapPartialAsyncResult)asyncResult)._messageID;
            }
 
            // Cancel the request.
            LdapPal.CancelDirectoryAsyncOperation(_ldapHandle, messageId);
 
            LdapRequestState resultObject = result._resultObject;
            if (resultObject != null)
            {
                resultObject._abortCalled = true;
            }
        }
 
        public PartialResultsCollection GetPartialResults(IAsyncResult asyncResult)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            ArgumentNullException.ThrowIfNull(asyncResult);
 
            if (!(asyncResult is LdapAsyncResult))
            {
                throw new ArgumentException(SR.Format(SR.NotReturnedAsyncResult, nameof(asyncResult)));
            }
 
            if (!(asyncResult is LdapPartialAsyncResult))
            {
                throw new InvalidOperationException(SR.NoPartialResults);
            }
 
            return s_partialResultsProcessor.GetPartialResults((LdapPartialAsyncResult)asyncResult);
        }
 
        public DirectoryResponse EndSendRequest(IAsyncResult asyncResult)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            ArgumentNullException.ThrowIfNull(asyncResult);
 
            if (!(asyncResult is LdapAsyncResult))
            {
                throw new ArgumentException(SR.Format(SR.NotReturnedAsyncResult, nameof(asyncResult)));
            }
 
            LdapAsyncResult result = (LdapAsyncResult)asyncResult;
 
            if (!result._partialResults)
            {
                // Not a partial results.
                if (!s_asyncResultTable.Contains(asyncResult))
                {
                    throw new ArgumentException(SR.InvalidAsyncResult);
                }
 
                // Remove the asyncResult from our connection table.
                s_asyncResultTable.Remove(asyncResult);
 
                asyncResult.AsyncWaitHandle.WaitOne();
 
                if (result._resultObject._exception != null)
                {
                    throw result._resultObject._exception;
                }
 
                return result._resultObject._response;
            }
 
            // Deal with partial results.
            s_partialResultsProcessor.NeedCompleteResult((LdapPartialAsyncResult)asyncResult);
            asyncResult.AsyncWaitHandle.WaitOne();
 
            return s_partialResultsProcessor.GetCompleteResult((LdapPartialAsyncResult)asyncResult);
        }
 
        private unsafe int SendRequestHelper(DirectoryRequest request, ref int messageID)
        {
            IntPtr serverControlArray = IntPtr.Zero;
            LdapControl[] managedServerControls = null;
            IntPtr clientControlArray = IntPtr.Zero;
            LdapControl[] managedClientControls = null;
 
            var ptrToFree = new ArrayList();
            LdapMod[] modifications = null;
            IntPtr modArray = IntPtr.Zero;
            int addModCount = 0;
 
            BerVal berValuePtr = null;
 
            IntPtr searchAttributes = IntPtr.Zero;
            int attributeCount = 0;
 
            int error = 0;
 
            // Connect to the server first if have not done so.
            if (!_connected)
            {
                Connect();
                _connected = true;
            }
 
            // Bind if user has not turned off automatic bind, have not done so or there is a need
            // to do rebind, also connectionless LDAP does not need to do bind.
            if (AutoBind && (!_bounded || _needRebind) && ((LdapDirectoryIdentifier)Directory).Connectionless != true)
            {
                Debug.WriteLine("rebind occurs\n");
                Bind();
            }
 
            try
            {
                // Build server control.
                managedServerControls = BuildControlArray(request.Controls, true);
                int structSize = Marshal.SizeOf<LdapControl>();
 
                if (managedServerControls != null)
                {
                    serverControlArray = Utility.AllocHGlobalIntPtrArray(managedServerControls.Length + 1);
                    void** pServerControlArray = (void**)serverControlArray;
                    for (int i = 0; i < managedServerControls.Length; i++)
                    {
                        IntPtr controlPtr = Marshal.AllocHGlobal(structSize);
                        Marshal.StructureToPtr(managedServerControls[i], controlPtr, false);
                        pServerControlArray[i] = (void*)controlPtr;
                    }
 
                    pServerControlArray[managedServerControls.Length] = null;
                }
 
                // build client control
                managedClientControls = BuildControlArray(request.Controls, false);
                if (managedClientControls != null)
                {
                    clientControlArray = Utility.AllocHGlobalIntPtrArray(managedClientControls.Length + 1);
                    void** pClientControlArray = (void**)clientControlArray;
                    for (int i = 0; i < managedClientControls.Length; i++)
                    {
                        IntPtr controlPtr = Marshal.AllocHGlobal(structSize);
                        Marshal.StructureToPtr(managedClientControls[i], controlPtr, false);
                        pClientControlArray[i] = (void*)controlPtr;
                    }
 
                    pClientControlArray[managedClientControls.Length] = null;
                }
 
                if (request is DeleteRequest)
                {
                    // It is an delete operation.
                    error = LdapPal.DeleteDirectoryEntry(_ldapHandle, ((DeleteRequest)request).DistinguishedName, serverControlArray, clientControlArray, ref messageID);
                }
                else if (request is ModifyDNRequest)
                {
                    // It is a modify dn operation
                    error = LdapPal.RenameDirectoryEntry(
                        _ldapHandle,
                        ((ModifyDNRequest)request).DistinguishedName,
                        ((ModifyDNRequest)request).NewName,
                        ((ModifyDNRequest)request).NewParentDistinguishedName,
                        ((ModifyDNRequest)request).DeleteOldRdn ? 1 : 0,
                        serverControlArray, clientControlArray, ref messageID);
                }
                else if (request is CompareRequest compareRequest)
                {
                    // It is a compare request.
                    DirectoryAttribute assertion = compareRequest.Assertion;
                    if (assertion == null)
                    {
                        throw new ArgumentException(SR.WrongAssertionCompare);
                    }
 
                    if (assertion.Count != 1)
                    {
                        throw new ArgumentException(SR.WrongNumValuesCompare);
                    }
 
                    // Process the attribute.
                    byte[] byteArray;
                    if (assertion[0] is string str)
                    {
                        var encoder = new UTF8Encoding();
                        byteArray = encoder.GetBytes(str);
                    }
                    else if (assertion[0] is Uri uri)
                    {
                        var encoder = new UTF8Encoding();
                        byteArray = encoder.GetBytes(uri.ToString());
                    }
                    else if (assertion[0] is byte[] bytes)
                    {
                        byteArray = bytes;
                    }
                    else
                    {
                        throw new ArgumentException(SR.ValidValueType);
                    }
 
                    berValuePtr = new BerVal
                    {
                        bv_len = new CLong(byteArray.Length),
                        bv_val = Marshal.AllocHGlobal(byteArray.Length)
                    };
                    Marshal.Copy(byteArray, 0, berValuePtr.bv_val, byteArray.Length);
 
                    // It is a compare request.
                    error = LdapPal.CompareDirectoryEntries(
                        _ldapHandle,
                        ((CompareRequest)request).DistinguishedName,
                        assertion.Name,
                        null,
                        berValuePtr,
                        serverControlArray, clientControlArray, ref messageID);
                }
                else if (request is AddRequest || request is ModifyRequest)
                {
                    // Build the attributes.
                    if (request is AddRequest)
                    {
                        modifications = BuildAttributes(((AddRequest)request).Attributes, ptrToFree);
                    }
                    else
                    {
                        modifications = BuildAttributes(((ModifyRequest)request).Modifications, ptrToFree);
                    }
 
                    addModCount = (modifications == null ? 1 : modifications.Length + 1);
                    modArray = Utility.AllocHGlobalIntPtrArray(addModCount);
                    void** pModArray = (void**)modArray;
                    int modStructSize = Marshal.SizeOf<LdapMod>();
                    int i = 0;
                    for (i = 0; i < addModCount - 1; i++)
                    {
                        IntPtr controlPtr = Marshal.AllocHGlobal(modStructSize);
                        Marshal.StructureToPtr(modifications[i], controlPtr, false);
                        pModArray[i] = (void*)controlPtr;
                    }
                    pModArray[i] = null;
 
                    if (request is AddRequest)
                    {
                        error = LdapPal.AddDirectoryEntry(
                            _ldapHandle,
                            ((AddRequest)request).DistinguishedName,
                            modArray,
                            serverControlArray, clientControlArray, ref messageID);
                    }
                    else
                    {
                        error = LdapPal.ModifyDirectoryEntry(
                            _ldapHandle,
                            ((ModifyRequest)request).DistinguishedName,
                            modArray,
                            serverControlArray, clientControlArray, ref messageID);
                    }
                }
                else if (request is ExtendedRequest extendedRequest)
                {
                    string name = extendedRequest.RequestName;
                    byte[] val = extendedRequest.RequestValue;
 
                    // process the requestvalue
                    if (val != null && val.Length != 0)
                    {
                        berValuePtr = new BerVal()
                        {
                            bv_len = new CLong(val.Length),
                            bv_val = Marshal.AllocHGlobal(val.Length)
                        };
                        Marshal.Copy(val, 0, berValuePtr.bv_val, val.Length);
                    }
 
                    error = LdapPal.ExtendedDirectoryOperation(
                        _ldapHandle,
                        name,
                        berValuePtr,
                        serverControlArray, clientControlArray, ref messageID);
                }
                else if (request is SearchRequest searchRequest)
                {
                    // Process the filter.
                    object filter = searchRequest.Filter;
                    if (filter != null)
                    {
                        // LdapConnection only supports ldap filter.
                        if (filter is XmlDocument)
                        {
                            throw new ArgumentException(SR.InvalidLdapSearchRequestFilter);
                        }
                    }
 
                    string searchRequestFilter = (string)filter;
 
                    // Process the attributes.
                    attributeCount = (searchRequest.Attributes == null ? 0 : searchRequest.Attributes.Count);
                    if (attributeCount != 0)
                    {
                        searchAttributes = Utility.AllocHGlobalIntPtrArray(attributeCount + 1);
                        void** pSearchAttributes = (void**)searchAttributes;
                        int i = 0;
                        for (i = 0; i < attributeCount; i++)
                        {
                            IntPtr controlPtr = LdapPal.StringToPtr(searchRequest.Attributes[i]);
                            pSearchAttributes[i] = (void*)controlPtr;
                        }
 
                        pSearchAttributes[i] = null;
                    }
 
                    // Process the scope.
                    int searchScope = (int)searchRequest.Scope;
 
                    // Process the timelimit.
                    int searchTimeLimit = (int)(searchRequest.TimeLimit.Ticks / TimeSpan.TicksPerSecond);
 
                    // Process the alias.
                    DereferenceAlias searchAliases = SessionOptions.DerefAlias;
                    SessionOptions.DerefAlias = searchRequest.Aliases;
 
                    try
                    {
                        error = LdapPal.SearchDirectory(
                            _ldapHandle,
                            searchRequest.DistinguishedName,
                            searchScope,
                            searchRequestFilter,
                            searchAttributes,
                            searchRequest.TypesOnly,
                            serverControlArray,
                            clientControlArray,
                            searchTimeLimit,
                            searchRequest.SizeLimit,
                            ref messageID);
                    }
                    finally
                    {
                        // Revert back.
                        SessionOptions.DerefAlias = searchAliases;
                    }
                }
                else
                {
                    throw new NotSupportedException(SR.InvliadRequestType);
                }
 
                // The asynchronous call itself timeout, this actually means that we time out the
                // LDAP_OPT_SEND_TIMEOUT specified in the session option wldap32 does not differentiate
                // that, but the application caller actually needs this information to determin what to
                // do with the error code
                if (error == (int)LdapError.TimeOut)
                {
                    error = (int)LdapError.SendTimeOut;
                }
 
                return error;
            }
            finally
            {
                GC.KeepAlive(modifications);
 
                if (serverControlArray != IntPtr.Zero)
                {
                    // Release the memory from the heap.
                    for (int i = 0; i < managedServerControls.Length; i++)
                    {
                        IntPtr tempPtr = Marshal.ReadIntPtr(serverControlArray, IntPtr.Size * i);
                        if (tempPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(tempPtr);
                        }
                    }
                    Marshal.FreeHGlobal(serverControlArray);
                }
 
                if (managedServerControls != null)
                {
                    for (int i = 0; i < managedServerControls.Length; i++)
                    {
                        if (managedServerControls[i].ldctl_oid != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(managedServerControls[i].ldctl_oid);
                        }
 
                        if (managedServerControls[i].ldctl_value != null)
                        {
                            if (managedServerControls[i].ldctl_value.bv_val != IntPtr.Zero)
                            {
                                Marshal.FreeHGlobal(managedServerControls[i].ldctl_value.bv_val);
                            }
                        }
                    }
                }
 
                if (clientControlArray != IntPtr.Zero)
                {
                    // Release the memory from the heap.
                    for (int i = 0; i < managedClientControls.Length; i++)
                    {
                        IntPtr tempPtr = Marshal.ReadIntPtr(clientControlArray, IntPtr.Size * i);
                        if (tempPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(tempPtr);
                        }
                    }
 
                    Marshal.FreeHGlobal(clientControlArray);
                }
 
                if (managedClientControls != null)
                {
                    for (int i = 0; i < managedClientControls.Length; i++)
                    {
                        if (managedClientControls[i].ldctl_oid != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(managedClientControls[i].ldctl_oid);
                        }
 
                        if (managedClientControls[i].ldctl_value != null)
                        {
                            if (managedClientControls[i].ldctl_value.bv_val != IntPtr.Zero)
                            {
                                Marshal.FreeHGlobal(managedClientControls[i].ldctl_value.bv_val);
                            }
                        }
                    }
                }
 
                if (modArray != IntPtr.Zero)
                {
                    // release the memory from the heap
                    for (int i = 0; i < addModCount - 1; i++)
                    {
                        IntPtr tempPtr = Marshal.ReadIntPtr(modArray, IntPtr.Size * i);
                        if (tempPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(tempPtr);
                        }
                    }
 
                    Marshal.FreeHGlobal(modArray);
                }
 
                // Free the pointers.
                for (int x = 0; x < ptrToFree.Count; x++)
                {
                    IntPtr tempPtr = (IntPtr)ptrToFree[x];
                    Marshal.FreeHGlobal(tempPtr);
                }
 
                if (berValuePtr != null && berValuePtr.bv_val != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(berValuePtr.bv_val);
                }
 
                if (searchAttributes != IntPtr.Zero)
                {
                    for (int i = 0; i < attributeCount; i++)
                    {
                        IntPtr tempPtr = Marshal.ReadIntPtr(searchAttributes, IntPtr.Size * i);
                        if (tempPtr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(tempPtr);
                        }
                    }
 
                    Marshal.FreeHGlobal(searchAttributes);
                }
            }
        }
 
        private unsafe Interop.BOOL ProcessClientCertificate(IntPtr ldapHandle, IntPtr CAs, IntPtr* certificate)
        {
            int count = ClientCertificates == null ? 0 : ClientCertificates.Count;
            if (count == 0 && SessionOptions._clientCertificateDelegate == null)
            {
                return Interop.BOOL.FALSE;
            }
 
            // If the user specify certificate through property and not though option, we don't need to check the certificate authority.
            if (SessionOptions._clientCertificateDelegate == null)
            {
                *certificate = ClientCertificates[0].Handle;
                return Interop.BOOL.TRUE;
            }
 
            // Processing the certificate authority.
            var list = new ArrayList();
            if (CAs != IntPtr.Zero)
            {
                SecPkgContext_IssuerListInfoEx trustedCAs = *(SecPkgContext_IssuerListInfoEx*)CAs;
                int issuerNumber = trustedCAs.cIssuers;
                for (int i = 0; i < issuerNumber; i++)
                {
                    IntPtr tempPtr = (IntPtr)((byte*)trustedCAs.aIssuers + sizeof(CRYPTOAPI_BLOB) * (nint)i);
                    CRYPTOAPI_BLOB info = *(CRYPTOAPI_BLOB*)tempPtr;
                    int dataLength = info.cbData;
 
                    byte[] context = new byte[dataLength];
                    Marshal.Copy(info.pbData, context, 0, dataLength);
                    list.Add(context);
                }
            }
 
            byte[][] certAuthorities = null;
            if (list.Count != 0)
            {
                certAuthorities = new byte[list.Count][];
                for (int i = 0; i < list.Count; i++)
                {
                    certAuthorities[i] = (byte[])list[i];
                }
            }
 
            X509Certificate cert = SessionOptions._clientCertificateDelegate(this, certAuthorities);
            if (cert != null)
            {
                *certificate = cert.Handle;
                return Interop.BOOL.TRUE;
            }
 
            *certificate = IntPtr.Zero;
            return Interop.BOOL.FALSE;
        }
 
        private void Connect()
        {
            // Currently ldap does not accept more than one certificate.
            if (ClientCertificates.Count > 1)
            {
                throw new InvalidOperationException(SR.InvalidClientCertificates);
            }
 
            // Set the certificate callback routine here if user adds the certificate to the certificate collection.
            if (ClientCertificates.Count != 0)
            {
                int certError = LdapPal.SetClientCertOption(_ldapHandle, LdapOption.LDAP_OPT_CLIENT_CERTIFICATE, _clientCertificateRoutine);
                if (certError != (int)ResultCode.Success)
                {
                    if (LdapErrorMappings.IsLdapError(certError))
                    {
                        string certerrorMessage = LdapErrorMappings.MapResultCode(certError);
                        throw new LdapException(certError, certerrorMessage);
                    }
 
                    throw new LdapException(certError);
                }
 
                // When certificate is specified, automatic bind is disabled.
                AutoBind = false;
            }
 
            // Set the LDAP_OPT_AREC_EXCLUSIVE flag if necessary.
            if (((LdapDirectoryIdentifier)Directory).FullyQualifiedDnsHostName && !_setFQDNDone)
            {
                SessionOptions.SetFqdnRequired();
                _setFQDNDone = true;
            }
 
            int error = InternalConnectToServer();
 
            // Failed, throw an exception.
            if (error != (int)ResultCode.Success)
            {
                if (LdapErrorMappings.IsLdapError(error))
                {
                    string errorMessage = LdapErrorMappings.MapResultCode(error);
                    throw new LdapException(error, errorMessage);
                }
 
                throw new LdapException(error);
            }
        }
 
        public void Bind() => BindHelper(_directoryCredential, needSetCredential: false);
 
        public void Bind(NetworkCredential newCredential) => BindHelper(newCredential, needSetCredential: true);
 
        private void BindHelper(NetworkCredential newCredential, bool needSetCredential)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
 
            // Throw if user wants to do anonymous bind but specifies credentials.
            if (AuthType == AuthType.Anonymous && (newCredential != null && (!string.IsNullOrEmpty(newCredential.Password) || !string.IsNullOrEmpty(newCredential.UserName))))
            {
                throw new InvalidOperationException(SR.InvalidAuthCredential);
            }
 
            // Set the credential.
            NetworkCredential tempCredential;
            if (needSetCredential)
            {
                _directoryCredential = tempCredential = (newCredential != null ? new NetworkCredential(newCredential.UserName, newCredential.Password, newCredential.Domain) : null);
            }
            else
            {
                tempCredential = _directoryCredential;
            }
 
            // Connect to the server first.
            if (!_connected)
            {
                Connect();
                _connected = true;
            }
 
            // Bind to the server.
            string username;
            string domainName;
            string password;
            if (tempCredential != null && tempCredential.UserName.Length == 0 && tempCredential.Password.Length == 0 && tempCredential.Domain.Length == 0)
            {
                // Default credentials.
                username = null;
                domainName = null;
                password = null;
            }
            else
            {
                username = tempCredential?.UserName;
                domainName = tempCredential?.Domain;
                password = tempCredential?.Password;
            }
 
            int error;
            if (AuthType == AuthType.Anonymous)
            {
                error = LdapPal.BindToDirectory(_ldapHandle, null, null);
            }
            else if (AuthType == AuthType.Basic)
            {
                var tempDomainName = new StringBuilder(100);
                if (!string.IsNullOrEmpty(domainName))
                {
                    tempDomainName.Append(domainName);
                    tempDomainName.Append('\\');
                }
 
                tempDomainName.Append(username);
                error = LdapPal.BindToDirectory(_ldapHandle, tempDomainName.ToString(), password);
            }
            else
            {
                var cred = new SEC_WINNT_AUTH_IDENTITY_EX()
                {
                    version = Interop.SEC_WINNT_AUTH_IDENTITY_VERSION,
                    length = Marshal.SizeOf<SEC_WINNT_AUTH_IDENTITY_EX>(),
                    flags = Interop.SEC_WINNT_AUTH_IDENTITY_UNICODE
                };
                if (AuthType == AuthType.Kerberos)
                {
                    cred.packageList = Interop.MICROSOFT_KERBEROS_NAME_W;
                    cred.packageListLength = cred.packageList.Length;
                }
 
                if (tempCredential != null)
                {
                    cred.user = username;
                    cred.userLength = (username == null ? 0 : username.Length);
                    cred.domain = domainName;
                    cred.domainLength = (domainName == null ? 0 : domainName.Length);
                    cred.password = password;
                    cred.passwordLength = (password == null ? 0 : password.Length);
                }
 
                BindMethod method = BindMethod.LDAP_AUTH_NEGOTIATE;
                switch (AuthType)
                {
                    case AuthType.Negotiate:
                        method = BindMethod.LDAP_AUTH_NEGOTIATE;
                        break;
                    case AuthType.Kerberos:
                        method = BindMethod.LDAP_AUTH_NEGOTIATE;
                        break;
                    case AuthType.Ntlm:
                        method = BindMethod.LDAP_AUTH_NTLM;
                        break;
                    case AuthType.Digest:
                        method = BindMethod.LDAP_AUTH_DIGEST;
                        break;
                    case AuthType.Sicily:
                        method = BindMethod.LDAP_AUTH_SICILY;
                        break;
                    case AuthType.Dpa:
                        method = BindMethod.LDAP_AUTH_DPA;
                        break;
                    case AuthType.Msn:
                        method = BindMethod.LDAP_AUTH_MSN;
                        break;
                    case AuthType.External:
                        method = BindMethod.LDAP_AUTH_EXTERNAL;
                        break;
                }
 
                error = InternalBind(tempCredential, cred, method);
            }
 
            // Failed, throw exception.
            if (error != (int)ResultCode.Success)
            {
                if (Utility.IsResultCode((ResultCode)error))
                {
                    string errorMessage = OperationErrorMappings.MapResultCode(error);
                    throw new DirectoryOperationException(null, errorMessage);
                }
                else if (LdapErrorMappings.IsLdapError(error))
                {
                    string errorMessage = LdapErrorMappings.MapResultCode(error);
                    string serverErrorMessage = SessionOptions.ServerErrorMessage;
                    if (!string.IsNullOrEmpty(serverErrorMessage))
                    {
                        throw new LdapException(error, errorMessage, serverErrorMessage);
                    }
 
                    throw new LdapException(error, errorMessage);
                }
 
                throw new LdapException(error);
            }
 
            // We successfully bound to the server.
            _bounded = true;
 
            // Rebind has been done.
            _needRebind = false;
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // We need to remove the handle from the handle table.
                lock (s_objectLock)
                {
                    if (_ldapHandle != null)
                    {
                        s_handleTable.Remove(_ldapHandle.DangerousGetHandle());
                    }
                }
            }
 
            // Close the ldap connection.
            if (_needDispose && _ldapHandle != null && !_ldapHandle.IsInvalid)
            {
                _ldapHandle.Dispose();
            }
 
            _ldapHandle = null;
            _disposed = true;
        }
 
        internal static LdapControl[] BuildControlArray(DirectoryControlCollection controls, bool serverControl)
        {
            LdapControl[] managedControls = null;
 
            if (controls != null && controls.Count != 0)
            {
                var controlList = new ArrayList();
                foreach (DirectoryControl col in controls)
                {
                    if (serverControl)
                    {
                        if (col.ServerSide)
                        {
                            controlList.Add(col);
                        }
                    }
                    else if (!col.ServerSide)
                    {
                        controlList.Add(col);
                    }
                }
 
                if (controlList.Count != 0)
                {
                    int count = controlList.Count;
                    managedControls = new LdapControl[count];
 
                    for (int i = 0; i < count; i++)
                    {
                        managedControls[i] = new LdapControl()
                        {
                            // Get the control type.
                            ldctl_oid = LdapPal.StringToPtr(((DirectoryControl)controlList[i]).Type),
 
                            // Get the control criticality.
                            ldctl_iscritical = ((DirectoryControl)controlList[i]).IsCritical
                        };
 
                        // Get the control value.
                        DirectoryControl tempControl = (DirectoryControl)controlList[i];
                        byte[] byteControlValue = tempControl.GetValue();
                        if (byteControlValue == null || byteControlValue.Length == 0)
                        {
                            // Treat the control value as null.
                            managedControls[i].ldctl_value = new BerVal
                            {
                                bv_len = new CLong(0),
                                bv_val = IntPtr.Zero
                            };
                        }
                        else
                        {
                            managedControls[i].ldctl_value = new BerVal
                            {
                                bv_len = new CLong(byteControlValue.Length),
                                bv_val = Marshal.AllocHGlobal(sizeof(byte) * byteControlValue.Length)
                            };
                            Marshal.Copy(byteControlValue, 0, managedControls[i].ldctl_value.bv_val, (int)managedControls[i].ldctl_value.bv_len.Value);
                        }
                    }
                }
            }
 
            return managedControls;
        }
 
        internal static unsafe LdapMod[] BuildAttributes(CollectionBase directoryAttributes, ArrayList ptrToFree)
        {
            LdapMod[] attributes = null;
 
            if (directoryAttributes != null && directoryAttributes.Count != 0)
            {
                var encoder = new UTF8Encoding();
                DirectoryAttributeModificationCollection modificationCollection = null;
                DirectoryAttributeCollection attributeCollection = null;
 
                if (directoryAttributes is DirectoryAttributeModificationCollection)
                {
                    modificationCollection = (DirectoryAttributeModificationCollection)directoryAttributes;
                }
                else
                {
                    attributeCollection = (DirectoryAttributeCollection)directoryAttributes;
                }
 
                attributes = new LdapMod[directoryAttributes.Count];
                for (int i = 0; i < directoryAttributes.Count; i++)
                {
                    // Get the managed attribute first.
                    DirectoryAttribute modAttribute;
                    if (attributeCollection != null)
                    {
                        modAttribute = attributeCollection[i];
                    }
                    else
                    {
                        modAttribute = modificationCollection[i];
                    }
 
                    attributes[i] = new LdapMod();
 
                    // Write the operation type.
                    if (modAttribute is DirectoryAttributeModification)
                    {
                        attributes[i].type = (int)((DirectoryAttributeModification)modAttribute).Operation;
                    }
                    else
                    {
                        attributes[i].type = (int)DirectoryAttributeOperation.Add;
                    }
 
                    // We treat all the values as binary
                    attributes[i].type |= LDAP_MOD_BVALUES;
 
                    // Write the attribute name.
                    attributes[i].attribute = LdapPal.StringToPtr(modAttribute.Name);
 
                    // Write the values.
                    int valuesCount = 0;
                    BerVal[] berValues = null;
                    if (modAttribute.Count > 0)
                    {
                        valuesCount = modAttribute.Count;
                        berValues = new BerVal[valuesCount];
                        for (int j = 0; j < valuesCount; j++)
                        {
                            byte[] byteArray;
                            if (modAttribute[j] is string)
                            {
                                byteArray = encoder.GetBytes((string)modAttribute[j]);
                            }
                            else if (modAttribute[j] is Uri)
                            {
                                byteArray = encoder.GetBytes(((Uri)modAttribute[j]).ToString());
                            }
                            else
                            {
                                byteArray = (byte[])modAttribute[j];
                            }
 
                            berValues[j] = new BerVal()
                            {
                                bv_len = new CLong(byteArray.Length),
                                bv_val = Marshal.AllocHGlobal(byteArray.Length)
                            };
 
                            // need to free the memory allocated on the heap when we are done
                            ptrToFree.Add(berValues[j].bv_val);
                            Marshal.Copy(byteArray, 0, berValues[j].bv_val, (int)berValues[j].bv_len.Value);
                        }
                    }
 
                    attributes[i].values = Utility.AllocHGlobalIntPtrArray(valuesCount + 1);
                    void** pAttributesValues = (void**)attributes[i].values;
                    int structSize = Marshal.SizeOf<BerVal>();
                    IntPtr controlPtr;
 
                    int m;
                    for (m = 0; m < valuesCount; m++)
                    {
                        controlPtr = Marshal.AllocHGlobal(structSize);
 
                        // Need to free the memory allocated on the heap when we are done.
                        ptrToFree.Add(controlPtr);
                        Marshal.StructureToPtr(berValues[m], controlPtr, false);
                        pAttributesValues[m] = (void*)controlPtr;
                    }
                    pAttributesValues[m] = null;
                }
            }
 
            return attributes;
        }
 
        internal async ValueTask<DirectoryResponse> ConstructResponseAsync(int messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeOut, bool exceptionOnTimeOut, bool sync)
        {
            var timeout = new LDAP_TIMEVAL()
            {
                tv_sec = (int)(requestTimeOut.Ticks / TimeSpan.TicksPerSecond)
            };
            IntPtr ldapResult = IntPtr.Zero;
            DirectoryResponse response = null;
 
            IntPtr requestName = IntPtr.Zero;
            IntPtr requestValue = IntPtr.Zero;
 
            IntPtr entryMessage;
 
            bool needAbandon = true;
 
            // processing for the partial results retrieval
            if (resultType != ResultAll.LDAP_MSG_ALL)
            {
                // we need to have 0 timeout as we are polling for the results and don't want to wait
                timeout.tv_sec = 0;
                timeout.tv_usec = 0;
 
                if (resultType == ResultAll.LDAP_MSG_POLLINGALL)
                {
                    resultType = ResultAll.LDAP_MSG_ALL;
                }
 
                // when doing partial results retrieving, if ldap_result failed, we don't do ldap_abandon here.
                needAbandon = false;
            }
 
            int error;
            if (sync)
            {
                error = LdapPal.GetResultFromAsyncOperation(_ldapHandle, messageId, (int)resultType, timeout, ref ldapResult);
            }
            else
            {
                timeout.tv_sec = 0;
                timeout.tv_usec = 0;
                int iterationDelay = 1;
                // Underlying native libraries don't support callback-based function, so we will instead use polling and
                // use a Stopwatch to track the timeout manually.
                Stopwatch watch = Stopwatch.StartNew();
                while (true)
                {
                    error = LdapPal.GetResultFromAsyncOperation(_ldapHandle, messageId, (int)resultType, timeout, ref ldapResult);
                    if (error != 0 || (requestTimeOut != Threading.Timeout.InfiniteTimeSpan && watch.Elapsed > requestTimeOut))
                    {
                        break;
                    }
                    await Task.Delay(Math.Min(iterationDelay, 100)).ConfigureAwait(false);
                    if (iterationDelay < 100)
                    {
                        iterationDelay *= 2;
                    }
                }
                watch.Stop();
            }
 
            if (error != -1 && error != 0)
            {
                // parsing the result
                int serverError = 0;
                try
                {
                    int resultError = 0;
                    string responseDn = null;
                    string responseMessage = null;
                    Uri[] responseReferral = null;
                    DirectoryControl[] responseControl = null;
 
                    // ldap_parse_result skips over messages of type LDAP_RES_SEARCH_ENTRY and LDAP_RES_SEARCH_REFERRAL
                    if (error != (int)LdapResult.LDAP_RES_SEARCH_ENTRY && error != (int)LdapResult.LDAP_RES_REFERRAL)
                    {
                        resultError = ConstructParsedResult(ldapResult, ref serverError, ref responseDn, ref responseMessage, ref responseReferral, ref responseControl);
                    }
 
                    if (resultError == 0)
                    {
                        resultError = serverError;
 
                        if (error == (int)LdapResult.LDAP_RES_ADD)
                        {
                            response = new AddResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                        }
                        else if (error == (int)LdapResult.LDAP_RES_MODIFY)
                        {
                            response = new ModifyResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                        }
                        else if (error == (int)LdapResult.LDAP_RES_DELETE)
                        {
                            response = new DeleteResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                        }
                        else if (error == (int)LdapResult.LDAP_RES_MODRDN)
                        {
                            response = new ModifyDNResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                        }
                        else if (error == (int)LdapResult.LDAP_RES_COMPARE)
                        {
                            response = new CompareResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                        }
                        else if (error == (int)LdapResult.LDAP_RES_EXTENDED)
                        {
                            response = new ExtendedResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
                            if (resultError == (int)ResultCode.Success)
                            {
                                resultError = LdapPal.ParseExtendedResult(_ldapHandle, ldapResult, ref requestName, ref requestValue, 0 /*not free it*/);
                                if (resultError == 0)
                                {
                                    string name = null;
                                    if (requestName != IntPtr.Zero)
                                    {
                                        name = LdapPal.PtrToString(requestName);
                                    }
 
                                    BerVal val = null;
                                    byte[] requestValueArray = null;
                                    if (requestValue != IntPtr.Zero)
                                    {
                                        val = new BerVal();
                                        Marshal.PtrToStructure(requestValue, val);
                                        if (val.bv_len.Value != 0 && val.bv_val != IntPtr.Zero)
                                        {
                                            requestValueArray = new byte[val.bv_len.Value];
                                            Marshal.Copy(val.bv_val, requestValueArray, 0, (int)val.bv_len.Value);
                                        }
                                    }
 
                                    ((ExtendedResponse)response).ResponseName = name;
                                    ((ExtendedResponse)response).ResponseValue = requestValueArray;
                                }
                            }
                        }
                        else if (error == (int)LdapResult.LDAP_RES_SEARCH_RESULT ||
                               error == (int)LdapResult.LDAP_RES_SEARCH_ENTRY ||
                               error == (int)LdapResult.LDAP_RES_REFERRAL)
                        {
                            response = new SearchResponse(responseDn, responseControl, (ResultCode)resultError, responseMessage, responseReferral);
 
                            //set the flag here so our partial result processor knows whether the search is done or not
                            if (error == (int)LdapResult.LDAP_RES_SEARCH_RESULT)
                            {
                                ((SearchResponse)response).searchDone = true;
                            }
 
                            SearchResultEntryCollection searchResultEntries = new SearchResultEntryCollection();
                            SearchResultReferenceCollection searchResultReferences = new SearchResultReferenceCollection();
 
                            // parsing the resultentry
                            entryMessage = LdapPal.GetFirstEntryFromResult(_ldapHandle, ldapResult);
 
                            int entrycount = 0;
                            while (entryMessage != IntPtr.Zero)
                            {
                                SearchResultEntry entry = ConstructEntry(entryMessage);
                                if (entry != null)
                                {
                                    searchResultEntries.Add(entry);
                                }
 
                                entrycount++;
                                entryMessage = LdapPal.GetNextEntryFromResult(_ldapHandle, entryMessage);
                            }
 
                            // Parse the reference.
                            IntPtr referenceMessage = LdapPal.GetFirstReferenceFromResult(_ldapHandle, ldapResult);
 
                            while (referenceMessage != IntPtr.Zero)
                            {
                                SearchResultReference reference = ConstructReference(referenceMessage);
                                if (reference != null)
                                {
                                    searchResultReferences.Add(reference);
                                }
 
                                referenceMessage = LdapPal.GetNextReferenceFromResult(_ldapHandle, referenceMessage);
                            }
 
                            ((SearchResponse)response).Entries = searchResultEntries;
                            ((SearchResponse)response).References = searchResultReferences;
                        }
 
                        if (resultError != (int)ResultCode.Success && resultError != (int)ResultCode.CompareFalse && resultError != (int)ResultCode.CompareTrue && resultError != (int)ResultCode.Referral && resultError != (int)ResultCode.ReferralV2)
                        {
                            // Throw operation exception.
                            if (Utility.IsResultCode((ResultCode)resultError))
                            {
                                throw new DirectoryOperationException(response, OperationErrorMappings.MapResultCode(resultError));
                            }
                            else
                            {
                                // This should not occur.
                                throw new DirectoryOperationException(response);
                            }
                        }
 
                        return response;
                    }
                    else
                    {
                        // Fall through, throw the exception beow.
                        error = resultError;
                    }
                }
                finally
                {
                    if (requestName != IntPtr.Zero)
                    {
                        LdapPal.FreeMemory(requestName);
                    }
 
                    if (requestValue != IntPtr.Zero)
                    {
                        LdapPal.FreeMemory(requestValue);
                    }
 
                    if (ldapResult != IntPtr.Zero)
                    {
                        LdapPal.FreeMessage(ldapResult);
                    }
                }
            }
            else
            {
                // ldap_result failed
                if (error == 0)
                {
                    if (exceptionOnTimeOut)
                    {
                        // Client side timeout.
                        error = (int)LdapError.TimeOut;
                    }
                    else
                    {
                        // If we don't throw exception on time out (notification search for example), we
                        // just return an empty response.
                        return null;
                    }
                }
                else
                {
                    error = LdapPal.GetLastErrorFromConnection(_ldapHandle);
                }
 
                // Abandon the request.
                if (needAbandon)
                {
                    LdapPal.CancelDirectoryAsyncOperation(_ldapHandle, messageId);
                }
            }
 
            // Throw the proper exception here.
            throw ConstructException(error, operation);
        }
 
        internal unsafe int ConstructParsedResult(IntPtr ldapResult, ref int serverError, ref string responseDn, ref string responseMessage, ref Uri[] responseReferral, ref DirectoryControl[] responseControl)
        {
            IntPtr dn = IntPtr.Zero;
            IntPtr message = IntPtr.Zero;
            IntPtr referral = IntPtr.Zero;
            IntPtr control = IntPtr.Zero;
 
            try
            {
                int resultError = LdapPal.ParseResult(_ldapHandle, ldapResult, ref serverError, ref dn, ref message, ref referral, ref control, 0 /* not free it */);
 
                if (resultError == 0)
                {
                    // Parse the dn.
                    responseDn = LdapPal.PtrToString(dn);
 
                    // Parse the message.
                    responseMessage = LdapPal.PtrToString(message);
 
                    // Parse the referral.
                    if (referral != IntPtr.Zero)
                    {
                        char** tempPtr = (char**)referral;
                        char* singleReferral = tempPtr[0];
                        int i = 0;
                        var referralList = new ArrayList();
                        while (singleReferral != null)
                        {
                            string s = LdapPal.PtrToString((IntPtr)singleReferral);
                            referralList.Add(s);
 
                            i++;
                            singleReferral = tempPtr[i];
                        }
 
                        if (referralList.Count > 0)
                        {
                            responseReferral = new Uri[referralList.Count];
                            for (int j = 0; j < referralList.Count; j++)
                            {
                                responseReferral[j] = new Uri((string)referralList[j]);
                            }
                        }
                    }
 
                    // Parse the control.
                    if (control != IntPtr.Zero)
                    {
                        int i = 0;
                        IntPtr tempControlPtr = control;
                        IntPtr singleControl = Marshal.ReadIntPtr(tempControlPtr, 0);
                        var controlList = new ArrayList();
                        while (singleControl != IntPtr.Zero)
                        {
                            DirectoryControl directoryControl = ConstructControl(singleControl);
                            controlList.Add(directoryControl);
 
                            i++;
                            singleControl = Marshal.ReadIntPtr(tempControlPtr, i * IntPtr.Size);
                        }
 
                        responseControl = new DirectoryControl[controlList.Count];
                        controlList.CopyTo(responseControl);
                    }
                }
                else
                {
                    // we need to take care of one special case, when can't connect to the server, ldap_parse_result fails with local error
                    if (resultError == (int)LdapError.LocalError)
                    {
                        int tmpResult = LdapPal.ResultToErrorCode(_ldapHandle, ldapResult, 0 /* not free it */);
                        if (tmpResult != 0)
                        {
                            resultError = tmpResult;
                        }
                    }
                }
 
                return resultError;
            }
            finally
            {
                if (dn != IntPtr.Zero)
                {
                    LdapPal.FreeMemory(dn);
                }
 
                if (message != IntPtr.Zero)
                {
                    LdapPal.FreeMemory(message);
                }
 
                if (referral != IntPtr.Zero)
                {
                    LdapPal.FreeValue(referral);
                }
 
                if (control != IntPtr.Zero)
                {
                    LdapPal.FreeDirectoryControls(control);
                }
            }
        }
 
        internal SearchResultEntry ConstructEntry(IntPtr entryMessage)
        {
            IntPtr dn = IntPtr.Zero;
            IntPtr attribute = IntPtr.Zero;
            IntPtr address = IntPtr.Zero;
 
            try
            {
                // Get the dn.
                string entryDn = null;
                dn = LdapPal.GetDistinguishedName(_ldapHandle, entryMessage);
                if (dn != IntPtr.Zero)
                {
                    entryDn = LdapPal.PtrToString(dn);
                    LdapPal.FreeMemory(dn);
                    dn = IntPtr.Zero;
                }
 
                SearchResultEntry resultEntry = new SearchResultEntry(entryDn);
                SearchResultAttributeCollection attributes = resultEntry.Attributes;
 
                // Get attributes.
                attribute = LdapPal.GetFirstAttributeFromEntry(_ldapHandle, entryMessage, ref address);
 
                int tempcount = 0;
                while (attribute != IntPtr.Zero)
                {
                    DirectoryAttribute attr = ConstructAttribute(entryMessage, attribute);
                    attributes.Add(attr.Name, attr);
 
                    LdapPal.FreeMemory(attribute);
                    tempcount++;
                    attribute = LdapPal.GetNextAttributeFromResult(_ldapHandle, entryMessage, address);
                }
 
                if (address != IntPtr.Zero)
                {
                    BerPal.FreeBerElement(address, 0);
                    address = IntPtr.Zero;
                }
 
                return resultEntry;
            }
            finally
            {
                if (dn != IntPtr.Zero)
                {
                    LdapPal.FreeMemory(dn);
                }
 
                if (attribute != IntPtr.Zero)
                {
                    LdapPal.FreeMemory(attribute);
                }
 
                if (address != IntPtr.Zero)
                {
                    BerPal.FreeBerElement(address, 0);
                }
            }
        }
 
        internal DirectoryAttribute ConstructAttribute(IntPtr entryMessage, IntPtr attributeName)
        {
            var attribute = new DirectoryAttribute()
            {
                _isSearchResult = true
            };
 
            string name = LdapPal.PtrToString(attributeName);
            attribute.Name = name;
            IntPtr valuesArray = LdapPal.GetValuesFromAttribute(_ldapHandle, entryMessage, name);
            try
            {
                if (valuesArray != IntPtr.Zero)
                {
                    int count = 0;
                    IntPtr tempPtr = Marshal.ReadIntPtr(valuesArray, IntPtr.Size * count);
                    while (tempPtr != IntPtr.Zero)
                    {
                        BerVal bervalue = new BerVal();
                        Marshal.PtrToStructure(tempPtr, bervalue);
                        byte[] byteArray;
                        if (bervalue.bv_len.Value > 0 && bervalue.bv_val != IntPtr.Zero)
                        {
                            byteArray = new byte[bervalue.bv_len.Value];
                            Marshal.Copy(bervalue.bv_val, byteArray, 0, (int)bervalue.bv_len.Value);
                            attribute.Add(byteArray);
                        }
 
                        count++;
                        tempPtr = Marshal.ReadIntPtr(valuesArray, IntPtr.Size * count);
                    }
                }
            }
            finally
            {
                if (valuesArray != IntPtr.Zero)
                {
                    LdapPal.FreeAttributes(valuesArray);
                }
            }
 
            return attribute;
        }
 
        internal SearchResultReference ConstructReference(IntPtr referenceMessage)
        {
            IntPtr referenceArray = IntPtr.Zero;
            int error = LdapPal.ParseReference(_ldapHandle, referenceMessage, ref referenceArray);
 
            try
            {
                if (error == 0)
                {
                    var referralList = new ArrayList();
                    IntPtr tempPtr;
                    int count = 0;
                    if (referenceArray != IntPtr.Zero)
                    {
                        tempPtr = Marshal.ReadIntPtr(referenceArray, IntPtr.Size * count);
                        while (tempPtr != IntPtr.Zero)
                        {
                            string s = LdapPal.PtrToString(tempPtr);
                            referralList.Add(s);
 
                            count++;
                            tempPtr = Marshal.ReadIntPtr(referenceArray, IntPtr.Size * count);
                        }
 
                        LdapPal.FreeValue(referenceArray);
                        referenceArray = IntPtr.Zero;
                    }
 
                    if (referralList.Count > 0)
                    {
                        Uri[] uris = new Uri[referralList.Count];
                        for (int i = 0; i < referralList.Count; i++)
                        {
                            uris[i] = new Uri((string)referralList[i]);
                        }
 
                        return new SearchResultReference(uris);
                    }
                }
            }
            finally
            {
                if (referenceArray != IntPtr.Zero)
                {
                    LdapPal.FreeValue(referenceArray);
                }
            }
 
            return null;
        }
 
        private DirectoryException ConstructException(int error, LdapOperation operation)
        {
            DirectoryResponse response = null;
 
            if (Utility.IsResultCode((ResultCode)error))
            {
                if (operation == LdapOperation.LdapAdd)
                {
                    response = new AddResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapModify)
                {
                    response = new ModifyResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapDelete)
                {
                    response = new DeleteResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapModifyDn)
                {
                    response = new ModifyDNResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapCompare)
                {
                    response = new CompareResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapSearch)
                {
                    response = new SearchResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
                else if (operation == LdapOperation.LdapExtendedRequest)
                {
                    response = new ExtendedResponse(null, null, (ResultCode)error, OperationErrorMappings.MapResultCode(error), null);
                }
 
                string errorMessage = OperationErrorMappings.MapResultCode(error);
                return new DirectoryOperationException(response, errorMessage);
            }
            else
            {
                if (LdapErrorMappings.IsLdapError(error))
                {
                    string errorMessage = LdapErrorMappings.MapResultCode(error);
                    string serverErrorMessage = SessionOptions.ServerErrorMessage;
                    if (!string.IsNullOrEmpty(serverErrorMessage))
                    {
                        throw new LdapException(error, errorMessage, serverErrorMessage);
                    }
 
                    return new LdapException(error, errorMessage);
                }
 
                return new LdapException(error);
            }
        }
 
        private static DirectoryControl ConstructControl(IntPtr controlPtr)
        {
            LdapControl control = new LdapControl();
            Marshal.PtrToStructure(controlPtr, control);
 
            Debug.Assert(control.ldctl_oid != IntPtr.Zero);
            string controlType = LdapPal.PtrToString(control.ldctl_oid);
 
            byte[] bytes = new byte[control.ldctl_value.bv_len.Value];
            Marshal.Copy(control.ldctl_value.bv_val, bytes, 0, (int)control.ldctl_value.bv_len.Value);
 
            bool criticality = control.ldctl_iscritical;
 
            return new DirectoryControl(controlType, bytes, criticality, true);
        }
 
        private static bool SameCredential(NetworkCredential oldCredential, NetworkCredential newCredential)
        {
            if (oldCredential == null && newCredential == null)
            {
                return true;
            }
            else if (oldCredential == null && newCredential != null)
            {
                return false;
            }
            else if (oldCredential != null && newCredential == null)
            {
                return false;
            }
            else
            {
                if (oldCredential.Domain == newCredential.Domain &&
                    oldCredential.UserName == newCredential.UserName &&
                    oldCredential.Password == newCredential.Password)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
    }
}