views:

179

answers:

5

Hi! I have some small questions...I have a program that stores a list of users in a database and compares on program startup if the user is in the list or is an administrator before letting them use it. At the moment, the way I'm using to check if the user is an administrator is simply by comparing the username to a string constant called 'ADMINISTRATOR'. Will this work on a non-Engish system? I.E. does Windows use a language specific version of 'administrator'? Or maybe is there an enumerated version of the Admin user that I can use to check with instead of my 'ADMINISTRATOR' string? (you know, just like how Windows folders are enumerated). I'm using Delphi 2009 by the way. Thanks in advance!

+6  A: 

No, don't do it that way. It will surely break. You could get a list of all the groups the user is a member of and check if one of the SIDs is S-1-5-32-544, which is the SID of the Administrators group. There is a list of well known SIDs. There is also an SID for the original administrator account.

Here is the list:

http://support.microsoft.com/kb/243330

BobbyShaftoe
-1: A simple search of a membership will return an incorrect result in >= Vista because the membership in Administrators is usually restricted in the token. The membership is available but Windows will check the DENY_ONLY_SID flag of the membership in the token and still deny access. Restricted Tokens have been available since Windows 2000 but were rarely used.
ChristianWimmer
[citation needed]
BobbyShaftoe
You suggestion is implemented here:http://www.techtricks.com/delphi/isadmin.php is incorrect for >= VISTA (and works on < Vista by accident).CreateRestrictedToken Function: msdn.microsoft.com/en-us/library/aa446583%28VS.85%29.aspx Minimum supported client: Windows 2000 Professional
ChristianWimmer
+1  A: 

It varies from windows version to windows version... in pre-vista... administrator username is in the primary windows language... for example, in spanish it is Administrador.

In post-vista, there's no administrator user. You shall store and check for user privileges.

I found this IsAdmin function and you may find it useful too...

jachguate
-1 : In post vista there is an Administrator, but it is deactivated. Every user with the membership in group Administrators is de facto an administrator. The IsAdmin function is incorrect, it does not take into account restricted token and just checks for the availability of the group Administrators in the token but ignores the DENY_ONLY flag. These sources are floating around on the internet a lot because they are just copied from an old, wrong and revised MSDN article.
ChristianWimmer
+6  A: 

I pick out a small portion of my private library for your convenience. To test whether user account of the access token is a member of local administrators group, pass WinBuiltinAdministratorsSid of JwaWinNT to eWellKnownSidType parameter. Note, it requires JEDI API Libray because Delphi Windows.pas unit didn't define CreateWellKnownSid().

//------------------------------------------------------------------------------
// Purpose: Test whether user account of the access token is a member of the
//   specified well known group, and report its elevation type.
// Parameter:
//   hToken [in,opt]: A handle to an access token having TOKEN_QUERY and
//     TOKEN_DUPLICATE access. If hToken is 0: if it is an impersonation token,
//     the access token of the calling thread is used; otherwise, the access
//     token associated with the process is used.
//   eWellKnownSidType [in]: Member of the WELL_KNOWN_SID_TYPE enumeration that
//     specifies what Sid the function will identify.
//   pDomainSid [in,opt]: A pointer to a SID that identifies the domain to use
//     when identifying the Sid. Pass nil to use the local computer.
//   peElevType [out,opt]: A pointer to a variable that receives elevation type
//     of the access token, that is:
//       TokenElevationTypeDefault: The access token does not have a linked
//         token. This value is reported under Windows prior to Windows Vista.
//       TokenElevationTypeFull: The access token is an elevated token.
//       TokenElevationTypeLimited: The access token is a limited token.
// Return: True if user account of the access token is a member of the well
//   known group specified in eWellKnownSidType parameter. False, otherwise.
//   To get error information, call GetLastError().
// Remarks: To test whether user account of the access token is a member of
//   local administrators group, pass JwaWinNT.WinBuiltinAdministratorsSid
//   to eWellKnownSidType parameter.
// Refs: How to Determine whether a Process or Thread is Running as an
//         Administrator [MSDN]
//------------------------------------------------------------------------------
function Inu_IsMemberOfWellKnownGroup(const hToken: HANDLE;
    const eWellKnownSidType: WELL_KNOWN_SID_TYPE;
    const pDomainSid: Windows.PSID = nil;
    peElevType: PTokenElevationType = nil): Boolean;
label
  lCleanUp1, lCleanUp2, lCleanUp3;
var
  hAccessToken: HANDLE;
  rOSVerInfo: Windows.OSVERSIONINFO;
  eTET: TTokenElevationType;
  iReturnLen: DWORD;
  hTokenToCheck: HANDLE;
  iSidLen: DWORD;
  pGroupSid: Windows.PSID;
  bMemberOfWellKnownGroup: BOOL;

