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 "BoostAliases.h"
#include "Common.h" #include "Common.h"
#include "Compat.h" #include "Compat.h"
#include "TResourceManager.h"
#include "VehicleData.h" #include "VehicleData.h"
class TServer; class TServer;
@@ -103,6 +104,8 @@ public:
TimeType::time_point ConnectionTime {}; TimeType::time_point ConnectionTime {};
ModMap AllowedMods;
private: private:
void InsertVehicle(int ID, const std::string& Data); 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 StrSendErrors = "Misc.SendErrors";
constexpr std::string_view StrSendErrorsMessageEnabled = "Misc.SendErrorsShowMessage"; constexpr std::string_view StrSendErrorsMessageEnabled = "Misc.SendErrorsShowMessage";
constexpr std::string_view StrHideUpdateMessages = "Misc.ImScaredOfUpdates"; constexpr std::string_view StrHideUpdateMessages = "Misc.ImScaredOfUpdates";
constexpr std::string_view StrIncludeSubdirectories = "Misc.IncludeSubdirectories";
// HTTP // HTTP
constexpr std::string_view StrHTTPServerEnabled = "HTTP.HTTPServerEnabled"; constexpr std::string_view StrHTTPServerEnabled = "HTTP.HTTPServerEnabled";
@@ -79,6 +80,9 @@ struct Version {
template <typename T> template <typename T>
using SparseArray = std::unordered_map<size_t, 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::variant;
using boost::container::flat_map; 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 * 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 { enum TLuaType {
String = 0, String = 0,
Int = 1, Int = 1,
Json = 2, Json = 2,
Bool = 3, Bool = 3,
StringStringMap = 4, StringStringMap = 4,
Float = 5, StringSizeTMap = 5,
Float = 6,
}; };
class TLuaPlugin; class TLuaPlugin;

View File

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

View File

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

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"); 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()); 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`"); 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 // HTTP
data["HTTP"][StrSSLKeyPath.data()] = Application::GetSettingString(StrSSLKeyPath.data()); data["HTTP"][StrSSLKeyPath.data()] = Application::GetSettingString(StrSSLKeyPath.data());
data["HTTP"][StrSSLCertPath.data()] = Application::GetSettingString(StrSSLCertPath.data()); data["HTTP"][StrSSLCertPath.data()] = Application::GetSettingString(StrSSLCertPath.data());

View File

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

View File

@@ -1054,6 +1054,15 @@ void TLuaEngine::StateThreadData::operator()() {
LuaArgs.push_back(sol::make_object(StateView, Table)); LuaArgs.push_back(sol::make_object(StateView, Table));
break; 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: default:
beammp_error("Unknown argument type, passed as nil"); beammp_error("Unknown argument type, passed as nil");
break; 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) { void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet) {
if (Packet.empty()) if (Packet.empty())
return; return;
@@ -627,7 +655,7 @@ void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Pac
case 'S': case 'S':
if (SubCode == 'R') { if (SubCode == 'R') {
beammp_debug("Sending Mod Info"); beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes(); std::string ToSend = TResourceManager::FormatForClient(GetClientMods(c));
if (ToSend.empty()) if (ToSend.empty())
ToSend = "-"; ToSend = "-";
if (!TCPSend(c, StringToVector(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) { 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()) { auto FileName = UnsafeName;
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;
if (!std::filesystem::exists(FileName)) { auto res = TResourceManager::IsModValid(FileName, c.AllowedMods);
if (res.has_value()) {
if (!TCPSend(c, StringToVector("CO"))) { 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; return;
} }

View File

@@ -5,31 +5,80 @@
namespace fs = std::filesystem; 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() { TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting); Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
std::string Path = Application::GetSettingString(StrResourceFolder) + "/Client"; std::string BasePath = Application::GetSettingString(StrResourceFolder) + "/Client";
if (!fs::exists(Path)) if (!fs::exists(BasePath))
fs::create_directories(Path); fs::create_directories(BasePath);
for (const auto& entry : fs::directory_iterator(Path)) { std::vector<std::string> modNames;
std::string File(entry.path().string());
if (auto pos = File.find(".zip"); pos != std::string::npos) { auto iterator = fs::recursive_directory_iterator(BasePath, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied);
if (File.length() - pos == 4) { for (const auto& entry : iterator) {
std::replace(File.begin(), File.end(), '\\', '/'); if (iterator.depth() > 0 && !Application::GetSettingBool(StrIncludeSubdirectories))
mFileList += File + ';'; continue;
if (auto i = File.find_last_of('/'); i != std::string::npos) { if ((entry.is_regular_file() || entry.is_symlink()) && entry.path().extension() == ".zip") {
++i; auto relativePath = fs::relative(entry.path(), BasePath);
File = File.substr(i, pos - i); beammp_infof("mod entry: {}", relativePath.string());
}
mTrimmedList += "/" + fs::path(File).filename().string() + ';'; mMods[relativePath.string()] = entry.file_size();
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';'; mTotalModSize += entry.file_size();
mMaxModSize += size_t(fs::file_size(entry.path()));
mModsLoaded++;
}
} }
} }
if (mModsLoaded) { if (!mMods.empty()) {
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods"); beammp_infof("Loaded {} mod{}", mMods.size(), mMods.size() != 1 ? 's' : ' ');
} }
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);