Internationalization Cookbook
This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.

How to test if a user is Administrator on localized systems?

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!”

Leave a comment