The ACLInfo Sample The sample program that accompanies this article is a console application that accepts a file or directory path as its command line argument and then retrieves and displays the ACL information for that file. This will only work correctly if the file or folder resides on an NTFS-formatted partition and you have Read access permissions to ACL information.
Listing 1 shows the interface definition of the ACLInfo class as it appears in the ACLInfo.h header file. You should construct an ACLInfo object supplying the path to the file or folder. Then call the Query method that loads the corresponding ACEs and places them into the linked list (m_sAceList) of the ace_list* type (the definition of ace_list appears in Listing 1 as well). Finally, call the Output method to display the ACL. The Main.cpp file (Listing 2) is the sample usage of the ACLInfo class.
The Query Method The Query method (Listing 3) calls the GetFileSecurityW function twice. In the first call it obtains the size required to store the security descriptor. Then you allocate the required memory and call the function again, this time supplying the pointer to the BYTE* buffer that will hold the security descriptor. Note that I pass DACL_SECURITY_INFORMATION as the second argument. This way, I inform the API that I want Discretionary Access Control List information. The second argument may be any valid combination of different flags that specify the type of security information you are querying. Read the MSDN documentation for GetFileSecurity to learn more about its arguments.
The next fragment retrieves DACL from the security descriptor:
// Getting DACL from Security Descriptor PACL pacl; BOOL bDaclPresent, bDaclDefaulted; bSuccess =GetSecurityDescriptorDacl( (SECURITY_DESCRIPTOR*)pSecDescriptorBuf, &bDaclPresent, &pacl, &bDaclDefaulted); // Check if we successfully retrieved DACL if (!bSuccess) { DWORD dwError = GetLastError(); cout
This code checks that DACL is actually present in the security descriptor. If there is no DACL present then GetSecurityDescriptorDacl sets bDaclPresent to False.
// Check if DACL present in security descriptor if (!bDaclPresent) { cout
After verifying that DACL was successfully retrieved you can iterate through its ACEs and fill in the linked list from ACLInfo. The following loop does just this:
for (USHORT i = 0; i AceCount; i++) { LPVOID pAce; bSuccess = GetAce(pacl, i, &pAce); if (!bSuccess) { DWORD dwError = GetLastError(); cout
The Output Method Most of the job of interpreting the queried ACEs is done in the Output method. Each node of m_sAceList has bAllowed field, which defines whether the stored ACE allows or denies permissions. Recall that the information about ACE type resides in its header represented by the ACE_HEADER structure. Knowing ACE type is not enough, because we have to know what exact permissions it allows or denies. At this stage, the AccessMask field comes to our aid. This field is present in both the ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE structures. It is just a combination (bitwise OR) of permission flags. Any advanced permission (those that appear in Figure 3) has its own bit. Examine the AccessMask field to see what permissions are allowed or denied by an ACE.
Visual C++ header files have predefined macros for advanced permission flags, such as FILE_READ_DATA, FILE_EXECUTE, etc. There are also macros that represent frequently used combinations of permission flags (FILE_GENERIC_READ, FILE_GENERIC_WRITE).
In the ACLInfo sample, I defined my own combinations for read, write, and execute permissions:
#define READ_PERMISSIONS (FILE_READ_DATA | FILE_READ_ATTRIBUTES) #define WRITE_PERMISSIONS (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA) #define EXECUTE_PERMISSIONS (FILE_READ_DATA | FILE_EXECUTE)
There are two different checks for allowed and denied ACE. When I examine an allowed ACE, this code executes:
// For Allowed aces if (pList->bAllowed) { // Read Permissions if ((maskPermissions & READ_PERMISSIONS) == READ_PERMISSIONS) { os
The above check assures that an allowed ACE has all the READ_PERMISSIONS flags turned on.
In the case of denied ACE, the check is opposite?if at least one flag of READ_PERMISSIONS is turned on, I decide that ACE denies reading:
. . . else // Denied Ace permissions { // Read Permissions if ((maskPermissions & READ_PERMISSIONS) != 0) { os
I perform similar checks for write and execute permissions.
Extracting Names It might sometimes be useful to extract the user or group name from an ACE. The following code fragment retrieves an SID that is stored in ACE (the bold lines):
pAce = pList->pAce; if (pList->bAllowed) { ACCESS_ALLOWED_ACE* pAllowed = (ACCESS_ALLOWED_ACE*)pAce; pAceSid = (SID*)(&(pAllowed->SidStart)); maskPermissions = pAllowed->Mask; } else { ACCESS_DENIED_ACE* pDenied = (ACCESS_DENIED_ACE*)pAce; pAceSid = (SID*)(&(pDenied->SidStart)); maskPermissions = pDenied->Mask; }
Having SID in hand, we may successfully obtain account information using the LookuAccounSid API call as shown below:
DWORD dwCbName = 0; DWORD dwCbDomainName = 0; SID_NAME_USE SidNameUse; TCHAR bufName[MAX_PATH]; TCHAR bufDomain[MAX_PATH]; dwCbName = sizeof(bufName); dwCbDomainName = sizeof(bufDomain); // Get account name for SID BOOL bSuccess = LookupAccountSid(NULL, pAceSid, bufName, &dwCbName, bufDomain, &dwCbDomainName, &SidNameUse); if (!bSuccess) { cout
LookupAccountSid stores user/group name into bufName and domain name into bufDomain. SidNameUse is populated with a type of security entity that SID represents (user, group, well-known group etc.)
In your programs, you may choose a different interpretation of the information stored in ACLs. Just remember that the account under which these programs run must have sufficient permissions to read permissions.
The Sample Run I ran the ACLInfo.exe program specifying the path to the file that resides on my NTFS partition (disk G) and obtained the following output:
D:ArticlesDevXNTFSACLInfoVS_7Debug>ACLInfo.exe "g:my documentsspecs.txt" Allowed to: BUILTINUsers [R X] Allowed to: LOTUSYevgeny Menaker [RWX] Allowed to: NT AUTHORITYSYSTEM [RWX] Allowed to: BUILTINAdministrators [RWX] Denied from: LOTUSYevgeny Menaker [ X] Denied from: LOTUSRob [R X]
Note that paths containing spaces should be enclosed by double quotes in the command line.
You should now have a working knowledge of how to use Win32 APIs to query security information from NTFS objects (files and folders). You may expand the supplied code to fit your needs and throw the heavy task of managing user permissions onto NTFS. Many products, such as Microsoft IIS already employ this technique. Now you can too.
|