Q. How to test in a locale-independent way if a user has administrative rights?
A. The short answer is: use the “Well Known Sid.” For ready to use code see below.
Introduction
Once in a while one might need to test if a user has administrative rights or not.
Except for applications specially developed for network or system management, very few applications have to deal with users, groups, or security rights. As a result, most developers are not familiar with the network and user management API. And even fewer are aware of the fact that the user/group names are localized on some versions of Windows.
The code
You can download from here all the code below, including headers, and a small test application.
There is not much explaining to do. The main idea is to use the a well known SID. But getting all the pieces together might be tricky.
So here is the code, ready to use. It works with everything from Visual Studio 6 to Visual Studio 2005 SP1. For some reasone CreateWellKnownSid
(working with VS 6) does not work anymore with VS 2005, so I had to replace it with AllocateAndInitializeSid
.
// IsAdminAPI.cpp : Tests if user is Administrator using plain Win32 API
// Copyright (c) April 2007, Mihai Nita
//
#include
<wtypes.h>#include
<Lm.h>// for ASSERT
#include
<crtdbg.h>#include
"IsAdminAPI.h"bool
IsAdminAPI( WCHARconst
*szUserName ) { _ASSERT(szUserName);bool
bAdmin = FALSE; LOCALGROUP_USERS_INFO_0* localGroups; DWORD entriesread, totalentries; NET_API_STATUS nts = NetUserGetLocalGroups( NULL, szUserName, 0, 0, (unsigned
char
**)&localGroups, MAX_PREFERRED_LENGTH, &entriesread, &totalentries);if
( nts != NERR_Success ) { NetApiBufferFree(localGroups);return
FALSE; }// Retrieve the Administrators group well-known SID
// For some reason CreateWellKnownSid generates error C3861 on Developer Studio .NET:
// error C3861: 'CreateWellKnownSid': identifier not found, even with argument-dependent lookup
BYTE SidAuth[] = SECURITY_NT_AUTHORITY; PSID pAdminSid; AllocateAndInitializeSid( (PSID_IDENTIFIER_AUTHORITY)SidAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, NULL, NULL, NULL, NULL, NULL, NULL, &pAdminSid );// Will use this to retrieve the SID of the group
BYTE buffSid[SECURITY_MAX_SID_SIZE];wchar_t
buffDomain[DNLEN+1]; DWORD dwSidSize; DWORD dwDomainSize; SID_NAME_USE m_sidnameuse;for
( DWORD i = 0; i < entriesread; ++i ) { dwSidSize = sizeof(buffSid); dwDomainSize = DNLEN;// Although in general is a bad idea to call directly the W or A versions of API
// we do it here to avoid converting the localGroups[i].lgrui0_name back to ANSI
// This kind of security API is only present on NT/2000/XP family only, so
// the W version is present and safe to use
if
( LookupAccountNameW( NULL, localGroups[i].lgrui0_name, buffSid, &dwSidSize, (LPWSTR)buffDomain, &dwDomainSize, &m_sidnameuse) )// no sid for the actual group
if
( EqualSid( buffSid, pAdminSid ) ) { bAdmin = TRUE;break
; } } FreeSid( pAdminSid ); NetApiBufferFree(localGroups);return
bAdmin; }
Starting with Visual Studio .NET 2003 (or maybe 2002, I am not sure), the ATL added support for user and network administration.
It is cleaner, it easyer to read and maitain. Just compare the ATL predefined Sids::Admins()
with the AllocateAndInitializeSid
above.
// IsAdminATL.cpp : Tests if user is Administrator using ATL
// Copyright (c) April 2007, Mihai Nita
//
#include
<wtypes.h>#include
<Lm.h>#include
"IsAdminATL.h"#ifdef
__DEV_STUDIO_NET__// This works with VS .NET, much cleaner
#include
<atlsecurity.h>bool
IsAdminATL( WCHARconst
*szUserName ) { _ASSERT(szUserName);bool
bAdmin = FALSE; LOCALGROUP_USERS_INFO_0* localGroups; DWORD entriesread, totalentries; NET_API_STATUS nts = NetUserGetLocalGroups( NULL, szUserName, 0, 0, (unsigned
char
**)&localGroups, MAX_PREFERRED_LENGTH, &entriesread, &totalentries);if
( nts == NERR_Success ) {for
( DWORD i = 0; i < entriesread; ++i ) { NonATLSid( localGroups[i].lgrui0_name );// You can find the list of all well-known SIDs in atlsecurity.h
try
{ CSid gsid( localGroups[i].lgrui0_name );if
( gsid == Sids::Admins() ) { bAdmin = TRUE;break
; } }catch
( CAtlException e ) {// we just go ahead with the next group
} } NetApiBufferFree(localGroups); }return
bAdmin; }#endif
// __DEV_STUDIO_NET__
Ok, this is it!
Not much to add, and it is late, so “good night!”