// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/win/access_control_list.h"

#include <aclapi.h>
#include <windows.h>

#include <utility>
#include <vector>

#include "base/check.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/win/scoped_localalloc.h"

namespace base::win {

namespace {

std::unique_ptr<uint8_t[]> AclToBuffer(const ACL* acl) {
  if (!acl) {
    return nullptr;
  }
  size_t size = acl->AclSize;
  DCHECK(size >= sizeof(*acl));
  std::unique_ptr<uint8_t[]> ptr = std::make_unique<uint8_t[]>(size);
  memcpy(ptr.get(), acl, size);
  return ptr;
}

std::unique_ptr<uint8_t[]> EmptyAclToBuffer() {
  ACL acl = {};
  acl.AclRevision = ACL_REVISION;
  acl.AclSize = static_cast<WORD>(sizeof(acl));
  return AclToBuffer(&acl);
}

ACCESS_MODE ConvertAccessMode(SecurityAccessMode access_mode) {
  switch (access_mode) {
    case SecurityAccessMode::kGrant:
      return GRANT_ACCESS;
    case SecurityAccessMode::kSet:
      return SET_ACCESS;
    case SecurityAccessMode::kDeny:
      return DENY_ACCESS;
    case SecurityAccessMode::kRevoke:
      return REVOKE_ACCESS;
  }
}

std::unique_ptr<uint8_t[]> AddACEToAcl(
    ACL* old_acl,
    const std::vector<ExplicitAccessEntry>& entries) {
  std::vector<EXPLICIT_ACCESS> access_entries(entries.size());
  auto entries_interator = access_entries.begin();
  for (const ExplicitAccessEntry& entry : entries) {
    EXPLICIT_ACCESS& new_access = *entries_interator++;
    new_access.grfAccessMode = ConvertAccessMode(entry.mode());
    new_access.grfAccessPermissions = entry.access_mask();
    new_access.grfInheritance = entry.inheritance();
    ::BuildTrusteeWithSid(&new_access.Trustee, entry.sid().GetPSID());
  }

  PACL new_acl = nullptr;
  DWORD error = ::SetEntriesInAcl(checked_cast<ULONG>(access_entries.size()),
                                  access_entries.data(), old_acl, &new_acl);
  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed adding ACEs to ACL";
    return nullptr;
  }
  auto new_acl_ptr = TakeLocalAlloc(new_acl);
  return AclToBuffer(new_acl_ptr.get());
}

}  // namespace

ExplicitAccessEntry ExplicitAccessEntry::Clone() const {
  return ExplicitAccessEntry{sid_, mode_, access_mask_, inheritance_};
}

ExplicitAccessEntry::ExplicitAccessEntry(const Sid& sid,
                                         SecurityAccessMode mode,
                                         DWORD access_mask,
                                         DWORD inheritance)
    : sid_(sid.Clone()),
      mode_(mode),
      access_mask_(access_mask),
      inheritance_(inheritance) {}

ExplicitAccessEntry::ExplicitAccessEntry(WellKnownSid known_sid,
                                         SecurityAccessMode mode,
                                         DWORD access_mask,
                                         DWORD inheritance)
    : ExplicitAccessEntry(Sid(known_sid), mode, access_mask, inheritance) {}

ExplicitAccessEntry::ExplicitAccessEntry(ExplicitAccessEntry&&) = default;
ExplicitAccessEntry& ExplicitAccessEntry::operator=(ExplicitAccessEntry&&) =
    default;
ExplicitAccessEntry::~ExplicitAccessEntry() = default;

absl::optional<AccessControlList> AccessControlList::FromPACL(ACL* acl) {
  if (acl && !::IsValidAcl(acl)) {
    ::SetLastError(ERROR_INVALID_ACL);
    return absl::nullopt;
  }
  return AccessControlList{acl};
}

absl::optional<AccessControlList> AccessControlList::FromMandatoryLabel(
    DWORD integrity_level,
    DWORD inheritance,
    DWORD mandatory_policy) {
  Sid sid = Sid::FromIntegrityLevel(integrity_level);
  // Get total ACL length. SYSTEM_MANDATORY_LABEL_ACE contains the first DWORD
  // of the SID so remove it from total.
  DWORD length = sizeof(ACL) + sizeof(SYSTEM_MANDATORY_LABEL_ACE) +
                 ::GetLengthSid(sid.GetPSID()) - sizeof(DWORD);
  std::unique_ptr<uint8_t[]> sacl_ptr = std::make_unique<uint8_t[]>(length);
  PACL sacl = reinterpret_cast<PACL>(sacl_ptr.get());

  if (!::InitializeAcl(sacl, length, ACL_REVISION)) {
    return absl::nullopt;
  }

  if (!::AddMandatoryAce(sacl, ACL_REVISION, inheritance, mandatory_policy,
                         sid.GetPSID())) {
    return absl::nullopt;
  }

  DCHECK(::IsValidAcl(sacl));
  AccessControlList ret;
  ret.acl_ = std::move(sacl_ptr);
  return ret;
}

AccessControlList::AccessControlList() : acl_(EmptyAclToBuffer()) {}
AccessControlList::AccessControlList(AccessControlList&&) = default;
AccessControlList& AccessControlList::operator=(AccessControlList&&) = default;
AccessControlList::~AccessControlList() = default;

bool AccessControlList::SetEntries(
    const std::vector<ExplicitAccessEntry>& entries) {
  if (entries.empty())
    return true;

  std::unique_ptr<uint8_t[]> acl = AddACEToAcl(get(), entries);
  if (!acl)
    return false;

  acl_ = std::move(acl);
  return true;
}

bool AccessControlList::SetEntry(const Sid& sid,
                                 SecurityAccessMode mode,
                                 DWORD access_mask,
                                 DWORD inheritance) {
  std::vector<ExplicitAccessEntry> ace_list;
  ace_list.emplace_back(sid, mode, access_mask, inheritance);
  return SetEntries(ace_list);
}

AccessControlList AccessControlList::Clone() const {
  return AccessControlList{get()};
}

void AccessControlList::Clear() {
  acl_ = EmptyAclToBuffer();
}

AccessControlList::AccessControlList(const ACL* acl) : acl_(AclToBuffer(acl)) {}

}  // namespace base::win
