InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Determining the type of an user account

When playing around with Microsoft Exchange security descriptors you inevitably come to the point where you must sort the security descriptors according to the following rules:

    1 REPEAT <n>

    2   GRANT ACCESS RIGHT FOR USER A

    3   DENY ACCESS RIGHT FOR USER A

    4 REPEAT <m>

    5   GRANT ACCESS RIGHT FOR DL B

    6   GRANT ACCESS RIGHT FOR DL C

    7 REPEAT <m>

    8   GRANT ACCESS DENY FOR DL B

    9   GRANT ACCESS DENY FOR DL C

   10 REPEAT <0 or 1>

   11 GRANT ACCESS RIGHT FOR EVERYONE

(This was taken from the article Exchange 5.5 Access Rights and the Exchange Store on MSDN)

So you must somehow distinguish between normal users, groups, the everyone group and the anonymous group. If you read the security descriptor as XML stream via the http://schemas.microsoft.com/exchange/security/descriptor property, you get this information for the existing entries. But if you add another principal to the list, you must insert the entry at the correct position, depending of the type of the account being added.

Unfortunately, you can get this information from the Security identifier of the entry only for a few exceptions: The Everyone group and the anonymous groups (see this KB article). To identify the all others, you have basically two options:

  • Get the corresponding entry from Active Directory and check the object class
  • Use Win32 functions LookupAccountSid and LookupAccountName, which return the type of the account

In this article, I'll present a .NET implementation for the latter approach.

The implementation

The following code is taken from my InfiniTec.Security library, and implements a class, which takes a IdentityReference instance in its constructor. The account type is then exposed via the AccountType property. Note that you can also specify a remote computer, on which the translation should be performed. Since no alternative credentials can be specified, you must impersonate another user account if needed.

    1 using System;

    2 using System.Runtime.InteropServices;

    3 using System.Security.Principal;

    4 using System.Text;

    5 

    6 ///<summary>

    7 /// Provides the ability to translate a <see cref="NTAccount"/> to a <see cref="SecurityIdentifier"/>

    8 /// and vice versa, optionally using a remote computer for the translation process.

    9 ///</summary>

   10 publicclassIdentityResolver: IEquatable<IdentityReference>

   11 {

   12     privatestring _ComputerName;

   13     privateAccountType _AccountType;

   14     privateNTAccount _Account;

   15     privateSecurityIdentifier _Sid;

   16 

   17     ///<summary>

   18     /// Returns the <see cref="SecurityIdentifier"/> of this instance.

   19     ///</summary>

   20     publicSecurityIdentifier Sid

   21     {

   22         get

   23         {

   24             if (_Sid == null) TranslateFromNTAccount();

   25             return _Sid;

   26         }

   27     }

   28     ///<summary>

   29     /// Returns the <see cref="NTAccount"/> of this instance.

   30     ///</summary>

   31     publicNTAccount Account

   32     {

   33         get

   34         {

   35             if (_Account == null) TranslateFromSecurityDescriptor();

   36             return _Account;

   37         }

   38     }

   39 

   40     ///<summary>

   41     /// Returns the <see cref="AccountType"/> of this instance

   42     ///</summary>

   43     publicAccountType AccountType

   44     {

   45         get {

   46             if (_Sid == null) TranslateFromNTAccount();

   47             elseif (_Account == null) TranslateFromSecurityDescriptor();

   48 

   49             return _AccountType;

   50         }

   51     }

   52 

   53     ///<summary>

   54     /// Creates a new instance of this class. A remote computer is not used for the

   55     /// translation process

   56     ///</summary>

   57     ///<param name="identity">The identity to translate</param>

   58     public IdentityResolver(IdentityReference identity): this(identity, null) { }

   59 

   60     ///<summary>

   61     /// Creates a new instance of this class.

   62     ///</summary>

   63     ///<param name="identity">The identity to translate</param>

   64     ///<param name="computerName">The computer to use for the translation</param>

   65     ///<remarks>The remote computer is not used for translation, if

   66     /// the provided identity is a <see cref="SecurityIdentifier"/> and the domain

   67     /// sid of that identity equals the domain sid of the current user</remarks>

   68     public IdentityResolver(IdentityReference identity, string computerName)

   69     {

   70         _Sid = identity asSecurityIdentifier;

   71         _Account = identity asNTAccount;

   72 

   73         if (_Sid == null || !_Sid.IsEqualDomainSid(WindowsIdentity.GetCurrent().User))

   74         {

   75             _ComputerName = computerName;

   76         }

   77 

   78     }

   79 

   80     privatevoid TranslateFromNTAccount()

   81     {

   82         byte[] binarySid;

   83         uint binarySidLength;

   84         StringBuilder referencedDomain;

   85         uint referencedDomainLength;

   86         bool result;

   87 

   88         binarySid = newbyte[SecurityIdentifier.MaxBinaryLength];

   89         binarySidLength = (uint) binarySid.Length;

   90         referencedDomain = newStringBuilder(0xff);

   91         referencedDomainLength = (uint) referencedDomain.Capacity;

   92 

   93         result = NativeMethods.LookupAccountName(_ComputerName, _Account.Value,

   94             binarySid, ref binarySidLength, referencedDomain,

   95             ref referencedDomainLength, out _AccountType);

   96 

   97         if (!result) throwMarshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

   98 

   99         _Sid = newSecurityIdentifier(binarySid, 0);

  100 

  101     }

  102 

  103     privatevoid TranslateFromSecurityDescriptor()

  104     {

  105         byte[] binarySid;

  106         StringBuilder name;

  107         StringBuilder domain;

  108         uint nameLength;

  109         uint domainLength;

  110         bool result;

  111 

  112         name = newStringBuilder(0x100);

  113         domain = newStringBuilder(0xff);

  114 

  115         nameLength = (uint) name.Capacity - 2;

  116         domainLength = (uint) domain.Capacity - 2;

  117 

  118         binarySid = newbyte[_Sid.BinaryLength];

  119         _Sid.GetBinaryForm(binarySid, 0);

  120 

  121         result = NativeMethods.LookupAccountSid(

  122             _ComputerName, binarySid, name, ref nameLength, domain, ref domainLength, out _AccountType);

  123 

  124         if (!result) throwMarshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

  125 

  126         _Account = newNTAccount(domain.ToString(), name.ToString());

  127     }

  128 

  129     #region IEquatable<NTAccount> Members

  130 

  131     ///<summary>

  132     /// Compares this identity with the specified othe identity.

  133     ///</summary>

  134     ///<param name="other">The identity to compare this identity with.</param>

  135     ///<returns>True, if both identities represent the same identity</returns>

  136     publicbool Equals(IdentityReference other)

  137     {

  138         if (other == null) returnfalse;

  139 

  140         if (other isNTAccount) return Account.Equals(other);

  141         elseif (other isSecurityIdentifier) Sid.Equals((SecurityIdentifier) other);

  142 

  143         returnfalse;

  144     }

  145 

  146     #endregion

  147 }

