Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi - how to check for any windows user if it is an administrator?

I need to check what kind of accounts are available on a local machine.

I already found how I can do this for the current logged user:

function IsWindowsAdmin: Boolean;
var
   hAccessToken: THandle;
   ptgGroups: PTokenGroups;
   dwInfoBufferSize: DWORD;
   psidAdministrators: PSID;
   g: Integer;
   bSuccess: BOOL;
begin
   Result:= False;
   bSuccess:= OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, hAccessToken);
   if not bSuccess then
   begin
     if GetLastError = ERROR_NO_TOKEN then
       bSuccess:= OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hAccessToken);
   end;

   if bSuccess then
   begin
     GetMem(ptgGroups, 1024);
     bSuccess:= GetTokenInformation(hAccessToken, TokenGroups, ptgGroups, 1024, dwInfoBufferSize);
     CloseHandle(hAccessToken);
     if bSuccess then
     begin
       AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
                                SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
                                0, 0, 0, 0, 0, 0, psidAdministrators);
       for g:= 0 to ptgGroups.GroupCount - 1 do
         if EqualSid(psidAdministrators, ptgGroups.Groups[g].Sid) then
         begin
           Result:= True;
           Break;
         end;
       FreeSid(psidAdministrators);
     end;
     FreeMem(ptgGroups);
   end;
end;

I also found how to get all users on a local machine. Is it possible to check for any user if it is an administrator or a limited account without having to log in each and every user?

like image 780
Georgi Bonchev Avatar asked Jul 25 '17 13:07

Georgi Bonchev


1 Answers

Here's my take on the subject, thanks to Sertac Akyuz for the hint about NetUserEnum with the USER_INFO_2 request and structure.

We can request users with following privilege levels

const
  USER_PRIV_GUEST = 0;
  USER_PRIV_USER  = 1;
  USER_PRIV_ADMIN = 2;
  USER_PRIV_ANY   = 3; // own invention

type
  TPrivLevel = USER_PRIV_GUEST..USER_PRIV_ANY;

Some declarations we need

const
  // some consts
  NERR_Success = 0;
  MAX_PREFERRED_LENGTH = $FFFFFFFF;

type
  NetApiStatus = DWORD;

// https://msdn.microsoft.com/en-us/library/windows/desktop/aa371337(v=vs.85).aspx
  TUserInfo2 = record
    usri2_name: LPWSTR ;
    usri2_password: LPWSTR ;
    usri2_password_age: DWORD  ;
    usri2_priv: DWORD  ;
    usri2_home_dir: LPWSTR ;
    usri2_comment: LPWSTR ;
    usri2_flags: DWORD  ;
    usri2_script_path: LPWSTR ;
    usri2_auth_flags: DWORD  ;
    usri2_full_name: LPWSTR ;
    usri2_usr_comment: LPWSTR ;
    usri2_parms: LPWSTR ;
    usri2_workstations: LPWSTR ;
    usri2_last_logon: DWORD  ;
    usri2_last_logoff: DWORD  ;
    usri2_acct_expires: DWORD  ;
    usri2_max_storage: DWORD  ;
    usri2_units_per_week: DWORD  ;
    usri2_logon_hours: PBYTE  ;
    usri2_bad_pw_count: DWORD  ;
    usri2_num_logons: DWORD  ;
    usri2_logon_server: LPWSTR ;
    usri2_country_code: DWORD  ;
    usri2_code_page: DWORD  ;
  end;
  PUSER_INFO_2 = ^TUserInfo2;
  LPUSER_INFO_2 = ^TUserInfo2;


// https://msdn.microsoft.com/en-us/library/windows/desktop/aa370304(v=vs.85).aspx
function NetApiBufferFree (Buffer: Pointer): NetApiStatus ;
                     stdcall; external 'netapi32.dll';

// https://msdn.microsoft.com/en-us/library/windows/desktop/aa370652%28v=vs.85%29.aspx
function NetUserEnum(
  servername: LPCWSTR;
  level: DWORD;
  filter: DWORD;
  var bufptr: pointer;
  prefmaxlen: DWORD;
  var entriesread: DWORD;
  var totalentries: DWORD;
  resume_handle: LPDWORD
): NetApiStatus; stdcall; external 'netapi32.dll';

And the procedure where PrivLevel is the privilege level for which you are asking the users and users is the TStringList to be populated with the user names.

function GetUsers(PrivLevel: TPrivLevel; users: TStrings): integer;
var
  i: integer;
  NetApiStatus: DWORD;
  bufptr: pointer;
  recptr: PUSER_INFO_2;
  EntriesRead,
  TotalEntries,
  HResume: DWORD;
begin
  HResume := 0;

  repeat
    NetApiStatus := NetUserEnum(
      nil,                         // local
      2,                           // USER_INFO_2
      0,                           // no special filter
      bufptr,
      MAX_PREFERRED_LENGTH,
      EntriesRead,
      TotalEntries,
      @HResume
    );

    if (NetApiStatus = NERR_Success) or (NetApiStatus = ERROR_MORE_DATA) then
    begin
      recptr := bufptr;
      for i := 0 to EntriesRead-1 do
      begin
        if (PrivLevel = USER_PRIV_ANY) or (recptr^.usri2_priv = PrivLevel) then
          users.Add(recptr^.usri2_name);
        inc(recptr);
      end;
      NetApiBufferFree(bufptr);
    end;

  until NetApiStatus <> ERROR_MORE_DATA;
  Result := NetApiStatus;
end;

Example of usage

procedure TForm1.Button1Click(Sender: TObject);
var
  res: integer;
begin
  res := GetUsers(USER_PRIV_ADMIN, Memo.Lines);
  if res <> 0 then Memo.Lines.Add('Error getting users! Error code '+IntToStr(res));
end;

Edit

I changed GetUsers() into a function and return the success/error code from the call to NetUserEnum()

Likely return values according to documentation

NERR_Success = 0
ERROR_ACCESS_DENIED  =    5; // $0005
ERROR_INVALID_LEVEL  =  124; // $007C
ERROR_MORE_DATA      =  234; // $00EA
NERR_BufTooSmall     = 2123; // $084B
NERR_InvalidComputer = 2351; // $092F
like image 104
Tom Brunberg Avatar answered Oct 22 '22 14:10

Tom Brunberg