potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。

C++20&Win API ファイルのセキュリティユーザーの名前を列挙する

ファイルセキュリティ記述子→DACL→ACEと取得してセキュリティユーザーの名前を列挙するサンプルです。

#include <bit>
#include <string>
#include <span>
#include <memory>
#include <vector>

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

// ファイルのセキュリティ情報をバイト配列として返します。
std::vector<BYTE> GetFileSecurityW(std::wstring_view Path, SECURITY_INFORMATION RequestedInformation)
{
    DWORD needed{ 0 };
    if (!::GetFileSecurityW(Path.data(), RequestedInformation, nullptr, 0, &needed)
        && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        return {};
    }

    std::vector<BYTE> buffer(needed);
    if (!::GetFileSecurityW(Path.data(), RequestedInformation,
        static_cast<PSECURITY_DESCRIPTOR>(buffer.data()), needed, &needed))
    {
        return {};
    }
    return std::move(buffer);
}

// SIDの名前・参照ドメイン名・名前の使い方を返します。
// auto [name, refDomainName, use] = LookupAccountSidW(SystemName, Sid);
std::tuple<std::wstring, std::wstring, SID_NAME_USE> LookupAccountSidW(
    std::wstring_view SystemName,
    PSID Sid)
{
    DWORD cchName{ 0 };
    DWORD cchRefDomainName{ 0 };
    SID_NAME_USE use;
    if (!::LookupAccountSidW(nullptr, Sid, 0,
        &cchName, nullptr, &cchRefDomainName, &use)
        && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        return {};
    }

    std::wstring name(cchName, L'\0');
    std::wstring refDomainName(cchRefDomainName, L'\0');
    if (!::LookupAccountSidW(nullptr, Sid,
        name.data(), &cchName, refDomainName.data(), &cchRefDomainName, &use))
    {
        return {};
    }

    return std::make_tuple(std::move(name), std::move(refDomainName), use);
}

struct HLOCAL_deleter {
    void operator() (void* p) const noexcept {
        ::LocalFree(p);
    }
};
template <typename T>
using unique_hlocal_ptr = std::unique_ptr<T, HLOCAL_deleter>;

using unique_EXPLICIT_ACCESS_W = unique_hlocal_ptr<EXPLICIT_ACCESS_W>;

// ACLのACE記述構造体配列を取得します。
// auto [p, entryCount, explicitEntries] = GetExplicitEntriesFromAclWInUniquePtr(pDacl);
std::tuple<unique_EXPLICIT_ACCESS_W, ULONG, std::span<EXPLICIT_ACCESS_W>> GetExplicitEntriesFromAclWInUniquePtr(
    PACL pAcl)
{
    ULONG entryCount;
    PEXPLICIT_ACCESS_W pExplicitEntries;
    if (::GetExplicitEntriesFromAclW(pAcl, &entryCount, &pExplicitEntries) != ERROR_SUCCESS)
    {
        return {};
    }

    return std::make_tuple(
        unique_EXPLICIT_ACCESS_W(pExplicitEntries),
        entryCount,
        std::span{ pExplicitEntries, pExplicitEntries + entryCount });
}

// TRUSTEE_W構造体から名前を取得します。
std::wstring GetTrusteeName(const TRUSTEE_W& Trustee)
{
    // https://docs.microsoft.com/en-us/windows/win32/api/accctrl/ns-accctrl-trustee_a
    // TRUSTEE_IS_NAME/TRUSTEE_IS_OBJECTS_AND_NAME/TRUSTEE_IS_OBJECTS_AND_SID/TRUSTEE_IS_SID
    switch (Trustee.TrusteeForm)
    {
    case TRUSTEE_IS_NAME:
        return Trustee.ptstrName;
    case TRUSTEE_IS_OBJECTS_AND_NAME:
        return std::bit_cast<OBJECTS_AND_NAME_W*>(Trustee.ptstrName)->ptstrName;
    case TRUSTEE_IS_OBJECTS_AND_SID:
        {
            auto [name, refDomainName, use] = LookupAccountSidW(
                {}, std::bit_cast<OBJECTS_AND_SID*>(Trustee.ptstrName)->pSid);
            return std::move(name);
        }
        break;
    case TRUSTEE_IS_SID:
        {
            auto [name, refDomainName, use] = LookupAccountSidW(
                {}, std::bit_cast<PSID>(Trustee.ptstrName));
            return std::move(name);
        }
    default:
        return {};
    }
}

#include <iostream>

int main()
{
    std::wstring_view path{ L"C:\\Windows" };

    // ファイルのDACLセキュリティ情報を取得します。
    auto secDescBuffer = GetFileSecurityW(path, DACL_SECURITY_INFORMATION);
    auto pSecDesc = static_cast<PSECURITY_DESCRIPTOR>(secDescBuffer.data());

    // ファイルのDACLの存在・ポインタ・既定かどうかを取得します。
    BOOL daclPresent;
    PACL pDacl;
    BOOL daclDefaulted;
    if (!::GetSecurityDescriptorDacl(pSecDesc, &daclPresent, &pDacl, &daclDefaulted))
    {
        return 0;
    }

    // DACLのエントリを取得します。
    auto [p, entryCount, explicitEntries] = GetExplicitEntriesFromAclWInUniquePtr(pDacl);
    for (auto& explicitEntry : explicitEntries)
    {
        std::wcout << GetTrusteeName(explicitEntry.Trustee) << std::endl;
    }

    return 0;
}