begin
  Result := False;
  if hToken = 0 then begin // If the caller doesn't supply a token handle,
        // Get the calling thread's access token
    if not OpenThreadToken(GetCurrentThread(), TOKEN_QUERY or TOKEN_DUPLICATE,
        True, hAccessToken) then begin
      if GetLastError() <> ERROR_NO_TOKEN then Exit();

      // If no thread token exists, retry against process token
      if not OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or
          TOKEN_DUPLICATE, hAccessToken) then Exit();
    end;
  end
  else // If the caller supplies a token handle,
    hAccessToken := hToken;

  // Determine whether the system is running Windows Vista or later (major
  // version >= 6) because they support linked tokens, previous versions don't.
  rOSVerInfo.dwOSVersionInfoSize := SizeOf(OSVERSIONINFO);
  if not Windows.GetVersionEx(rOSVerInfo) then goto lCleanUp3;
  if rOSVerInfo.dwMajorVersion >= 6 then begin
    // Retrieve information about the elevation level of the access token
    if not Windows.GetTokenInformation(hAccessToken, TokenElevationType, @eTET,
        SizeOf(TTokenElevationType), iReturnLen) then goto lCleanUp3;

    // If the access token is a limited token, retrieve the linked token
    // information from the access token.
    if eTET = TokenElevationTypeLimited then begin
      if not Windows.GetTokenInformation(hAccessToken, TokenLinkedToken,
          @hTokenToCheck, SizeOf(TTokenLinkedToken), iReturnLen) then
        goto lCleanUp3;
    end;
    // Report the elevation type if it is wanted
    if Assigned(peElevType) then
      peElevType^ := eTET;
  end
  else begin // if rOSVerInfo.dwMajorVersion < 6
    hTokenToCheck := 0;
    // There is no concept of elevation prior to Windows Vista
    if Assigned(peElevType) then
      peElevType^ := TokenElevationTypeDefault;
  end;

  // CheckTokenMembership() requires an impersonation token. If we just got a
  // linked token, it is already an impersonation token. Otherwise, duplicate
  // the original as an impersonation token for CheckTokenMembership().
  if (hTokenToCheck = 0) and (not DuplicateToken(hAccessToken,
      SecurityIdentification, @hTokenToCheck)) then goto lCleanUp3;

  // Allocate enough memory for the longest possible Sid
  iSidLen := JwaWinNT.SECURITY_MAX_SID_SIZE;
  pGroupSid := Pointer(LocalAlloc(LMEM_FIXED, iSidLen));
  if not Assigned(pGroupSid) then goto lCleanUp2;

  // Create a Sid for the predefined alias as specified in eWellKnownSidType
  if not JwaWinBase.CreateWellKnownSid(eWellKnownSidType, pDomainSid,
      pGroupSid, iSidLen) then goto lCleanUp1;

  // Now, check presence of the created Sid in the user and group Sids of the
  // access token. In other words, it determines whether the user is a member of
  // the well known group specified in eWellKnownSidType parameter.
  if not JwaWinBase.CheckTokenMembership(hTokenToCheck, pGroupSid,
      bMemberOfWellKnownGroup) then goto lCleanUp1;

  // Free the allocated memory for the Sid created by CreateWellKnownSid()
  if LocalFree(HLOCAL(pGroupSid)) <> 0 then goto lCleanUp2;
  // Close the new duplicate token handle, if exists
  if (hTokenToCheck <> 0) and (not CloseHandle(hTokenToCheck)) then
    goto lCleanUp3;
  // Close the access token handle
  if CloseHandle(hAccessToken) then
    Result := bMemberOfWellKnownGroup;

  Exit();

lCleanUp1:
  LocalFree(HLOCAL(pGroupSid));
lCleanUp2:
  if hTokenToCheck <> 0 then
    CloseHandle(hTokenToCheck);
lCleanUp3:
  CloseHandle(hAccessToken);
  Result := False;
