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

#include "base/win/security_util.h"

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

#include <string>

#include "base/check.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_localalloc.h"
#include "base/win/win_util.h"

namespace base {
namespace win {

bool GrantAccessToPath(const FilePath& path,
                       const std::vector<Sid>& sids,
                       DWORD access_mask,
                       DWORD inheritance,
                       bool recursive) {
  DCHECK(!path.empty());
  if (sids.empty())
    return true;

  std::wstring object_name = path.value();
  PSECURITY_DESCRIPTOR sd = nullptr;
  PACL dacl = nullptr;

  // Get the existing DACL.
  DWORD error = ::GetNamedSecurityInfo(object_name.c_str(), SE_FILE_OBJECT,
                                       DACL_SECURITY_INFORMATION, nullptr,
                                       nullptr, &dacl, nullptr, &sd);
  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed getting DACL for path \"" << path.value() << "\"";
    return false;
  }
  auto sd_ptr = TakeLocalAlloc(sd);
  std::vector<EXPLICIT_ACCESS> access_entries(sids.size());
  auto entries_interator = access_entries.begin();
  for (const Sid& sid : sids) {
    EXPLICIT_ACCESS& new_access = *entries_interator++;
    new_access.grfAccessMode = GRANT_ACCESS;
    new_access.grfAccessPermissions = access_mask;
    new_access.grfInheritance = inheritance;
    ::BuildTrusteeWithSid(&new_access.Trustee, sid.GetPSID());
  }

  PACL new_dacl = nullptr;
  error = ::SetEntriesInAcl(access_entries.size(), access_entries.data(), dacl,
                            &new_dacl);
  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed adding ACEs to DACL for path \"" << path.value()
                 << "\"";
    return false;
  }
  auto new_dacl_ptr = TakeLocalAlloc(new_dacl);
  if (recursive) {
    error = ::SetNamedSecurityInfo(&object_name[0], SE_FILE_OBJECT,
                                   DACL_SECURITY_INFORMATION, nullptr, nullptr,
                                   new_dacl_ptr.get(), nullptr);
  } else {
    ScopedHandle handle(::CreateFile(path.value().c_str(), WRITE_DAC, 0,
                                     nullptr, OPEN_EXISTING,
                                     FILE_FLAG_BACKUP_SEMANTICS, nullptr));
    if (!handle.is_valid()) {
      DPLOG(ERROR) << "Failed opening path \"" << path.value()
                   << "\" to write DACL";
      return false;
    }
    error = ::SetSecurityInfo(handle.get(), SE_KERNEL_OBJECT,
                              DACL_SECURITY_INFORMATION, nullptr, nullptr,
                              new_dacl_ptr.get(), nullptr);
  }

  if (error != ERROR_SUCCESS) {
    ::SetLastError(error);
    DPLOG(ERROR) << "Failed setting DACL for path \"" << path.value() << "\"";
    return false;
  }

  return true;
}

std::vector<Sid> CloneSidVector(const std::vector<Sid>& sids) {
  std::vector<Sid> clone;
  clone.reserve(sids.size());
  for (const Sid& sid : sids) {
    clone.push_back(sid.Clone());
  }
  return clone;
}

void AppendSidVector(std::vector<Sid>& base_sids,
                     const std::vector<Sid>& append_sids) {
  for (const Sid& sid : append_sids) {
    base_sids.push_back(sid.Clone());
  }
}

}  // namespace win
}  // namespace base
