add event 'onPlayerRequestMods', restructure modloading

now every player has their own list of allowed client mods, this can be modified by lua upon joining and is later used as a whitelist to ensure only those files can be sent to each client
This commit is contained in:
20dka
2022-11-14 00:10:36 +01:00
committed by Lion Kortlepel
parent 056827546e
commit 468a6b340e
10 changed files with 140 additions and 50 deletions

View File

@@ -10,6 +10,7 @@
#include "BoostAliases.h"
#include "Common.h"
#include "Compat.h"
#include "TResourceManager.h"
#include "VehicleData.h"
class TServer;
@@ -103,6 +104,8 @@ public:
TimeType::time_point ConnectionTime {};
ModMap AllowedMods;
private:
void InsertVehicle(int ID, const std::string& Data);

View File

@@ -55,6 +55,7 @@ constexpr std::string_view StrLogChat = "LogChat";
constexpr std::string_view StrSendErrors = "Misc.SendErrors";
constexpr std::string_view StrSendErrorsMessageEnabled = "Misc.SendErrorsShowMessage";
constexpr std::string_view StrHideUpdateMessages = "Misc.ImScaredOfUpdates";
constexpr std::string_view StrIncludeSubdirectories = "Misc.IncludeSubdirectories";
// HTTP
constexpr std::string_view StrHTTPServerEnabled = "HTTP.HTTPServerEnabled";
@@ -79,6 +80,9 @@ struct Version {
template <typename T>
using SparseArray = std::unordered_map<size_t, T>;
template <typename K, typename V>
using HashMap = std::unordered_map<K, V>;
using boost::variant;
using boost::container::flat_map;

View File

@@ -35,14 +35,15 @@ namespace fs = std::filesystem;
/**
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
*/
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>;
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, std::unordered_map<std::string, size_t>, float>;
enum TLuaType {
String = 0,
Int = 1,
Json = 2,
Bool = 3,
StringStringMap = 4,
Float = 5,
StringSizeTMap = 5,
Float = 6,
};
class TLuaPlugin;

View File

@@ -46,6 +46,7 @@ private:
void Looper(const std::weak_ptr<TClient>& c);
int OpenID();
void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr);
ModMap GetClientMods(TClient& Client);
void HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet);
void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);

View File

@@ -1,21 +1,22 @@
#pragma once
#include "Common.h"
#include <optional>
using ModMap = HashMap<std::string, size_t>;
class TResourceManager {
public:
TResourceManager();
[[nodiscard]] size_t MaxModSize() const { return mMaxModSize; }
[[nodiscard]] std::string FileList() const { return mFileList; }
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] size_t TotalModsSize() const { return mTotalModSize; }
[[nodiscard]] ModMap FileMap() const { return mMods; }
[[nodiscard]] static std::string FormatForBackend(const ModMap& mods);
[[nodiscard]] static std::string FormatForClient(const ModMap& mods);
[[nodiscard]] static std::optional<std::string> IsModValid(std::string& pathString, const ModMap& mods);
[[nodiscard]] int LoadedModCount() const { return mMods.size(); }
private:
size_t mMaxModSize = 0;
std::string mFileSizes;
std::string mFileList;
std::string mTrimmedList;
int mModsLoaded = 0;
size_t mTotalModSize = 0; // size of all mods
ModMap mMods; // map of mod names
};

View File