end; // endfunction Inu_IsMemberOfWellKnownGroup
//==============================================================================
Vantomex
+1: For using JEDI API -1: for using goto: This is really not necessary here and it seems you converted a c source one to one? It makes the code really hard to read. (I'm a try/finally follower) +1: for adding a lot of comments. You check the return values of LocalFree and CloseHandle! This does not make sense imho. If you already have a result bMemberOfWellKnownGroup then you can just call the free and close handle functions and you can be happy. If they fail then there is a real problem which is disguised nontheless. Usually we had to check every Free and Close call with raiseLastOsError.
ChristianWimmer
I always use `try..finally/except` in my *pure/mixed* pascal code, but not when dealing with merely Winapi functions because `try..except` only understand its own exceptions. In regard to your `LocalFree()` some people might don't care about it. For `goto`, I never use it in my *pure/mixed* pascal code. I use it sometimes in Winapi code to reduce indentation and a lot of same code repetition, but ONLY in a simple (noncomplex) function.
Vantomex
I'm not talking about try/except because they are useless in pure API calls. Instead try/finally can be used to close handles or free memory even if you call exit. In this way you don't need "IF" clauses all over your code just for this. Checking CloseHandle and such is only usuable in debug mode or if you take extra effort into it. However, in your code you create an incorrect result even if the result was computed correctly shortly before.
ChristianWimmer
No, I didn't converted a C source one to one. You can compare my function with my original reference `http://msdn.microsoft.com/en-us/windows/ff420334.aspx`, didn't they much differ?
Vantomex
@Christian, in pure Winapi code (without any RTL/VCL code), we even don't need a `try/finally` just to ensure everything closed. About checking CloseHandle, I assume you didn't notice that MSDN has many such a code, because by doing that, end-users could also notice closing failure when it happens, so that they can debug it and/or report it to its programmer. BTW, could you mention explicitly where is the incorrect result you meant?
Vantomex
I don't blame you! I just say that it seems like you converted it from c. The goto exit strategies are really used often in MS c code in MSDN. But try/finally makes the code much more clear to read in Delphi imho.
ChristianWimmer
if not CloseHandle(hAccessToken) then Exit(); Result := bMemberOfWellKnownGroup;This CloseHandle will alter the result value of the function even if bMemberOf... is true. CloseHandle failed, you cannot do anything about it, but the function returns FALSE nonetheless.Try/finally is not for the WinAPI call but for EXIT() calls if they fail. In this way you can just exit and finally will be called though. No need for goto here.
ChristianWimmer
In addition, if any of the normal winapi calls fail, you just exit with false. In a way, you map all errors to FALSE which is also a correct return value. How can you distinguish between them? So the solution is to use exceptions. if not wincall then raiseLastOsError.There also comes try/finally quite handy.
ChristianWimmer
Your link (http://msdn.microsoft.com/en-us/windows/ff420334.aspx) even makes it correct:if (wincall) {lastErr = ERROR_SUCCESS;}else{lastErr = GetLastError();} and thenif (ERROR_SUCCESS != lastErr) { throw (lastErr);} return (fIsAdmin);The return values of all CloseHandle calls are irgnored.
ChristianWimmer
+1 for the telling the wrong place of Result (I missed that, and I'll correct it) :-). However, `Try/Finally` never knows whether an API function calls success or not. Without any `if` as well as `try..finally` in a pure Winapi code, remaining code will be always executed even if an API function fails. As you see, there was no even an RTL/VCL statement in the above code. So, `Try/Finally` is just a mess.
Vantomex
My low level functions were designed to work just like Winapi functions works. To get the error detail, user can call `GetLastError()`.
Vantomex
As I said before, checking the return value of `CloseHandle()` or `LocalFree()` has its use, MSDN does it in many places, e.g. see `http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx`; Again, it is just a matter of preference.
Vantomex
There a so many coding style out there, a code may sometimes emphasize on its eligibility, and sometimes emphasize on its speed (e.g. Fastcode project). Do you think MSDN people are not able to not use `goto` in their code? Readability sometimes a relative view, someone might say it is easier to read deep nested `IF` and the same code repetition instead of use `goto` to reduce them; and someone might say the latter is more readable.
Vantomex
"My low level functions were designed to work just like Winapi functions works. To get the error detail, user can call GetLastError()". No you did not! The user cannot call GetLastError on your function because the ERROR return value FALSE is part of a successfull return value. And since GetLastError is never resetted to 0 your code can return FALSE on success but GetLastError contains the error code from a failure 10 stack calls before your function. So how can a caller know whether the given member is a member of admins or the function failed? WinAPI uses out Parameter to return the result!
ChristianWimmer
"As I said before, checking the return value of CloseHandle() or LocalFree() has its use, MSDN does it in many places, e.g. see"... of course, you can also find as many examples where they don't. But why do you just check one CloseHandle but leave 2 others alone? Under a debugger, you don't need to do this at all because CloseHandle throws an exception as MSDN reads. Also AFAIK Application Verifier can check this handle, too.
ChristianWimmer
"However, Try/Finally never knows whether an API function calls success or not. Without any if as well as try..finally in a pure Winapi code, remaining code will be always executed even if an API function fails." Did you even read my comments at all? I've already said that. IMHO, goto should be replaced in Delphi with exit in combination with try/finally. Old plain C does not know exceptions and therefore many codes don't use this technique which is okay for them. In my career I could read lots of such c codes and I was always glad to read a Delphi code.
ChristianWimmer
Converting these codes to Delphi without adapting them leaves Delphi features unused which are there to make code better.Checkout JEDI Windows Security Code Library. There are many WinAPI functions called in this way using try/finally (and also exit). And it works great because errors are propagated using Delphi exceptions with lots of additional information which GetLastError cannot provide. They even can't fail silently as your function where a caller don't know if FALSE is a valid or error value.
ChristianWimmer
-1 for bad error handling and ignoring comments/suggestions to improve them.
Remko
@Remko, I'd already admitted the bug in my comment before, but I didn't have enough time to fix it, but now it is. Don't expect every people to fix as soon as possible, we are busy people. BTW even if I didn't fix it, the questioner already got what steps to achieve his/her purpose, and everybody can fix such a simple code.
Vantomex
@Christian, you toke a chance to criticize the code result while it was not fixed yet at the time. In regard to your comment about `try..finally`, I want to say, I didn't hate it, I like it, I use it in my programs as I said before, but that is a small function containing pure API calls, `try..finally` just make the function slower a bit. I know that the function is not a critical one to optimize, but I have other low-level functions in the same library which are critical. So, the coding style is just for style-consistency in that library.
Vantomex
@Remko, -1 for voting down action just because you find a flaw or you don't agree with how something is implemented.
Vantomex
@Vantomex: Don't take me wrong. I hate to criticize without showing ways for solutions or improvements. I try not to make everything bad. But be aware that by posting source code people will take it for granted that your code as an expert is also working for them (otherwise the code that checks for admin wouldn't be copied wrong over and over again ... see link in one of the answers here again). However, you seem to know the problems in your code already but didn't comment on them. This makes it more problematic. IMO, you did not help but just made it worse.
ChristianWimmer
try/finally makes code slower? Hmm, do you have proof of that? I can give a counter proof from Dr. Bob from http://www.drbob42.com/delphi/perform.htm"FinallyMeasurements of the try-finally block that also was introduced with Delphi exceptions don't indicate any significant performance loss either. This, combined with the fact that a try-finally block is a very helpful way to safeguard resources and prevent any leakages, makes the try-finally-block my preferred way of programming!"This is what I was talking about the whole time!
ChristianWimmer
"Delphi exceptions don't indicate any significant performance loss either." Yes, that's true, that's why I said "a bit slower". You might said it's just nano/micro/miliseconds difference, but it's still slower. I saw the compiler add some extra code when `try..finally` was applied. Nevertheless, I never said `try..finally` was bad. Please forgive me, it's just for style-consistency in that library.
Vantomex
@Christian, however, I must say thank for your explanation about *try-exit-finally* trick, I missed such a thought. :-)
Vantomex
A: 

There is the CreateWellKnownSid function.

But explicit check for admin account may be not a good idea. Just do the operation and ask for elevation, if you got 'access denied' error.

Alexander
+1  A: 

This is an excerpt from JwsclToken.pas from the JEDI API&WSCL. Both functions do the same check but in different ways. You see how little code is used? The same code in plain WinAPI would be at least 5 times bigger. Of course you can just call these functions from the unit itself. No need to copy here!

function JwCheckAdministratorAccess: boolean;
var
  SD: TJwSecurityDescriptor;
begin
  if not Assigned(JwAdministratorsSID) then
    JwInitWellKnownSIDs;

  SD := TJwSecurityDescriptor.Create;
  try
    SD.PrimaryGroup := JwNullSID;
    SD.Owner   := JwAdministratorsSID;
    SD.OwnDACL := True;

    SD.DACL.Add(TJwDiscretionaryAccessControlEntryAllow.Create(nil,
      [], STANDARD_RIGHTS_ALL, JwAdministratorsSID, False));

    Result := TJwSecureGeneralObject.AccessCheck(SD, nil,
      STANDARD_RIGHTS_ALL, TJwSecurityGenericMapping);
  finally
    FreeAndNil(SD);
  end;
end;

function JwIsMemberOfAdministratorsGroup: boolean;
var
  Token: TJwSecurityToken;
begin
  Token := TJwSecurityToken.CreateTokenEffective(TOKEN_READ or
    TOKEN_DUPLICATE);
  try
    Token.ConvertToImpersonatedToken(SecurityImpersonation, MAXIMUM_ALLOWED);
    Result := Token.CheckTokenMembership(JwAdministratorsSID)
  finally
    FreeAndNil(Token);
  end;
end;
ChristianWimmer
Shouldn't you do the `Assigned(JwAdministratorsSID)` check in the second function? Or is it initialised in one of the previous statements?
The_Fox
ChristianWimmer