views:

427

answers:

1

I am trying to write a simple function for windows that answers the following question.

Does user (U) have rights (R) on file (F)?
Where,
   R is some combination of (GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE)
   U does not have to be logged in or impersonated

The code that I wrote is shown below. The application calls the first UserHasPermission that is shown.

The access rights returned by GetEffectiveRightsFromAcl are the same for all user/file combinations that I tested ($001200A9). I double checked and $001200A9 is not just a pointer to the location where the access rights are actually stored.

My question is twofold:
1. Is there a better way of doing this?
2. Can anyone tell me where I am going wrong?

function UserHasPermission(APermission: Longword; out HasPermission: Boolean; AFileName: WideString; AUserName: String; ADomainName: String): Boolean;
    var
      SID: PSID;
      ACL: PACL;
    begin
      SID := nil;
      ACL := nil;
      try
        Result := GetUserSID(SID, AUserNAme, ADomainName);
        Result := Result and GetFileDACL(AFileName, ACL);
        Result := Result and UserHasPermission(APermission, HasPermission, ACL, SID);
      finally
        Dispose(SID);
      end;
    end;

    function UserHasPermission(APermission: Longword; out HasPermission: Boolean; AACL: PACL; AUserSID: PSID): Boolean;
    var
      T: TRUSTEE;
      Rights: ACCESS_MASK;
    begin
      BuildTrusteeWithSid(@T, AUserSID);
      Result := GetEffectiveRightsFromAcl(AACL, @T, @Rights) = ERROR_SUCCESS;
      HasPermission := (Rights and APermission) = APermission;
    end;

    function GetUserSID(out ASID: PSID; AUserName: WideString; const ADomainName: WideString): Boolean;
    var
      NSID, NDomain: Longword;
      Use: SID_NAME_USE;
      DomainName: WideString;
    begin
      Result := False;
      if Length(AUserName) > 0 then
        begin
          if Length(ADomainName) > 0 then
            AUserName := ADomainName + '\' + AUserName;

          // determine memory requirements
          NSID := 0;
          NDomain := 0;
          LookupAccountNameW(nil, PWideChar(AUserName), nil, NSID, nil, NDomain, Use);

          // allocate memory
          GetMem(ASID, NSID);
          SetLength(DomainName, NDomain);

          Result := LookupAccountNameW(nil, PWideChar(AUserName), ASID, NSID, PWideChar(DomainName), NDomain, Use);
        end;
    end;

    function GetFileDACL(AFileName: WideString; out AACL: PACL): Boolean;
    var
      SD: PSecurityDescriptor;
      NSD, NNeeded: Longword;
      Present, Defualted: Longbool;
    begin
      GetFileSecurityW(PWideChar(AFileName), DACL_SECURITY_INFORMATION, nil, 0, NNeeded);
      GetMem(SD, NNeeded);
      try
        NSD := NNeeded;
        Result := GetFileSecurityW(PWideChar(AFileName), DACL_SECURITY_INFORMATION, SD, NSD, NNeeded);
        Result := Result and GetSecurityDescriptorDacl(SD, Present, AACL, Defualted);
        Result := Result and Present;
      finally
        Dispose(SD);
      end;
    end;
+2  A: 

GetEffectiveRightsFromAcl are the same for all user/file combinations that I tested ($001200A9).

That all depends on the ACL, e.g. if Everyone is granted full control then any use will have full control.

The code looks reasonable, and you are using one of the Win32 security APIs (GetEffectiveRightsFromAcl) to do the heavy lifting.

Suggestion: Create very specific ACLs to test your code (SDDL makes this easier), starting with one that makes no grants, then one that only includes a different user.

Richard
Your right the code was pretty much correct. I was tired and frustrated, i.e. careless, while testing. Thanks for the code review, everything is working and now I understand what is going on.
Lawrence Barsanti
Be aware that GetEffectiveRightsFromAcl does not take into account complex, nested group structures and privileges. That means the function may grant or deny access although it should not. The AccessCheck API Function is the safest way to check for it. Unfortunately, the app needs to impersonate the user while calling AccessCheck.In the end both function may create incorrect results when used on remote objects because additional access checks are made by the remote system.
ChristianWimmer