// This file is part of BOINC.
// https://boinc.berkeley.edu
// Copyright (C) 2026 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

#include "user_group_helper.h"

#include <LMaccess.h>
#include <lmerr.h>
#include <LMAPIbuf.h>
#include <sddl.h>
#include <stdexcept>

bool userExists(const std::string& username) {
    auto un = boinc_ascii_to_wide(username);
    LPUSER_INFO_0 pBuf = nullptr;
    const auto result = NetUserGetInfo(nullptr,un.c_str(),0,
        reinterpret_cast<LPBYTE*>(&pBuf));
    if (pBuf != nullptr) {
        NetApiBufferFree(pBuf);
    }

    return result == NERR_Success;
}

bool userCreate(const std::string& username, const std::string& password) {
    auto un = boinc_ascii_to_wide(username);
    auto up = boinc_ascii_to_wide(password);
    USER_INFO_1 ui;
    ui.usri1_name = un.data();
    ui.usri1_password = up.data();
    ui.usri1_comment = nullptr;
    ui.usri1_priv = USER_PRIV_USER;
    ui.usri1_home_dir = nullptr;
    ui.usri1_comment = nullptr;
    ui.usri1_flags = UF_SCRIPT;
    ui.usri1_script_path = nullptr;

    DWORD dwParameterError;
    return NetUserAdd(nullptr, 1,
        reinterpret_cast<LPBYTE>(&ui), &dwParameterError) == NERR_Success;
}

bool userDelete(const std::string& username) {
    auto un = boinc_ascii_to_wide(username);
    return NetUserDel(nullptr, un.c_str()) == NERR_Success;
}

wil::unique_sid getCurrentUserSid() {
    wil::unique_handle token = nullptr;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
        return nullptr;
    }
    DWORD len = 0;
    GetTokenInformation(token.get(), TokenUser, nullptr, 0, &len);
    std::vector<BYTE> buf(len);
    if (!GetTokenInformation(token.get(), TokenUser, buf.data(), len, &len)) {
        return nullptr;
    }
    auto tu = reinterpret_cast<TOKEN_USER*>(buf.data());
    const auto sidLen = GetLengthSid(tu->User.Sid);
    wil::unique_sid sid(static_cast<PSID>(LocalAlloc(LMEM_FIXED, sidLen)));
    CopySid(sidLen, sid.get(), tu->User.Sid);
    return std::move(sid);
}

wil::unique_sid getUserSid(const std::string& username) {
    DWORD sidSize = 0;
    SID_NAME_USE use;
    DWORD domainSize = 0;
    LookupAccountName(nullptr, username.c_str(), nullptr, &sidSize,
        nullptr, &domainSize, &use);
    if (sidSize == 0 || domainSize == 0) {
        return nullptr;
    }
    std::string domainName(domainSize, '\0');
    std::vector<BYTE> sidBuf(sidSize);
    domainSize = 256;
    if (!LookupAccountName(nullptr, username.c_str(), sidBuf.data(),
        &sidSize, domainName.data(), &domainSize, &use)) {
        return nullptr;
    }
    wil::unique_sid sid(static_cast<PSID>(LocalAlloc(LMEM_FIXED, sidSize)));
    CopySid(sidSize, sid.get(), reinterpret_cast<PSID>(sidBuf.data()));
    return std::move(sid);
}

std::string getSidStringFromSid(wil::unique_sid&& userSid) {
    LPSTR sidStr = nullptr;
    if (!ConvertSidToStringSid(userSid.get(), &sidStr)) {
        if (sidStr) {
            LocalFree(sidStr);
        }
        return {};
    }
    std::string result = sidStr ? sidStr : "";
    LocalFree(sidStr);
    return result;
}

std::string getCurrentUserSidString() {
    return getSidStringFromSid(std::move(getCurrentUserSid()));
}

bool isAccountMemberOfLocalGroup(const std::string& accountName,
    const std::string& groupName) {
    wil::unique_sid acctSid = nullptr;
    if (accountName.empty()) {
        acctSid = getCurrentUserSid();
        if (!acctSid) {
            return false;
        }
    }
    else {
        acctSid = getUserSid(accountName);
        if (!acctSid) {
            return false;
        }
    }
    LOCALGROUP_MEMBERS_INFO_0* members = nullptr;
    DWORD entries = 0, total = 0;
    const auto gn = boinc_ascii_to_wide(groupName);
    const auto rc = NetLocalGroupGetMembers(nullptr, gn.c_str(), 0,
        reinterpret_cast<LPBYTE*>(&members), MAX_PREFERRED_LENGTH,
        &entries, &total, nullptr);
    if (rc != NERR_Success) {
        if (members) {
            NetApiBufferFree(members);
        }
        return false;
    }
    if (!members) {
        return false;
    }
    auto found = false;
    for (auto i = 0u; i < entries; ++i) {
        if (EqualSid(members[i].lgrmi0_sid, acctSid.get())) {
            found = true;
            break;
        }
    }
    if (members) {
        NetApiBufferFree(members);
    }
    return found;
}

std::string getLocalizedGroupName(wil::unique_sid&& sid) {
    auto nameSize = 0ul;
    auto domainSize = 0ul;
    SID_NAME_USE use;
    LookupAccountSid(nullptr, sid.get(), nullptr, &nameSize,
        nullptr, &domainSize, &use);
    std::string name(static_cast<size_t>(nameSize), '\0');
    std::string domain(static_cast<size_t>(domainSize), '\0');
    if (!LookupAccountSid(nullptr, sid.get(), name.data(), &nameSize,
        domain.data(), &domainSize, &use)) {
        return "Users";
    }
    name.erase(name.find('\0'));
    return name;
}

