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

namespace System.DirectoryServices.ActiveDirectory
{
    public enum NotificationStatus
    {
        NoNotification = 0,
        IntraSiteOnly = 1,
        NotificationAlways = 2
    }

    public enum ReplicationSpan
    {
        IntraSite = 0,
        InterSite = 1
    }

    public class ReplicationConnection : IDisposable
    {
        internal readonly DirectoryContext context;
        internal readonly DirectoryEntry cachedDirectoryEntry;
        internal bool existingConnection;
        private bool _disposed;
        private bool _isADAMServer;
        private int _options;

        private readonly string _connectionName;
        private string? _sourceServerName;
        private string? _destinationServerName;
        private readonly ActiveDirectoryTransportType _transport = ActiveDirectoryTransportType.Rpc;

        private const string ADAMGuid = "1.2.840.113556.1.4.1851";

        public static ReplicationConnection FindByName(DirectoryContext context, string name)
        {
            ValidateArgument(context, name);

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

            // bind to the rootdse to get the servername property
            DirectoryEntry de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
            try
            {
                string serverDN = (string)PropertyManager.GetPropertyValue(context, de, PropertyManager.ServerName)!;
                string connectionContainer = "CN=NTDS Settings," + serverDN;
                de = DirectoryEntryManager.GetDirectoryEntry(context, connectionContainer);
                // doing the search to find the connection object based on its name
                ADSearcher adSearcher = new ADSearcher(de,
                                                      "(&(objectClass=nTDSConnection)(objectCategory=NTDSConnection)(name=" + Utils.GetEscapedFilterValue(name) + "))",
                                                      ActiveDirectorySite.s_distinguishedName,
                                                      SearchScope.OneLevel,
                                                      false, /* no paged search */
                                                      false /* don't cache results */);
                SearchResult? srchResult = null;
                try
                {
                    srchResult = adSearcher.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.DSNotFound, typeof(ReplicationConnection), name);
                    }
                    else
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }
                }

