mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
add: add Whitelist to Dedicated Server
This commit is contained in:
parent
4ad824713f
commit
cfa0a7de7f
|
|
@ -97,6 +97,7 @@ list(APPEND MINECRAFT_SERVER_SOURCES
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Windows64/ServerMain.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Access/Access.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Access/BanManager.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Access/WhitelistManager.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/ServerCli.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/ServerCliInput.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/ServerCliParser.cpp"
|
||||
|
|
@ -111,6 +112,7 @@ list(APPEND MINECRAFT_SERVER_SOURCES
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandStop.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandList.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandTp.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandWhitelist.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandGamemode.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Common/FileUtils.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Common/StringUtils.cpp"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "Settings.h"
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "..\Minecraft.Server\ServerLogManager.h"
|
||||
#include "..\Minecraft.Server\Access\Access.h"
|
||||
#include "..\Minecraft.World\Socket.h"
|
||||
#endif
|
||||
// #ifdef __PS3__
|
||||
|
|
@ -213,6 +214,22 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
bannedXuid = server->getPlayers()->isXuidBanned(packet->m_onlineXuid);
|
||||
}
|
||||
|
||||
bool whitelistSatisfied = true;
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (ServerRuntime::Access::IsWhitelistEnabled())
|
||||
{
|
||||
whitelistSatisfied = false;
|
||||
if (loginXuid != INVALID_XUID)
|
||||
{
|
||||
whitelistSatisfied = ServerRuntime::Access::IsPlayerWhitelisted(loginXuid);
|
||||
}
|
||||
if (!whitelistSatisfied && packet->m_onlineXuid != INVALID_XUID && packet->m_onlineXuid != loginXuid)
|
||||
{
|
||||
whitelistSatisfied = ServerRuntime::Access::IsPlayerWhitelisted(packet->m_onlineXuid);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( sentDisconnect )
|
||||
{
|
||||
// Do nothing
|
||||
|
|
@ -221,6 +238,13 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_BannedXuid);
|
||||
#endif
|
||||
disconnect(DisconnectPacket::eDisconnect_Banned);
|
||||
}
|
||||
else if (!whitelistSatisfied)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_NotWhitelisted);
|
||||
#endif
|
||||
disconnect(DisconnectPacket::eDisconnect_Banned);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
#include "Access.h"
|
||||
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerLogger.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
|
|
@ -25,6 +28,8 @@ namespace ServerRuntime
|
|||
std::mutex stateLock;
|
||||
std::mutex writeLock;
|
||||
std::shared_ptr<BanManager> banManager;
|
||||
std::shared_ptr<WhitelistManager> whitelistManager;
|
||||
bool whitelistEnabled = false;
|
||||
};
|
||||
|
||||
AccessState g_accessState;
|
||||
|
|
@ -48,6 +53,18 @@ namespace ServerRuntime
|
|||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
g_accessState.banManager = banManager;
|
||||
}
|
||||
|
||||
static std::shared_ptr<WhitelistManager> GetWhitelistManagerSnapshot()
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
return g_accessState.whitelistManager;
|
||||
}
|
||||
|
||||
static void PublishWhitelistManagerSnapshot(const std::shared_ptr<WhitelistManager> &whitelistManager)
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
g_accessState.whitelistManager = whitelistManager;
|
||||
}
|
||||
}
|
||||
|
||||
std::string FormatXuid(PlayerUID xuid)
|
||||
|
|
@ -62,70 +79,175 @@ namespace ServerRuntime
|
|||
return buffer;
|
||||
}
|
||||
|
||||
bool Initialize(const std::string &baseDirectory)
|
||||
bool TryParseXuid(const std::string &text, PlayerUID *outXuid)
|
||||
{
|
||||
if (outXuid == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string trimmed = StringUtils::TrimAscii(text);
|
||||
if (trimmed.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char *end = nullptr;
|
||||
unsigned long long value = _strtoui64(trimmed.c_str(), &end, 0);
|
||||
if (end == trimmed.c_str() || errno != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')
|
||||
{
|
||||
++end;
|
||||
}
|
||||
|
||||
if (*end != 0 || value == 0ULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outXuid = (PlayerUID)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Initialize(const std::string &baseDirectory, bool whitelistEnabled)
|
||||
{
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
|
||||
// Build the replacement manager privately so readers keep using the last published snapshot during disk I/O.
|
||||
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(baseDirectory);
|
||||
std::shared_ptr<WhitelistManager> whitelistManager = std::make_shared<WhitelistManager>(baseDirectory);
|
||||
if (!banManager->EnsureBanFilesExist())
|
||||
{
|
||||
LogError("access", "failed to ensure dedicated server ban files exist");
|
||||
return false;
|
||||
}
|
||||
if (!whitelistManager->EnsureWhitelistFileExists())
|
||||
{
|
||||
LogError("access", "failed to ensure dedicated server whitelist file exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!banManager->Reload())
|
||||
{
|
||||
LogError("access", "failed to load dedicated server ban files");
|
||||
return false;
|
||||
}
|
||||
if (!whitelistManager->Reload())
|
||||
{
|
||||
LogError("access", "failed to load dedicated server whitelist file");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<BannedPlayerEntry> playerEntries;
|
||||
std::vector<BannedIpEntry> ipEntries;
|
||||
std::vector<WhitelistedPlayerEntry> whitelistEntries;
|
||||
banManager->SnapshotBannedPlayers(&playerEntries);
|
||||
banManager->SnapshotBannedIps(&ipEntries);
|
||||
whitelistManager->SnapshotWhitelistedPlayers(&whitelistEntries);
|
||||
PublishBanManagerSnapshot(banManager);
|
||||
PublishWhitelistManagerSnapshot(whitelistManager);
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
g_accessState.whitelistEnabled = whitelistEnabled;
|
||||
}
|
||||
|
||||
LogInfof(
|
||||
"access",
|
||||
"loaded %u player bans and %u ip bans",
|
||||
"loaded %u player bans, %u ip bans, and %u whitelist entries (whitelist=%s)",
|
||||
(unsigned)playerEntries.size(),
|
||||
(unsigned)ipEntries.size());
|
||||
(unsigned)ipEntries.size(),
|
||||
(unsigned)whitelistEntries.size(),
|
||||
whitelistEnabled ? "enabled" : "disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
PublishBanManagerSnapshot(std::shared_ptr<BanManager>());
|
||||
PublishBanManagerSnapshot(std::shared_ptr<BanManager>{});
|
||||
PublishWhitelistManagerSnapshot(std::shared_ptr<WhitelistManager>{});
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
g_accessState.whitelistEnabled = false;
|
||||
}
|
||||
|
||||
bool Reload()
|
||||
{
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
|
||||
if (current == nullptr)
|
||||
std::shared_ptr<WhitelistManager> currentWhitelist = GetWhitelistManagerSnapshot();
|
||||
if (current == nullptr || currentWhitelist == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
|
||||
std::shared_ptr<WhitelistManager> whitelistManager = std::make_shared<WhitelistManager>(*currentWhitelist);
|
||||
if (!banManager->EnsureBanFilesExist())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!whitelistManager->EnsureWhitelistFileExists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!banManager->Reload())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!whitelistManager->Reload())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PublishBanManagerSnapshot(banManager);
|
||||
PublishWhitelistManagerSnapshot(whitelistManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReloadWhitelist()
|
||||
{
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
const auto current = GetWhitelistManagerSnapshot();
|
||||
if (current == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
|
||||
if (!whitelistManager->EnsureWhitelistFileExists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!whitelistManager->Reload())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PublishWhitelistManagerSnapshot(whitelistManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInitialized()
|
||||
{
|
||||
return GetBanManagerSnapshot() != nullptr;
|
||||
return GetBanManagerSnapshot() != nullptr && GetWhitelistManagerSnapshot() != nullptr;
|
||||
}
|
||||
|
||||
bool IsWhitelistEnabled()
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
return g_accessState.whitelistEnabled;
|
||||
}
|
||||
|
||||
void SetWhitelistEnabled(bool enabled)
|
||||
{
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
|
||||
g_accessState.whitelistEnabled = enabled;
|
||||
}
|
||||
|
||||
bool IsPlayerBanned(PlayerUID xuid)
|
||||
|
|
@ -146,6 +268,18 @@ namespace ServerRuntime
|
|||
return (banManager != nullptr) ? banManager->IsIpBanned(ip) : false;
|
||||
}
|
||||
|
||||
bool IsPlayerWhitelisted(PlayerUID xuid)
|
||||
{
|
||||
const std::string formatted = FormatXuid(xuid);
|
||||
if (formatted.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<WhitelistManager> whitelistManager = GetWhitelistManagerSnapshot();
|
||||
return (whitelistManager != nullptr) ? whitelistManager->IsPlayerWhitelistedByXuid(formatted) : false;
|
||||
}
|
||||
|
||||
bool AddPlayerBan(PlayerUID xuid, const std::string &name, const BanMetadata &metadata)
|
||||
{
|
||||
const std::string formatted = FormatXuid(xuid);
|
||||
|
|
@ -241,6 +375,57 @@ namespace ServerRuntime
|
|||
return true;
|
||||
}
|
||||
|
||||
bool AddWhitelistedPlayer(PlayerUID xuid, const std::string &name, const WhitelistMetadata &metadata)
|
||||
{
|
||||
const auto formatted = FormatXuid(xuid);
|
||||
if (formatted.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
const auto current = GetWhitelistManagerSnapshot();
|
||||
if (current == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
|
||||
const WhitelistedPlayerEntry entry = { formatted, name, metadata };
|
||||
if (!whitelistManager->AddPlayer(entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PublishWhitelistManagerSnapshot(whitelistManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveWhitelistedPlayer(PlayerUID xuid)
|
||||
{
|
||||
const auto formatted = FormatXuid(xuid);
|
||||
if (formatted.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
|
||||
const auto current = GetWhitelistManagerSnapshot();
|
||||
if (current == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
|
||||
if (!whitelistManager->RemovePlayerByXuid(formatted))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PublishWhitelistManagerSnapshot(whitelistManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotBannedPlayers(std::vector<BannedPlayerEntry> *outEntries)
|
||||
{
|
||||
if (outEntries == nullptr)
|
||||
|
|
@ -274,5 +459,22 @@ namespace ServerRuntime
|
|||
|
||||
return banManager->SnapshotBannedIps(outEntries);
|
||||
}
|
||||
|
||||
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries)
|
||||
{
|
||||
if (outEntries == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto whitelistManager = GetWhitelistManagerSnapshot();
|
||||
if (whitelistManager == nullptr)
|
||||
{
|
||||
outEntries->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return whitelistManager->SnapshotWhitelistedPlayers(outEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "BanManager.h"
|
||||
#include "WhitelistManager.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
|
|
@ -9,18 +10,24 @@ namespace ServerRuntime
|
|||
*/
|
||||
namespace Access
|
||||
{
|
||||
bool Initialize(const std::string &baseDirectory = ".");
|
||||
bool Initialize(const std::string &baseDirectory = ".", bool whitelistEnabled = false);
|
||||
void Shutdown();
|
||||
bool Reload();
|
||||
bool ReloadWhitelist();
|
||||
bool IsInitialized();
|
||||
bool IsWhitelistEnabled();
|
||||
void SetWhitelistEnabled(bool enabled);
|
||||
|
||||
bool IsPlayerBanned(PlayerUID xuid);
|
||||
bool IsIpBanned(const std::string &ip);
|
||||
bool IsPlayerWhitelisted(PlayerUID xuid);
|
||||
|
||||
bool AddPlayerBan(PlayerUID xuid, const std::string &name, const BanMetadata &metadata);
|
||||
bool AddIpBan(const std::string &ip, const BanMetadata &metadata);
|
||||
bool RemovePlayerBan(PlayerUID xuid);
|
||||
bool RemoveIpBan(const std::string &ip);
|
||||
bool AddWhitelistedPlayer(PlayerUID xuid, const std::string &name, const WhitelistMetadata &metadata);
|
||||
bool RemoveWhitelistedPlayer(PlayerUID xuid);
|
||||
|
||||
/**
|
||||
* Copies the current cached player bans for inspection or command output
|
||||
|
|
@ -32,7 +39,9 @@ namespace ServerRuntime
|
|||
* 現在のIP BAN一覧を複製取得
|
||||
*/
|
||||
bool SnapshotBannedIps(std::vector<BannedIpEntry> *outEntries);
|
||||
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries);
|
||||
|
||||
std::string FormatXuid(PlayerUID xuid);
|
||||
bool TryParseXuid(const std::string &text, PlayerUID *outXuid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
439
Minecraft.Server/Access/WhitelistManager.cpp
Normal file
439
Minecraft.Server/Access/WhitelistManager.cpp
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "WhitelistManager.h"
|
||||
|
||||
#include "..\Common\FileUtils.h"
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerLogger.h"
|
||||
#include "..\vendor\nlohmann\json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
namespace Access
|
||||
{
|
||||
using OrderedJson = nlohmann::ordered_json;
|
||||
|
||||
namespace
|
||||
{
|
||||
static const char *kWhitelistFileName = "whitelist.json";
|
||||
|
||||
static bool FileExists(const std::string &path)
|
||||
{
|
||||
const std::wstring widePath = StringUtils::Utf8ToWide(path);
|
||||
if (widePath.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD attrs = GetFileAttributesW(widePath.c_str());
|
||||
return (attrs != INVALID_FILE_ATTRIBUTES) && ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0);
|
||||
}
|
||||
|
||||
static bool EnsureJsonListFile(const std::string &path)
|
||||
{
|
||||
if (FileExists(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return FileUtils::WriteTextFileAtomic(path, "[]\n");
|
||||
}
|
||||
|
||||
static bool TryParseNumericXuid(const std::string &text, unsigned long long *outValue)
|
||||
{
|
||||
if (outValue == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string trimmed = StringUtils::TrimAscii(text);
|
||||
if (trimmed.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char *end = nullptr;
|
||||
unsigned long long value = _strtoui64(trimmed.c_str(), &end, 0);
|
||||
if (end == trimmed.c_str() || errno != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')
|
||||
{
|
||||
++end;
|
||||
}
|
||||
|
||||
if (*end != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryGetStringField(const OrderedJson &object, const char *key, std::string *outValue)
|
||||
{
|
||||
if (key == nullptr || outValue == nullptr || !object.is_object())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto it = object.find(key);
|
||||
if (it == object.end() || !it->is_string())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outValue = it->get<std::string>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
WhitelistManager::WhitelistManager(const std::string &baseDirectory)
|
||||
: m_baseDirectory(baseDirectory.empty() ? "." : baseDirectory)
|
||||
{
|
||||
}
|
||||
|
||||
bool WhitelistManager::EnsureWhitelistFileExists() const
|
||||
{
|
||||
const std::string path = GetWhitelistFilePath();
|
||||
if (!EnsureJsonListFile(path))
|
||||
{
|
||||
LogErrorf("access", "failed to create %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhitelistManager::Reload()
|
||||
{
|
||||
std::vector<WhitelistedPlayerEntry> players;
|
||||
if (!LoadPlayers(&players))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_whitelistedPlayers.swap(players);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhitelistManager::Save() const
|
||||
{
|
||||
std::vector<WhitelistedPlayerEntry> players;
|
||||
return SnapshotWhitelistedPlayers(&players) && SavePlayers(players);
|
||||
}
|
||||
|
||||
bool WhitelistManager::LoadPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const
|
||||
{
|
||||
if (outEntries == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
outEntries->clear();
|
||||
|
||||
std::string text;
|
||||
const std::string path = GetWhitelistFilePath();
|
||||
if (!FileUtils::ReadTextFile(path, &text))
|
||||
{
|
||||
LogErrorf("access", "failed to read %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.empty())
|
||||
{
|
||||
text = "[]";
|
||||
}
|
||||
|
||||
OrderedJson root;
|
||||
try
|
||||
{
|
||||
root = OrderedJson::parse(StringUtils::StripUtf8Bom(text));
|
||||
}
|
||||
catch (const nlohmann::json::exception &e)
|
||||
{
|
||||
LogErrorf("access", "failed to parse %s: %s", path.c_str(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!root.is_array())
|
||||
{
|
||||
LogErrorf("access", "failed to parse %s: root json value is not an array", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &object : root)
|
||||
{
|
||||
if (!object.is_object())
|
||||
{
|
||||
LogWarnf("access", "skipping whitelist entry that is not an object in %s", path.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string rawXuid;
|
||||
if (!TryGetStringField(object, "xuid", &rawXuid))
|
||||
{
|
||||
LogWarnf("access", "skipping whitelist entry without xuid in %s", path.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
WhitelistedPlayerEntry entry;
|
||||
entry.xuid = NormalizeXuid(rawXuid);
|
||||
if (entry.xuid.empty())
|
||||
{
|
||||
LogWarnf("access", "skipping whitelist entry with empty xuid in %s", path.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
TryGetStringField(object, "name", &entry.name);
|
||||
TryGetStringField(object, "created", &entry.metadata.created);
|
||||
TryGetStringField(object, "source", &entry.metadata.source);
|
||||
NormalizeMetadata(&entry.metadata);
|
||||
|
||||
outEntries->push_back(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhitelistManager::SavePlayers(const std::vector<WhitelistedPlayerEntry> &entries) const
|
||||
{
|
||||
OrderedJson root = OrderedJson::array();
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
OrderedJson object = OrderedJson::object();
|
||||
object["xuid"] = NormalizeXuid(entry.xuid);
|
||||
object["name"] = entry.name;
|
||||
object["created"] = entry.metadata.created;
|
||||
object["source"] = entry.metadata.source;
|
||||
root.push_back(object);
|
||||
}
|
||||
|
||||
const std::string path = GetWhitelistFilePath();
|
||||
const std::string json = root.empty() ? std::string("[]\n") : (root.dump(2) + "\n");
|
||||
if (!FileUtils::WriteTextFileAtomic(path, json))
|
||||
{
|
||||
LogErrorf("access", "failed to write %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<WhitelistedPlayerEntry> &WhitelistManager::GetWhitelistedPlayers() const
|
||||
{
|
||||
return m_whitelistedPlayers;
|
||||
}
|
||||
|
||||
bool WhitelistManager::SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const
|
||||
{
|
||||
if (outEntries == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outEntries = m_whitelistedPlayers;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhitelistManager::IsPlayerWhitelistedByXuid(const std::string &xuid) const
|
||||
{
|
||||
const auto normalized = NormalizeXuid(xuid);
|
||||
if (normalized.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::any_of(
|
||||
m_whitelistedPlayers.begin(),
|
||||
m_whitelistedPlayers.end(),
|
||||
[&normalized](const WhitelistedPlayerEntry &entry)
|
||||
{
|
||||
return entry.xuid == normalized;
|
||||
});
|
||||
}
|
||||
|
||||
bool WhitelistManager::AddPlayer(const WhitelistedPlayerEntry &entry)
|
||||
{
|
||||
std::vector<WhitelistedPlayerEntry> updatedEntries;
|
||||
if (!SnapshotWhitelistedPlayers(&updatedEntries))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto normalized = entry;
|
||||
normalized.xuid = NormalizeXuid(normalized.xuid);
|
||||
NormalizeMetadata(&normalized.metadata);
|
||||
if (normalized.xuid.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto existing = std::find_if(
|
||||
updatedEntries.begin(),
|
||||
updatedEntries.end(),
|
||||
[&normalized](const WhitelistedPlayerEntry &candidate)
|
||||
{
|
||||
return candidate.xuid == normalized.xuid;
|
||||
});
|
||||
|
||||
if (existing != updatedEntries.end())
|
||||
{
|
||||
*existing = normalized;
|
||||
if (!SavePlayers(updatedEntries))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_whitelistedPlayers.swap(updatedEntries);
|
||||
return true;
|
||||
}
|
||||
|
||||
updatedEntries.push_back(normalized);
|
||||
if (!SavePlayers(updatedEntries))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_whitelistedPlayers.swap(updatedEntries);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhitelistManager::RemovePlayerByXuid(const std::string &xuid)
|
||||
{
|
||||
const auto normalized = NormalizeXuid(xuid);
|
||||
if (normalized.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<WhitelistedPlayerEntry> updatedEntries;
|
||||
if (!SnapshotWhitelistedPlayers(&updatedEntries))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto oldSize = updatedEntries.size();
|
||||
updatedEntries.erase(
|
||||
std::remove_if(
|
||||
updatedEntries.begin(),
|
||||
updatedEntries.end(),
|
||||
[&normalized](const WhitelistedPlayerEntry &entry) { return entry.xuid == normalized; }),
|
||||
updatedEntries.end());
|
||||
|
||||
if (updatedEntries.size() == oldSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SavePlayers(updatedEntries))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_whitelistedPlayers.swap(updatedEntries);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string WhitelistManager::GetWhitelistFilePath() const
|
||||
{
|
||||
return BuildPath(kWhitelistFileName);
|
||||
}
|
||||
|
||||
WhitelistMetadata WhitelistManager::BuildDefaultMetadata(const char *source)
|
||||
{
|
||||
WhitelistMetadata metadata;
|
||||
|
||||
SYSTEMTIME utc;
|
||||
GetSystemTime(&utc);
|
||||
|
||||
char created[64] = {};
|
||||
sprintf_s(
|
||||
created,
|
||||
sizeof(created),
|
||||
"%04u-%02u-%02uT%02u:%02u:%02uZ",
|
||||
(unsigned)utc.wYear,
|
||||
(unsigned)utc.wMonth,
|
||||
(unsigned)utc.wDay,
|
||||
(unsigned)utc.wHour,
|
||||
(unsigned)utc.wMinute,
|
||||
(unsigned)utc.wSecond);
|
||||
metadata.created = created;
|
||||
metadata.source = (source != nullptr) ? source : "Server";
|
||||
return metadata;
|
||||
}
|
||||
|
||||
std::string WhitelistManager::NormalizeXuid(const std::string &xuid)
|
||||
{
|
||||
std::string trimmed = StringUtils::TrimAscii(xuid);
|
||||
if (trimmed.empty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
unsigned long long numericXuid = 0;
|
||||
if (TryParseNumericXuid(trimmed, &numericXuid))
|
||||
{
|
||||
if (numericXuid == 0ULL)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
char buffer[32] = {};
|
||||
sprintf_s(buffer, sizeof(buffer), "0x%016llx", numericXuid);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
return StringUtils::ToLowerAscii(trimmed);
|
||||
}
|
||||
|
||||
void WhitelistManager::NormalizeMetadata(WhitelistMetadata *metadata)
|
||||
{
|
||||
if (metadata == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
metadata->created = StringUtils::TrimAscii(metadata->created);
|
||||
metadata->source = StringUtils::TrimAscii(metadata->source);
|
||||
}
|
||||
|
||||
std::string WhitelistManager::BuildPath(const char *fileName) const
|
||||
{
|
||||
if (fileName == nullptr || fileName[0] == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::wstring wideFileName = StringUtils::Utf8ToWide(fileName);
|
||||
if (wideFileName.empty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (m_baseDirectory.empty() || m_baseDirectory == ".")
|
||||
{
|
||||
return StringUtils::WideToUtf8(wideFileName);
|
||||
}
|
||||
|
||||
const std::wstring wideBaseDirectory = StringUtils::Utf8ToWide(m_baseDirectory);
|
||||
if (wideBaseDirectory.empty())
|
||||
{
|
||||
return StringUtils::WideToUtf8(wideFileName);
|
||||
}
|
||||
|
||||
const wchar_t last = wideBaseDirectory[wideBaseDirectory.size() - 1];
|
||||
if (last == L'\\' || last == L'/')
|
||||
{
|
||||
return StringUtils::WideToUtf8(wideBaseDirectory + wideFileName);
|
||||
}
|
||||
|
||||
return StringUtils::WideToUtf8(wideBaseDirectory + L"\\" + wideFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Minecraft.Server/Access/WhitelistManager.h
Normal file
69
Minecraft.Server/Access/WhitelistManager.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
namespace Access
|
||||
{
|
||||
/**
|
||||
* Information stored with dedicated-server whitelist entries
|
||||
* ホワイトリスト項目に保存する補助情報
|
||||
*/
|
||||
struct WhitelistMetadata
|
||||
{
|
||||
std::string created;
|
||||
std::string source;
|
||||
};
|
||||
|
||||
struct WhitelistedPlayerEntry
|
||||
{
|
||||
std::string xuid;
|
||||
std::string name;
|
||||
WhitelistMetadata metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dedicated server whitelist file manager.
|
||||
*
|
||||
* Files:
|
||||
* - whitelist.json
|
||||
*
|
||||
* Stores and normalizes XUID-based allow entries.
|
||||
*/
|
||||
class WhitelistManager
|
||||
{
|
||||
public:
|
||||
explicit WhitelistManager(const std::string &baseDirectory = ".");
|
||||
|
||||
bool EnsureWhitelistFileExists() const;
|
||||
bool Reload();
|
||||
bool Save() const;
|
||||
|
||||
bool LoadPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const;
|
||||
bool SavePlayers(const std::vector<WhitelistedPlayerEntry> &entries) const;
|
||||
|
||||
const std::vector<WhitelistedPlayerEntry> &GetWhitelistedPlayers() const;
|
||||
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const;
|
||||
|
||||
bool IsPlayerWhitelistedByXuid(const std::string &xuid) const;
|
||||
bool AddPlayer(const WhitelistedPlayerEntry &entry);
|
||||
bool RemovePlayerByXuid(const std::string &xuid);
|
||||
|
||||
std::string GetWhitelistFilePath() const;
|
||||
|
||||
static WhitelistMetadata BuildDefaultMetadata(const char *source = "Server");
|
||||
|
||||
private:
|
||||
static std::string NormalizeXuid(const std::string &xuid);
|
||||
static void NormalizeMetadata(WhitelistMetadata *metadata);
|
||||
|
||||
std::string BuildPath(const char *fileName) const;
|
||||
|
||||
private:
|
||||
std::string m_baseDirectory;
|
||||
std::vector<WhitelistedPlayerEntry> m_whitelistedPlayers;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +101,36 @@ namespace ServerRuntime
|
|||
return lowered;
|
||||
}
|
||||
|
||||
std::string JoinTokens(const std::vector<std::string> &tokens, size_t startIndex, const char *separator)
|
||||
{
|
||||
if (startIndex >= tokens.size())
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const auto joinSeparator = std::string((separator != nullptr) ? separator : " ");
|
||||
size_t totalLength = 0;
|
||||
for (size_t i = startIndex; i < tokens.size(); ++i)
|
||||
{
|
||||
totalLength += tokens[i].size();
|
||||
}
|
||||
|
||||
totalLength += (tokens.size() - startIndex - 1) * joinSeparator.size();
|
||||
std::string joined;
|
||||
joined.reserve(totalLength);
|
||||
for (size_t i = startIndex; i < tokens.size(); ++i)
|
||||
{
|
||||
if (!joined.empty())
|
||||
{
|
||||
joined += joinSeparator;
|
||||
}
|
||||
|
||||
joined += tokens[i];
|
||||
}
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
bool StartsWithIgnoreCase(const std::string &value, const std::string &prefix)
|
||||
{
|
||||
if (prefix.size() > value.size())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
|
|
@ -13,6 +14,7 @@ namespace ServerRuntime
|
|||
|
||||
std::string TrimAscii(const std::string &value);
|
||||
std::string ToLowerAscii(const std::string &value);
|
||||
std::string JoinTokens(const std::vector<std::string> &tokens, size_t startIndex = 0, const char *separator = " ");
|
||||
bool StartsWithIgnoreCase(const std::string &value, const std::string &prefix);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "commands\CliCommandPardonIp.h"
|
||||
#include "commands\CliCommandStop.h"
|
||||
#include "commands\CliCommandTp.h"
|
||||
#include "commands\CliCommandWhitelist.h"
|
||||
#include "..\Common\StringUtils.h"
|
||||
#include "..\ServerShutdown.h"
|
||||
#include "..\ServerLogger.h"
|
||||
|
|
@ -49,6 +50,7 @@ namespace ServerRuntime
|
|||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandPardon()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandPardonIp()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandBanList()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandWhitelist()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandTp()));
|
||||
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandGamemode()));
|
||||
}
|
||||
|
|
|
|||
284
Minecraft.Server/Console/commands/CliCommandWhitelist.cpp
Normal file
284
Minecraft.Server/Console/commands/CliCommandWhitelist.cpp
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "CliCommandWhitelist.h"
|
||||
|
||||
#include "..\ServerCliEngine.h"
|
||||
#include "..\ServerCliParser.h"
|
||||
#include "..\..\Access\Access.h"
|
||||
#include "..\..\Common\StringUtils.h"
|
||||
#include "..\..\ServerProperties.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static const char *kWhitelistUsage = "whitelist <on|off|list|add|remove|reload> [...]";
|
||||
|
||||
static bool CompareWhitelistEntries(const ServerRuntime::Access::WhitelistedPlayerEntry &left, const ServerRuntime::Access::WhitelistedPlayerEntry &right)
|
||||
{
|
||||
const auto leftName = StringUtils::ToLowerAscii(left.name);
|
||||
const auto rightName = StringUtils::ToLowerAscii(right.name);
|
||||
if (leftName != rightName)
|
||||
{
|
||||
return leftName < rightName;
|
||||
}
|
||||
|
||||
return StringUtils::ToLowerAscii(left.xuid) < StringUtils::ToLowerAscii(right.xuid);
|
||||
}
|
||||
|
||||
static bool PersistWhitelistToggle(bool enabled)
|
||||
{
|
||||
auto config = LoadServerPropertiesConfig();
|
||||
config.whiteListEnabled = enabled;
|
||||
return SaveServerPropertiesConfig(config);
|
||||
}
|
||||
|
||||
static std::string BuildWhitelistEntryRow(const ServerRuntime::Access::WhitelistedPlayerEntry &entry)
|
||||
{
|
||||
std::string row = " ";
|
||||
row += entry.xuid;
|
||||
if (!entry.name.empty())
|
||||
{
|
||||
row += " - ";
|
||||
row += entry.name;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
static void LogWhitelistMode(ServerCliEngine *engine)
|
||||
{
|
||||
engine->LogInfo(std::string("Whitelist is ") + (ServerRuntime::Access::IsWhitelistEnabled() ? "enabled." : "disabled."));
|
||||
}
|
||||
|
||||
static bool LogWhitelistEntries(ServerCliEngine *engine)
|
||||
{
|
||||
std::vector<ServerRuntime::Access::WhitelistedPlayerEntry> entries;
|
||||
if (!ServerRuntime::Access::SnapshotWhitelistedPlayers(&entries))
|
||||
{
|
||||
engine->LogError("Failed to read whitelist entries.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::sort(entries.begin(), entries.end(), CompareWhitelistEntries);
|
||||
LogWhitelistMode(engine);
|
||||
engine->LogInfo("There are " + std::to_string(entries.size()) + " whitelisted player(s).");
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
engine->LogInfo(BuildWhitelistEntryRow(entry));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryParseWhitelistXuid(const std::string &text, ServerCliEngine *engine, PlayerUID *outXuid)
|
||||
{
|
||||
if (ServerRuntime::Access::TryParseXuid(text, outXuid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
engine->LogWarn("Invalid XUID: " + text);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SuggestLiteral(const std::string &candidate, const ServerCliCompletionContext &context, std::vector<std::string> *out)
|
||||
{
|
||||
if (out == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (StringUtils::StartsWithIgnoreCase(candidate, context.prefix))
|
||||
{
|
||||
out->push_back(context.linePrefix + candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *CliCommandWhitelist::Name() const
|
||||
{
|
||||
return "whitelist";
|
||||
}
|
||||
|
||||
const char *CliCommandWhitelist::Usage() const
|
||||
{
|
||||
return kWhitelistUsage;
|
||||
}
|
||||
|
||||
const char *CliCommandWhitelist::Description() const
|
||||
{
|
||||
return "Manage the dedicated-server XUID whitelist.";
|
||||
}
|
||||
|
||||
bool CliCommandWhitelist::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
|
||||
{
|
||||
if (line.tokens.size() < 2)
|
||||
{
|
||||
engine->LogWarn(std::string("Usage: ") + kWhitelistUsage);
|
||||
return false;
|
||||
}
|
||||
if (!ServerRuntime::Access::IsInitialized())
|
||||
{
|
||||
engine->LogWarn("Access manager is not initialized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto subcommand = StringUtils::ToLowerAscii(line.tokens[1]);
|
||||
if (subcommand == "on" || subcommand == "off")
|
||||
{
|
||||
if (line.tokens.size() != 2)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist <on|off>");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool enabled = (subcommand == "on");
|
||||
if (!PersistWhitelistToggle(enabled))
|
||||
{
|
||||
engine->LogError("Failed to persist whitelist mode to server.properties.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerRuntime::Access::SetWhitelistEnabled(enabled);
|
||||
engine->LogInfo(std::string("Whitelist ") + (enabled ? "enabled." : "disabled."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subcommand == "list")
|
||||
{
|
||||
if (line.tokens.size() != 2)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist list");
|
||||
return false;
|
||||
}
|
||||
|
||||
return LogWhitelistEntries(engine);
|
||||
}
|
||||
|
||||
if (subcommand == "reload")
|
||||
{
|
||||
if (line.tokens.size() != 2)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist reload");
|
||||
return false;
|
||||
}
|
||||
if (!ServerRuntime::Access::ReloadWhitelist())
|
||||
{
|
||||
engine->LogError("Failed to reload whitelist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto config = LoadServerPropertiesConfig();
|
||||
ServerRuntime::Access::SetWhitelistEnabled(config.whiteListEnabled);
|
||||
engine->LogInfo("Reloaded whitelist from disk.");
|
||||
LogWhitelistMode(engine);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subcommand == "add")
|
||||
{
|
||||
if (line.tokens.size() < 3)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist add <xuid> [name ...]");
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerUID xuid = INVALID_XUID;
|
||||
if (!TryParseWhitelistXuid(line.tokens[2], engine, &xuid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ServerRuntime::Access::IsPlayerWhitelisted(xuid))
|
||||
{
|
||||
engine->LogWarn("That XUID is already whitelisted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto metadata = ServerRuntime::Access::WhitelistManager::BuildDefaultMetadata("Console");
|
||||
const auto name = StringUtils::JoinTokens(line.tokens, 3);
|
||||
if (!ServerRuntime::Access::AddWhitelistedPlayer(xuid, name, metadata))
|
||||
{
|
||||
engine->LogError("Failed to write whitelist entry.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message = "Whitelisted XUID " + ServerRuntime::Access::FormatXuid(xuid) + ".";
|
||||
if (!name.empty())
|
||||
{
|
||||
message += " Name: " + name;
|
||||
}
|
||||
engine->LogInfo(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subcommand == "remove")
|
||||
{
|
||||
if (line.tokens.size() != 3)
|
||||
{
|
||||
engine->LogWarn("Usage: whitelist remove <xuid>");
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerUID xuid = INVALID_XUID;
|
||||
if (!TryParseWhitelistXuid(line.tokens[2], engine, &xuid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ServerRuntime::Access::IsPlayerWhitelisted(xuid))
|
||||
{
|
||||
engine->LogWarn("That XUID is not whitelisted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ServerRuntime::Access::RemoveWhitelistedPlayer(xuid))
|
||||
{
|
||||
engine->LogError("Failed to remove whitelist entry.");
|
||||
return false;
|
||||
}
|
||||
|
||||
engine->LogInfo("Removed XUID " + ServerRuntime::Access::FormatXuid(xuid) + " from the whitelist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
engine->LogWarn(std::string("Usage: ") + kWhitelistUsage);
|
||||
return false;
|
||||
}
|
||||
|
||||
void CliCommandWhitelist::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
|
||||
{
|
||||
(void)engine;
|
||||
if (out == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.currentTokenIndex == 1)
|
||||
{
|
||||
SuggestLiteral("on", context, out);
|
||||
SuggestLiteral("off", context, out);
|
||||
SuggestLiteral("list", context, out);
|
||||
SuggestLiteral("add", context, out);
|
||||
SuggestLiteral("remove", context, out);
|
||||
SuggestLiteral("reload", context, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.currentTokenIndex == 2 && context.parsed.tokens.size() >= 2 && StringUtils::ToLowerAscii(context.parsed.tokens[1]) == "remove")
|
||||
{
|
||||
std::vector<ServerRuntime::Access::WhitelistedPlayerEntry> entries;
|
||||
if (!ServerRuntime::Access::SnapshotWhitelistedPlayers(&entries))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
SuggestLiteral(entry.xuid, context, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Minecraft.Server/Console/commands/CliCommandWhitelist.h
Normal file
16
Minecraft.Server/Console/commands/CliCommandWhitelist.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "IServerCliCommand.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
class CliCommandWhitelist : public IServerCliCommand
|
||||
{
|
||||
public:
|
||||
const char *Name() const override;
|
||||
const char *Usage() const override;
|
||||
const char *Description() const override;
|
||||
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
|
||||
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
|
||||
};
|
||||
}
|
||||
|
|
@ -126,6 +126,7 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="Access\Access.cpp" />
|
||||
<ClCompile Include="Access\BanManager.cpp" />
|
||||
<ClCompile Include="Access\WhitelistManager.cpp" />
|
||||
<ClCompile Include="ServerLogManager.cpp" />
|
||||
<ClCompile Include="..\Minecraft.Client\AbstractTexturePack.cpp" />
|
||||
<ClCompile Include="..\Minecraft.Client\AchievementPopup.cpp" />
|
||||
|
|
@ -662,6 +663,7 @@
|
|||
<ClCompile Include="Console\commands\CliCommandPardonIp.cpp" />
|
||||
<ClCompile Include="Console\commands\CliCommandStop.cpp" />
|
||||
<ClCompile Include="Console\commands\CliCommandTp.cpp" />
|
||||
<ClCompile Include="Console\commands\CliCommandWhitelist.cpp" />
|
||||
<ClCompile Include="Console\ServerCliEngine.cpp" />
|
||||
<ClCompile Include="Console\ServerCliParser.cpp" />
|
||||
<ClCompile Include="Console\ServerCliRegistry.cpp" />
|
||||
|
|
@ -684,6 +686,7 @@
|
|||
<ItemGroup>
|
||||
<ClInclude Include="Access\Access.h" />
|
||||
<ClInclude Include="Access\BanManager.h" />
|
||||
<ClInclude Include="Access\WhitelistManager.h" />
|
||||
<ClInclude Include="Console\ServerCli.h" />
|
||||
<ClInclude Include="Console\ServerCliInput.h" />
|
||||
<ClInclude Include="Console\commands\CliCommandBan.h" />
|
||||
|
|
@ -696,6 +699,7 @@
|
|||
<ClInclude Include="Console\commands\CliCommandPardonIp.h" />
|
||||
<ClInclude Include="Console\commands\CliCommandStop.h" />
|
||||
<ClInclude Include="Console\commands\CliCommandTp.h" />
|
||||
<ClInclude Include="Console\commands\CliCommandWhitelist.h" />
|
||||
<ClInclude Include="Console\commands\IServerCliCommand.h" />
|
||||
<ClInclude Include="Console\ServerCliEngine.h" />
|
||||
<ClInclude Include="Console\ServerCliParser.h" />
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@
|
|||
<ClCompile Include="Access\BanManager.cpp">
|
||||
<Filter>Server\Access</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Access\WhitelistManager.cpp">
|
||||
<Filter>Server\Access</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Common\FileUtils.cpp">
|
||||
<Filter>Server\Common</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -588,6 +591,9 @@
|
|||
<ClCompile Include="Console\commands\CliCommandTp.cpp">
|
||||
<Filter>Server\Console\Commands</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\CliCommandWhitelist.cpp">
|
||||
<Filter>Server\Console\Commands</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Console\ServerCli.h">
|
||||
|
|
@ -608,6 +614,9 @@
|
|||
<ClInclude Include="Access\BanManager.h">
|
||||
<Filter>Server\Access</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Access\WhitelistManager.h">
|
||||
<Filter>Server\Access</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Common\FileUtils.h">
|
||||
<Filter>Server\Common</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -660,6 +669,9 @@
|
|||
<ClInclude Include="Console\commands\CliCommandTp.h">
|
||||
<Filter>Server\Console\Commands</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\CliCommandWhitelist.h">
|
||||
<Filter>Server\Console\Commands</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\IServerCliCommand.h">
|
||||
<Filter>Server\Console\Commands</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ namespace ServerRuntime
|
|||
switch (reason)
|
||||
{
|
||||
case eLoginRejectReason_BannedXuid: return "banned-xuid";
|
||||
case eLoginRejectReason_NotWhitelisted: return "not-whitelisted";
|
||||
case eLoginRejectReason_DuplicateXuid: return "duplicate-xuid";
|
||||
case eLoginRejectReason_DuplicateName: return "duplicate-name";
|
||||
default: return "unknown";
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace ServerRuntime
|
|||
enum ELoginRejectReason
|
||||
{
|
||||
eLoginRejectReason_BannedXuid = 0,
|
||||
eLoginRejectReason_NotWhitelisted,
|
||||
eLoginRejectReason_DuplicateXuid,
|
||||
eLoginRejectReason_DuplicateName
|
||||
};
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ static const ServerPropertyDefault kServerPropertyDefaults[] =
|
|||
{ "server-ip", "0.0.0.0" },
|
||||
{ "server-name", "DedicatedServer" },
|
||||
{ "server-port", "25565" },
|
||||
{ "white-list", "false" },
|
||||
{ "lan-advertise", "false" },
|
||||
{ "spawn-animals", "true" },
|
||||
{ "spawn-monsters", "true" },
|
||||
|
|
@ -691,6 +692,7 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
|
|||
config.serverPort = ReadNormalizedIntProperty(&merged, "server-port", kDefaultServerPort, 1, 65535, &shouldWrite);
|
||||
config.serverIp = ReadNormalizedStringProperty(&merged, "server-ip", "0.0.0.0", 255, &shouldWrite);
|
||||
config.lanAdvertise = ReadNormalizedBoolProperty(&merged, kLanAdvertisePropertyKey, false, &shouldWrite);
|
||||
config.whiteListEnabled = ReadNormalizedBoolProperty(&merged, "white-list", false, &shouldWrite);
|
||||
config.serverName = ReadNormalizedStringProperty(&merged, "server-name", "DedicatedServer", 16, &shouldWrite);
|
||||
config.maxPlayers = ReadNormalizedIntProperty(&merged, "max-players", kDefaultMaxPlayers, 1, kMaxDedicatedPlayers, &shouldWrite);
|
||||
config.seed = 0;
|
||||
|
|
@ -750,7 +752,7 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
|
|||
*
|
||||
* Saves world identity fields while preserving as many other settings as possible
|
||||
* - Reads existing file and merges including unknown keys
|
||||
* - Updates only `level-name` and `level-id` before writing back
|
||||
* - Updates `level-name`, `level-id`, and `white-list` before writing back
|
||||
* ワールド識別情報の保存処理
|
||||
*/
|
||||
bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
|
||||
|
|
@ -787,6 +789,7 @@ bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
|
|||
|
||||
merged["level-name"] = worldName;
|
||||
merged["level-id"] = worldSaveId;
|
||||
merged["white-list"] = BoolToString(config.whiteListEnabled);
|
||||
|
||||
return WriteServerPropertiesFile(kServerPropertiesPath, merged);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace ServerRuntime
|
|||
std::string serverIp;
|
||||
/** `lan-advertise` */
|
||||
bool lanAdvertise;
|
||||
/** `white-list` */
|
||||
bool whiteListEnabled;
|
||||
/** `server-name` (max 16 chars at runtime) */
|
||||
std::string serverName;
|
||||
/** `max-players` */
|
||||
|
|
@ -85,9 +87,10 @@ namespace ServerRuntime
|
|||
* server.properties saver
|
||||
*
|
||||
* - `level-name` と `level-id` を更新
|
||||
* - `white-list` を更新
|
||||
* - それ以外の既存キーは極力保持
|
||||
*
|
||||
* @param config 保存するワールド識別情報
|
||||
* @param config 保存するワールド識別情報と永続化対象設定
|
||||
* @return 書き込み成功時 `true`
|
||||
*/
|
||||
bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config);
|
||||
|
|
|
|||
|
|
@ -389,13 +389,14 @@ int main(int argc, char **argv)
|
|||
LogStartupStep("initializing server log manager");
|
||||
ServerRuntime::ServerLogManager::Initialize();
|
||||
LogStartupStep("initializing dedicated access control");
|
||||
if (!ServerRuntime::Access::Initialize("."))
|
||||
if (!ServerRuntime::Access::Initialize(".", serverProperties.whiteListEnabled))
|
||||
{
|
||||
LogError("startup", "Failed to initialize dedicated server access control.");
|
||||
return 2;
|
||||
}
|
||||
accessShutdownGuard.Activate();
|
||||
LogInfof("startup", "LAN advertise: %s", serverProperties.lanAdvertise ? "enabled" : "disabled");
|
||||
LogInfof("startup", "Whitelist: %s", serverProperties.whiteListEnabled ? "enabled" : "disabled");
|
||||
|
||||
LogStartupStep("registering hidden window class");
|
||||
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||
|
|
|
|||
Loading…
Reference in a new issue