potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++20&Win API ファイルセキュリティのグループ名またはユーザー名をACE毎に取得する

ファイルのプロパティ、セキュリティタブのグループ名またはユーザー名に表示される名前をACE毎に取得するコードです。セキュリティ記述子とDACLの学習用でもあります。

コード

#include <array>
#include <string>
#include <vector>

#define STRICT
#define NOMINMAX
#include <Windows.h>

std::wstring GetWindowsDirectoryW()
{
    auto len = ::GetWindowsDirectoryW(nullptr, 0);
    if (len == 0)
        return {};
    std::wstring s(len - 1, L'\0');
    if (::GetWindowsDirectoryW(s.data(), len) == 0)
        return {};
    return s;
}

std::vector<BYTE> GetFileSecurityW(
    std::wstring_view filename,
    SECURITY_INFORMATION requestedInformation)
{
    DWORD needSize;
    if (!::GetFileSecurityW(
        filename.data(),
        requestedInformation,
        nullptr,
        0,
        &needSize))
    {
        if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            return {};
    }

    std::vector<BYTE> v(needSize);
    if (!::GetFileSecurityW(
        filename.data(),
        requestedInformation,
        v.data(),
        needSize,
        &needSize))
    {
        return {};
    }
    return v;
}

struct LookupAccountSidWResult {
    std::wstring name;
    std::wstring referenceDomainName;
    SID_NAME_USE use;
};

LookupAccountSidWResult LookupAccountSidW(
    PSID psid,
    std::wstring_view systemName = {})
{
    DWORD nameLen{};
    DWORD refDomainNameLen{};
    SID_NAME_USE use;
    if (!::LookupAccountSidW(systemName.data(), psid,
        nullptr, &nameLen, nullptr, &refDomainNameLen, &use))
    {
        if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            return {};
    }
    std::wstring name(nameLen - 1, L'\0');
    std::wstring refDomainName(refDomainNameLen - 1, L'\0');
    if (!::LookupAccountSidW(systemName.data(), psid,
        name.data(), &nameLen, refDomainName.data(), &refDomainNameLen, &use))
    {
        return {};
    }
    return LookupAccountSidWResult(std::move(name), std::move(refDomainName), use);
}

constexpr std::wstring_view SID_NAME_USEToStringW(SID_NAME_USE use) noexcept
{
    auto names = std::array{
        L"SidTypeUser",
        L"SidTypeGroup",
        L"SidTypeDomain",
        L"SidTypeAlias",
        L"SidTypeWellKnownGroup",
        L"SidTypeDeletedAccount",
        L"SidTypeInvalid",
        L"SidTypeUnknown",
        L"SidTypeComputer",
        L"SidTypeLabel",
        L"SidTypeLogonSession"};
    auto i = static_cast<int>(use - 1);
    if (0 <= i && i < names.size())
        return names[i];
    else
        return {};
}

constexpr std::wstring_view AceTypeToStringW(BYTE aceType) noexcept
{
    auto names = std::array{
        L"ACCESS_ALLOWED_ACE_TYPE",
        L"ACCESS_DENIED_ACE_TYPE",
        L"SYSTEM_AUDIT_ACE_TYPE",
        L"SYSTEM_ALARM_ACE_TYPE",
        L"ACCESS_ALLOWED_COMPOUND_ACE_TYPE",
        L"ACCESS_ALLOWED_OBJECT_ACE_TYPE",
        L"ACCESS_DENIED_OBJECT_ACE_TYPE",
        L"SYSTEM_AUDIT_OBJECT_ACE_TYPE",
        L"SYSTEM_ALARM_OBJECT_ACE_TYPE",
        L"ACCESS_ALLOWED_CALLBACK_ACE_TYPE",
        L"ACCESS_DENIED_CALLBACK_ACE_TYPE",
        L"ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE",
        L"ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE",
        L"SYSTEM_AUDIT_CALLBACK_ACE_TYPE",
        L"SYSTEM_ALARM_CALLBACK_ACE_TYPE",
        L"SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE",
        L"SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE",
        L"SYSTEM_MANDATORY_LABEL_ACE_TYPE",
        L"SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE",
        L"SYSTEM_SCOPED_POLICY_ID_ACE_TYPE",
        L"SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE",
        L"SYSTEM_ACCESS_FILTER_ACE_TYPE"
    };
    if (0 <= aceType && aceType < names.size())
        return names[aceType];
    else
        return {};
}

