File: System\DirectoryServices\ActiveDirectory\ADAMInstance.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices\src\System.DirectoryServices.csproj (System.DirectoryServices)
// 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.Net;
using System.Runtime.InteropServices;

namespace System.DirectoryServices.ActiveDirectory
{
    public class AdamInstance : DirectoryServer
    {
        private readonly string[] _becomeRoleOwnerAttrs;
        private bool _disposed;

        // for public properties
        private string? _cachedHostName;
        private int _cachedLdapPort = -1;
        private int _cachedSslPort = -1;
        private bool _defaultPartitionInitialized;
        private bool _defaultPartitionModified;
        private ConfigurationSet? _currentConfigSet;
        private string? _cachedDefaultPartition;
        private AdamRoleCollection? _cachedRoles;

        private IntPtr _ADAMHandle = (IntPtr)0;
        private IntPtr _authIdentity = IntPtr.Zero;
        private SyncUpdateCallback? _userDelegate;
        private readonly SyncReplicaFromAllServersCallback _syncAllFunctionPointer;
        private static readonly char[] s_comma = new char[] { ',' };

        #region constructors
        internal AdamInstance(DirectoryContext context, string adamInstanceName)
            : this(context, adamInstanceName, new DirectoryEntryManager(context), true)
        {
        }

        internal AdamInstance(DirectoryContext context, string adamInstanceName, DirectoryEntryManager directoryEntryMgr, bool nameIncludesPort)
        {
            this.context = context;
            this.replicaName = adamInstanceName;
            this.directoryEntryMgr = directoryEntryMgr;

            // initialize the transfer role owner attributes
            _becomeRoleOwnerAttrs = new string[2];
            _becomeRoleOwnerAttrs[0] = PropertyManager.BecomeSchemaMaster;
            _becomeRoleOwnerAttrs[1] = PropertyManager.BecomeDomainMaster;

            // initialize the callback function
            _syncAllFunctionPointer = new SyncReplicaFromAllServersCallback(SyncAllCallbackRoutine);
        }

        internal AdamInstance(DirectoryContext context, string adamHostName, DirectoryEntryManager directoryEntryMgr)
        {
            this.context = context;

            // the replica name should be in the form dnshostname:port
            this.replicaName = adamHostName;
            string? portNumber;
            Utils.SplitServerNameAndPortNumber(context.Name!, out portNumber);
            if (portNumber != null)
            {
                this.replicaName = this.replicaName + ":" + portNumber;
            }

            // initialize the directory entry manager
            this.directoryEntryMgr = directoryEntryMgr;

            // initialize the transfer role owner attributes
            _becomeRoleOwnerAttrs = new string[2];
            _becomeRoleOwnerAttrs[0] = PropertyManager.BecomeSchemaMaster;
            _becomeRoleOwnerAttrs[1] = PropertyManager.BecomeDomainMaster;

            // initialize the callback function
            _syncAllFunctionPointer = new SyncReplicaFromAllServersCallback(SyncAllCallbackRoutine);
        }
        #endregion constructors

        #region IDisposable

        ~AdamInstance()
        {
            // finalizer is called => Dispose has not been called yet.
            Dispose(false);
        }

