File: System\DirectoryServices\AccountManagement\AD\SidList.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices.AccountManagement\src\System.DirectoryServices.AccountManagement.csproj (System.DirectoryServices.AccountManagement)
// 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.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace System.DirectoryServices.AccountManagement
{
    internal sealed class SidList
    {
        internal SidList(List<byte[]> sidListByteFormat) : this(sidListByteFormat, null, null)
        {
        }

        internal SidList(List<byte[]> sidListByteFormat, string target, NetCred credentials)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "SidList", "SidList: processing {0} ByteFormat SIDs", sidListByteFormat.Count);
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "SidList", "SidList: Targeting {0} ", target ?? "local store");

            // Build the list of SIDs to resolve
            IntPtr hUser = IntPtr.Zero;

            int sidCount = sidListByteFormat.Count;
            IntPtr[] pSids = new IntPtr[sidCount];

            for (int i = 0; i < sidCount; i++)
            {
                pSids[i] = Utils.ConvertByteArrayToIntPtr(sidListByteFormat[i]);
            }

            try
            {
                if (credentials != null)
                {
                    Utils.BeginImpersonation(credentials, out hUser);
                }

                TranslateSids(target, pSids);
            }
            finally
            {
                if (hUser != IntPtr.Zero)
                    Utils.EndImpersonation(hUser);
            }
        }

        internal SidList(Interop.SID_AND_ATTRIBUTES[] sidAndAttr)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "SidList", "SidList: processing {0} Sid+Attr SIDs", sidAndAttr.Length);

            // Build the list of SIDs to resolve
            int sidCount = sidAndAttr.Length;
            IntPtr[] pSids = new IntPtr[sidCount];

            for (int i = 0; i < sidCount; i++)
            {
                pSids[i] = sidAndAttr[i].Sid;
            }

            TranslateSids(null, pSids);
        }

        private unsafe void TranslateSids(string target, IntPtr[] pSids)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "SidList: processing {0} SIDs", pSids.Length);

            // if there are no SIDs to translate return
            if (pSids.Length == 0)
            {
                return;
            }

            // Build the list of SIDs to resolve
            int sidCount = pSids.Length;

            // Translate the SIDs in bulk
            SafeLsaPolicyHandle policyHandle = null;
            SafeLsaMemoryHandle domainsHandle = null;
            SafeLsaMemoryHandle namesHandle = null;
            try
            {
                //
                // Get the policy handle
                //
                Interop.OBJECT_ATTRIBUTES oa = default;

                uint err = Interop.Advapi32.LsaOpenPolicy(
                                    target,
                                    ref oa,
                                    (int)Interop.Advapi32.PolicyRights.POLICY_LOOKUP_NAMES,
                                    out policyHandle);
                if (err != 0)
                {
                    policyHandle.Dispose();

                    GlobalDebug.WriteLineIf(GlobalDebug.Warn, "AuthZSet", "SidList: couldn't get policy handle, err={0}", err);

                    throw new PrincipalOperationException(SR.Format(
                                                               SR.AuthZErrorEnumeratingGroups,
                                                               Interop.Advapi32.LsaNtStatusToWinError(err)));
                }

                Debug.Assert(!policyHandle.IsInvalid);

                //
                // Translate the SIDs
                //

                err = Interop.Advapi32.LsaLookupSids(
                                    policyHandle,
                                    sidCount,
                                    pSids,
                                    out domainsHandle,
                                    out namesHandle);

                // Ignore error STATUS_SOME_NOT_MAPPED and STATUS_NONE_MAPPED
                if (err != Interop.StatusOptions.STATUS_SUCCESS &&
                     err != Interop.StatusOptions.STATUS_SOME_NOT_MAPPED &&
                     err != Interop.StatusOptions.STATUS_NONE_MAPPED)
                {
                    domainsHandle.Dispose();
                    namesHandle.Dispose();

                    GlobalDebug.WriteLineIf(GlobalDebug.Warn, "AuthZSet", "SidList: LsaLookupSids failed, err={0}", err);

                    throw new PrincipalOperationException(SR.Format(
                                                               SR.AuthZErrorEnumeratingGroups,
                                                               Interop.Advapi32.LsaNtStatusToWinError(err)));
                }

                //
                // Get the group names in managed form
                //
                namesHandle.Initialize((uint)sidCount, (uint)Marshal.SizeOf<Interop.LSA_TRANSLATED_NAME>());

                Interop.LSA_TRANSLATED_NAME[] names = new Interop.LSA_TRANSLATED_NAME[sidCount];
                namesHandle.ReadArray(0, names, 0, names.Length);

                //
                // Get the domain names in managed form
                //
                domainsHandle.InitializeReferencedDomainsList();
                Interop.LSA_REFERENCED_DOMAIN_LIST domainList = domainsHandle.Read<Interop.LSA_REFERENCED_DOMAIN_LIST>(0);

                // Extract LSA_REFERENCED_DOMAIN_LIST.Entries

                int domainCount = domainList.Entries;

                // Extract LSA_REFERENCED_DOMAIN_LIST.Domains, by iterating over the array and marshalling
                // each native LSA_TRUST_INFORMATION into a managed LSA_TRUST_INFORMATION.

                Interop.LSA_TRUST_INFORMATION[] domains = new Interop.LSA_TRUST_INFORMATION[domainCount];

                IntPtr pCurrentDomain = domainList.Domains;

                for (int i = 0; i < domainCount; i++)
                {
                    domains[i] = *(Interop.LSA_TRUST_INFORMATION*)pCurrentDomain;
                    pCurrentDomain += sizeof(Interop.LSA_TRUST_INFORMATION);
                }

                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "SidList: got {0} groups in {1} domains", sidCount, domainCount);

                //
                // Build the list of entries
                //
                Debug.Assert(names.Length == sidCount);

                for (int i = 0; i < names.Length; i++)
                {
                    Interop.LSA_TRANSLATED_NAME name = names[i];

                    // Build an entry.  Note that LSA_UNICODE_STRING.length is in bytes,
                    // while PtrToStringUni expects a length in characters.
                    SidListEntry entry = new SidListEntry();

                    Debug.Assert(name.Name.Length % 2 == 0);
                    entry.name = Marshal.PtrToStringUni(name.Name.Buffer, name.Name.Length / 2);

                    // Get the domain associated with this name
                    Debug.Assert(name.DomainIndex < domains.Length);
                    if (name.DomainIndex >= 0)
                    {
                        Interop.LSA_TRUST_INFORMATION domain = domains[name.DomainIndex];
                        Debug.Assert(domain.Name.Length % 2 == 0);
                        entry.sidIssuerName = Marshal.PtrToStringUni(domain.Name.Buffer, domain.Name.Length / 2);
                    }

                    entry.pSid = pSids[i];

                    _entries.Add(entry);
                }

                // Sort the list so they are oriented by the issuer name.
                // this.entries.Sort( new SidListComparer());
            }
            finally
            {
                domainsHandle?.Dispose();
                namesHandle?.Dispose();
                policyHandle?.Dispose();
            }
        }

        private readonly List<SidListEntry> _entries = new List<SidListEntry>();

        public SidListEntry this[int index]
        {
            get { return _entries[index]; }
        }

        public int Length
        {
            get { return _entries.Count; }
        }

        public void RemoveAt(int index)
        {
            _entries[index].Dispose();
            _entries.RemoveAt(index);
        }

        public void Clear()
        {
            foreach (SidListEntry sl in _entries)
                sl.Dispose();

            _entries.Clear();
        }
    }

    /******
            class SidListComparer : IComparer<SidListEntry>
            {
              public int Compare(SidListEntry entry1, SidListEntry entry2)
              {
                 return ( string.Compare( entry1.sidIssuerName, entry2.sidIssuerName, true, CultureInfo.InvariantCulture));
              }

            }
    ********/
    internal sealed class SidListEntry : IDisposable
    {
        public IntPtr pSid = IntPtr.Zero;
        public string name;
        public string sidIssuerName;
        //
        // IDisposable
        //
        public void Dispose()
        {
            if (pSid != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pSid);
                pSid = IntPtr.Zero;
            }
        }
    }
}