File: System\DirectoryServices\ActiveDirectory\ApplicationPartition.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;

namespace System.DirectoryServices.ActiveDirectory
{
    internal enum NCFlags : int
    {
        InstanceTypeIsNCHead = 1,
        InstanceTypeIsWriteable = 4
    }

    internal enum ApplicationPartitionType : int
    {
        Unknown = -1,
        ADApplicationPartition = 0,
        ADAMApplicationPartition = 1
    }

    public class ApplicationPartition : ActiveDirectoryPartition
    {
        private bool _disposed;
        private ApplicationPartitionType _appType = (ApplicationPartitionType)(-1);
        private bool _committed = true;
        private DirectoryEntry? _domainDNSEntry;
        private DirectoryEntry? _crossRefEntry;
        private string? _dnsName;
        private DirectoryServerCollection? _cachedDirectoryServers;
        private bool _securityRefDomainModified;
        private string? _securityRefDomain;

        #region constructors
        // Public Constructors
        public ApplicationPartition(DirectoryContext context, string distinguishedName)
        {
            // validate the parameters
            ValidateApplicationPartitionParameters(context, distinguishedName, null, false);

            // call private function for creating the application partition
            CreateApplicationPartition(distinguishedName, "domainDns");
        }

        public ApplicationPartition(DirectoryContext context, string distinguishedName, string objectClass)
        {
            // validate the parameters
            ValidateApplicationPartitionParameters(context, distinguishedName, objectClass, true);

            // call private function for creating the application partition
            CreateApplicationPartition(distinguishedName, objectClass);
        }

        // Internal Constructors
        internal ApplicationPartition(DirectoryContext context, string distinguishedName, string? dnsName, ApplicationPartitionType appType, DirectoryEntryManager directoryEntryMgr)
            : base(context, distinguishedName)
        {
            this.directoryEntryMgr = directoryEntryMgr;
            _appType = appType;
            _dnsName = dnsName;
        }

        internal ApplicationPartition(DirectoryContext context, string distinguishedName, string dnsName, DirectoryEntryManager directoryEntryMgr)
            : this(context, distinguishedName, dnsName, GetApplicationPartitionType(context), directoryEntryMgr)
        {
        }
        #endregion constructors

        #region IDisposable
        // 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.
                    if (_crossRefEntry != null)
                    {
                        _crossRefEntry.Dispose();
                        _crossRefEntry = null;
                    }
                    if (_domainDNSEntry != null)
                    {
                        _domainDNSEntry.Dispose();
                        _domainDNSEntry = null;
                    }