        // private Dispose method
        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                try
                {
                    // if there are any managed or unmanaged
                    // resources to be freed, those should be done here
                    // if disposing = true, only unmanaged resources should
                    // be freed, else both managed and unmanaged.
                    FreeADAMHandle();
                    _disposed = true;
                }
                finally
                {
                    base.Dispose();
                }
            }
        }
        #endregion IDisposable

        #region public methods

        public static AdamInstance GetAdamInstance(DirectoryContext context)
        {
            DirectoryEntryManager? directoryEntryMgr = null;
            string? dnsHostName = null;

            // check that the context is not null
            ArgumentNullException.ThrowIfNull(context);

            // contexttype should be DirectoryServer
            if (context.ContextType != DirectoryContextType.DirectoryServer)
            {
                throw new ArgumentException(SR.TargetShouldBeADAMServer, nameof(context));
            }

            // target must be a server
            if ((!context.isServer()))
            {
                throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(AdamInstance), context.Name);
            }

            //  work with copy of the context
            context = new DirectoryContext(context);

            // bind to the given adam instance
            try
            {
                directoryEntryMgr = new DirectoryEntryManager(context);
                DirectoryEntry rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);

                // This will ensure that we are talking to ADAM instance only
                if (!Utils.CheckCapability(rootDSE, Capability.ActiveDirectoryApplicationMode))
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(AdamInstance), context.Name);
                }
                dnsHostName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DnsHostName)!;
            }
            catch (COMException e)
            {
                int errorCode = e.ErrorCode;

                if (errorCode == unchecked((int)0x8007203a))
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(AdamInstance), context.Name);
                }
                else
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }

            return new AdamInstance(context, dnsHostName, directoryEntryMgr);
        }

        public static AdamInstance FindOne(DirectoryContext context, string partitionName)
        {
            // validate parameters (partitionName validated by the call to ConfigSet)
            ArgumentNullException.ThrowIfNull(context);

            // contexttype should be ConfigurationSet
            if (context.ContextType != DirectoryContextType.ConfigurationSet)
            {
                throw new ArgumentException(SR.TargetShouldBeConfigSet, nameof(context));
            }

            ArgumentNullException.ThrowIfNull(partitionName);

            if (partitionName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partitionName));
            }

            //  work with copy of the context
            context = new DirectoryContext(context);

            return ConfigurationSet.FindOneAdamInstance(context, partitionName, null);
        }

        public static AdamInstanceCollection FindAll(DirectoryContext context, string partitionName)
        {
            AdamInstanceCollection? adamInstanceCollection = null;

            // validate parameters (partitionName validated by the call to ConfigSet)
            ArgumentNullException.ThrowIfNull(context);

            // contexttype should be ConfigurationSet
            if (context.ContextType != DirectoryContextType.ConfigurationSet)
            {
                throw new ArgumentException(SR.TargetShouldBeConfigSet, nameof(context));
            }

            ArgumentNullException.ThrowIfNull(partitionName);

            if (partitionName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partitionName));
            }

            //  work with copy of the context
            context = new DirectoryContext(context);

            try
            {
                adamInstanceCollection = ConfigurationSet.FindAdamInstances(context, partitionName, null);
            }
            catch (ActiveDirectoryObjectNotFoundException)
            {
                // this is the case where  we could not find an ADAM instance in that config set (return empty collection)
                adamInstanceCollection = new AdamInstanceCollection(new ArrayList());
            }

            return adamInstanceCollection;
        }

        public void TransferRoleOwnership(AdamRole role)
        {
            CheckIfDisposed();

            if (role < AdamRole.SchemaRole || role > AdamRole.NamingRole)
            {
                throw new InvalidEnumArgumentException(nameof(role), (int)role, typeof(AdamRole));
            }

            // set the appropriate attribute on the root dse
            try
            {
                DirectoryEntry rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
                rootDSE.Properties[_becomeRoleOwnerAttrs[(int)role]].Value = 1;
                rootDSE.CommitChanges();
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }

            // invalidate the role collection so that it gets loaded again next time
            _cachedRoles = null;
        }

        public void SeizeRoleOwnership(AdamRole role)
        {
            // set the "fsmoRoleOwner" attribute on the appropriate role object
            // to the NTDSAObjectName of this ADAM Instance
            string? roleObjectDN = null;

            CheckIfDisposed();

            switch (role)
            {
                case AdamRole.SchemaRole:
                    {
                        roleObjectDN = directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.SchemaNamingContext);
                        break;
                    }
                case AdamRole.NamingRole:
                    {
                        roleObjectDN = directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer);
                        break;
                    }
                default:
                    {
                        throw new InvalidEnumArgumentException(nameof(role), (int)role, typeof(AdamRole));
                    }
            }

            DirectoryEntry? roleObjectEntry = null;
            try
            {
                roleObjectEntry = DirectoryEntryManager.GetDirectoryEntry(context, roleObjectDN);
                roleObjectEntry.Properties[PropertyManager.FsmoRoleOwner].Value = NtdsaObjectName;
                roleObjectEntry.CommitChanges();
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                roleObjectEntry?.Dispose();
            }

            // invalidate the role collection so that it gets loaded again next time
            _cachedRoles = null;
        }

        public override void CheckReplicationConsistency()
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            // get the handle
            GetADAMHandle();

            // call private helper function
            CheckConsistencyHelper(_ADAMHandle, DirectoryContext.ADAMHandle);
        }

        public override ReplicationCursorCollection GetReplicationCursors(string partition)
        {
            IntPtr info = (IntPtr)0;
            int context = 0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (partition == null)
                throw new ArgumentNullException(nameof(partition));

            if (partition.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partition));

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_CURSORS_3_FOR_NC, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_CURSORS_FOR_NC, partition, ref advanced, context, DirectoryContext.ADAMHandle);
            return ConstructReplicationCursors(_ADAMHandle, advanced, info, partition, this, DirectoryContext.ADAMHandle);
        }

        public override ReplicationOperationInformation GetReplicationOperationInformation()
        {
            IntPtr info = (IntPtr)0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_PENDING_OPS, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_PENDING_OPS, null, ref advanced, 0, DirectoryContext.ADAMHandle);
            return ConstructPendingOperations(info, this, DirectoryContext.ADAMHandle);
        }

        public override ReplicationNeighborCollection GetReplicationNeighbors(string partition)
        {
            IntPtr info = (IntPtr)0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (partition == null)
                throw new ArgumentNullException(nameof(partition));

            if (partition.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partition));

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_NEIGHBORS, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_NEIGHBORS, partition, ref advanced, 0, DirectoryContext.ADAMHandle);
            return ConstructNeighbors(info, this, DirectoryContext.ADAMHandle);
        }

        public override ReplicationNeighborCollection GetAllReplicationNeighbors()
        {
            IntPtr info = (IntPtr)0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_NEIGHBORS, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_NEIGHBORS, null, ref advanced, 0, DirectoryContext.ADAMHandle);
            return ConstructNeighbors(info, this, DirectoryContext.ADAMHandle);
        }

        public override ReplicationFailureCollection GetReplicationConnectionFailures()
        {
            return GetReplicationFailures(DS_REPL_INFO_TYPE.DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES);
        }

        public override ActiveDirectoryReplicationMetadata GetReplicationMetadata(string objectPath)
        {
            IntPtr info = (IntPtr)0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (objectPath == null)
                throw new ArgumentNullException(nameof(objectPath));

            if (objectPath.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(objectPath));

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_METADATA_2_FOR_OBJ, (int)DS_REPL_INFO_TYPE.DS_REPL_INFO_METADATA_FOR_OBJ, objectPath, ref advanced, 0, DirectoryContext.ADAMHandle);
            return ConstructMetaData(advanced, info, this, DirectoryContext.ADAMHandle);
        }

        public override void SyncReplicaFromServer(string partition, string sourceServer)
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (partition == null)
                throw new ArgumentNullException(nameof(partition));

            if (partition.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partition));

            if (sourceServer == null)
                throw new ArgumentNullException(nameof(sourceServer));

            if (sourceServer.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(sourceServer));

            // get the dsHandle
            GetADAMHandle();
            SyncReplicaHelper(_ADAMHandle, true, partition, sourceServer, 0, DirectoryContext.ADAMHandle);
        }

        public override void TriggerSyncReplicaFromNeighbors(string partition)
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (partition == null)
                throw new ArgumentNullException(nameof(partition));

            if (partition.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partition));

            // get the dsHandle
            GetADAMHandle();
            SyncReplicaHelper(_ADAMHandle, true, partition, null, DS_REPSYNC_ASYNCHRONOUS_OPERATION | DS_REPSYNC_ALL_SOURCES, DirectoryContext.ADAMHandle);
        }

        public override void SyncReplicaFromAllServers(string partition, SyncFromAllServersOptions options)
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (partition == null)
                throw new ArgumentNullException(nameof(partition));

            if (partition.Length == 0)
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partition));

            // get the dsHandle
            GetADAMHandle();
            SyncReplicaAllHelper(_ADAMHandle, _syncAllFunctionPointer, partition, options, SyncFromAllServersCallback, DirectoryContext.ADAMHandle);
        }

        public void Save()
        {
            CheckIfDisposed();

            // only thing to be saved is the ntdsa entry (for default partition)
            if (_defaultPartitionModified)
            {
                DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                try
                {
                    ntdsaEntry.CommitChanges();
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }

            // reset the bools associated with this property
            _defaultPartitionInitialized = false;
            _defaultPartitionModified = false;
        }

        #endregion public methods

        #region public properties

        public ConfigurationSet ConfigurationSet
        {
            get
            {
                CheckIfDisposed();
                if (_currentConfigSet == null)
                {
                    DirectoryContext configSetContext = Utils.GetNewDirectoryContext(Name, DirectoryContextType.DirectoryServer, context);
                    _currentConfigSet = ConfigurationSet.GetConfigurationSet(configSetContext);
                }
                return _currentConfigSet;
            }
        }

        public string HostName
        {
            get
            {
                CheckIfDisposed();
                if (_cachedHostName == null)
                {
                    DirectoryEntry serverEntry = directoryEntryMgr.GetCachedDirectoryEntry(ServerObjectName);
                    _cachedHostName = (string)PropertyManager.GetPropertyValue(context, serverEntry, PropertyManager.DnsHostName)!;
                }
                return _cachedHostName;
            }
        }

        public int LdapPort
        {
            get
            {
                CheckIfDisposed();
                if (_cachedLdapPort == -1)
                {
                    DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                    _cachedLdapPort = (int)PropertyManager.GetPropertyValue(context, ntdsaEntry, PropertyManager.MsDSPortLDAP)!;
                }
                return _cachedLdapPort;
            }
        }

        public int SslPort
        {
            get
            {
                CheckIfDisposed();
                if (_cachedSslPort == -1)
                {
                    DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                    _cachedSslPort = (int)PropertyManager.GetPropertyValue(context, ntdsaEntry, PropertyManager.MsDSPortSSL)!;
                }
                return _cachedSslPort;
            }
        }

        // Abstract Properties

        public AdamRoleCollection Roles
        {
            get
            {
                CheckIfDisposed();
                DirectoryEntry? schemaEntry = null;
                DirectoryEntry? partitionsEntry = null;

                try
                {
                    if (_cachedRoles == null)
                    {
                        // check for the fsmoRoleOwner attribute on the Config and Schema objects
                        ArrayList roleList = new ArrayList();
                        schemaEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.SchemaNamingContext));

                        if (NtdsaObjectName.Equals((string)PropertyManager.GetPropertyValue(context, schemaEntry, PropertyManager.FsmoRoleOwner)!))
                        {
                            roleList.Add(AdamRole.SchemaRole);
                        }

                        partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer));

                        if (NtdsaObjectName.Equals((string)PropertyManager.GetPropertyValue(context, partitionsEntry, PropertyManager.FsmoRoleOwner)!))
                        {
                            roleList.Add(AdamRole.NamingRole);
                        }

                        _cachedRoles = new AdamRoleCollection(roleList);
                    }
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
                finally
                {
                    schemaEntry?.Dispose();
                    partitionsEntry?.Dispose();
                }
                return _cachedRoles;
            }
        }

        public string? DefaultPartition
        {
            get
            {
                CheckIfDisposed();
                if (!_defaultPartitionInitialized || _defaultPartitionModified)
                {
                    DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                    try
                    {
                        ntdsaEntry.RefreshCache();
                        if (ntdsaEntry.Properties[PropertyManager.MsDSDefaultNamingContext].Value == null)
                        {
                            // property has not been set
                            _cachedDefaultPartition = null;
                        }
                        else
                        {
                            _cachedDefaultPartition = (string)PropertyManager.GetPropertyValue(context, ntdsaEntry, PropertyManager.MsDSDefaultNamingContext)!;
                        }
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                    _defaultPartitionInitialized = true;
                }
                return _cachedDefaultPartition;
            }
            set
            {
                CheckIfDisposed();

                DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                if (value == null)
                {
                    if (ntdsaEntry.Properties.Contains(PropertyManager.MsDSDefaultNamingContext))
                    {
                        ntdsaEntry.Properties[PropertyManager.MsDSDefaultNamingContext].Clear();
                    }
                }
                else
                {
                    //
                    // should be in DN format
                    //
                    if (!Utils.IsValidDNFormat(value))
                    {
                        throw new ArgumentException(SR.InvalidDNFormat, nameof(value));
                    }

                    // this value should be one of partitions currently being hosted by
                    // this adam instance
                    if (!Partitions.Contains(value))
                    {
                        throw new ArgumentException(SR.ServerNotAReplica, nameof(value));
                    }
                    ntdsaEntry.Properties[PropertyManager.MsDSDefaultNamingContext].Value = value;
                }
                _defaultPartitionModified = true;
            }
        }

        public override string? IPAddress
        {
            get
            {
                CheckIfDisposed();

                IPHostEntry hostEntry = Dns.GetHostEntry(HostName);
                if (hostEntry.AddressList.GetLength(0) > 0)
                {
                    return (hostEntry.AddressList[0]).ToString();
                }
                else
                {
                    return null;
                }
            }
        }

        public override string SiteName
        {
            get
            {
                CheckIfDisposed();
                if (cachedSiteName == null)
                {
                    DirectoryEntry siteEntry = DirectoryEntryManager.GetDirectoryEntry(context, SiteObjectName);
                    try
                    {
                        cachedSiteName = (string)PropertyManager.GetPropertyValue(context, siteEntry, PropertyManager.Cn)!;
                    }
                    finally
                    {
                        siteEntry.Dispose();
                    }
                }
                return cachedSiteName;
            }
        }

        internal string SiteObjectName
        {
            get
            {
                CheckIfDisposed();
                if (cachedSiteObjectName == null)
                {
                    // get the site object name from the server object name
                    // CN=server1,CN=Servers,CN=Site1,CN=Sites
                    // the site object name is the third component onwards
                    string[] components = ServerObjectName.Split(s_comma);
                    if (components.GetLength(0) < 3)
                    {
                        // should not happen
                        throw new ActiveDirectoryOperationException(SR.InvalidServerNameFormat);
                    }
                    cachedSiteObjectName = components[2];
                    for (int i = 3; i < components.GetLength(0); i++)
                    {
                        cachedSiteObjectName += "," + components[i];
                    }
                }
                return cachedSiteObjectName;
            }
        }

        internal string ServerObjectName
        {
            get
            {
                CheckIfDisposed();
                if (cachedServerObjectName == null)
                {
                    DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
                    try
                    {
                        cachedServerObjectName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.ServerName)!;
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                    finally
                    {
                        rootDSE.Dispose();
                    }
                }
                return cachedServerObjectName;
            }
        }

        internal string NtdsaObjectName
        {
            get
            {
                CheckIfDisposed();
                if (cachedNtdsaObjectName == null)
                {
                    DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
                    try
                    {
                        cachedNtdsaObjectName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DsServiceName)!;
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                    finally
                    {
                        rootDSE.Dispose();
                    }
                }
                return cachedNtdsaObjectName;
            }
        }

        internal Guid NtdsaObjectGuid
        {
            get
            {
                CheckIfDisposed();
                if (cachedNtdsaObjectGuid == Guid.Empty)
                {
                    DirectoryEntry ntdsaEntry = directoryEntryMgr.GetCachedDirectoryEntry(NtdsaObjectName);
                    byte[] guidByteArray = (byte[])PropertyManager.GetPropertyValue(context, ntdsaEntry, PropertyManager.ObjectGuid)!;
                    cachedNtdsaObjectGuid = new Guid(guidByteArray);
                }
                return cachedNtdsaObjectGuid;
            }
        }

        public override SyncUpdateCallback? SyncFromAllServersCallback
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                return _userDelegate;
            }

            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                _userDelegate = value;
            }
        }

        public override ReplicationConnectionCollection InboundConnections => GetInboundConnectionsHelper();

        public override ReplicationConnectionCollection OutboundConnections => GetOutboundConnectionsHelper();

        #endregion public properties

        #region private methods

        private ReplicationFailureCollection GetReplicationFailures(DS_REPL_INFO_TYPE type)
        {
            IntPtr info = (IntPtr)0;
            bool advanced = true;

            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            // get the handle
            GetADAMHandle();
            info = GetReplicationInfoHelper(_ADAMHandle, (int)type, (int)type, null, ref advanced, 0, DirectoryContext.ADAMHandle);
            return ConstructFailures(info, this, DirectoryContext.ADAMHandle);
        }

        private void GetADAMHandle()
        {
            // this part of the code needs to be synchronized
            lock (this)
            {
                if (_ADAMHandle == IntPtr.Zero)
                {
                    // get the credentials object
                    if (_authIdentity == IntPtr.Zero)
                    {
                        _authIdentity = Utils.GetAuthIdentity(context, DirectoryContext.ADAMHandle);
                    }

                    // DSBind, but we need to have port as annotation specified
                    string bindingString = HostName + ":" + LdapPort;

                    _ADAMHandle = Utils.GetDSHandle(bindingString, null, _authIdentity, DirectoryContext.ADAMHandle);
                }
            }
        }

        private void FreeADAMHandle()
        {
            lock (this)
            {
                // DsUnbind
                Utils.FreeDSHandle(_ADAMHandle, DirectoryContext.ADAMHandle);

                // free the credentials object
                Utils.FreeAuthIdentity(_authIdentity, DirectoryContext.ADAMHandle);
            }
        }

        #endregion private methods
    }
}