2022-02-27 07:53:44 -05:00
|
|
|
#include <STDInclude.hpp>
|
2022-12-26 07:07:24 -05:00
|
|
|
#include "Bans.hpp"
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
namespace Components
|
|
|
|
{
|
2023-01-25 13:20:44 -05:00
|
|
|
const char* Bans::BanListFile = "userraw/bans.json";
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
// Have only one instance of IW4x read/write the file
|
|
|
|
std::unique_lock<Utils::NamedMutex> Bans::Lock()
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
static Utils::NamedMutex mutex{"iw4x-ban-list-lock"};
|
|
|
|
std::unique_lock lock{mutex};
|
|
|
|
return lock;
|
|
|
|
}
|
2021-12-17 13:49:25 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
bool Bans::IsBanned(const banEntry& entry)
|
|
|
|
{
|
|
|
|
BanList list;
|
|
|
|
LoadBans(&list);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2017-02-18 03:42:55 -05:00
|
|
|
if (entry.first.bits)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-04 08:59:14 -04:00
|
|
|
for (const auto& idEntry : list.idList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2017-02-18 03:42:55 -05:00
|
|
|
if (idEntry.bits == entry.first.bits)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entry.second.full)
|
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
for (const auto& ipEntry : list.ipList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
if (ipEntry.full == entry.second.full)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
void Bans::InsertBan(const banEntry& entry)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
BanList list;
|
|
|
|
LoadBans(&list);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2017-02-18 03:42:55 -05:00
|
|
|
if (entry.first.bits)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
bool found = false;
|
2022-06-28 03:26:43 -04:00
|
|
|
for (const auto& idEntry : list.idList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2017-02-18 03:42:55 -05:00
|
|
|
if (idEntry.bits == entry.first.bits)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
list.idList.push_back(entry.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entry.second.full)
|
|
|
|
{
|
|
|
|
bool found = false;
|
2022-06-28 03:26:43 -04:00
|
|
|
for (const auto& ipEntry : list.ipList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
if (ipEntry.full == entry.second.full)
|
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
list.ipList.push_back(entry.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
SaveBans(&list);
|
2018-10-13 11:37:37 -04:00
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
void Bans::SaveBans(const BanList* list)
|
2018-10-13 11:37:37 -04:00
|
|
|
{
|
2023-01-25 13:20:44 -05:00
|
|
|
assert(list);
|
2022-06-28 03:26:43 -04:00
|
|
|
|
|
|
|
const auto _ = Lock();
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2017-01-19 16:23:59 -05:00
|
|
|
std::vector<std::string> idVector;
|
|
|
|
std::vector<std::string> ipVector;
|
|
|
|
|
2022-06-13 11:32:45 -04:00
|
|
|
for (const auto& idEntry : list->idList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-13 11:32:45 -04:00
|
|
|
idVector.emplace_back(Utils::String::VA("%llX", idEntry.bits));
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-06-13 11:32:45 -04:00
|
|
|
for (const auto& ipEntry : list->ipList)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-13 11:32:45 -04:00
|
|
|
ipVector.emplace_back(Utils::String::VA("%u.%u.%u.%u",
|
2017-01-19 16:23:59 -05:00
|
|
|
ipEntry.bytes[0] & 0xFF,
|
|
|
|
ipEntry.bytes[1] & 0xFF,
|
|
|
|
ipEntry.bytes[2] & 0xFF,
|
2023-01-25 13:20:44 -05:00
|
|
|
ipEntry.bytes[3] & 0xFF)
|
|
|
|
);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-07-29 15:54:18 -04:00
|
|
|
const nlohmann::json bans = nlohmann::json
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
{ "ip", ipVector },
|
|
|
|
{ "id", idVector },
|
|
|
|
};
|
|
|
|
|
2023-01-25 13:20:44 -05:00
|
|
|
Utils::IO::WriteFile(BanListFile, bans.dump());
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
void Bans::LoadBans(BanList* list)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2023-01-25 13:20:44 -05:00
|
|
|
assert(list);
|
2022-06-28 03:26:43 -04:00
|
|
|
|
|
|
|
const auto _ = Lock();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2023-01-25 13:20:44 -05:00
|
|
|
const auto bans = Utils::IO::ReadFile(BanListFile);
|
|
|
|
if (bans.empty())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
Logger::Debug("bans.json does not exist");
|
|
|
|
return;
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
nlohmann::json banData;
|
|
|
|
try
|
|
|
|
{
|
2023-01-25 13:20:44 -05:00
|
|
|
banData = nlohmann::json::parse(bans);
|
2022-10-16 11:17:42 -04:00
|
|
|
}
|
2023-01-25 13:20:44 -05:00
|
|
|
catch (const std::exception& ex)
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
2022-06-28 03:26:43 -04:00
|
|
|
return;
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2023-01-25 13:20:44 -05:00
|
|
|
if (!banData.contains("id") || !banData.contains("ip"))
|
|
|
|
{
|
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "bans.json contains invalid data\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
const auto& idList = banData["id"];
|
|
|
|
const auto& ipList = banData["ip"];
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
if (idList.is_array())
|
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
const nlohmann::json::array_t arr = idList;
|
2022-07-29 15:54:18 -04:00
|
|
|
|
|
|
|
for (auto &idEntry : arr)
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
|
|
|
if (idEntry.is_string())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
SteamID id;
|
2022-08-04 05:24:32 -04:00
|
|
|
auto guid = idEntry.get<std::string>();
|
|
|
|
id.bits = std::strtoull(guid.data(), nullptr, 16);
|
2022-06-28 03:26:43 -04:00
|
|
|
|
|
|
|
list->idList.push_back(id);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
if (ipList.is_array())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
const nlohmann::json::array_t arr = ipList;
|
2022-07-29 15:54:18 -04:00
|
|
|
|
|
|
|
for (auto &ipEntry : arr)
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
|
|
|
if (ipEntry.is_string())
|
|
|
|
{
|
2022-07-29 15:54:18 -04:00
|
|
|
Network::Address addr(ipEntry.get<std::string>());
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
list->ipList.push_back(addr.getIP());
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
2022-06-28 03:26:43 -04:00
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
void Bans::BanClient(Game::client_t* cl, const std::string& reason)
|
|
|
|
{
|
2017-01-19 16:23:59 -05:00
|
|
|
SteamID guid;
|
2022-06-28 03:26:43 -04:00
|
|
|
guid.bits = cl->steamID;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-08-20 06:09:41 -04:00
|
|
|
InsertBan({guid, cl->header.netchan.remoteAddress.ip});
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
Game::SV_DropClient(cl, reason.data(), true);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2018-10-13 11:37:37 -04:00
|
|
|
void Bans::UnbanClient(SteamID id)
|
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
BanList list;
|
|
|
|
LoadBans(&list);
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
const auto entry = std::find_if(list.idList.begin(), list.idList.end(), [&id](const SteamID& entry)
|
2018-10-13 11:37:37 -04:00
|
|
|
{
|
|
|
|
return id.bits == entry.bits;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (entry != list.idList.end())
|
|
|
|
{
|
|
|
|
list.idList.erase(entry);
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
SaveBans(&list);
|
2018-10-13 11:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Bans::UnbanClient(Game::netIP_t ip)
|
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
BanList list;
|
|
|
|
LoadBans(&list);
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
const auto entry = std::find_if(list.ipList.begin(), list.ipList.end(), [&ip](const Game::netIP_t& entry)
|
2018-10-13 11:37:37 -04:00
|
|
|
{
|
|
|
|
return ip.full == entry.full;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (entry != list.ipList.end())
|
|
|
|
{
|
|
|
|
list.ipList.erase(entry);
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
SaveBans(&list);
|
2018-10-13 11:37:37 -04:00
|
|
|
}
|
|
|
|
|
2017-01-19 16:23:59 -05:00
|
|
|
Bans::Bans()
|
|
|
|
{
|
2022-06-28 03:26:43 -04:00
|
|
|
Command::Add("banClient", [](Command::Params* params)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-11-15 17:18:00 -05:00
|
|
|
if (!Dedicated::IsRunning())
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
|
|
|
Logger::Print("Server is not running.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (params->size() < 2)
|
|
|
|
{
|
|
|
|
Logger::Print("{} <client number> : permanently ban a client\n", params->get(0));
|
|
|
|
return;
|
|
|
|
}
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
const auto* input = params->get(1);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
for (auto i = 0; input[i] != '\0'; ++i)
|
|
|
|
{
|
|
|
|
if (input[i] < '0' || input[i] > '9')
|
|
|
|
{
|
|
|
|
Logger::Print("Bad slot number: {}\n", input);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-23 10:08:21 -04:00
|
|
|
const auto clientNum = std::strtoul(input, nullptr, 10);
|
|
|
|
if (clientNum >= Game::MAX_CLIENTS)
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
2023-03-23 10:08:21 -04:00
|
|
|
Logger::Print("Bad client slot: {}\n", clientNum);
|
2022-06-28 03:26:43 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-16 09:07:09 -04:00
|
|
|
auto* cl = &Game::svs_clients[clientNum];
|
2023-03-23 10:08:21 -04:00
|
|
|
if (cl->header.state < Game::CS_ACTIVE)
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
2023-03-23 10:08:21 -04:00
|
|
|
Logger::Print("Client {} is not active\n", clientNum);
|
2022-06-28 03:26:43 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-25 11:37:28 -05:00
|
|
|
if (cl->bIsTestClient)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2);
|
2023-04-16 09:07:09 -04:00
|
|
|
BanClient(cl, reason);
|
2017-01-19 16:23:59 -05:00
|
|
|
});
|
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
Command::Add("unbanClient", [](Command::Params* params)
|
2018-10-13 11:37:37 -04:00
|
|
|
{
|
2022-11-15 17:18:00 -05:00
|
|
|
if (!Dedicated::IsRunning())
|
2022-06-28 03:26:43 -04:00
|
|
|
{
|
|
|
|
Logger::Print("Server is not running.\n");
|
|
|
|
return;
|
|
|
|
}
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
if (params->size() < 3)
|
|
|
|
{
|
|
|
|
Logger::Print("{} <type> <ip or guid>\n", params->get(0));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto* type = params->get(1);
|
2018-10-13 11:37:37 -04:00
|
|
|
|
|
|
|
if (type == "ip"s)
|
|
|
|
{
|
|
|
|
Network::Address address(params->get(2));
|
2022-06-28 03:26:43 -04:00
|
|
|
UnbanClient(address.getIP());
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-12 17:07:53 -04:00
|
|
|
Logger::Print("Unbanned IP {}\n", params->get(2));
|
2018-10-13 11:37:37 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
else if (type == "guid"s)
|
|
|
|
{
|
|
|
|
SteamID id;
|
2023-03-23 10:08:21 -04:00
|
|
|
id.bits = std::strtoull(params->get(2), nullptr, 16);
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-28 03:26:43 -04:00
|
|
|
UnbanClient(id);
|
2018-10-13 11:37:37 -04:00
|
|
|
|
2022-06-12 17:07:53 -04:00
|
|
|
Logger::Print("Unbanned GUID {}\n", params->get(2));
|
2018-10-13 11:37:37 -04:00
|
|
|
}
|
|
|
|
});
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|