@@ -87,6 +87,8 @@ void TConfig::FlushToFile() {
SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::GetSettingBool(StrSendErrorsMessageEnabled.data());
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
data["Misc"][StrIncludeSubdirectories.data()] = Application::GetSettingBool(StrIncludeSubdirectories.data());
SetComment(data["Misc"][StrIncludeSubdirectories.data()].comments(), " Whether or not to include subdirectories in General.ResourceFolder/Client when searching for `.zip`s");
// HTTP
data["HTTP"][StrSSLKeyPath.data()] = Application::GetSettingString(StrSSLKeyPath.data());
data["HTTP"][StrSSLCertPath.data()] = Application::GetSettingString(StrSSLCertPath.data());

View File

@@ -145,9 +145,9 @@ std::string THeartbeatThread::GenerateCall() {
<< "&version=" << Application::ServerVersionString()
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
<< "&name=" << Application::GetSettingString(StrName)
<< "&modlist=" << mResourceManager.TrimmedList()
<< "&modstotalsize=" << mResourceManager.MaxModSize()
<< "&modstotal=" << mResourceManager.ModsLoaded()
<< "&modlist=" << TResourceManager::FormatForBackend(mResourceManager.FileMap())
<< "&modstotalsize=" << mResourceManager.TotalModsSize()
<< "&modstotal=" << mResourceManager.LoadedModCount()
<< "&playerslist=" << GetPlayers()
<< "&desc=" << Application::GetSettingString(StrDescription);
return Ret.str();

View File

@@ -1054,6 +1054,15 @@ void TLuaEngine::StateThreadData::operator()() {
LuaArgs.push_back(sol::make_object(StateView, Table));
break;
}
case TLuaType::StringSizeTMap: {
auto Map = std::get<std::unordered_map<std::string, size_t>>(Arg);
auto Table = StateView.create_table();
for (const auto& [k, v] : Map) {
Table[k] = v;
}
LuaArgs.push_back(sol::make_object(StateView, Table));
break;
}
default:
beammp_error("Unknown argument type, passed as nil");
break;

View File

@@ -614,6 +614,34 @@ void TNetwork::SyncResources(TClient& c) {
}
}
ModMap TNetwork::GetClientMods(TClient& Client) {
auto AllMods = mResourceManager.FileMap();
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerRequestMods", "", Client.GetName(), Client.GetRoles(), Client.IsGuest(), Client.GetIdentifiers(), AllMods);
TLuaEngine::WaitForAll(Futures);
ModMap AllowedMods = AllMods;
for (const std::shared_ptr<TLuaResult>& Future : Futures) {
sol::table Result;
if (!Future->Error && Future->Result.is<sol::table>()) {
Result = Future->Result.as<sol::table>();
for (const auto& [name, size] : AllMods) {
auto val = Result.get<sol::optional<int>>(name);
if (!val.has_value()) {
AllowedMods.erase(name);
beammp_debugf("Not sending mod '{}' to player '{}' (from state '{}')", name, Client.GetName(), Future->StateId);
}
}
}
}
Client.AllowedMods = AllowedMods;
return AllowedMods;
}
void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet) {
if (Packet.empty())
return;
@@ -627,7 +655,7 @@ void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Pac
case 'S':
if (SubCode == 'R') {
beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
std::string ToSend = TResourceManager::FormatForClient(GetClientMods(c));
if (ToSend.empty())
ToSend = "-";
if (!TCPSend(c, StringToVector(ToSend))) {
@@ -641,24 +669,16 @@ void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Pac
}
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
beammp_infof("{} ({}) requesting : '{}'", c.GetName(), c.GetID(), UnsafeName);
beammp_infof("{} ({}) requesting mod: '{}'", c.GetName(), c.GetID(), UnsafeName);
if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, StringToVector("CO"))) {
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because the requested file doesn't contain a valid filename");
return;
}
beammp_warn("File " + UnsafeName + " is not a file!");
return;
}
auto FileName = fs::path(UnsafeName).filename().string();
FileName = Application::GetSettingString(StrResourceFolder) + "/Client/" + FileName;
auto FileName = UnsafeName;
if (!std::filesystem::exists(FileName)) {
auto res = TResourceManager::IsModValid(FileName, c.AllowedMods);
if (res.has_value()) {
if (!TCPSend(c, StringToVector("CO"))) {
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because the requested file doesn't exist or couldn't be accessed");
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because " + res.value());
}
beammp_warn("File " + UnsafeName + " could not be accessed!");
return;
}

View File

@@ -5,31 +5,80 @@
namespace fs = std::filesystem;
std::string TResourceManager::FormatForBackend(const ModMap& mods) {
std::string monkey;
for (const auto& [name, size] : mods) {
monkey += fs::path(name).filename().string() + ';';
}
return monkey;
}
std::string TResourceManager::FormatForClient(const ModMap& mods) {
std::string monkey;
for (const auto& [name, size] : mods) {
monkey += '/' + name + ';';
}
for (const auto& [name, size] : mods) {
monkey += std::to_string(size) + ';';
}
return monkey;
}
/// @brief Sanitizes a requested mod string
/// @param pathString Raw mod path string
/// @param mods List of allowed mods for this client
/// @return Error, if any
std::optional<std::string> TResourceManager::IsModValid(std::string& pathString, const ModMap& mods) {
auto path = fs::path(pathString);
if (!path.has_filename()) {
beammp_warn("File " + pathString + " is not a file!");
return { "the requested file doesn't contain a valid filename" };
}
auto BasePath = fs::path(Application::GetSettingString(StrResourceFolder) + "/Client");
auto CombinedPath = fs::path(BasePath.string() + pathString).lexically_normal();
// beammp_infof("path: {}, base: {}, combined: {}", pathString, BasePath.string(), CombinedPath.string());
if (!std::filesystem::exists(CombinedPath)) {
beammp_warn("File " + pathString + " could not be accessed!");
return { "the requested file doesn't exist or couldn't be accessed" };
}
auto relative = fs::relative(CombinedPath, BasePath);
if (mods.count(relative.string()) == 0) {
beammp_warn("File " + pathString + " is disallowed for this player!");
return { "the requested file is disallowed for this player" };
}
pathString = CombinedPath.string();
return {};
}
TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
std::string Path = Application::GetSettingString(StrResourceFolder) + "/Client";
if (!fs::exists(Path))
fs::create_directories(Path);
for (const auto& entry : fs::directory_iterator(Path)) {
std::string File(entry.path().string());
if (auto pos = File.find(".zip"); pos != std::string::npos) {
if (File.length() - pos == 4) {
std::replace(File.begin(), File.end(), '\\', '/');
mFileList += File + ';';
if (auto i = File.find_last_of('/'); i != std::string::npos) {
++i;
File = File.substr(i, pos - i);
}
mTrimmedList += "/" + fs::path(File).filename().string() + ';';
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
mMaxModSize += size_t(fs::file_size(entry.path()));
mModsLoaded++;
}
std::string BasePath = Application::GetSettingString(StrResourceFolder) + "/Client";
if (!fs::exists(BasePath))
fs::create_directories(BasePath);
std::vector<std::string> modNames;
auto iterator = fs::recursive_directory_iterator(BasePath, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied);
for (const auto& entry : iterator) {
if (iterator.depth() > 0 && !Application::GetSettingBool(StrIncludeSubdirectories))
continue;
if ((entry.is_regular_file() || entry.is_symlink()) && entry.path().extension() == ".zip") {
auto relativePath = fs::relative(entry.path(), BasePath);
beammp_infof("mod entry: {}", relativePath.string());
mMods[relativePath.string()] = entry.file_size();
mTotalModSize += entry.file_size();
}
}
if (mModsLoaded) {
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
if (!mMods.empty()) {
beammp_infof("Loaded {} mod{}", mMods.size(), mMods.size() != 1 ? 's' : ' ');
}
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);