                if (srchResult == null)
                {
                    // no such connection object
                    Exception e = new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ReplicationConnection), name);
                    throw e;
                }
                else
                {
                    DirectoryEntry connectionEntry = srchResult.GetDirectoryEntry();
                    return new ReplicationConnection(context, connectionEntry, name);
                }
            }
            finally
            {
                de.Dispose();
            }
        }

        internal ReplicationConnection(DirectoryContext context, DirectoryEntry connectionEntry, string name)
        {
            this.context = context;
            cachedDirectoryEntry = connectionEntry;
            _connectionName = name;
            // this is an existing connection object
            existingConnection = true;
        }

        public ReplicationConnection(DirectoryContext context, string name, DirectoryServer sourceServer) : this(context, name, sourceServer, null, ActiveDirectoryTransportType.Rpc)
        {
        }

        public ReplicationConnection(DirectoryContext context, string name, DirectoryServer sourceServer, ActiveDirectorySchedule? schedule) : this(context, name, sourceServer, schedule, ActiveDirectoryTransportType.Rpc)
        {
        }

        public ReplicationConnection(DirectoryContext context, string name, DirectoryServer sourceServer, ActiveDirectoryTransportType transport) : this(context, name, sourceServer, null, transport)
        {
        }

        public ReplicationConnection(DirectoryContext context, string name, DirectoryServer sourceServer, ActiveDirectorySchedule? schedule, ActiveDirectoryTransportType transport)
        {
            ValidateArgument(context, name);

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

            if (transport < ActiveDirectoryTransportType.Rpc || transport > ActiveDirectoryTransportType.Smtp)
                throw new InvalidEnumArgumentException("value", (int)transport, typeof(ActiveDirectoryTransportType));

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

            ValidateTargetAndSourceServer(context, sourceServer);

            this.context = context;
            _connectionName = name;
            _transport = transport;

            DirectoryEntry de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
            try
            {
                string serverDN = (string)PropertyManager.GetPropertyValue(context, de, PropertyManager.ServerName)!;
                string connectionContainer = "CN=NTDS Settings," + serverDN;
                de = DirectoryEntryManager.GetDirectoryEntry(context, connectionContainer);

                // create the connection entry
                string rdn = "cn=" + _connectionName;
                rdn = Utils.GetEscapedPath(rdn);
                cachedDirectoryEntry = de.Children.Add(rdn, "nTDSConnection");

                // set all the properties

                // sourceserver property
                DirectoryContext sourceServerContext = sourceServer.Context;
                de = DirectoryEntryManager.GetDirectoryEntry(sourceServerContext, WellKnownDN.RootDSE);
                string serverName = (string)PropertyManager.GetPropertyValue(sourceServerContext, de, PropertyManager.ServerName)!;
                serverName = "CN=NTDS Settings," + serverName;

                cachedDirectoryEntry.Properties["fromServer"].Add(serverName);

                // schedule property
                if (schedule != null)
                    cachedDirectoryEntry.Properties[nameof(schedule)].Value = schedule.GetUnmanagedSchedule();

                // transporttype property
                string transportPath = Utils.GetDNFromTransportType(TransportType, context);
                // verify that the transport is supported
                de = DirectoryEntryManager.GetDirectoryEntry(context, transportPath);
                try
                {
                    de.Bind(true);
                }
                catch (COMException e)
                {
                    if (e.ErrorCode == unchecked((int)0x80072030))
                    {
                        // if it is ADAM and transport type is SMTP, throw NotSupportedException.
                        DirectoryEntry tmpDE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
                        if (Utils.CheckCapability(tmpDE, Capability.ActiveDirectoryApplicationMode) && transport == ActiveDirectoryTransportType.Smtp)
                        {
                            throw new NotSupportedException(SR.NotSupportTransportSMTP);
                        }
                    }

                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                cachedDirectoryEntry.Properties["transportType"].Add(transportPath);

                // enabledConnection property
                cachedDirectoryEntry.Properties["enabledConnection"].Value = false;

                // options
                cachedDirectoryEntry.Properties["options"].Value = 0;
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                de.Close();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing && cachedDirectoryEntry != null)
                    cachedDirectoryEntry.Dispose();

                _disposed = true;
            }
        }

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

        public string Name
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                return _connectionName;
            }
        }

        public string? SourceServer
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                // get the source server
                if (_sourceServerName == null)
                {
                    string sourceServerDN = (string)PropertyManager.GetPropertyValue(context, cachedDirectoryEntry, PropertyManager.FromServer)!;
                    DirectoryEntry de = DirectoryEntryManager.GetDirectoryEntry(context, sourceServerDN);
                    if (IsADAM)
                    {
                        int portnumber = (int)PropertyManager.GetPropertyValue(context, de, PropertyManager.MsDSPortLDAP)!;
                        string? tmpServerName = (string?)PropertyManager.GetPropertyValue(context, de.Parent, PropertyManager.DnsHostName);
                        if (portnumber != 389)
                        {
                            _sourceServerName = tmpServerName + ":" + portnumber;
                        }
                    }
                    else
                    {
                        _sourceServerName = (string?)PropertyManager.GetPropertyValue(context, de.Parent, PropertyManager.DnsHostName);
                    }
                }
                return _sourceServerName;
            }
        }

        public string DestinationServer
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                if (_destinationServerName == null)
                {
                    DirectoryEntry? NTDSObject = null;
                    DirectoryEntry? serverObject = null;
                    try
                    {
                        NTDSObject = cachedDirectoryEntry.Parent;
                        serverObject = NTDSObject.Parent;
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }

                    string hostName = (string)PropertyManager.GetPropertyValue(context, serverObject, PropertyManager.DnsHostName)!;
                    if (IsADAM)
                    {
                        int portnumber = (int)PropertyManager.GetPropertyValue(context, NTDSObject, PropertyManager.MsDSPortLDAP)!;
                        if (portnumber != 389)
                        {
                            _destinationServerName = hostName + ":" + portnumber;
                        }
                        else
                            _destinationServerName = hostName;
                    }
                    else
                        _destinationServerName = hostName;
                }
                return _destinationServerName;
            }
        }

        public bool Enabled
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                // fetch the property value
                try
                {
                    if (cachedDirectoryEntry.Properties.Contains("enabledConnection"))
                        return (bool)cachedDirectoryEntry.Properties["enabledConnection"][0]!;
                    else
                        return false;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                try
                {
                    cachedDirectoryEntry.Properties["enabledConnection"].Value = value;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public ActiveDirectoryTransportType TransportType
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                // for exisint connection, we need to check its property, for newly created and not committed one, we just return
                // the member variable value directly
                if (existingConnection)
                {
                    PropertyValueCollection? propValue = null;
                    try
                    {
                        propValue = cachedDirectoryEntry.Properties["transportType"];
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                    }

                    if (propValue.Count == 0)
                    {
                        // if the property does not exist, then default is to RPC over IP
                        return ActiveDirectoryTransportType.Rpc;
                    }
                    else
                    {
                        return Utils.GetTransportTypeFromDN((string)propValue[0]!);
                    }
                }
                else
                    return _transport;
            }
        }

        public bool GeneratedByKcc
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                PropertyValueCollection? propValue = null;

                try
                {
                    propValue = cachedDirectoryEntry.Properties["options"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (propValue.Count == 0)
                {
                    _options = 0;
                }
                else
                {
                    _options = (int)propValue[0]!;
                }

                // NTDSCONN_OPT_IS_GENERATED ( 1 << 0 )   object generated by DS, not admin
                if ((_options & 0x1) == 0)
                    return false;
                else
                    return true;
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                try
                {
                    PropertyValueCollection propValue = cachedDirectoryEntry.Properties["options"];
                    if (propValue.Count == 0)
                    {
                        _options = 0;
                    }
                    else
                    {
                        _options = (int)propValue[0]!;
                    }

                    // NTDSCONN_OPT_IS_GENERATED ( 1 << 0 )   object generated by DS, not admin
                    if (value)
                    {
                        _options |= 0x1;
                    }
                    else
                    {
                        _options &= (~(0x1));
                    }

                    // put the value into cache
                    cachedDirectoryEntry.Properties["options"].Value = _options;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public bool ReciprocalReplicationEnabled
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                PropertyValueCollection? propValue = null;
                try
                {
                    propValue = cachedDirectoryEntry.Properties["options"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (propValue.Count == 0)
                {
                    // if the property does not exist, then default is to RPC over IP
                    _options = 0;
                }
                else
                {
                    _options = (int)propValue[0]!;
                }

                // NTDSCONN_OPT_TWOWAY_SYNC  ( 1 << 1 )  force sync in opposite direction at end of sync
                if ((_options & 0x2) == 0)
                    return false;
                else
                    return true;
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                try
                {
                    PropertyValueCollection propValue = cachedDirectoryEntry.Properties["options"];
                    if (propValue.Count == 0)
                    {
                        _options = 0;
                    }
                    else
                    {
                        _options = (int)propValue[0]!;
                    }

                    // NTDSCONN_OPT_TWOWAY_SYNC  ( 1 << 1 )  force sync in opposite direction at end of sync
                    if (value)
                    {
                        _options |= 0x2;
                    }
                    else
                    {
                        _options &= (~(0x2));
                    }

                    // put the value into cache
                    cachedDirectoryEntry.Properties["options"].Value = _options;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public NotificationStatus ChangeNotificationStatus
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                PropertyValueCollection? propValue = null;
                try
                {
                    propValue = cachedDirectoryEntry.Properties["options"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (propValue.Count == 0)
                {
                    // if the property does not exist, then default is to RPC over IP
                    _options = 0;
                }
                else
                {
                    _options = (int)propValue[0]!;
                }

                int overrideNotify = _options & 0x4;
                int userNotify = _options & 0x8;
                if (overrideNotify == 0x4 && userNotify == 0)
                    return NotificationStatus.NoNotification;
                else if (overrideNotify == 0x4 && userNotify == 0x8)
                    return NotificationStatus.NotificationAlways;
                else
                    return NotificationStatus.IntraSiteOnly;
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                if (value < NotificationStatus.NoNotification || value > NotificationStatus.NotificationAlways)
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(NotificationStatus));

                try
                {
                    PropertyValueCollection propValue = cachedDirectoryEntry.Properties["options"];
                    if (propValue.Count == 0)
                    {
                        _options = 0;
                    }
                    else
                    {
                        _options = (int)propValue[0]!;
                    }

                    if (value == NotificationStatus.IntraSiteOnly)
                    {
                        _options &= (~(0x4));
                        _options &= (~(0x8));
                    }
                    else if (value == NotificationStatus.NoNotification)
                    {
                        _options |= (0x4);
                        _options &= (~(0x8));
                    }
                    else
                    {
                        _options |= (0x4);
                        _options |= (0x8);
                    }

                    // put the value into cache
                    cachedDirectoryEntry.Properties["options"].Value = _options;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public bool DataCompressionEnabled
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                PropertyValueCollection? propValue = null;
                try
                {
                    propValue = cachedDirectoryEntry.Properties["options"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (propValue.Count == 0)
                {
                    // if the property does not exist, then default is to RPC over IP
                    _options = 0;
                }
                else
                {
                    _options = (int)propValue[0]!;
                }

                //NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION    (1 << 4)
                //  0 - Compression of replication data enabled
                //  1 - Compression of replication data disabled
                if ((_options & 0x10) == 0)
                    return true;
                else
                    return false;
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                try
                {
                    PropertyValueCollection propValue = cachedDirectoryEntry.Properties["options"];
                    if (propValue.Count == 0)
                    {
                        _options = 0;
                    }
                    else
                    {
                        _options = (int)propValue[0]!;
                    }

                    //NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION    (1 << 4)
                    //  0 - Compression of replication data enabled
                    //  1 - Compression of replication data disabled
                    if (!value)
                    {
                        _options |= 0x10;
                    }
                    else
                    {
                        _options &= (~(0x10));
                    }

                    // put the value into cache
                    cachedDirectoryEntry.Properties["options"].Value = _options;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public bool ReplicationScheduleOwnedByUser
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                PropertyValueCollection? propValue = null;
                try
                {
                    propValue = cachedDirectoryEntry.Properties["options"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (propValue.Count == 0)
                {
                    // if the property does not exist, then default is to RPC over IP
                    _options = 0;
                }
                else
                {
                    _options = (int)propValue[0]!;
                }

                // NTDSCONN_OPT_USER_OWNED_SCHEDULE (1 << 5)
                if ((_options & 0x20) == 0)
                    return false;
                else
                    return true;
            }
            set
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                try
                {
                    PropertyValueCollection propValue = cachedDirectoryEntry.Properties["options"];
                    if (propValue.Count == 0)
                    {
                        _options = 0;
                    }
                    else
                    {
                        _options = (int)propValue[0]!;
                    }

                    // NTDSCONN_OPT_USER_OWNED_SCHEDULE (1 << 5)
                    if (value)
                    {
                        _options |= 0x20;
                    }
                    else
                    {
                        _options &= (~(0x20));
                    }

                    // put the value into cache
                    cachedDirectoryEntry.Properties["options"].Value = _options;
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        public ReplicationSpan ReplicationSpan
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                // find out whether the site and the destination is in the same site
                string destinationPath = (string)PropertyManager.GetPropertyValue(context, cachedDirectoryEntry, PropertyManager.FromServer)!;
                string? destinationSite = Utils.GetDNComponents(destinationPath)[3].Value;

                DirectoryEntry de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
                string serverDN = (string)PropertyManager.GetPropertyValue(context, de, PropertyManager.ServerName)!;
                string? serverSite = Utils.GetDNComponents(serverDN)[2].Value;

                if (Utils.Compare(destinationSite, serverSite) == 0)
                {
                    return ReplicationSpan.IntraSite;
                }
                else
                {
                    return ReplicationSpan.InterSite;
                }
            }
        }

        public ActiveDirectorySchedule? ReplicationSchedule
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                ActiveDirectorySchedule? schedule = null;
                bool scheduleExists = false;
                try
                {
                    scheduleExists = cachedDirectoryEntry.Properties.Contains("schedule");
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (scheduleExists)
                {
                    byte[] tmpSchedule = (byte[])cachedDirectoryEntry.Properties["schedule"][0]!;
                    Debug.Assert(tmpSchedule != null && tmpSchedule.Length == 188);
                    schedule = new ActiveDirectorySchedule();
                    schedule.SetUnmanagedSchedule(tmpSchedule);
                }

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

                try
                {
                    if (value == null)
                    {
                        if (cachedDirectoryEntry.Properties.Contains("schedule"))
                            cachedDirectoryEntry.Properties["schedule"].Clear();
                    }
                    else
                    {
                        cachedDirectoryEntry.Properties["schedule"].Value = value.GetUnmanagedSchedule();
                    }
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

        private bool IsADAM
        {
            get
            {
                DirectoryEntry de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
                PropertyValueCollection values;
                try
                {
                    values = de.Properties["supportedCapabilities"];
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (values.Contains(ADAMGuid))
                    _isADAMServer = true;

                return _isADAMServer;
            }
        }

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

            if (!existingConnection)
            {
                throw new InvalidOperationException(SR.CannotDelete);
            }
            else
            {
                try
                {
                    cachedDirectoryEntry.Parent.Children.Remove(cachedDirectoryEntry);
                }
                catch (COMException e)
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
        }

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

            try
            {
                cachedDirectoryEntry.CommitChanges();
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }

            if (!existingConnection)
            {
                existingConnection = true;
            }
        }

        public override string ToString()
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            return Name;
        }

        public DirectoryEntry GetDirectoryEntry()
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);

            if (!existingConnection)
            {
                throw new InvalidOperationException(SR.CannotGetObject);
            }
            else
            {
                return DirectoryEntryManager.GetDirectoryEntryInternal(context, cachedDirectoryEntry.Path);
            }
        }

        private static void ValidateArgument(DirectoryContext context, string name)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            // the target of the scope must be server
            if (context.Name == null || !context.isServer())
                throw new ArgumentException(SR.DirectoryContextNeedHost);

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

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

        private void ValidateTargetAndSourceServer(DirectoryContext context, DirectoryServer sourceServer)
        {
            bool targetIsDC = false;
            DirectoryEntry? targetDE = null;
            DirectoryEntry? sourceDE = null;

            // first find out target is a dc or ADAM instance
            targetDE = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE);
            try
            {
                if (Utils.CheckCapability(targetDE, Capability.ActiveDirectory))
                {
                    targetIsDC = true;
                }
                else if (!Utils.CheckCapability(targetDE, Capability.ActiveDirectoryApplicationMode))
                {
                    // if it is also not an ADAM instance, it is invalid then
                    throw new ArgumentException(SR.DirectoryContextNeedHost, nameof(context));
                }

                if (targetIsDC && !(sourceServer is DomainController))
                {
                    // target and sourceServer are not of the same type
                    throw new ArgumentException(SR.ConnectionSourcServerShouldBeDC, nameof(sourceServer));
                }
                else if (!targetIsDC && (sourceServer is DomainController))
                {
                    // target and sourceServer are not of the same type
                    throw new ArgumentException(SR.ConnectionSourcServerShouldBeADAM, nameof(sourceServer));
                }

                sourceDE = DirectoryEntryManager.GetDirectoryEntry(sourceServer.Context, WellKnownDN.RootDSE);

                // now if they are both dc, we need to check whether they come from the same forest
                if (targetIsDC)
                {
                    string? targetRoot = (string?)PropertyManager.GetPropertyValue(context, targetDE, PropertyManager.RootDomainNamingContext);
                    string? sourceRoot = (string?)PropertyManager.GetPropertyValue(sourceServer.Context, sourceDE, PropertyManager.RootDomainNamingContext);
                    if (Utils.Compare(targetRoot, sourceRoot) != 0)
                    {
                        throw new ArgumentException(SR.ConnectionSourcServerSameForest, nameof(sourceServer));
                    }
                }
                else
                {
                    string? targetRoot = (string?)PropertyManager.GetPropertyValue(context, targetDE, PropertyManager.ConfigurationNamingContext);
                    string? sourceRoot = (string?)PropertyManager.GetPropertyValue(sourceServer.Context, sourceDE, PropertyManager.ConfigurationNamingContext);
                    if (Utils.Compare(targetRoot, sourceRoot) != 0)
                    {
                        throw new ArgumentException(SR.ConnectionSourcServerSameConfigSet, nameof(sourceServer));
                    }
                }
            }
            catch (COMException e)
            {
                ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                targetDE?.Close();
                sourceDE?.Close();
            }
        }
    }
}