File: System\DirectoryServices\ActiveDirectory\ForestTrustRelationshipInformation.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.Collections.Specialized;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace System.DirectoryServices.ActiveDirectory
{
    public class ForestTrustRelationshipInformation : TrustRelationshipInformation
    {
        private TopLevelNameCollection _topLevelNames = new TopLevelNameCollection();
        private StringCollection _excludedNames = new StringCollection();
        private ForestTrustDomainInfoCollection _domainInfo = new ForestTrustDomainInfoCollection();
        private ArrayList _binaryData = new ArrayList();
        private ArrayList _binaryRecordType = new ArrayList();
        private Hashtable _excludedNameTime = new Hashtable();
        private ArrayList _binaryDataTime = new ArrayList();
        internal bool retrieved;

        internal ForestTrustRelationshipInformation(DirectoryContext context, string source, Interop.Netapi32.DS_DOMAIN_TRUSTS unmanagedTrust, TrustType type)
        {
            string? tmpDNSName = null;
            string? tmpNetBIOSName = null;

            // security context
            this.context = context;
            // source
            this.source = source;
            // target
            if (unmanagedTrust.DnsDomainName != (IntPtr)0)
                tmpDNSName = Marshal.PtrToStringUni(unmanagedTrust.DnsDomainName);
            if (unmanagedTrust.NetbiosDomainName != (IntPtr)0)
                tmpNetBIOSName = Marshal.PtrToStringUni(unmanagedTrust.NetbiosDomainName);

            this.target = tmpDNSName ?? tmpNetBIOSName;
            // direction
            if ((unmanagedTrust.Flags & Interop.Netapi32.DS_DOMAINTRUST_FLAG.DS_DOMAIN_DIRECT_OUTBOUND) != 0 &&
                (unmanagedTrust.Flags & Interop.Netapi32.DS_DOMAINTRUST_FLAG.DS_DOMAIN_DIRECT_INBOUND) != 0)
                direction = TrustDirection.Bidirectional;
            else if ((unmanagedTrust.Flags & Interop.Netapi32.DS_DOMAINTRUST_FLAG.DS_DOMAIN_DIRECT_OUTBOUND) != 0)
                direction = TrustDirection.Outbound;
            else if ((unmanagedTrust.Flags & Interop.Netapi32.DS_DOMAINTRUST_FLAG.DS_DOMAIN_DIRECT_INBOUND) != 0)
                direction = TrustDirection.Inbound;
            // type
            this.type = type;
        }

        public TopLevelNameCollection TopLevelNames
        {
            get
            {
                if (!retrieved)
                    GetForestTrustInfoHelper();
                return _topLevelNames;
            }
        }

        public StringCollection ExcludedTopLevelNames
        {
            get
            {
                if (!retrieved)
                    GetForestTrustInfoHelper();
                return _excludedNames;
            }
        }

        public ForestTrustDomainInfoCollection TrustedDomainInformation
        {
            get
            {
                if (!retrieved)
                    GetForestTrustInfoHelper();
                return _domainInfo;
            }
        }

        public unsafe void Save()
        {
            int count = 0;
            IntPtr records = (IntPtr)0;
            int currentCount = 0;
            IntPtr tmpPtr = (IntPtr)0;
            IntPtr forestInfo = (IntPtr)0;
            SafeLsaPolicyHandle? handle = null;
            IntPtr collisionInfo = (IntPtr)0;
            ArrayList ptrList = new ArrayList();
            ArrayList sidList = new ArrayList();
            bool impersonated = false;
            IntPtr target = (IntPtr)0;
            string? serverName = null;
            IntPtr fileTime = (IntPtr)0;

            // first get the count of all the records
            int toplevelNamesCount = TopLevelNames.Count;
            int excludedNamesCount = ExcludedTopLevelNames.Count;
            int trustedDomainCount = TrustedDomainInformation.Count;
            int binaryDataCount = _binaryData.Count;

            checked
            {
                count += toplevelNamesCount;
                count += excludedNamesCount;
                count += trustedDomainCount;
                count += binaryDataCount;

                // allocate the memory for all the records
                records = Marshal.AllocHGlobal(count * IntPtr.Size);
            }

            try
            {
                try
                {
                    IntPtr ptr = 0;
                    fileTime = Marshal.AllocHGlobal(Marshal.SizeOf<FileTime>());
                    Interop.Kernel32.GetSystemTimeAsFileTime(fileTime);

                    // set the time
                    FileTime currentTime = new FileTime();
                    Marshal.PtrToStructure(fileTime, currentTime);

                    for (int i = 0; i < toplevelNamesCount; i++)
                    {
                        // now begin to construct top leve name record
                        LSA_FOREST_TRUST_RECORD record = new LSA_FOREST_TRUST_RECORD();
                        record.Flags = (int)_topLevelNames[i].Status;
                        record.ForestTrustType = LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustTopLevelName;
                        TopLevelName TLN = _topLevelNames[i];
                        record.Time = TLN.time;
                        ptr = Marshal.StringToHGlobalUni(TLN.Name);
                        ptrList.Add(ptr);
                        Interop.NtDll.RtlInitUnicodeString(out record.TopLevelName, ptr);

                        tmpPtr = Marshal.AllocHGlobal(Marshal.SizeOf<LSA_FOREST_TRUST_RECORD>());
                        ptrList.Add(tmpPtr);
                        Marshal.StructureToPtr(record, tmpPtr, false);

                        Marshal.WriteIntPtr(records, IntPtr.Size * currentCount, tmpPtr);

                        currentCount++;
                    }

                    for (int i = 0; i < excludedNamesCount; i++)
                    {
                        // now begin to construct excluded top leve name record
                        LSA_FOREST_TRUST_RECORD record = new LSA_FOREST_TRUST_RECORD();
                        record.Flags = 0;
                        record.ForestTrustType = LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustTopLevelNameEx;
                        if (_excludedNameTime.Contains(_excludedNames[i]!))
                        {
                            record.Time = (LARGE_INTEGER)_excludedNameTime[i]!;
                        }
                        else
                        {
                            record.Time = new LARGE_INTEGER();
                            record.Time.lowPart = currentTime.lower;
                            record.Time.highPart = currentTime.higher;
                        }

                        ptr = Marshal.StringToHGlobalUni(_excludedNames[i]);
                        ptrList.Add(ptr);
                        Interop.NtDll.RtlInitUnicodeString(out record.TopLevelName, ptr);
                        tmpPtr = Marshal.AllocHGlobal(Marshal.SizeOf<LSA_FOREST_TRUST_RECORD>());
                        ptrList.Add(tmpPtr);
                        Marshal.StructureToPtr(record, tmpPtr, false);

                        Marshal.WriteIntPtr(records, IntPtr.Size * currentCount, tmpPtr);

                        currentCount++;
                    }

                    for (int i = 0; i < trustedDomainCount; i++)
                    {
                        // now begin to construct domain info record
                        LSA_FOREST_TRUST_RECORD record = new LSA_FOREST_TRUST_RECORD();
                        record.Flags = (int)_domainInfo[i].Status;
                        record.ForestTrustType = LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustDomainInfo;
                        ForestTrustDomainInformation tmp = _domainInfo[i];
                        record.Time = tmp.time;
                        void* pSid = null;
                        global::Interop.BOOL result = global::Interop.Advapi32.ConvertStringSidToSid(tmp.DomainSid, out pSid);
                        if (result == global::Interop.BOOL.FALSE)
                        {
                            throw ExceptionHelper.GetExceptionFromErrorCode(Marshal.GetLastPInvokeError());
                        }
                        record.DomainInfo.sid = (IntPtr)pSid;
                        sidList.Add((IntPtr)pSid);
                        record.DomainInfo.DNSNameBuffer = Marshal.StringToHGlobalUni(tmp.DnsName);
                        ptrList.Add(record.DomainInfo.DNSNameBuffer);
                        record.DomainInfo.DNSNameLength = (short)(tmp.DnsName == null ? 0 : tmp.DnsName.Length * 2);             // sizeof(WCHAR)
                        record.DomainInfo.DNSNameMaximumLength = (short)(tmp.DnsName == null ? 0 : tmp.DnsName.Length * 2);
                        record.DomainInfo.NetBIOSNameBuffer = Marshal.StringToHGlobalUni(tmp.NetBiosName);
                        ptrList.Add(record.DomainInfo.NetBIOSNameBuffer);
                        record.DomainInfo.NetBIOSNameLength = (short)(tmp.NetBiosName == null ? 0 : tmp.NetBiosName.Length * 2);
                        record.DomainInfo.NetBIOSNameMaximumLength = (short)(tmp.NetBiosName == null ? 0 : tmp.NetBiosName.Length * 2);
                        tmpPtr = Marshal.AllocHGlobal(Marshal.SizeOf<LSA_FOREST_TRUST_RECORD>());
                        ptrList.Add(tmpPtr);
                        Marshal.StructureToPtr(record, tmpPtr, false);

                        Marshal.WriteIntPtr(records, IntPtr.Size * currentCount, tmpPtr);

                        currentCount++;
                    }

                    for (int i = 0; i < binaryDataCount; i++)
                    {
                        LSA_FOREST_TRUST_RECORD record = new LSA_FOREST_TRUST_RECORD();
                        record.Flags = 0;
                        record.Time = (LARGE_INTEGER)_binaryDataTime[i]!;
                        record.Data.Length = ((byte[])_binaryData[i]!).Length;
                        record.ForestTrustType = (LSA_FOREST_TRUST_RECORD_TYPE)_binaryRecordType[i]!;
                        if (record.Data.Length == 0)
                        {
                            record.Data.Buffer = (IntPtr)0;
                        }
                        else
                        {
                            record.Data.Buffer = Marshal.AllocHGlobal(record.Data.Length);
                            ptrList.Add(record.Data.Buffer);
                            Marshal.Copy((byte[])_binaryData[i]!, 0, record.Data.Buffer, record.Data.Length);
                        }
                        tmpPtr = Marshal.AllocHGlobal(Marshal.SizeOf<LSA_FOREST_TRUST_RECORD>());
                        ptrList.Add(tmpPtr);
                        Marshal.StructureToPtr(record, tmpPtr, false);

                        Marshal.WriteIntPtr(records, IntPtr.Size * currentCount, tmpPtr);

                        currentCount++;
                    }

                    // finally construct the LSA_FOREST_TRUST_INFORMATION
                    LSA_FOREST_TRUST_INFORMATION trustInformation = new LSA_FOREST_TRUST_INFORMATION();
                    trustInformation.RecordCount = count;
                    trustInformation.Entries = records;
                    forestInfo = Marshal.AllocHGlobal(Marshal.SizeOf<LSA_FOREST_TRUST_INFORMATION>());
                    Marshal.StructureToPtr(trustInformation, forestInfo, false);

                    // get policy server name
                    serverName = Utils.GetPolicyServerName(context, true, true, SourceName);

                    // do impersonation first
                    impersonated = Utils.Impersonate(context);

                    // get the policy handle
                    handle = Utils.GetPolicyHandle(serverName);

                    // get the target name
                    global::Interop.UNICODE_STRING trustedDomainName;
                    target = Marshal.StringToHGlobalUni(TargetName);
                    Interop.NtDll.RtlInitUnicodeString(out trustedDomainName, target);

                    // call the unmanaged function
                    uint error = Interop.Advapi32.LsaSetForestTrustInformation(handle, trustedDomainName, forestInfo, true, out collisionInfo);
                    if (error != 0)
                    {
                        throw ExceptionHelper.GetExceptionFromErrorCode((int)global::Interop.Advapi32.LsaNtStatusToWinError(error), serverName);
                    }

                    // there is collision, throw proper exception so user can deal with it
                    if (collisionInfo != (IntPtr)0)
                    {
                        throw ExceptionHelper.CreateForestTrustCollisionException(collisionInfo);
                    }

                    // commit the changes
                    error = Interop.Advapi32.LsaSetForestTrustInformation(handle, trustedDomainName, forestInfo, false, out collisionInfo);
                    if (error != 0)
                    {
                        throw ExceptionHelper.GetExceptionFromErrorCode((int)error, serverName);
                    }

                    // now next time property is invoked, we need to go to the server
                    retrieved = false;
                }
                finally
                {
                    if (impersonated)
                        Utils.Revert();

                    // release the memory
                    for (int i = 0; i < ptrList.Count; i++)
                    {
                        Marshal.FreeHGlobal((IntPtr)ptrList[i]!);
                    }

                    for (int i = 0; i < sidList.Count; i++)
                    {
                        global::Interop.Kernel32.LocalFree((void*)(nint)sidList[i]!);
                    }

                    if (records != (IntPtr)0)
                    {
                        Marshal.FreeHGlobal(records);
                    }

                    if (forestInfo != (IntPtr)0)
                    {
                        Marshal.FreeHGlobal(forestInfo);
                    }

                    if (collisionInfo != (IntPtr)0)
                        global::Interop.Advapi32.LsaFreeMemory(collisionInfo);

                    if (target != (IntPtr)0)
                        Marshal.FreeHGlobal(target);

                    if (fileTime != (IntPtr)0)
                        Marshal.FreeHGlobal(fileTime);
                }
            }
            catch { throw; }
        }

        private unsafe void GetForestTrustInfoHelper()
        {
            IntPtr forestTrustInfo = (IntPtr)0;
            SafeLsaPolicyHandle? handle = null;
            bool impersonated = false;
            IntPtr targetPtr = (IntPtr)0;
            string? serverName = null;

            TopLevelNameCollection tmpTLNs = new TopLevelNameCollection();
            StringCollection tmpExcludedTLNs = new StringCollection();
            ForestTrustDomainInfoCollection tmpDomainInformation = new ForestTrustDomainInfoCollection();

            // internal members
            ArrayList tmpBinaryData = new ArrayList();
            Hashtable tmpExcludedNameTime = new Hashtable();
            ArrayList tmpBinaryDataTime = new ArrayList();
            ArrayList tmpBinaryRecordType = new ArrayList();

            try
            {
                try
                {
                    // get the target name
                    global::Interop.UNICODE_STRING tmpName;
                    targetPtr = Marshal.StringToHGlobalUni(TargetName);
                    Interop.NtDll.RtlInitUnicodeString(out tmpName, targetPtr);

                    serverName = Utils.GetPolicyServerName(context, true, false, source);

                    // do impersonation
                    impersonated = Utils.Impersonate(context);

                    // get the policy handle
                    handle = Utils.GetPolicyHandle(serverName);

                    uint result = Interop.Advapi32.LsaQueryForestTrustInformation(handle, tmpName, ref forestTrustInfo);
                    // check the result
                    if (result != 0)
                    {
                        uint win32Error = global::Interop.Advapi32.LsaNtStatusToWinError(result);
                        if (win32Error != 0)
                        {
                            throw ExceptionHelper.GetExceptionFromErrorCode((int)win32Error, serverName);
                        }
                    }

                    try
                    {
                        if (forestTrustInfo != (IntPtr)0)
                        {
                            LSA_FOREST_TRUST_INFORMATION trustInfo = new LSA_FOREST_TRUST_INFORMATION();
                            Marshal.PtrToStructure(forestTrustInfo, trustInfo);

                            int count = trustInfo.RecordCount;
                            IntPtr addr = (IntPtr)0;
                            for (int i = 0; i < count; i++)
                            {
                                addr = Marshal.ReadIntPtr(trustInfo.Entries, i * IntPtr.Size);
                                LSA_FOREST_TRUST_RECORD record = new LSA_FOREST_TRUST_RECORD();
                                Marshal.PtrToStructure(addr, record);

                                if (record.ForestTrustType == LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustTopLevelName)
                                {
                                    IntPtr myPtr = IntPtr.Add(addr, 16);
                                    record.TopLevelName = *(global::Interop.UNICODE_STRING*)myPtr;
                                    TopLevelName TLN = new TopLevelName(record.Flags, record.TopLevelName, record.Time);
                                    tmpTLNs.Add(TLN);
                                }
                                else if (record.ForestTrustType == LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustTopLevelNameEx)
                                {
                                    // get the excluded TLN and put it in our collection
                                    IntPtr myPtr = IntPtr.Add(addr, 16);
                                    record.TopLevelName = *(global::Interop.UNICODE_STRING*)myPtr;
                                    string excludedName = Marshal.PtrToStringUni(record.TopLevelName.Buffer, record.TopLevelName.Length / 2);
                                    tmpExcludedTLNs.Add(excludedName);
                                    tmpExcludedNameTime.Add(excludedName, record.Time);
                                }
                                else if (record.ForestTrustType == LSA_FOREST_TRUST_RECORD_TYPE.ForestTrustDomainInfo)
                                {
                                    IntPtr myPtr = IntPtr.Add(addr, 16);
                                    record.DomainInfo = *(LSA_FOREST_TRUST_DOMAIN_INFO*)myPtr;
                                    ForestTrustDomainInformation dom = new ForestTrustDomainInformation(record.Flags, record.DomainInfo!, record.Time);
                                    tmpDomainInformation.Add(dom);
                                }
                                else
                                {
                                    IntPtr myPtr = IntPtr.Add(addr, 16);
                                    record.Data = *(LSA_FOREST_TRUST_BINARY_DATA*)myPtr;
                                    int length = record.Data.Length;
                                    byte[] byteArray = new byte[length];
                                    if ((record.Data.Buffer != (IntPtr)0) && (length != 0))
                                    {
                                        Marshal.Copy(record.Data.Buffer, byteArray, 0, length);
                                    }
                                    tmpBinaryData.Add(byteArray);
                                    tmpBinaryDataTime.Add(record.Time);
                                    tmpBinaryRecordType.Add((int)record.ForestTrustType);
                                }
                            }
                        }
                    }
                    finally
                    {
                        global::Interop.Advapi32.LsaFreeMemory(forestTrustInfo);
                    }

                    _topLevelNames = tmpTLNs;
                    _excludedNames = tmpExcludedTLNs;
                    _domainInfo = tmpDomainInformation;

                    _binaryData = tmpBinaryData;
                    _excludedNameTime = tmpExcludedNameTime;
                    _binaryDataTime = tmpBinaryDataTime;
                    _binaryRecordType = tmpBinaryRecordType;

                    // mark it as retrieved
                    retrieved = true;
                }
                finally
                {
                    if (impersonated)
                        Utils.Revert();

                    if (targetPtr != (IntPtr)0)
                    {
                        Marshal.FreeHGlobal(targetPtr);
                    }
                }
            }
            catch { throw; }
        }
    }
}