PSID GetAceSid(PACE_HEADER pheader)
{
    auto pv{static_cast<PVOID>(pheader)};
    switch (pheader->AceType)
    {
    case ACCESS_ALLOWED_ACE_TYPE:
        return &static_cast<PACCESS_ALLOWED_ACE>(pv)->SidStart;
    case ACCESS_DENIED_ACE_TYPE:
        return &static_cast<PACCESS_DENIED_ACE>(pv)->SidStart;
    case SYSTEM_AUDIT_ACE_TYPE:
        return &static_cast<PSYSTEM_AUDIT_ACE>(pv)->SidStart;
    case SYSTEM_ALARM_ACE_TYPE:
        return &static_cast<PSYSTEM_ALARM_ACE>(pv)->SidStart;
    case ACCESS_ALLOWED_COMPOUND_ACE_TYPE:
        return nullptr;
    case ACCESS_ALLOWED_OBJECT_ACE_TYPE:
        return &static_cast<PACCESS_ALLOWED_OBJECT_ACE>(pv)->SidStart;
    case ACCESS_DENIED_OBJECT_ACE_TYPE:
        return &static_cast<PACCESS_DENIED_OBJECT_ACE>(pv)->SidStart;
    case SYSTEM_AUDIT_OBJECT_ACE_TYPE:
        return &static_cast<PSYSTEM_AUDIT_OBJECT_ACE>(pv)->SidStart;
    case SYSTEM_ALARM_OBJECT_ACE_TYPE:
        return &static_cast<PSYSTEM_ALARM_OBJECT_ACE>(pv)->SidStart;
    case ACCESS_ALLOWED_CALLBACK_ACE_TYPE:
        return &static_cast<PACCESS_ALLOWED_CALLBACK_ACE>(pv)->SidStart;
    case ACCESS_DENIED_CALLBACK_ACE_TYPE:
        return &static_cast<PACCESS_DENIED_CALLBACK_ACE>(pv)->SidStart;
    case ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE:
        return &static_cast<PACCESS_ALLOWED_CALLBACK_OBJECT_ACE>(pv)->SidStart;
    case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
        return &static_cast<PACCESS_DENIED_CALLBACK_OBJECT_ACE>(pv)->SidStart;
    case SYSTEM_AUDIT_CALLBACK_ACE_TYPE:
        return &static_cast<PSYSTEM_AUDIT_CALLBACK_ACE>(pv)->SidStart;
    case SYSTEM_ALARM_CALLBACK_ACE_TYPE:
        return &static_cast<PSYSTEM_ALARM_CALLBACK_ACE>(pv)->SidStart;
    case SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE:
        return &static_cast<PSYSTEM_AUDIT_CALLBACK_OBJECT_ACE>(pv)->SidStart;
    case SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE:
        return &static_cast<PSYSTEM_ALARM_CALLBACK_OBJECT_ACE>(pv)->SidStart;
    case SYSTEM_MANDATORY_LABEL_ACE_TYPE:
        return &static_cast<PSYSTEM_MANDATORY_LABEL_ACE>(pv)->SidStart;
    case SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE:
        return &static_cast<PSYSTEM_RESOURCE_ATTRIBUTE_ACE>(pv)->SidStart;
    case SYSTEM_SCOPED_POLICY_ID_ACE_TYPE:
        return &static_cast<PSYSTEM_SCOPED_POLICY_ID_ACE>(pv)->SidStart;
    case SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE:
        return &static_cast<PSYSTEM_PROCESS_TRUST_LABEL_ACE>(pv)->SidStart;
    case SYSTEM_ACCESS_FILTER_ACE_TYPE:
        return &static_cast<PSYSTEM_ACCESS_FILTER_ACE>(pv)->SidStart;
    default:
        return nullptr;
    }
}

#include <format>
#include <iostream>

int main()
{
    std::wcout.imbue(std::locale("", std::locale::ctype));

    auto windir{GetWindowsDirectoryW()};
    auto secutiryDescriptor{::GetFileSecurityW(windir,
        OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION)};

    PSID ownerSid;
    BOOL ownerDefaulted;
    if (!GetSecurityDescriptorOwner(
        secutiryDescriptor.data(),
        &ownerSid,
        &ownerDefaulted))
    {
        return -1;
    }

    auto ownerSidNames{LookupAccountSidW(ownerSid)};
    std::wcout << std::format(L"オーナーSID: {} {} ({})\n",
        ownerSidNames.name,
        ownerSidNames.referenceDomainName,
        SID_NAME_USEToStringW(ownerSidNames.use));

    PACL pdacl;
    BOOL daclPresent;
    BOOL daclDefaulted;
    if (!::GetSecurityDescriptorDacl(secutiryDescriptor.data(), &daclPresent, &pdacl, &daclDefaulted))
    {
        return -1;
    }

    for (DWORD aceIndex = 0; aceIndex < pdacl->AceCount; aceIndex++)
    {
        LPVOID pace;
        if (!::GetAce(pdacl, aceIndex, &pace))
        {
            return -1;
        }

        auto paceHeader{static_cast<PACE_HEADER>(pace)};
        auto psidAce{GetAceSid(paceHeader)};
        auto aceSidNames{LookupAccountSidW(psidAce)};
        std::wcout << std::format(L"ACE{} ({})\n  SID: {} {} ({})\n",
            aceIndex,
            AceTypeToStringW(paceHeader->AceType),
            aceSidNames.name,
            aceSidNames.referenceDomainName,
            SID_NAME_USEToStringW(aceSidNames.use));
    }

    return 0;
}

メモ

  • グループ名またはユーザー名を取得するにはセキュリティ記述子→ACL→ACE→SIDの順で情報を取得する。
  • ACEは型によって構造体の形式が異なる(いくつかの構造体は名前だけ違う)。SIDの取得には場合分けが必要。
  • エクスプローラーのプロパティは名前をまとめて表示してくれるが、実用的にはACEのSIDからそれぞれ取得する必要がある。