std::string getLocalizedUsersGroupName() {
    std::array<BYTE, SECURITY_MAX_SID_SIZE> buffer;
    DWORD size = SECURITY_MAX_SID_SIZE;
    if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, &buffer,
        &size)) {
        return "Users";
    }

    wil::unique_sid sid(static_cast<PSID>(LocalAlloc(LMEM_FIXED, size)));
    CopySid(size, sid.get(), reinterpret_cast<PSID>(buffer.data()));

    return getLocalizedGroupName(std::move(sid));
}

std::string getLocalizedAdministratorsGroupName() {
    std::array<BYTE, SECURITY_MAX_SID_SIZE> buffer;
    DWORD size = SECURITY_MAX_SID_SIZE;
    if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, &buffer,
        &size)) {
        return "Administrators";
    }
    wil::unique_sid sid(static_cast<PSID>(LocalAlloc(LMEM_FIXED, size)));
    CopySid(size, sid.get(), reinterpret_cast<PSID>(buffer.data()));
    return getLocalizedGroupName(std::move(sid));
}

bool localGroupExists(const std::string& groupName) {
    LOCALGROUP_INFO_0* info = nullptr;
    const auto gn = boinc_ascii_to_wide(groupName);
    const auto rc = NetLocalGroupGetInfo(nullptr, gn.c_str(), 0,
        reinterpret_cast<LPBYTE*>(&info));
    if (info) {
        NetApiBufferFree(info);
    }
    return rc == NERR_Success;
}

bool createLocalGroup(const std::string& groupName) {
    auto gn = boinc_ascii_to_wide(groupName);
    LOCALGROUP_INFO_0 info;
    info.lgrpi0_name = gn.data();
    const auto rc = NetLocalGroupAdd(nullptr,
        0,
        reinterpret_cast<LPBYTE>(&info),
        nullptr);
    return rc == NERR_Success || rc == NERR_GroupExists;
}

bool deleteLocalGroup(const std::string& groupName) {
    const auto gn = boinc_ascii_to_wide(groupName);
    auto res = NetLocalGroupDel(nullptr, gn.c_str());
    return res == NERR_Success;
}

bool addUserToTheBuiltinAdministratorsGroup(wil::unique_sid&& userSid) {
    std::array<BYTE, SECURITY_MAX_SID_SIZE> adminSidBuf{};
    DWORD sidSize = SECURITY_MAX_SID_SIZE;
    if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr,
        adminSidBuf.data(), &sidSize)) {
        return false;
    }

    wil::unique_sid sid(static_cast<PSID>(LocalAlloc(LMEM_FIXED, sidSize)));
    CopySid(sidSize, sid.get(), reinterpret_cast<PSID>(adminSidBuf.data()));
    auto adminGroupName =
        boinc_ascii_to_wide(getLocalizedGroupName(std::move(sid)));


    LOCALGROUP_MEMBERS_INFO_0 member{};
    member.lgrmi0_sid = reinterpret_cast<PSID>(userSid.get());
    const auto rc = NetLocalGroupAddMembers(
        nullptr,
        adminGroupName.c_str(),
        0,
        reinterpret_cast<LPBYTE>(&member),
        1
    );
    if (rc != NERR_Success && rc != ERROR_MEMBER_IN_ALIAS) {
        return false;
    }
    return true;
}

LSA_HANDLE GetPolicyHandle()
{
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    LSA_HANDLE lsahPolicyHandle;
    ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

    const auto ntsResult = LsaOpenPolicy(nullptr, &ObjectAttributes,
        POLICY_ALL_ACCESS, &lsahPolicyHandle);

    if (ntsResult != STATUS_SUCCESS) {
        return nullptr;
    }
    return lsahPolicyHandle;
}

std::vector<std::string> getAccountRights(const std::string& username) {
    auto policyHandle = GetPolicyHandle();
    if (policyHandle == nullptr) {
        return {};
    }
    unique_hlsa pHandle(policyHandle);

    PLSA_UNICODE_STRING userRights = nullptr;
    ULONG countOfRights = 0;
    const auto sid = getUserSid(username);
    if (!sid.is_valid()) {
        return {};
    }
    const auto result = LsaEnumerateAccountRights(policyHandle,
        sid.get(), &userRights, &countOfRights);
    if (userRights == nullptr) {
        return {};
    }
    unique_lsamem_ptr<LSA_UNICODE_STRING> pUserRights(userRights);

    if (result != STATUS_SUCCESS || countOfRights == 0) {
        return {};
    }
    std::vector<std::string> rights;
    rights.reserve(countOfRights);
    for (decltype(countOfRights) i = 0; i < countOfRights; ++i) {
        std::wstring right(userRights[i].Buffer,
            userRights[i].Length / sizeof(WCHAR));
        rights.emplace_back(boinc_wide_to_ascii(right));
    }
    return rights;
}

LSA_UNICODE_STRING toLsaUnicodeString(const std::wstring& str) {
    LSA_UNICODE_STRING lsaWStr;
    const auto len = str.length();
    lsaWStr.Buffer = const_cast<wchar_t*>(str.c_str());
    lsaWStr.Length = static_cast<USHORT>((len) * sizeof(WCHAR));
    lsaWStr.MaximumLength =
        static_cast<USHORT>((len + 1) * sizeof(WCHAR));
    return lsaWStr;
}
