File: System\DirectoryServices\AccountManagement\AuthZSet.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;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace System.DirectoryServices.AccountManagement
{
    internal sealed class AuthZSet : ResultSet
    {
        internal unsafe AuthZSet(
                    byte[] userSid,
                    NetCred credentials,
                    ContextOptions contextOptions,
                    string flatUserAuthority,
                    StoreCtx userStoreCtx,
                    object userCtxBase)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info,
                                    "AuthZSet",
                                    "AuthZSet: SID={0}, authority={1}, storeCtx={2}",
                                    Utils.ByteArrayToString(userSid),
                                    flatUserAuthority,
                                    userStoreCtx.GetType());

            _userType = userStoreCtx.OwningContext.ContextType;
            _userStoreCtx = userStoreCtx;
            _credentials = credentials;
            _contextOptions = contextOptions;

            // flatUserAuthority is flat domain name if userType == Domain,
            // flat host name if userType == LocalMachine
            _flatUserAuthority = flatUserAuthority;

            // Preload the PrincipalContext cache with the user's PrincipalContext
            _contexts[flatUserAuthority] = userStoreCtx.OwningContext;

            //
            // Get the SIDs of the groups to which the user belongs
            //

            IntPtr pClientContext = IntPtr.Zero;
            IntPtr pResManager = IntPtr.Zero;
            IntPtr pBuffer = IntPtr.Zero;

            try
            {
                Interop.LUID luid = default;

                _psMachineSid = new SafeMemoryPtr(Utils.GetMachineDomainSid());
                _psUserSid = new SafeMemoryPtr(Utils.ConvertByteArrayToIntPtr(userSid));

                bool f;
                int lastError = 0;

                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Initializing resource manager");

                // Create a resource manager
                f = Interop.Authz.AuthzInitializeResourceManager(
                                            Interop.Authz.AUTHZ_RM_FLAG_NO_AUDIT,
                                            IntPtr.Zero,
                                            IntPtr.Zero,
                                            IntPtr.Zero,
                                            null,
                                            out pResManager
                                            );

                if (f)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Getting ctx from SID");

                    // Construct a context for the user based on the user's SID
                    f = Interop.Authz.AuthzInitializeContextFromSid(
                                                0,                  // default flags
                                                _psUserSid.DangerousGetHandle(),
                                                pResManager,
                                                IntPtr.Zero,
                                                luid,
                                                IntPtr.Zero,
                                                out pClientContext
                                                );

                    if (f)
                    {
                        int bufferSize = 0;

                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Getting info from ctx");

                        // Extract the group SIDs from the user's context.  Determine the size of the buffer we need.
                        f = Interop.Authz.AuthzGetInformationFromContext(
                                                    pClientContext,
                                                    2,                    // AuthzContextInfoGroupsSids
                                                    0,
                                                    out bufferSize,
                                                    IntPtr.Zero
                                                    );
                        if (!f && (bufferSize > 0) && (Marshal.GetLastPInvokeError() == 122) /*ERROR_INSUFFICIENT_BUFFER*/)
                        {
                            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Getting info from ctx (size={0})", bufferSize);

                            Debug.Assert(bufferSize > 0);

                            // Set up the needed buffer
                            pBuffer = Marshal.AllocHGlobal(bufferSize);

                            // Extract the group SIDs from the user's context, into our buffer.0
                            f = Interop.Authz.AuthzGetInformationFromContext(
                                                        pClientContext,
                                                        2,                    // AuthzContextInfoGroupsSids
                                                        bufferSize,
                                                        out bufferSize,
                                                        pBuffer
                                                        );

                            if (f)
                            {
                                // Marshall the native buffer into managed SID_AND_ATTR structures.
                                // The native buffer holds a TOKEN_GROUPS structure:
                                //
                                //        struct TOKEN_GROUPS {
                                //                DWORD GroupCount;
                                //                SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY];
                                //        };
                                //

                                // Extract TOKEN_GROUPS.GroupCount

                                Interop.TOKEN_GROUPS tokenGroups = *(Interop.TOKEN_GROUPS*)pBuffer;

                                uint groupCount = tokenGroups.GroupCount;

                                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Found {0} groups", groupCount);

                                // Extract TOKEN_GROUPS.Groups, by iterating over the array and marshalling
                                // each native SID_AND_ATTRIBUTES into a managed SID_AND_ATTR.
                                Interop.SID_AND_ATTRIBUTES[] groups = new Interop.SID_AND_ATTRIBUTES[groupCount];

                                IntPtr currentItem = pBuffer + sizeof(Interop.TOKEN_GROUPS) - sizeof(Interop.SID_AND_ATTRIBUTES);

                                for (int i = 0; i < groupCount; i++)
                                {
                                    groups[i] = *(Interop.SID_AND_ATTRIBUTES*)currentItem;

                                    currentItem += sizeof(Interop.SID_AND_ATTRIBUTES);
                                }

                                _groupSidList = new SidList(groups);
                            }
                            else
                            {
                                lastError = Marshal.GetLastPInvokeError();
                            }
                        }
                        else
                        {
                            lastError = Marshal.GetLastPInvokeError();
                            Debug.Fail("With a zero-length buffer, this should have never succeeded");
                        }
                    }
                    else
                    {
                        lastError = Marshal.GetLastPInvokeError();
                    }
                }
                else
                {
                    lastError = Marshal.GetLastPInvokeError();
                }

                if (!f)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Warn, "AuthZSet", "Failed to retrieve group list, {0}", lastError);

                    throw new PrincipalOperationException(
                                    SR.Format(
                                            SR.AuthZFailedToRetrieveGroupList,
                                            lastError));
                }

                // Save off the buffer since it still holds the native SIDs referenced by SidList
                _psBuffer = new SafeMemoryPtr(pBuffer);
                pBuffer = IntPtr.Zero;
            }
            catch (Exception e)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Error, "AuthZSet", "Caught exception {0} with message {1}", e.GetType(), e.Message);

                _psBuffer?.Dispose();
                _psUserSid?.Dispose();
                _psMachineSid?.Dispose();

                // We're on a platform that doesn't have the AuthZ library
                if (e is DllNotFoundException)
                    throw new NotSupportedException(SR.AuthZNotSupported, e);

                if (e is EntryPointNotFoundException)
                    throw new NotSupportedException(SR.AuthZNotSupported, e);

                throw;
            }
            finally
            {
                if (pClientContext != IntPtr.Zero)
                    Interop.Authz.AuthzFreeContext(pClientContext);

                if (pResManager != IntPtr.Zero)
                    Interop.Authz.AuthzFreeResourceManager(pResManager);

                if (pBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(pBuffer);
            }
        }

        internal override object CurrentAsPrincipal
        {
            get
            {
                Debug.Assert(_currentGroup >= 0 && _currentGroup < _groupSidList.Length);

                GlobalDebug.WriteLineIf(
                                GlobalDebug.Info,
                                "AuthZSet",
                                "CurrentAsPrincipal: currentGroup={0}, list length={1}",
                                _currentGroup,
                                _groupSidList.Length);

                // Convert native SID to byte[] SID
                IntPtr pSid = _groupSidList[_currentGroup].pSid;
                byte[] sid = Utils.ConvertNativeSidToByteArray(pSid);

                // sidIssuerName is null only if SID was not resolved
                // return a fake principal back
                if (null == _groupSidList[_currentGroup].sidIssuerName)
                {
                    string name = _groupSidList[_currentGroup].name;
                    // Create a Principal object to represent it
                    GroupPrincipal g = GroupPrincipal.MakeGroup(_userStoreCtx.OwningContext);
                    g.fakePrincipal = true;
                    g.LoadValueIntoProperty(PropertyNames.PrincipalDisplayName, name);
                    g.LoadValueIntoProperty(PropertyNames.PrincipalName, name);
                    SecurityIdentifier sidObj = new SecurityIdentifier(Utils.ConvertSidToSDDL(sid));
                    g.LoadValueIntoProperty(PropertyNames.PrincipalSid, sidObj);
                    g.LoadValueIntoProperty(PropertyNames.GroupIsSecurityGroup, true);
                    return g;
                }

                GroupPrincipal group = null;

                // Classify the SID
                SidType sidType = Utils.ClassifySID(pSid);

                // It's a fake principal.  Construct and respond the corresponding fake Principal object.
                if (sidType == SidType.FakeObject)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: fake principal {0}", Utils.ByteArrayToString(sid));
                    return _userStoreCtx.ConstructFakePrincipalFromSID(sid);
                }

                // Try to figure out who issued the SID
                string sidIssuerName = null;

                if (sidType == SidType.RealObjectFakeDomain)
                {
                    // BUILTIN principal --> issuer is the same authority as the user's SID
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: builtin principal {0}", Utils.ByteArrayToString(sid));

                    sidIssuerName = _flatUserAuthority;
                }
                else
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: real principal {0}", Utils.ByteArrayToString(sid));

                    // Is the SID from the same domain as the user?
                    bool sameDomain = false;

                    bool success = Interop.Advapi32.EqualDomainSid(_psUserSid.DangerousGetHandle(), pSid, ref sameDomain);

                    // if failed, psUserSid must not be a domain sid
                    if (!success)
                    {
#if !SUPPORT_WK_USER_OBJS
                        Debug.Fail("AuthZSet.CurrentAsPrincipal: hit a user with a non-domain SID");
#endif // SUPPORT_WK_USER_OBJS

                        sameDomain = false;
                    }

                    // same domain --> issuer is the same authority as the user's SID
                    if (sameDomain)
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: same domain as user ({0})", _flatUserAuthority);
                        sidIssuerName = _flatUserAuthority;
                    }
                }

                // The SID comes from another domain.  Use the domain name that the OS resolved the SID to.
                if (sidIssuerName == null)
                {
                    sidIssuerName = _groupSidList[_currentGroup].sidIssuerName;
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: different domain ({0}) than user ({1})", sidIssuerName, _flatUserAuthority);
                }

                Debug.Assert(sidIssuerName != null);
                Debug.Assert(sidIssuerName.Length > 0);

                // Determine whether it's a local (WinNT) or Active Directory domain (LDAP) group
                bool isLocalGroup = false;
                if (_userType == ContextType.Machine)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: local group (user is SAM)");

                    // Machine local user ---> must be a local group
                    isLocalGroup = true;
                }
                else
                {
                    Debug.Assert(_userType == ContextType.Domain);

                    // Domain user, but the group SID is from the machine domain --> must be a local group
                    // EqualDomainSid will return false if pSid is a BUILTIN SID, but that's okay, we treat those as domain (not local)
                    // groups for domain users.
                    bool inMachineDomain = false;
                    if (Interop.Advapi32.EqualDomainSid(_psMachineSid.DangerousGetHandle(), pSid, ref inMachineDomain))
                        if (inMachineDomain)
                        {
                            // At this point we know that the group was issued by the local machine.  Now determine if this machine is
                            // a DC.  Cache the results of the read.

                            if (!_localMachineIsDC.HasValue)
                            {
                                _localMachineIsDC = (bool?)Utils.IsMachineDC(null);
                                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: IsLocalMachine a DC, localMachineIsDC={0}", _localMachineIsDC.Value);
                            }

                            isLocalGroup = !_localMachineIsDC.Value;
                        }

                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "CurrentAsPrincipal: user is non-SAM, isLocalGroup={0}", isLocalGroup);
                }

                if (isLocalGroup)
                {
                    // It's a local group, because either (1) it's a local machine user, and local users can't be a member of a domain group,
                    // or (2) it's a domain user that's a member of a group on the local machine.  Pass the default machine context options
                    // If we initially targeted AD then those options will not be valid for the machine store.

                    PrincipalContext ctx = SDSCache.LocalMachine.GetContext(
                                                                    sidIssuerName,
                                                                    _credentials,
                                                                    DefaultContextOptions.MachineDefaultContextOption);
                    SecurityIdentifier sidObj = new SecurityIdentifier(sid, 0);
                    group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sidObj.ToString());
                }
                else
                {
                    Debug.Assert((_userType == ContextType.Domain) &&
                                 !string.Equals(Utils.GetComputerFlatName(), sidIssuerName, StringComparison.OrdinalIgnoreCase));

                    // It's a domain group, because it's a domain user and the SID issuer isn't the local machine

                    PrincipalContext ctx = SDSCache.Domain.GetContext(
                                                                sidIssuerName,
                                                                _credentials,
                                                                _contextOptions);

                    // Retrieve the group.  We'd normally just do a Group.FindByIdentity here, but
                    // because the AZMan API can return "old" SIDs, we also need to check the SID
                    // history.  So we'll use the special FindPrincipalBySID method that the ADStoreCtx
                    // exposes for that purpose.
                    Debug.Assert(ctx.QueryCtx is ADStoreCtx);

                    IdentityReference ir = new IdentityReference();
                    // convert byte sid to SDDL string.
                    SecurityIdentifier sidObj = new SecurityIdentifier(sid, 0);
                    ir.UrnScheme = UrnScheme.SidScheme;
                    ir.UrnValue = sidObj.ToString();

                    group = (GroupPrincipal)((ADStoreCtx)ctx.QueryCtx).FindPrincipalBySID(typeof(GroupPrincipal), ir, true);
                }

                if (group == null)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Warn, "AuthZSet", "CurrentAsPrincipal: Couldn't find group {0}");
                    throw new NoMatchingPrincipalException(SR.AuthZCantFindGroup);
                }

                return group;
            }
        }

        internal override bool MoveNext()
        {
            bool needToRetry;

            do
            {
                needToRetry = false;

                _currentGroup++;

                if (_currentGroup >= _groupSidList.Length)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "MoveNext: ran off end of list ({0})", _groupSidList.Length);
                    return false;
                }

                // Test for the NONE group for a local user.  We recognize it by:
                //      * we're enumerating the authz groups for a local machine user
                //      * it's a domain sid (SidType.RealObject) for the same domain as the user
                //      * it has the RID of the "Domain Users" group, 513
                if (_userType == ContextType.Machine)
                {
                    IntPtr pSid = _groupSidList[_currentGroup].pSid;

                    bool sameDomain = false;
                    if (Utils.ClassifySID(pSid) == SidType.RealObject && Interop.Advapi32.EqualDomainSid(_psUserSid.DangerousGetHandle(), pSid, ref sameDomain))
                    {
                        if (sameDomain)
                        {
                            int lastRid = Utils.GetLastRidFromSid(pSid);

                            if (lastRid == 513)     // DOMAIN_GROUP_RID_USERS
                            {
                                // This is the NONE group for a local user.  This isn't a real group, and
                                // has no impact on authorization (per ColinBr).  Skip it.
                                needToRetry = true;

                                GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "MoveNext: found NONE group, skipping");
                            }
                        }
                    }
                }
            }
            while (needToRetry);

            return true;
        }

        internal override void Reset()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Reset");

            _currentGroup = -1;
        }

        // IDisposable implementation
        public override void Dispose()
        {
            try
            {
                if (!_disposed)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "Dispose: disposing");

                    _psBuffer.Close();
                    _psUserSid.Close();
                    _psMachineSid.Close();

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

        //
        // Private fields
        //

        // The user whose groups we're retrieving
        private readonly SafeMemoryPtr _psUserSid;

        // The SID of the machine domain of the machine we're running on
        private readonly SafeMemoryPtr _psMachineSid;

        // The user's StoreCtx
        private readonly StoreCtx _userStoreCtx;

        // The user's credentials
        private readonly NetCred _credentials;

        // The user's options
        private readonly ContextOptions _contextOptions;

        // The type (domain, local, etc.) of the user
        private readonly ContextType _userType;

        // The authority's name (hostname or domainname)
        private readonly string _flatUserAuthority;

        // The index (into this.groupSidList) of the group we're currently enumerating
        private int _currentGroup = -1;

        // The groups we're enumerating over
        private readonly SidList _groupSidList;

        // The native TOKEN_GROUPS returned by AuthzGetInformationFromContext
        private readonly SafeMemoryPtr _psBuffer;

        // Have we been disposed?
        private bool _disposed;

        // Maps sidIssuerName --> PrincipalContext
        private readonly Hashtable _contexts = new Hashtable();

        // Contains cached results if the local machine is  a DC.
        private bool? _localMachineIsDC;

        //
        // Guarantees finalization of the native resources
        //
        private sealed class SafeMemoryPtr : SafeHandle
        {
            public SafeMemoryPtr() : base(IntPtr.Zero, true)
            {
            }

            internal SafeMemoryPtr(IntPtr handle) : base(IntPtr.Zero, true)
            {
                SetHandle(handle);
            }

            // for the critial finalizer object
            public override bool IsInvalid
            {
                get { return (handle == IntPtr.Zero); }
            }

            protected override bool ReleaseHandle()
            {
                if (handle != IntPtr.Zero)
                    Marshal.FreeHGlobal(handle);

                return true;
            }
        }

        /*
                //
                // Holds the list of group SIDs.  Also translates them in bulk into domain name and group name.
                //
                class SidList
                {
                    public SidList(UnsafeNativeMethods.SID_AND_ATTR[] groupSidAndAttrs)
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "AuthZSet", "SidList: processing {0} SIDs", groupSidAndAttrs.Length);

                        // Build the list of SIDs to resolve
                        int groupCount = groupSidAndAttrs.Length;
                        IntPtr[] pGroupSids = new IntPtr[groupCount];

                        for (int i=0; i < groupCount; i++)
                        {
                            pGroupSids[i] = groupSidAndAttrs[i].pSid;

                        }

                        // Translate the SIDs in bulk
                        IntPtr pOA = IntPtr.Zero;
                        IntPtr pPolicyHandle = IntPtr.Zero;

                        IntPtr pDomains = IntPtr.Zero;
                        UnsafeNativeMethods.LSA_TRUST_INFORMATION[] domains;

                        IntPtr pNames = IntPtr.Zero;
                        UnsafeNativeMethods.LSA_TRANSLATED_NAME[] names;

                        try
                        {
                            //
                            // Get the policy handle
                            //
                            UnsafeNativeMethods.LSA_OBJECT_ATTRIBUTES oa = new UnsafeNativeMethods.LSA_OBJECT_ATTRIBUTES();

                            pOA = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UnsafeNativeMethods.LSA_OBJECT_ATTRIBUTES)));
                            Marshal.StructureToPtr(oa, pOA, false);
                            int err = UnsafeNativeMethods.LsaOpenPolicy(
                                            IntPtr.Zero,
                                            pOA,
                                            0x800,        // POLICY_LOOKUP_NAMES
                                            ref pPolicyHandle);

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

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

                            Debug.Assert(pPolicyHandle != IntPtr.Zero);

                            //
                            // Translate the SIDs
                            //

                            err = UnsafeNativeMethods.LsaLookupSids(
                                                pPolicyHandle,
                                                groupCount,
                                                pGroupSids,
                                                out pDomains,
                                                out pNames);

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

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

                            //
                            // Get the group names in managed form
                            //
                            names = new UnsafeNativeMethods.LSA_TRANSLATED_NAME[groupCount];
                            IntPtr pCurrentName = pNames;

                            for (int i=0; i < groupCount; i++)
                            {
                                names[i] = (UnsafeNativeMethods.LSA_TRANSLATED_NAME)
                                                Marshal.PtrToStructure(pCurrentName, typeof(UnsafeNativeMethods.LSA_TRANSLATED_NAME));

                                pCurrentName = new IntPtr(pCurrentName.ToInt64() + Marshal.SizeOf(typeof(UnsafeNativeMethods.LSA_TRANSLATED_NAME)));
                            }

                            //
                            // Get the domain names in managed form
                            //

                            // Extract LSA_REFERENCED_DOMAIN_LIST.Entries
                            int domainCount = Marshal.ReadInt32(pDomains);

                            // Extract LSA_REFERENCED_DOMAIN_LIST.Domains, by iterating over the array and marshalling
                            // each native LSA_TRUST_INFORMATION into a managed LSA_TRUST_INFORMATION.
                            domains = new UnsafeNativeMethods.LSA_TRUST_INFORMATION[domainCount];

                            IntPtr pCurrentDomain = Marshal.ReadIntPtr(pDomains, Marshal.SizeOf(typeof(Int32)));

                            for (int i=0; i < domainCount; i++)
                            {
                                domains[i] =(UnsafeNativeMethods.LSA_TRUST_INFORMATION) Marshal.PtrToStructure(pCurrentDomain, typeof(UnsafeNativeMethods.LSA_TRUST_INFORMATION));
                                pCurrentDomain = new IntPtr(pCurrentDomain.ToInt64() + Marshal.SizeOf(typeof(UnsafeNativeMethods.LSA_TRUST_INFORMATION)));
                            }

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

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

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

                                // Get the domain associated with this name
                                Debug.Assert(name.domainIndex >= 0);
                                Debug.Assert(name.domainIndex < domains.Length);
                                UnsafeNativeMethods.LSA_TRUST_INFORMATION domain = domains[name.domainIndex];

                                // 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);

                                Debug.Assert(domain.name.length % 2 == 0);
                                entry.sidIssuerName = Marshal.PtrToStringUni(domain.name.buffer, domain.name.length/2);

                                entry.pSid = groupSidAndAttrs[i].pSid;

                                this.entries.Add(entry);
                            }

                        }
                        finally
                        {
                            if (pDomains != IntPtr.Zero)
                                UnsafeNativeMethods.LsaFreeMemory(pDomains);

                            if (pNames != IntPtr.Zero)
                                UnsafeNativeMethods.LsaFreeMemory(pNames);

                            if (pPolicyHandle != IntPtr.Zero)
                                UnsafeNativeMethods.LsaClose(pPolicyHandle);

                            if (pOA != IntPtr.Zero)
                                Marshal.FreeHGlobal(pOA);
                        }
                    }

                    List<SidListEntry> entries = new List<SidListEntry>();

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

                    public int Length
                    {
                        get { return this.entries.Count; }
                    }
                }

                class SidListEntry
                {
                    public IntPtr pSid;

                    public string name;
                    public string sidIssuerName;
                }
        */
    }
}