评论中的一些进一步信息:
应用程序确定正在检查其访问权限的用户的 SID。
调用GetNamedSecurityInfo
每个目录后,应用程序GetEffectiveRightsFromAcl
使用用户的 SID 进行调用。花费大部分时间的是后一个调用。
GetEffectiveRightsFromAcl
根据用户的 SID 和用户所属的任何组的 SID 检查 ACL。它可能很慢,因为确定用户组需要往返域控制器。
有两种可能的修复方法和死胡同:
模拟 GetEffectiveRightsFromAcl
在循环之外,确定用户的 SID 和用户组的 SID。(TODO:检查嵌套组是否自动处理,或者是否必须递归解析。)
要确定 ACL 的有效权限:
- 使用 ACCESS_MASK(实际上是 DWORD)来表示权限掩码。将其初始化为零。
- 以相反的顺序处理 ACL 中的 ACE。这可确保较早的 ACE 优先。
- 如果 ACE 引用任何先前确定的 SID,则对于允许访问的 ACE 或带有您的权限掩码的 ACE 掩码,对于拒绝访问的 ACE,从您的权限掩码中屏蔽掉 ACE 的掩码。
处理完所有 ACE 后,您的权限掩码就拥有了答案。
跳过继承的 ACL
在许多目录层次结构中,大多数或所有文件和目录都将从其父级继承其权限。但是,这无济于事。继承的 ACL 可能在父级上未激活,因此子级的有效权限与父级的有效权限不匹配。因此,即使继承了 ACL,仍然必须检查它。
缓存 GetEffectiveRightsFromAcl 的结果
只需创建从 ACL 到有效权限掩码的映射。为此,您需要一种比较 ACL 的方法。您不能只使用 memcmp 比较整个 ACL,因为 ACL.AclSize 包括额外填充的大小。相反,比较 ACE 的数量,如果它们相同,则使用 memcmp 比较各个 ACE。
我在我的Program Files
目录上试过这个。扫描整个目录结构需要 6 次调用GetEffectiveRightsFromAcl
. 剩下的 2,708 个目录是从缓存中解析出来的,所以速度要快得多。
以下实现了GetEffectiveRightsFromAcl
. 请注意,缺少错误处理,并且它永远不会释放它放入映射中的 PACL。
// Compare two access-control lists.
// Return <0 if acl1<acl2, 0 if acl1==acl2 and >0 if acl1>acl2.
// The ordering is arbitrary but consistent.
int aclcmp(PACL acl1, PACL acl2)
{
// First compare by number of ACEs
int c = acl1->AceCount - acl2->AceCount;
if (c)
return c;
// We have the same number of ACEs, so compare each ACE
int aceCount = acl1->AceCount;
for (int aceIndex = 0; aceIndex != aceCount; ++aceIndex)
{
// Get the ACEs
PACE_HEADER ace1;
PACE_HEADER ace2;
GetAce(acl1, aceIndex, (LPVOID*)&ace1);
GetAce(acl2, aceIndex, (LPVOID*)&ace2);
// Compare the ACE sizes
c = ace1->AceSize - ace2->AceSize;
if (c)
return c;
// Compare the ACE content
c = memcmp(ace1, ace2, ace1->AceSize);
if (c)
return c;
}
return 0;
}
// Less-than operator for pointers to ACLs
class ComparePAcl
{
public:
bool operator()(const PACL& acl1, const PACL& acl2) const
{
return aclcmp(acl1, acl2) < 0;
}
};
// Map from pointers-to-ACLs to access masks
typedef std::map<PACL, ACCESS_MASK, ComparePAcl> AclToAccessMask;
AclToAccessMask aclToAccessMask;
// Just to check how the cache performs
DWORD foundCount = 0;
DWORD notFoundCount = 0;
// Same as GetEffectiveRightsFromAcl but caches results.
// Note that this must be called with the same trustee to get meaningful results.
DWORD CachedGetEffectiveRightsFromAcl(PACL pacl, PTRUSTEE pTrustee, PACCESS_MASK pAccessRights)
{
AclToAccessMask::const_iterator it = aclToAccessMask.find(pacl);
if (it != aclToAccessMask.end())
{
// The ACL is in the cache
++foundCount;
*pAccessRights = it->second;
}
else
{
// The ACL is not in the cache
++notFoundCount;
DWORD rc = GetEffectiveRightsFromAcl(pacl, pTrustee, pAccessRights);
if (rc != ERROR_SUCCESS)
return rc;
// TODO: Clean up copies of ACLs afterwards
PACL aclcopy = (PACL)malloc(pacl->AclSize);
memcpy(aclcopy, pacl, pacl->AclSize);
aclToAccessMask.insert(AclToAccessMask::value_type(aclcopy, *pAccessRights));
}
return ERROR_SUCCESS;
}