The interesting stuff happens in the TranslateFromSecurityDescriptor and the TranslateFromNTAccount method. They prepare some buffers and call the LookupAccountSid and LookupAccountName function. On return of either these functions, the account type is stored in the _AccountType field.

The various account types

The example code above exposes the account type as an enumeration. Here is the implementation of this enumeration:

    1 ///<summary>

    2 /// Defines the various account types of a Windows accunt

    3 ///</summary>

    4 publicenumAccountType

    5 {

    6     ///<summary>

    7     /// No account type

    8     ///</summary>

    9     None = 0,

   10     ///<summary>

   11     /// The account is a user

   12     ///</summary>

   13     User,

   14     ///<summary>

   15     /// The account is a security group

   16     ///</summary>

   17     Group,

   18     ///<summary>

   19     /// The account defines a domain

   20     ///</summary>

   21     Domain,

   22     ///<summary>

   23     /// The account is an alias

   24     ///</summary>

   25     Alias,

   26     ///<summary>

   27     /// The account is a well-known group, such as BUILTIN\Administrators

   28     ///</summary>

   29     WellknownGroup,

   30     ///<summary>

   31     /// The account was deleted

   32     ///</summary>

   33     DeletedAccount,

   34     ///<summary>

   35     /// The account is invalid

   36     ///</summary>

   37     Invalid,

   38     ///<summary>

   39     /// The type of the account is unknown

   40     ///</summary>

   41     Unknown,

   42     ///<summary>

   43     /// The account is a computer account

   44     ///</summary>

   45     Computer

   46 }

Interop methods

To use the Win32 functions from .NET, we must implement a class with the interop definitions for the two methods:

    1 staticclassNativeMethods

    2 {

    3     [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]

    4     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

    5     [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]

    6     [return: MarshalAs(UnmanagedType.Bool)]

    7     publicstaticexternbool LookupAccountSid(

    8         [In] string systemName,

    9         [In, MarshalAs(UnmanagedType.LPArray)] byte[] sid,

   10         [Out] StringBuilder name,

   11         [In, Out] refuint nameLength,

   12         [Out] StringBuilder referencedDomainName,

   13         [In, Out] refuint referencedDomainNameLength,

   14         [Out] outAccountType usage);

   15 

   16     [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]

   17     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

   18     [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]

   19     [return: MarshalAs(UnmanagedType.Bool)]

   20     publicstaticexternbool LookupAccountName(

   21         [In] string systemName,

   22         [In] string accountName,

   23         [Out, MarshalAs(UnmanagedType.LPArray)] byte[] sid,

   24         [In, Out] refuint sidSize,

   25         [Out] StringBuilder referencedDomainName,

   26         [In, Out] refuint referencedDomainNameLength,

   27         [Out] outAccountType accountType);

   28 }

This class allows us to call the methods from .NET


Technorati:

Posted by Henning Krause on Monday, March 12, 2007 12:00 AM, last modified on Monday, March 12, 2007 8:00 PM
Permalink | Post RSSRSS comment feed