// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; namespace System.DirectoryServices.ActiveDirectory { public class ActiveDirectorySiteLinkBridge : IDisposable { internal readonly DirectoryContext context; private readonly string _name; private readonly ActiveDirectoryTransportType _transport = ActiveDirectoryTransportType.Rpc; private bool _disposed; private bool _existing; internal DirectoryEntry? cachedEntry; private readonly ActiveDirectorySiteLinkCollection _links = new ActiveDirectorySiteLinkCollection(); private bool _linksRetrieved; public ActiveDirectorySiteLinkBridge(DirectoryContext context, string bridgeName) : this(context, bridgeName, ActiveDirectoryTransportType.Rpc) { } public ActiveDirectorySiteLinkBridge(DirectoryContext context, string bridgeName, ActiveDirectoryTransportType transport) { ValidateArgument(context, bridgeName, transport); // work with copy of the context context = new DirectoryContext(context); this.context = context; _name = bridgeName; _transport = transport; // bind to the rootdse to get the configurationnamingcontext DirectoryEntry de; try { de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE); string config = (string)PropertyManager.GetPropertyValue(context, de, PropertyManager.ConfigurationNamingContext)!; string parentDN; if (transport == ActiveDirectoryTransportType.Rpc) parentDN = "CN=IP,CN=Inter-Site Transports,CN=Sites," + config; else parentDN = "CN=SMTP,CN=Inter-Site Transports,CN=Sites," + config; de = DirectoryEntryManager.GetDirectoryEntry(context, parentDN); } 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)); } try { string rdn = "cn=" + _name; rdn = Utils.GetEscapedPath(rdn); cachedEntry = de.Children.Add(rdn, "siteLinkBridge"); } 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); } finally { de.Dispose(); } } internal ActiveDirectorySiteLinkBridge(DirectoryContext context, string bridgeName, ActiveDirectoryTransportType transport, bool existing) { this.context = context; _name = bridgeName; _transport = transport; _existing = existing; } public static ActiveDirectorySiteLinkBridge FindByName(DirectoryContext context, string bridgeName) { return FindByName(context, bridgeName, ActiveDirectoryTransportType.Rpc); } public static ActiveDirectorySiteLinkBridge FindByName(DirectoryContext context, string bridgeName, ActiveDirectoryTransportType transport) { ValidateArgument(context, bridgeName, transport); // work with copy of the context context = new DirectoryContext(context); // bind to the rootdse to get the configurationnamingcontext DirectoryEntry de; try { de = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.RootDSE); string config = (string)PropertyManager.GetPropertyValue(context, de, PropertyManager.ConfigurationNamingContext)!; string containerDN = "CN=Inter-Site Transports,CN=Sites," + config; if (transport == ActiveDirectoryTransportType.Rpc) containerDN = "CN=IP," + containerDN; else containerDN = "CN=SMTP," + containerDN; de = DirectoryEntryManager.GetDirectoryEntry(context, containerDN); } 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)); } try { ADSearcher adSearcher = new ADSearcher(de, "(&(objectClass=siteLinkBridge)(objectCategory=SiteLinkBridge)(name=" + Utils.GetEscapedFilterValue(bridgeName) + "))", ActiveDirectorySite.s_distinguishedName, SearchScope.OneLevel, false, /* don't need paged search */ false /* don't need to cache result */); SearchResult? srchResult = adSearcher.FindOne(); if (srchResult == null) { // no such site link bridge object Exception e = new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySiteLinkBridge), bridgeName); throw e; } else { DirectoryEntry connectionEntry = srchResult.GetDirectoryEntry(); // it is an existing site link bridge object ActiveDirectorySiteLinkBridge bridge = new ActiveDirectorySiteLinkBridge(context, bridgeName, transport, true); bridge.cachedEntry = connectionEntry; return bridge; } } 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); } else { // object is not found since we cannot even find the container in which to search throw new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySiteLinkBridge), bridgeName); } } throw ExceptionHelper.GetExceptionFromCOMException(context, e); } finally { de.Dispose(); } } public string Name { get { if (_disposed) throw new ObjectDisposedException(GetType().Name); return _name; } } public ActiveDirectorySiteLinkCollection SiteLinks { get { if (_disposed) throw new ObjectDisposedException(GetType().Name); if (_existing) { // if asked the first time, we need to properly construct the subnets collection if (!_linksRetrieved) { _links.initialized = false; _links.Clear(); GetLinks(); _linksRetrieved = true; } } _links.initialized = true; _links.de = cachedEntry; _links.context = context; return _links; } } public ActiveDirectoryTransportType TransportType { get { if (_disposed) throw new ObjectDisposedException(GetType().Name); return _transport; } } public void Save() { if (_disposed) throw new ObjectDisposedException(GetType().Name); try { cachedEntry!.CommitChanges(); } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(context, e); } if (_existing) { // indicates that nex time user asks for SiteLinks property, we might need to fetch it from server _linksRetrieved = false; } else { _existing = true; } } public void Delete() { if (_disposed) throw new ObjectDisposedException(GetType().Name); if (!_existing) { throw new InvalidOperationException(SR.CannotDelete); } else { try { cachedEntry!.Parent.Children.Remove(cachedEntry); } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(context, e); } } } public override string ToString() { if (_disposed) throw new ObjectDisposedException(GetType().Name); return _name; } public DirectoryEntry GetDirectoryEntry() { if (_disposed) throw new ObjectDisposedException(GetType().Name); if (!_existing) { throw new InvalidOperationException(SR.CannotGetObject); } else { return DirectoryEntryManager.GetDirectoryEntryInternal(context, cachedEntry!.Path); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // free other state (managed objects) cachedEntry?.Dispose(); } // free your own state (unmanaged objects) _disposed = true; } private static void ValidateArgument(DirectoryContext context, string bridgeName, ActiveDirectoryTransportType transport) { // basic validation first if (context == null) throw new ArgumentNullException(nameof(context)); // if target is not specified, then we determin the target from the logon credential, so if it is a local user context, it should fail if ((context.Name == null) && (!context.isRootDomain())) { throw new ArgumentException(SR.ContextNotAssociatedWithDomain, nameof(context)); } // more validation for the context, if the target is not null, then it should be either forest name or server name if (context.Name != null) { if (!(context.isRootDomain() || context.isServer() || context.isADAMConfigSet())) throw new ArgumentException(SR.NotADOrADAM, nameof(context)); } if (bridgeName == null) throw new ArgumentNullException(nameof(bridgeName)); if (bridgeName.Length == 0) throw new ArgumentException(SR.EmptyStringParameter, nameof(bridgeName)); if (transport < ActiveDirectoryTransportType.Rpc || transport > ActiveDirectoryTransportType.Smtp) throw new InvalidEnumArgumentException("value", (int)transport, typeof(ActiveDirectoryTransportType)); } private void GetLinks() { ArrayList propertyList = new ArrayList(); NativeComInterfaces.IAdsPathname? pathCracker = null; pathCracker = (NativeComInterfaces.IAdsPathname)new NativeComInterfaces.Pathname(); // need to turn off the escaping for name pathCracker.EscapedMode = NativeComInterfaces.ADS_ESCAPEDMODE_OFF_EX; string propertyName = "siteLinkList"; propertyList.Add(propertyName); Hashtable values = Utils.GetValuesWithRangeRetrieval(cachedEntry!, "(objectClass=*)", propertyList, SearchScope.Base); ArrayList siteLinkLists = (ArrayList)values[propertyName.ToLowerInvariant()]!; // somehow no site link list if (siteLinkLists == null) return; // construct the site link object for (int i = 0; i < siteLinkLists.Count; i++) { string dn = (string)siteLinkLists[i]!; // escaping manipulation pathCracker.Set(dn, NativeComInterfaces.ADS_SETTYPE_DN); string rdn = pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_LEAF); Debug.Assert(rdn != null && Utils.Compare(rdn, 0, 3, "CN=", 0, 3) == 0); rdn = rdn.Substring(3); DirectoryEntry entry = DirectoryEntryManager.GetDirectoryEntry(context, dn); ActiveDirectorySiteLink link = new ActiveDirectorySiteLink(context, rdn, _transport, true, entry); _links.Add(link); } } } } |