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

@@ -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);