8.3 Getting User and Group Information on Windows

8.3.1 Problem

You need to discover information about a user or group, and you have a username or user ID or a group name or ID.

8.3.2 Solution

Windows identifies users and groups using security identifiers (SIDs), which are unique, variably sized values assigned by an authority such as the local machine or a Windows NT server domain. Functions and data structures typically represent users and groups using SIDs, rather than using names.

The Win32 API provides numerous functions for manipulating SIDs, but of particular interest to us in this recipe are the functions LookupAccountName( ) and LookupAccountSid( ), which are used to map between names and SIDs.

8.3.3 Discussion

The Win32 API function LookupAccountName( ) is used to find the SID that corresponds to a name. You can use it to obtain information about a name on either the local system or a remote system. While it might seem that mapping a name to a SID is a simple operation, LookupAccountName( ) actually requires a large number of arguments to allow it to complete its work.

LookupAccountName( ) has the following signature:

BOOL LookupAccountName(LPCTSTR lpSystemName, LPCTSTR lpAccountName, PSID Sid,
                       LPDWORD cbSid, LPTSTR ReferencedDomainName,
                       LPDWORD cbReferencedDomainName, PSID_NAME_USE peUse);

This function has the following arguments:

lpSystemName

String representing the name of the remote system on which to look up the name. If you specify this argument as NULL, the lookup will be done on the local system.

lpAccountName

String representing the name of the user or group to look up. This argument may not be specified as NULL.

Sid

Buffer into which the SID will be written. Initially, you may specify this argument as NULL to determine how large a buffer is required to hold the SID.

cbSid

Pointer to an integer that both specifies the size of the buffer to receive the SID, and receives the size of the buffer required for the SID.

ReferencedDomainName

Buffer into which the domain name where the user or group name was found is to be written. Initially, you may specify this argument as NULL to determine how large a buffer is required to hold the domain name.

cbReferencedDomainName

Pointer to an integer that both specifies the size of the buffer to receive the domain name, and receives the size of the buffer required for the domain name.

peUse

Pointer to an enumeration that receives the type of SID to which the looked-up name corresponds. The most commonly returned values are SidTypeUser (1) and SidTypeGroup (2).

The following function, SpcLookupName( ), is essentially a wrapper around LookupAccountName( ). It handles the nuances of performing user and group name lookup, including allocating the necessary buffers and error conditions. If the name is successfully found, the return will be a pointer to a dynamically allocated SID structure, which you must later free using LocalFree( ). If the name could not be found, NULL will be returned, and GetLastError( ) will return ERROR_NONE_MAPPED. If any other kind of error occurs, SpcLookupName( ) will return NULL, and GetLastError( ) will return the relevant error code.

#include <windows.h>
   
PSID SpcLookupName(LPCTSTR lpszSystemName, LPCTSTR lpszAccountName) {
  PSID         Sid;
  DWORD        cbReferencedDomainName, cbSid;
  LPTSTR       ReferencedDomainName;
  SID_NAME_USE eUse;
   
  cbReferencedDomainName = cbSid = 0;
  if (LookupAccountName(lpszSystemName, lpszAccountName, 0, &cbSid, 
                        0, &cbReferencedDomainName, &eUse)) {
    SetLastError(ERROR_NONE_MAPPED);
    return 0;
  }
  if (GetLastError(  ) != ERROR_INSUFFICIENT_BUFFER) return 0;
   
  if (!(Sid = (PSID)LocalAlloc(LMEM_FIXED, cbSid))) return 0;
  ReferencedDomainName = (LPTSTR)LocalAlloc(LMEM_FIXED, cbReferencedDomainName);
  if (!ReferencedDomainName) {
    LocalFree(Sid);
    return 0;
  }
   
  if (!LookupAccountName(lpszSystemName, lpszAccountName, Sid, &cbSid,
                         ReferencedDomainName, &cbReferencedDomainName, &eUse)) {
    LocalFree(ReferencedDomainName);
    LocalFree(Sid);
    return 0;
  }
   
  LocalFree(ReferencedDomainName);
  return Sid;
}

The Win32 API function LookupAccountSid( ) is used to find the name that corresponds to a SID. You can use it to obtain information about a SID on either the local system or a remote system. While it might seem that mapping a SID to a name is a simple operation, LookupAccountSid( ) actually requires a large number of arguments to allow it to complete its work.

LookupAccountSid( ) has the following signature:

BOOL LookupAccountSid(LPCTSTR lpSystemName, PSID Sid,LPTSTR Name, LPDWORD cbName,
                       LPTSTR ReferencedDomainName, LPDWORD cbReferencedDomainName,
                       PSID_NAME_USE peUse);

This function has the following arguments:

lpSystemName

String representing the name of the remote system on which to look up the SID. If you specify this argument as NULL, the lookup will be done on the local system.

Sid

Buffer containing the SID to look up. This argument may not be specified as NULL.

Name

Buffer into which the name will be written. Initially, you may specify this argument as NULL to determine how large a buffer is required to hold the name.

cbName

Pointer to an integer that both specifies the size of the buffer to receive the name, and receives the size of the buffer required for the name.

ReferencedDomainName

Buffer into which the domain name where the SID was found is to be written. Initially, you may specify this argument as NULL to determine how large a buffer is required to hold the domain name.

cbReferencedDomainName

Pointer to an integer that both specifies the size of the buffer to receive the domain name, and receives the size of the buffer required for the domain name.

peUse

Pointer to an enumeration that receives the type of SID to which the looked-up SID corresponds. The most commonly returned values are SidTypeUser (1) and SidTypeGroup (2).

The following function, SpcLookupSid( ), is essentially a wrapper around LookupAccountSid( ). It handles the nuances of performing SID lookup, including allocating the necessary buffers and error conditions. If the SID is successfully found, the return will be a pointer to a dynamically allocated buffer containing the user or group name, which you must later free using LocalFree( ). If the SID could not be found, NULL will be returned, and GetLastError( ) will return ERROR_NONE_MAPPED. If any other kind of error occurs, SpcLookupSid( ) will return NULL, and GetLastError( ) will return the relevant error code.

#include <windows.h>
   
LPTSTR SpcLookupSid(LPCTSTR lpszSystemName, PSID Sid) {
  DWORD        cbName, cbReferencedDomainName;
  LPTSTR       lpszName, ReferencedDomainName;
  SID_NAME_USE eUse;
   
  cbName = cbReferencedDomainName = 0;
  if (LookupAccountSid(lpszSystemName, Sid, 0, &cbName,
                       0, &cbReferencedDomainName, &eUse)) {
    SetLastError(ERROR_NONE_MAPPED);
    return 0;
  }
  if (GetLastError(  ) != ERROR_INSUFFICIENT_BUFFER) return 0;
   
  if (!(lpszName = (LPTSTR)LocalAlloc(LMEM_FIXED, cbName))) return 0;
  ReferencedDomainName = (LPTSTR)LocalAlloc(LMEM_FIXED, cbReferencedDomainName);
  if (!ReferencedDomainName) {
    LocalFree(lpszName);
    return 0;
  }
   
  if (!LookupAccountSid(lpszSystemName, Sid, lpszName, &cbName,
                        ReferencedDomainName, &cbReferencedDomainName, &eUse)) {
    LocalFree(ReferencedDomainName);
    LocalFree(lpszName);
    return 0;
  }
   
  LocalFree(ReferencedDomainName);
  return lpszName;
}