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( WCHAR const *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( WCHAR const *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!”