                    _disposed = true;
                }
                finally
                {
                    base.Dispose();
                }
            }
        }
        #endregion IDisposable

        #region public methods

        public static ApplicationPartition GetApplicationPartition(DirectoryContext context)
        {
            // validate the context
            ArgumentNullException.ThrowIfNull(context);

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

            // target must be ndnc dns name
            if (!context.isNdnc())
            {
                throw new ActiveDirectoryObjectNotFoundException(SR.NDNCNotFound, typeof(ApplicationPartition), context.Name);
            }

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

            // bind to the application partition head (this will verify credentials)
            string distinguishedName = Utils.GetDNFromDnsName(context.Name!);
            DirectoryEntryManager directoryEntryMgr = new DirectoryEntryManager(context);
            DirectoryEntry? appNCHead = null;

            try
            {
                appNCHead = directoryEntryMgr.GetCachedDirectoryEntry(distinguishedName);
                // need to force the bind
                appNCHead.Bind(true);
            }
            catch (COMException e)
            {
                int errorCode = e.ErrorCode;

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

            return new ApplicationPartition(context, distinguishedName, context.Name, ApplicationPartitionType.ADApplicationPartition, directoryEntryMgr);
        }

        public static ApplicationPartition FindByName(DirectoryContext context, string distinguishedName)
        {
            ApplicationPartition? partition = null;
            DirectoryEntryManager? directoryEntryMgr = null;
            DirectoryContext? appNCContext = null;

            // check that the argument is not null
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if ((context.Name == null) && (!context.isRootDomain()))
            {
                throw new ArgumentException(SR.ContextNotAssociatedWithDomain, nameof(context));
            }

            if (context.Name != null)
            {
                // the target should be a valid forest name, configset name or a server
                if (!((context.isRootDomain()) || (context.isADAMConfigSet()) || context.isServer()))
                {
                    throw new ArgumentException(SR.NotADOrADAM, nameof(context));
                }
            }

            // check that the distingushed name of the application partition is not null or empty
            if (distinguishedName == null)
                throw new ArgumentNullException(nameof(distinguishedName));

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

            if (!Utils.IsValidDNFormat(distinguishedName))
                throw new ArgumentException(SR.InvalidDNFormat, nameof(distinguishedName));

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

            // search in the partitions container of the forest for
            // crossRef objects that have their nCName set to the specified distinguishedName
            directoryEntryMgr = new DirectoryEntryManager(context);
            DirectoryEntry? partitionsEntry = null;

            try
            {
                partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer));
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            catch (ActiveDirectoryObjectNotFoundException)
            {
                // this is the case where the context is a config set and we could not find an ADAM instance in that config set
                throw new ActiveDirectoryOperationException(SR.Format(SR.ADAMInstanceNotFoundInConfigSet, context.Name));
            }

            // build the filter
            StringBuilder str = new StringBuilder(15);
            str.Append("(&(");
            str.Append(PropertyManager.ObjectCategory);
            str.Append("=crossRef)(");
            str.Append(PropertyManager.SystemFlags);
            str.Append(":1.2.840.113556.1.4.804:=");
            str.Append((int)SystemFlag.SystemFlagNtdsNC);
            str.Append(")(!(");
            str.Append(PropertyManager.SystemFlags);
            str.Append(":1.2.840.113556.1.4.803:=");
            str.Append((int)SystemFlag.SystemFlagNtdsDomain);
            str.Append("))(");
            str.Append(PropertyManager.NCName);
            str.Append('=');
            str.Append(Utils.GetEscapedFilterValue(distinguishedName));
            str.Append("))");

            string filter = str.ToString();
            string[] propertiesToLoad = new string[2];

            propertiesToLoad[0] = PropertyManager.DnsRoot;
            propertiesToLoad[1] = PropertyManager.NCName;

            ADSearcher searcher = new ADSearcher(partitionsEntry, filter, propertiesToLoad, SearchScope.OneLevel, false /*not paged search*/, false /*no cached results*/);
            SearchResult? res = null;

            try
            {
                res = searcher.FindOne();
            }
            catch (COMException e)
            {
                if (e.ErrorCode == unchecked((int)0x80072030))
                {
                    // object is not found since we cannot even find the container in which to search
                    throw new ActiveDirectoryObjectNotFoundException(SR.AppNCNotFound, typeof(ApplicationPartition), distinguishedName);
                }
                else
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
            finally
            {
                partitionsEntry.Dispose();
            }

            if (res == null)
            {
                // the specified application partition could not be found in the given forest
                throw new ActiveDirectoryObjectNotFoundException(SR.AppNCNotFound, typeof(ApplicationPartition), distinguishedName);
            }

            string? appNCDnsName = null;
            try
            {
                appNCDnsName = (res.Properties[PropertyManager.DnsRoot].Count > 0) ? (string)res.Properties[PropertyManager.DnsRoot][0]! : null;
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }

            // verify that if the target is a server, then this partition is a naming context on it
            ApplicationPartitionType appType = GetApplicationPartitionType(context);
            if (context.ContextType == DirectoryContextType.DirectoryServer)
            {
                bool hostsCurrentPartition = false;
                DistinguishedName appNCDN = new DistinguishedName(distinguishedName);
                DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);

                try
                {
                    foreach (string namingContext in rootDSE.Properties[PropertyManager.NamingContexts])
                    {
                        DistinguishedName dn = new DistinguishedName(namingContext);

                        if (dn.Equals(appNCDN))
                        {
                            hostsCurrentPartition = true;
                            break;
                        }
                    }
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
                finally
                {
                    rootDSE.Dispose();
                }

                if (!hostsCurrentPartition)
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.AppNCNotFound, typeof(ApplicationPartition), distinguishedName);
                }

                appNCContext = context;
            }
            else
            {
                // we need to find a server which hosts this application partition
                if (appType == ApplicationPartitionType.ADApplicationPartition)
                {
                    int errorCode = 0;
                    DomainControllerInfo domainControllerInfo;

                    errorCode = Locator.DsGetDcNameWrapper(null, appNCDnsName, null, (long)PrivateLocatorFlags.OnlyLDAPNeeded, out domainControllerInfo);

                    if (errorCode == Interop.Errors.ERROR_NO_SUCH_DOMAIN)
                    {
                        throw new ActiveDirectoryObjectNotFoundException(SR.AppNCNotFound, typeof(ApplicationPartition), distinguishedName);
                    }
                    else if (errorCode != 0)
                    {
                        throw ExceptionHelper.GetExceptionFromErrorCode(errorCode);
                    }

                    Debug.Assert(domainControllerInfo.DomainControllerName.Length > 2, "ApplicationPartition:FindByName - domainControllerInfo.DomainControllerName.Length <= 2");
                    string serverName = domainControllerInfo.DomainControllerName.Substring(2);
                    appNCContext = Utils.GetNewDirectoryContext(serverName, DirectoryContextType.DirectoryServer, context);
                }
                else
                {
                    // this will find an adam instance that hosts this partition and which is alive and responding.
                    string adamInstName = ConfigurationSet.FindOneAdamInstance(context.Name, context, distinguishedName, null).Name;
                    appNCContext = Utils.GetNewDirectoryContext(adamInstName, DirectoryContextType.DirectoryServer, context);
                }
            }
            partition = new ApplicationPartition(appNCContext, (string)PropertyManager.GetSearchResultPropertyValue(res, PropertyManager.NCName)!, appNCDnsName, appType, directoryEntryMgr);

            return partition;
        }

        public DirectoryServer FindDirectoryServer()
        {
            DirectoryServer? directoryServer = null;

            CheckIfDisposed();

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                directoryServer = FindDirectoryServerInternal(null, false);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }
                directoryServer = ConfigurationSet.FindOneAdamInstance(context, Name, null);
            }
            return directoryServer;
        }

        public DirectoryServer FindDirectoryServer(string siteName)
        {
            DirectoryServer? directoryServer = null;

            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(siteName);

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                directoryServer = FindDirectoryServerInternal(siteName, false);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }

                directoryServer = ConfigurationSet.FindOneAdamInstance(context, Name, siteName);
            }
            return directoryServer;
        }

        public DirectoryServer FindDirectoryServer(bool forceRediscovery)
        {
            DirectoryServer? directoryServer = null;

            CheckIfDisposed();

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                directoryServer = FindDirectoryServerInternal(null, forceRediscovery);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }

                // forceRediscovery is ignored for ADAM Application partition
                directoryServer = ConfigurationSet.FindOneAdamInstance(context, Name, null);
            }
            return directoryServer;
        }

        public DirectoryServer FindDirectoryServer(string siteName, bool forceRediscovery)
        {
            DirectoryServer? directoryServer = null;

            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(siteName);

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                directoryServer = FindDirectoryServerInternal(siteName, forceRediscovery);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }

                // forceRediscovery is ignored for ADAM Application partition
                directoryServer = ConfigurationSet.FindOneAdamInstance(context, Name, siteName);
            }
            return directoryServer;
        }

        public ReadOnlyDirectoryServerCollection FindAllDirectoryServers()
        {
            CheckIfDisposed();

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                return FindAllDirectoryServersInternal(null);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }

                ReadOnlyDirectoryServerCollection directoryServers = new ReadOnlyDirectoryServerCollection();
                directoryServers.AddRange(ConfigurationSet.FindAdamInstances(context, Name, null));
                return directoryServers;
            }
        }

        public ReadOnlyDirectoryServerCollection FindAllDirectoryServers(string siteName)
        {
            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(siteName);

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                return FindAllDirectoryServersInternal(siteName);
            }
            else
            {
                // Check that the application partition has been committed
                if (!_committed)
                {
                    throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
                }

                ReadOnlyDirectoryServerCollection directoryServers = new ReadOnlyDirectoryServerCollection();
                directoryServers.AddRange(ConfigurationSet.FindAdamInstances(context, Name, siteName));
                return directoryServers;
            }
        }

        public ReadOnlyDirectoryServerCollection FindAllDiscoverableDirectoryServers()
        {
            CheckIfDisposed();

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                return FindAllDiscoverableDirectoryServersInternal(null);
            }
            else
            {
                //
                // throw exception for ADAM
                //
                throw new NotSupportedException(SR.OperationInvalidForADAM);
            }
        }

        public ReadOnlyDirectoryServerCollection FindAllDiscoverableDirectoryServers(string siteName)
        {
            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(siteName);

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                // AD
                return FindAllDiscoverableDirectoryServersInternal(siteName);
            }
            else
            {
                //
                // throw exception for ADAM
                //
                throw new NotSupportedException(SR.OperationInvalidForADAM);
            }
        }

        public void Delete()
        {
            CheckIfDisposed();

            // Check that the application partition has been committed
            if (!_committed)
            {
                throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
            }

            // Get the partitions container and delete the crossRef entry for this
            // application partition
            DirectoryEntry partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer));
            try
            {
                GetCrossRefEntry();
                partitionsEntry.Children.Remove(_crossRefEntry);
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                partitionsEntry.Dispose();
            }
        }

        public void Save()
        {
            CheckIfDisposed();

            if (!_committed)
            {
                bool createManually = false;

                if (_appType == ApplicationPartitionType.ADApplicationPartition)
                {
                    try
                    {
                        _domainDNSEntry!.CommitChanges();
                    }
                    catch (COMException e)
                    {
                        if (e.ErrorCode == unchecked((int)0x80072029))
                        {
                            // inappropriate authentication (we might have fallen back to NTLM)
                            createManually = true;
                        }
                        else
                        {
                            throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                        }
                    }
                }
                else
                {
                    // for ADAM we always create the crossRef manually before creating the domainDNS object
                    createManually = true;
                }

                if (createManually)
                {
                    // we need to first save the cross ref entry
                    try
                    {
                        InitializeCrossRef(partitionName);
                        _crossRefEntry.CommitChanges();
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }

                    try
                    {
                        _domainDNSEntry!.CommitChanges();
                    }
                    catch (COMException e)
                    {
                        //
                        //delete the crossRef entry
                        //
                        DirectoryEntry partitionsEntry = _crossRefEntry.Parent;
                        try
                        {
                            partitionsEntry.Children.Remove(_crossRefEntry);
                        }
                        catch (COMException e2)
                        {
                            throw ExceptionHelper.GetExceptionFromCOMException(e2);
                        }

                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }

                    // if the crossRef is created manually we need to refresh the cross ref entry to get the changes that were made
                    // due to the creation of the partition
                    try
                    {
                        _crossRefEntry.RefreshCache();
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                }

                // When we create a domainDNS object on DC1 (Naming Master = DC2),
                // then internally DC1 will contact DC2 to create the disabled crossRef object.
                // DC2 will force replicate the crossRef object to DC1. DC1 will then create
                // the domainDNS object and enable the crossRef on DC1 (not DC2).
                // Here we need to force replicate the enabling of the crossRef to the FSMO (DC2)
                // so that we can later add replicas (which need to modify an attribute on the crossRef
                // on DC2, the FSMO, and can only be done if the crossRef on DC2 is enabled)
                // get the ntdsa name of the server on which the partition is created
                DirectoryEntry rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
                string primaryServerNtdsaName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DsServiceName)!;

                // get the DN of the crossRef entry that needs to be replicated to the fsmo role
                if (_appType == ApplicationPartitionType.ADApplicationPartition)
                {
                    // for AD we may not have the crossRef entry yet
                    GetCrossRefEntry();
                }
                string crossRefDN = (string)PropertyManager.GetPropertyValue(context, _crossRefEntry!, PropertyManager.DistinguishedName)!;

                // Now set the operational attribute "replicateSingleObject" on the Rootdse of the fsmo role
                // to <ntdsa name of the source>:<DN of the crossRef object which needs to be replicated>
                DirectoryContext fsmoContext = Utils.GetNewDirectoryContext(GetNamingRoleOwner(), DirectoryContextType.DirectoryServer, context);
                DirectoryEntry fsmoRootDSE = DirectoryEntryManager.GetDirectoryEntry(fsmoContext, WellKnownDN.RootDSE);

                try
                {
                    fsmoRootDSE.Properties[PropertyManager.ReplicateSingleObject].Value = primaryServerNtdsaName + ":" + crossRefDN;
                    fsmoRootDSE.CommitChanges();
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
                finally
                {
                    fsmoRootDSE.Dispose();
                }

                // the partition has been created
                _committed = true;

                // commit the replica locations information or security reference domain if applicable
                if ((_cachedDirectoryServers != null) || (_securityRefDomainModified))
                {
                    if (_cachedDirectoryServers != null)
                    {
                        _crossRefEntry!.Properties[PropertyManager.MsDSNCReplicaLocations].AddRange(_cachedDirectoryServers.GetMultiValuedProperty());
                    }
                    if (_securityRefDomainModified)
                    {
                        _crossRefEntry!.Properties[PropertyManager.MsDSSDReferenceDomain].Value = _securityRefDomain;
                    }
                    try
                    {
                        _crossRefEntry!.CommitChanges();
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                }
            }
            else
            {
                // just save the crossRef entry for teh directory servers and the
                // security reference domain information
                if ((_cachedDirectoryServers != null) || (_securityRefDomainModified))
                {
                    try
                    {
                        // we should already have the crossRef entries as some attribute on it has already
                        // been modified
                        Debug.Assert(_crossRefEntry != null, "ApplicationPartition::Save - crossRefEntry on already committed partition which is being modified is null.");
                        _crossRefEntry.CommitChanges();
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                }
            }

            // invalidate cached info
            _cachedDirectoryServers = null;
            _securityRefDomainModified = false;
        }

        public override DirectoryEntry GetDirectoryEntry()
        {
            CheckIfDisposed();

            if (!_committed)
            {
                throw new InvalidOperationException(SR.CannotGetObject);
            }

            return DirectoryEntryManager.GetDirectoryEntry(context, Name);
        }

        #endregion public methods

        #region public properties

        public DirectoryServerCollection DirectoryServers
        {
            get
            {
                CheckIfDisposed();
                if (_cachedDirectoryServers == null)
                {
                    ReadOnlyDirectoryServerCollection servers = (_committed) ? FindAllDirectoryServers() : new ReadOnlyDirectoryServerCollection();
                    bool isADAM = (_appType == ApplicationPartitionType.ADAMApplicationPartition) ? true : false;

                    // Get the cross ref entry if we don't already have it
                    if (_committed)
                    {
                        GetCrossRefEntry();
                    }
                    //
                    // If the application partition is already committed at this point, we pass in the directory entry for teh crossRef, so any modifications
                    // are made directly on the crossRef entry. If at this point we do not have a crossRefEntry, we pass in null, while saving, we get the information
                    // from the collection and set it on the appropriate attribute on the crossRef directory entry.
                    //
                    //
                    _cachedDirectoryServers = new DirectoryServerCollection(context, (_committed) ? _crossRefEntry : null, isADAM, servers);
                }
                return _cachedDirectoryServers;
            }
        }

        public string? SecurityReferenceDomain
        {
            get
            {
                CheckIfDisposed();

                if (_appType == ApplicationPartitionType.ADAMApplicationPartition)
                {
                    throw new NotSupportedException(SR.PropertyInvalidForADAM);
                }

                if (_committed)
                {
                    GetCrossRefEntry();

                    try
                    {
                        if (_crossRefEntry.Properties[PropertyManager.MsDSSDReferenceDomain].Count > 0)
                        {
                            return (string?)_crossRefEntry.Properties[PropertyManager.MsDSSDReferenceDomain].Value;
                        }
                        else
                        {
                            return null;
                        }
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                }
                else
                {
                    return _securityRefDomain;
                }
            }
            set
            {
                CheckIfDisposed();

                if (_appType == ApplicationPartitionType.ADAMApplicationPartition)
                {
                    throw new NotSupportedException(SR.PropertyInvalidForADAM);
                }

                if (_committed)
                {
                    GetCrossRefEntry();
                    // modify the security reference domain
                    // this will get committed when the crossRefEntry is committed
                    if (value == null)
                    {
                        if (_crossRefEntry.Properties.Contains(PropertyManager.MsDSSDReferenceDomain))
                        {
                            _crossRefEntry.Properties[PropertyManager.MsDSSDReferenceDomain].Clear();
                            _securityRefDomainModified = true;
                        }
                    }
                    else
                    {
                        _crossRefEntry.Properties[PropertyManager.MsDSSDReferenceDomain].Value = value;
                        _securityRefDomainModified = true;
                    }
                }
                else
                {
                    if (!((_securityRefDomain == null) && (value == null)))
                    {
                        _securityRefDomain = value;
                        _securityRefDomainModified = true;
                    }
                }
            }
        }
        #endregion public properties

        #region private methods
        private void ValidateApplicationPartitionParameters(DirectoryContext context, string distinguishedName, string? objectClass, bool objectClassSpecified)
        {
            // validate context
            ArgumentNullException.ThrowIfNull(context);

            // contexttype should be DirectoryServer
            if ((context.Name == null) || (!context.isServer()))
            {
                throw new ArgumentException(SR.TargetShouldBeServer, nameof(context));
            }

            // check that the distinguished name is not null or empty
            ArgumentNullException.ThrowIfNull(distinguishedName);

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

            // initialize private variables
            this.context = new DirectoryContext(context);
            this.directoryEntryMgr = new DirectoryEntryManager(this.context);

            // validate the distinguished name
            // Utils.GetDnsNameFromDN will throw an ArgumentException if the dn is not valid (cannot be syntactically converted to dns name)
            _dnsName = Utils.GetDnsNameFromDN(distinguishedName);
            this.partitionName = distinguishedName;

            //
            // if the partition being created is a one-level partition, we do not support it
            //
            Component[] components = Utils.GetDNComponents(distinguishedName);
            if (components.Length == 1)
            {
                throw new NotSupportedException(SR.OneLevelPartitionNotSupported);
            }

            // check if the object class can be specified
            _appType = GetApplicationPartitionType(this.context);
            if ((_appType == ApplicationPartitionType.ADApplicationPartition) && (objectClassSpecified))
            {
                throw new InvalidOperationException(SR.NoObjectClassForADPartition);
            }
            else if (objectClassSpecified)
            {
                // ADAM case and objectClass is explicitly specified, so must be validated

                ArgumentNullException.ThrowIfNull(objectClass);

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

            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                //
                // the target in the directory context could be the netbios name of the server, so we will get the dns name
                // (since application partition creation will fail if dns name is not specified)
                //

                string? serverDnsName = null;
                try
                {
                    DirectoryEntry rootDSEEntry = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
                    serverDnsName = (string?)PropertyManager.GetPropertyValue(this.context, rootDSEEntry, PropertyManager.DnsHostName);
                }
                catch (COMException e)
                {
                    ExceptionHelper.GetExceptionFromCOMException(this.context, e);
                }

                this.context = Utils.GetNewDirectoryContext(serverDnsName, DirectoryContextType.DirectoryServer, context);
            }
        }

        [MemberNotNull(nameof(_domainDNSEntry))]
        private void CreateApplicationPartition(string distinguishedName, string objectClass)
        {
            if (_appType == ApplicationPartitionType.ADApplicationPartition)
            {
                //
                // AD
                // 1. Bind to the non-existent application partition using the fast bind and delegation option
                // 2. Get the Parent object and create a new "domainDNS" object under it
                // 3. Set the instanceType and the description for the application partitin object
                //

                DirectoryEntry? tempEntry = null;
                DirectoryEntry? parent = null;

                try
                {
                    AuthenticationTypes authType = Utils.DefaultAuthType | AuthenticationTypes.FastBind | AuthenticationTypes.Delegation;


                    authType |= AuthenticationTypes.ServerBind;

                    tempEntry = new DirectoryEntry("LDAP://" + context.GetServerName() + "/" + distinguishedName, context.UserName, context.Password, authType);
                    parent = tempEntry.Parent;
                    _domainDNSEntry = parent.Children.Add(Utils.GetRdnFromDN(distinguishedName), PropertyManager.DomainDNS);
                    // set the instance type to 5
                    _domainDNSEntry.Properties[PropertyManager.InstanceType].Value = NCFlags.InstanceTypeIsNCHead | NCFlags.InstanceTypeIsWriteable;
                    // mark this as uncommitted
                    _committed = false;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
                finally
                {
                    // dispose all resources
                    parent?.Dispose();
                    tempEntry?.Dispose();
                }
            }
            else
            {
                //
                // ADAM
                // 1. Bind to the partitions container on the domain naming owner instance
                // 2. Create a disabled crossRef object for the new application partition
                // 3. Bind to the target hint and follow the same steps as for AD
                //

                try
                {
                    InitializeCrossRef(distinguishedName);

                    DirectoryEntry? tempEntry = null;
                    DirectoryEntry? parent = null;

                    try
                    {
                        AuthenticationTypes authType = Utils.DefaultAuthType | AuthenticationTypes.FastBind;


                        authType |= AuthenticationTypes.ServerBind;

                        tempEntry = new DirectoryEntry("LDAP://" + context.Name + "/" + distinguishedName, context.UserName, context.Password, authType);
                        parent = tempEntry.Parent;
                        _domainDNSEntry = parent.Children.Add(Utils.GetRdnFromDN(distinguishedName), objectClass);

                        // set the instance type to 5
                        _domainDNSEntry.Properties[PropertyManager.InstanceType].Value = NCFlags.InstanceTypeIsNCHead | NCFlags.InstanceTypeIsWriteable;

                        // mark this as uncommitted
                        _committed = false;
                    }
                    finally
                    {
                        // dispose all resources
                        parent?.Dispose();
                        tempEntry?.Dispose();
                    }
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        [MemberNotNull(nameof(_crossRefEntry))]
        private void InitializeCrossRef(string distinguishedName)
        {
            if (_crossRefEntry != null)
                // already initialized
                return;

            DirectoryEntry? partitionsEntry = null;

            try
            {
                string namingFsmoName = GetNamingRoleOwner();
                DirectoryContext roleOwnerContext = Utils.GetNewDirectoryContext(namingFsmoName, DirectoryContextType.DirectoryServer, context);
                partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(roleOwnerContext, WellKnownDN.PartitionsContainer);
                string uniqueName = "CN={" + Guid.NewGuid() + "}";
                _crossRefEntry = partitionsEntry.Children.Add(uniqueName, "crossRef");

                string? dnsHostName = null;
                if (_appType == ApplicationPartitionType.ADAMApplicationPartition)
                {
                    // Bind to rootdse and get the server name
                    DirectoryEntry rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
                    string ntdsaName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DsServiceName)!;
                    dnsHostName = Utils.GetAdamHostNameAndPortsFromNTDSA(context, ntdsaName);
                }
                else
                {
                    // for AD the name in the context will be the dns name of the server
                    dnsHostName = context.Name;
                }

                // create disabled cross ref object
                _crossRefEntry.Properties[PropertyManager.DnsRoot].Value = dnsHostName;
                _crossRefEntry.Properties[PropertyManager.Enabled].Value = false;
                _crossRefEntry.Properties[PropertyManager.NCName].Value = distinguishedName;
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                partitionsEntry?.Dispose();
            }
        }

        private static ApplicationPartitionType GetApplicationPartitionType(DirectoryContext context)
        {
            ApplicationPartitionType type = ApplicationPartitionType.Unknown;

            DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
            try
            {
                foreach (string supportedCapability in rootDSE.Properties[PropertyManager.SupportedCapabilities])
                {
                    if (string.Equals(supportedCapability, SupportedCapability.ADOid, StringComparison.OrdinalIgnoreCase))
                    {
                        type = ApplicationPartitionType.ADApplicationPartition;
                    }
                    if (string.Equals(supportedCapability, SupportedCapability.ADAMOid, StringComparison.OrdinalIgnoreCase))
                    {
                        type = ApplicationPartitionType.ADAMApplicationPartition;
                    }
                }
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                rootDSE.Dispose();
            }

            // should not happen
            if (type == ApplicationPartitionType.Unknown)
            {
                throw new ActiveDirectoryOperationException(SR.ApplicationPartitionTypeUnknown);
            }
            return type;
        }

        // we always get the crossEntry bound to the FSMO role
        // this is so that we do not encounter any replication delay related issues
        [MemberNotNull(nameof(_crossRefEntry))]
        internal DirectoryEntry GetCrossRefEntry()
        {
            if (_crossRefEntry != null)
            {
                return _crossRefEntry;
            }

            DirectoryEntry partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer));
            try
            {
                _crossRefEntry = Utils.GetCrossRefEntry(context, partitionsEntry, Name);
            }
            finally
            {
                partitionsEntry.Dispose();
            }
            return _crossRefEntry;
        }

        internal string GetNamingRoleOwner()
        {
            string? namingFsmo = null;
            DirectoryEntry partitionsEntry = DirectoryEntryManager.GetDirectoryEntry(context, directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.PartitionsContainer));
            try
            {
                if (_appType == ApplicationPartitionType.ADApplicationPartition)
                {
                    namingFsmo = Utils.GetDnsHostNameFromNTDSA(context, (string)PropertyManager.GetPropertyValue(context, partitionsEntry, PropertyManager.FsmoRoleOwner)!);
                }
                else
                {
                    namingFsmo = Utils.GetAdamDnsHostNameFromNTDSA(context, (string)PropertyManager.GetPropertyValue(context, partitionsEntry, PropertyManager.FsmoRoleOwner)!);
                }
            }
            finally
            {
                partitionsEntry.Dispose();
            }
            return namingFsmo;
        }

        private DirectoryServer FindDirectoryServerInternal(string? siteName, bool forceRediscovery)
        {
            DirectoryServer? directoryServer = null;
            LocatorOptions flag = 0;
            int errorCode = 0;
            DomainControllerInfo domainControllerInfo;

            if (siteName != null && siteName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(siteName));
            }

            // Check that the application partition has been committed
            if (!_committed)
            {
                throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
            }

            // set the force rediscovery flag if required
            if (forceRediscovery)
            {
                flag = LocatorOptions.ForceRediscovery;
            }

            // call DsGetDcName
            errorCode = Locator.DsGetDcNameWrapper(null, _dnsName, siteName, (long)flag | (long)PrivateLocatorFlags.OnlyLDAPNeeded, out domainControllerInfo);

            if (errorCode == Interop.Errors.ERROR_NO_SUCH_DOMAIN)
            {
                throw new ActiveDirectoryObjectNotFoundException(SR.ReplicaNotFound, typeof(DirectoryServer), null);
            }
            else if (errorCode != 0)
            {
                throw ExceptionHelper.GetExceptionFromErrorCode(errorCode);
            }

            Debug.Assert(domainControllerInfo.DomainControllerName.Length > 2, "ApplicationPartition:FindDirectoryServerInternal - domainControllerInfo.DomainControllerName.Length <= 2");
            string dcName = domainControllerInfo.DomainControllerName.Substring(2);

            // create a new context object for the domain controller passing on only the
            // credentials from the forest context
            DirectoryContext dcContext = Utils.GetNewDirectoryContext(dcName, DirectoryContextType.DirectoryServer, context);

            directoryServer = new DomainController(dcContext, dcName);
            return directoryServer;
        }

        private ReadOnlyDirectoryServerCollection FindAllDirectoryServersInternal(string? siteName)
        {
            if (siteName != null && siteName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(siteName));
            }

            // Check that the application partition has been committed
            if (!_committed)
            {
                throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
            }

            ArrayList dcList = new ArrayList();
            foreach (string dcName in Utils.GetReplicaList(context, Name, siteName, false /* isDefaultNC */, false /* isADAM */, false /* mustBeGC */))
            {
                DirectoryContext dcContext = Utils.GetNewDirectoryContext(dcName, DirectoryContextType.DirectoryServer, context);
                dcList.Add(new DomainController(dcContext, dcName));
            }

            return new ReadOnlyDirectoryServerCollection(dcList);
        }

        private ReadOnlyDirectoryServerCollection FindAllDiscoverableDirectoryServersInternal(string? siteName)
        {
            if (siteName != null && siteName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(siteName));
            }

            // Check that the application partition has been committed
            if (!_committed)
            {
                throw new InvalidOperationException(SR.CannotPerformOperationOnUncommittedObject);
            }

            long flag = (long)PrivateLocatorFlags.OnlyLDAPNeeded;

            return new ReadOnlyDirectoryServerCollection(Locator.EnumerateDomainControllers(context, _dnsName, siteName, flag));
        }

        #endregion private methods
    }
}