Jessica James
3 years ago
6 changed files with 0 additions and 786 deletions
@ -1,23 +0,0 @@ |
|||||
; File: RenX.FuckCronus.ini |
|
||||
; |
|
||||
; Function: |
|
||||
; Sanitizes logs going to the Renegade X devbot (server list), and sanitizes commands coming from the Renegade X devbot. |
|
||||
; |
|
||||
; Rationale: |
|
||||
; Certain server owners report great unease about a specific Totem Arts sysadmin who is entirely unrestrained, who has |
|
||||
; entirely unrestrained access to player personal information such as IP addresses, Hardware IDs (MAC addresses), |
|
||||
; SteamIDs, and usernames, particularly since it's known that this sysadmin logs all of that information every time any |
|
||||
; player joins any Renegade X server. It's also known that this data is stored alongside C&C Renegade player information, |
|
||||
; meaning that C&C Renegade players and Renegade X players can be tracked across each game. |
|
||||
; By sanitizing personal information going to the DevBot, players can be feel safe knowing that their data is not being |
|
||||
; tracked by a sysadmin who is extremely well known to hoard, steal, and misuse data. |
|
||||
; |
|
||||
; Settings: |
|
||||
; SanitizeNames=Bool (Default: true; sanitizes all player names from messages) |
|
||||
; SanitizeIPs=Bool (Default: true; sanitizes all player IPs from all messages) |
|
||||
; SanitizeHWIDs=Bool (Default: true; sanitizes all player HWIDs from all messages) |
|
||||
; SanitizeSteamIDs=Bool (Default: true; sanitizes all player SteamIDs from all messages) |
|
||||
; SanitizeUnknownCmds=Bool (Default: true; sanitizes all unknown commands coming from devbot) |
|
||||
; SanitizeBlacklistedCmds=Bool (Default: true; sanitizes all non-informational commands coming from devbot) |
|
||||
; SuppressChatLogs=Bool (Default: true; suppresses all chat logs) |
|
||||
; |
|
@ -1,3 +0,0 @@ |
|||||
add_renx_plugin(RenX.FuckCronus |
|
||||
FuckCronus.cpp |
|
||||
FuckCronus.h) |
|
@ -1,672 +0,0 @@ |
|||||
/**
|
|
||||
* Copyright (C) 2021 Jessica James. All rights reserved. |
|
||||
* Written by Jessica James <jessica.aj@outlook.com> |
|
||||
*/ |
|
||||
|
|
||||
#include "FuckCronus.h" |
|
||||
#include <regex> |
|
||||
#include <random> |
|
||||
#include <memory> |
|
||||
#include <string_view> |
|
||||
#include <unordered_set> |
|
||||
#include "Jupiter/IRC.h" |
|
||||
#include "RenX_Functions.h" |
|
||||
#include "RenX_Server.h" |
|
||||
#include "RenX_PlayerInfo.h" |
|
||||
|
|
||||
using namespace Jupiter::literals; |
|
||||
using namespace std::literals; |
|
||||
constexpr const char* g_devbot_hostname = "devbot.ren-x.com"; |
|
||||
constexpr uint16_t g_devbot_port = 21337; |
|
||||
constexpr const char g_blank_steamid[] = "0x0000000000000000"; |
|
||||
constexpr std::chrono::steady_clock::duration g_reconnect_delay = std::chrono::seconds{15 }; |
|
||||
constexpr std::chrono::steady_clock::duration g_activity_timeout = std::chrono::seconds{ 60 }; |
|
||||
|
|
||||
int RenX_FuckCronusPlugin::think() { |
|
||||
for (auto& server_pair : m_server_info_map) { |
|
||||
auto server = server_pair.first; |
|
||||
auto& server_info = server_pair.second; |
|
||||
auto& devbot_socket = server_info.m_socket; |
|
||||
|
|
||||
if (!devbot_socket) { |
|
||||
// This should never happen
|
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
if (!server_info.m_devbot_connected) { |
|
||||
// Not connected; attempt retry if needed
|
|
||||
if (std::chrono::steady_clock::now() >= server_info.m_last_connect_attempt + g_reconnect_delay) { |
|
||||
if (devbot_socket->connect(g_devbot_hostname, g_devbot_port)) { |
|
||||
// There's some handshake garbage that needs to go on here so the devbot accepts us
|
|
||||
devbot_connected(*server, server_info); |
|
||||
server->sendLogChan(IRCCOLOR "03[RenX]" IRCCOLOR " Socket successfully reconnected to DevBot; game server now listed."); |
|
||||
} |
|
||||
else { |
|
||||
server->sendLogChan(IRCCOLOR "04[Error]" IRCCOLOR " Failed to reconnect to DevBot; game server not listed."); |
|
||||
} |
|
||||
|
|
||||
// Update our timings
|
|
||||
server_info.m_last_connect_attempt = std::chrono::steady_clock::now(); |
|
||||
server_info.m_last_activity = server_info.m_last_connect_attempt; |
|
||||
} |
|
||||
} |
|
||||
else if (std::chrono::steady_clock::now() - server_info.m_last_activity >= g_activity_timeout) // ping timeout
|
|
||||
{ |
|
||||
// Ping timeout; disconnect immediately
|
|
||||
server->sendLogChan(STRING_LITERAL_AS_REFERENCE(IRCCOLOR "04[Error]" IRCCOLOR " Disconnected from DevBot (ping timeout); game server is no longer listed.")); |
|
||||
devbot_disconnected(*server, server_info); |
|
||||
} |
|
||||
else { |
|
||||
// Connected and fine
|
|
||||
if (devbot_socket->recv() > 0) // Data received
|
|
||||
{ |
|
||||
Jupiter::ReadableString::TokenizeResult<Jupiter::Reference_String> result = Jupiter::ReferenceString::tokenize(devbot_socket->getBuffer(), '\n'); |
|
||||
if (result.token_count != 0) |
|
||||
{ |
|
||||
server_info.m_last_activity = std::chrono::steady_clock::now(); |
|
||||
server_info.m_last_line.concat(result.tokens[0]); |
|
||||
if (result.token_count != 1) |
|
||||
{ |
|
||||
// Process devbot message received
|
|
||||
process_devbot_message(server, server_info.m_last_line); |
|
||||
server_info.m_last_line = result.tokens[result.token_count - 1]; |
|
||||
|
|
||||
for (size_t index = 1; index != result.token_count - 1; ++index) |
|
||||
process_devbot_message(server, result.tokens[index]); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
else if (Jupiter::Socket::getLastError() == JUPITER_SOCK_EWOULDBLOCK) // Operation would block (no new data)
|
|
||||
{ |
|
||||
if (std::chrono::steady_clock::now() - server_info.m_last_activity >= g_activity_timeout) { |
|
||||
devbot_disconnected(*server, server_info); |
|
||||
} |
|
||||
} |
|
||||
else // This is a serious error
|
|
||||
{ |
|
||||
devbot_disconnected(*server, server_info); |
|
||||
|
|
||||
server->sendLogChan(IRCCOLOR "07[Warning]" IRCCOLOR " Connection to DevBot lost. Reconnection attempt in progress."); |
|
||||
if (devbot_socket->connect(g_devbot_hostname, g_devbot_port)) { |
|
||||
devbot_connected(*server, server_info); |
|
||||
server->sendLogChan(IRCCOLOR "06[Progress]" IRCCOLOR " Connection to DevBot reestablished. Initializing Renegade-X RCON protocol..."); |
|
||||
} |
|
||||
else { |
|
||||
server->sendLogChan(IRCCOLOR "04[Error]" IRCCOLOR " Connection to DevBot lost. Reconnection attempt failed."); |
|
||||
} |
|
||||
|
|
||||
// Update our timings
|
|
||||
server_info.m_last_connect_attempt = std::chrono::steady_clock::now(); |
|
||||
server_info.m_last_activity = server_info.m_last_connect_attempt; |
|
||||
|
|
||||
return 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
bool RenX_FuckCronusPlugin::initialize() { |
|
||||
m_init_time = std::chrono::steady_clock::now(); |
|
||||
m_sanitize_names = config.get<bool>("SanitizeNames"_jrs, true); |
|
||||
m_sanitize_ips = config.get<bool>("SanitizeIPs"_jrs, true); |
|
||||
m_sanitize_hwids = config.get<bool>("SanitizeHWIDs"_jrs, true); |
|
||||
m_sanitize_steam_ids = config.get<bool>("SanitizeSteamIDs"_jrs, true); |
|
||||
m_sanitize_unknown_commands = config.get<bool>("SanitizeUnknownCmds"_jrs, true); |
|
||||
m_sanitize_blacklisted_commands = config.get<bool>("SanitizeBlacklistedCmds"_jrs, true); |
|
||||
m_suppress_chat_logs = config.get<bool>("SuppressChatLogs"_jrs, true); |
|
||||
|
|
||||
return RenX::Plugin::initialize(); |
|
||||
} |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::RenX_OnServerCreate(RenX::Server &server) { |
|
||||
auto& server_info = m_server_info_map[&server]; |
|
||||
|
|
||||
server_info.m_socket = std::unique_ptr<Jupiter::TCPSocket>(new Jupiter::TCPSocket()); |
|
||||
if (server_info.m_socket->connect(g_devbot_hostname, g_devbot_port)) { |
|
||||
devbot_connected(server, server_info); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::RenX_OnServerDisconnect(RenX::Server &server, RenX::DisconnectReason reason) { |
|
||||
auto pair_itr = m_server_info_map.find(&server); |
|
||||
if (pair_itr != m_server_info_map.end()) { |
|
||||
auto& socket_ptr = pair_itr->second.m_socket; |
|
||||
if (socket_ptr) { |
|
||||
socket_ptr->close(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
m_server_info_map.erase(&server); |
|
||||
} |
|
||||
|
|
||||
// There's not truly any way to know for certain that a token is a player token without message-specific positional context,
|
|
||||
// but the format is just specific enough that there shouldn't be many false positives. For false positives that do occur,
|
|
||||
// we likely don't really care anyways, since this is just getting forwarded to the devbot
|
|
||||
bool is_player_token(const char* begin, const char* end) { |
|
||||
std::regex player_token_regex{ "[A-Za-z]*,b?[0-9]+,.+" }; |
|
||||
std::cmatch match_result; |
|
||||
return std::regex_match(begin, end, match_result, player_token_regex); |
|
||||
} |
|
||||
|
|
||||
/** Copied from Rx_TCPLink so that the same formatting bug is included */ |
|
||||
|
|
||||
const char hexadecimal_rep_table_upper[][3] = { |
|
||||
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", |
|
||||
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", |
|
||||
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", |
|
||||
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", |
|
||||
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", |
|
||||
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", |
|
||||
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", |
|
||||
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", |
|
||||
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", |
|
||||
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", |
|
||||
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", |
|
||||
"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", |
|
||||
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", |
|
||||
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", |
|
||||
"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", |
|
||||
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" |
|
||||
}; |
|
||||
|
|
||||
struct HWID { |
|
||||
union { |
|
||||
uint64_t hwid; |
|
||||
struct { |
|
||||
uint32_t left; |
|
||||
uint32_t right; |
|
||||
}; |
|
||||
}; |
|
||||
}; |
|
||||
|
|
||||
template<typename T> |
|
||||
std::string to_hex(T in_integer) { |
|
||||
std::string result; |
|
||||
uint8_t* begin = reinterpret_cast<uint8_t*>(&in_integer); |
|
||||
uint8_t* itr = begin + sizeof(T); |
|
||||
|
|
||||
result.reserve(sizeof(in_integer) * 2); |
|
||||
while (itr != begin) { |
|
||||
--itr; |
|
||||
result += hexadecimal_rep_table_upper[*itr]; |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
static const std::unordered_set<std::string_view> g_known_commands { |
|
||||
"addmap"sv, |
|
||||
"amsg"sv, |
|
||||
"botlist"sv, |
|
||||
"botvarlist"sv, |
|
||||
"buildinginfo"sv, |
|
||||
"binfo"sv, |
|
||||
"buildinglist"sv, |
|
||||
"blist"sv, |
|
||||
"cancelvote"sv, |
|
||||
"votestop"sv, |
|
||||
"changemap"sv, |
|
||||
"setmap"sv, |
|
||||
"changename"sv, |
|
||||
"changeplayername"sv, |
|
||||
"clientlist"sv, |
|
||||
"clientvarlist"sv, |
|
||||
"disarm"sv, |
|
||||
"disarmbeacon"sv, |
|
||||
"disarmb"sv, |
|
||||
"disarmc4"sv, |
|
||||
"dumpkilllog"sv, |
|
||||
"dumpkills"sv, |
|
||||
"endmap"sv, |
|
||||
"gameover"sv, |
|
||||
"endgame"sv, |
|
||||
"fkick"sv, |
|
||||
"forcekick"sv, |
|
||||
"forcenonseamless"sv, |
|
||||
"forceseamless"sv, |
|
||||
"gameinfo"sv, |
|
||||
"ginfo"sv, |
|
||||
"hascommand"sv, |
|
||||
"help"sv, |
|
||||
"hostprivatesay"sv, |
|
||||
"page"sv, |
|
||||
"hostsay"sv, |
|
||||
"say"sv, |
|
||||
"kick"sv, |
|
||||
"kickban"sv, |
|
||||
"kill"sv, |
|
||||
"listmutators"sv, |
|
||||
"listmutator"sv, |
|
||||
"mutatorlist"sv, |
|
||||
"mutatorslist"sv, |
|
||||
"loadmutator"sv, |
|
||||
"mutatorload"sv, |
|
||||
"lockbuildings"sv, |
|
||||
"lockhealth"sv, |
|
||||
"lockb"sv, |
|
||||
"lockh"sv, |
|
||||
"lb"sv, |
|
||||
"makeadmin"sv, |
|
||||
"map"sv, |
|
||||
"getmap"sv, |
|
||||
"mineban"sv, |
|
||||
"mban"sv, |
|
||||
"minelimit"sv, |
|
||||
"mlimit"sv, |
|
||||
"mineunban"sv, |
|
||||
"unmineban"sv, |
|
||||
"munban"sv, |
|
||||
"unmban"sv, |
|
||||
"mutateasnone"sv, |
|
||||
"mutateasplayer"sv, |
|
||||
"normalmode"sv, |
|
||||
"nmode"sv, |
|
||||
"ping"sv, |
|
||||
"playerinfo"sv, |
|
||||
"pamsg"sv, |
|
||||
"recorddemo"sv, |
|
||||
"demorecord"sv, |
|
||||
"demorec"sv, |
|
||||
"removemap"sv, |
|
||||
"rotation"sv, |
|
||||
"serverinfo"sv, |
|
||||
"sinfo"sv, |
|
||||
"setcommander"sv, |
|
||||
"spectatemode"sv, |
|
||||
"smode"sv, |
|
||||
"swapteams"sv, |
|
||||
"teamswap"sv, |
|
||||
"team"sv, |
|
||||
"changeteam"sv, |
|
||||
"team2"sv, |
|
||||
"changeteam2"sv, |
|
||||
"teaminfo"sv, |
|
||||
"tinfo"sv, |
|
||||
"textmute"sv, |
|
||||
"mute"sv, |
|
||||
"textunmute"sv, |
|
||||
"unmute"sv, |
|
||||
"togglebotvoice"sv, |
|
||||
"mutebot"sv, |
|
||||
"mutebots"sv, |
|
||||
"unmutebot"sv, |
|
||||
"unmutebots"sv, |
|
||||
"cheatbots"sv, |
|
||||
"togglecheatbots"sv, |
|
||||
"switchcheatbots"sv, |
|
||||
"forcebots"sv, |
|
||||
"toggleforcebots"sv, |
|
||||
"switchforcebots"sv, |
|
||||
"togglesuspect"sv, |
|
||||
"travel"sv, |
|
||||
"removemutator"sv, |
|
||||
"mutatorremove"sv, |
|
||||
"unloadmutator"sv, |
|
||||
"mutatorunload"sv, |
|
||||
"vehiclelimit"sv, |
|
||||
"vlimit"sv, |
|
||||
"warn"sv |
|
||||
}; |
|
||||
|
|
||||
static const std::unordered_set<std::string_view> g_whitelist_commands { |
|
||||
"map"sv, |
|
||||
"help"sv, |
|
||||
"playerinfo"sv, |
|
||||
"sinfo"sv, |
|
||||
"teaminfo"sv, |
|
||||
"hascommand"sv, |
|
||||
"buildinglist"sv, |
|
||||
"blist"sv, |
|
||||
"clientvarlist"sv, |
|
||||
"getmap"sv, |
|
||||
"buildinginfo"sv, |
|
||||
"botlist"sv, |
|
||||
"vlimit"sv, |
|
||||
"serverinfo"sv, |
|
||||
"ginfo"sv, |
|
||||
"rotation"sv, |
|
||||
"binfo"sv, |
|
||||
"vehiclelimit"sv, |
|
||||
"tinfo"sv, |
|
||||
"botvarlist"sv, |
|
||||
"minelimit"sv, |
|
||||
"gameinfo"sv, |
|
||||
"clientlist"sv, |
|
||||
"mlimit"sv, |
|
||||
"ping"sv, |
|
||||
}; |
|
||||
|
|
||||
static const std::unordered_set<std::string_view> g_blacklist_commands { |
|
||||
"addmap"sv, |
|
||||
"admin"sv, // Console command
|
|
||||
"amsg"sv, |
|
||||
"cancelvote"sv, |
|
||||
"votestop"sv, |
|
||||
"changemap"sv, |
|
||||
"setmap"sv, |
|
||||
"changename"sv, |
|
||||
"changeplayername"sv, |
|
||||
"disarm"sv, |
|
||||
"disarmbeacon"sv, |
|
||||
"disarmb"sv, |
|
||||
"disarmc4"sv, |
|
||||
"dumpkilllog"sv, |
|
||||
"dumpkills"sv, |
|
||||
"endmap"sv, |
|
||||
"gameover"sv, |
|
||||
"endgame"sv, |
|
||||
"fkick"sv, |
|
||||
"forcekick"sv, |
|
||||
"forcenonseamless"sv, |
|
||||
"forceseamless"sv, |
|
||||
"hostprivatesay"sv, |
|
||||
"page"sv, |
|
||||
"hostsay"sv, |
|
||||
"say"sv, |
|
||||
"kick"sv, |
|
||||
"kickban"sv, |
|
||||
"kill"sv, |
|
||||
"listmutators"sv, |
|
||||
"listmutator"sv, |
|
||||
"mutatorlist"sv, |
|
||||
"mutatorslist"sv, |
|
||||
"loadmutator"sv, |
|
||||
"mutatorload"sv, |
|
||||
"lockbuildings"sv, |
|
||||
"lockhealth"sv, |
|
||||
"lockb"sv, |
|
||||
"lockh"sv, |
|
||||
"lb"sv, |
|
||||
"makeadmin"sv, |
|
||||
"mineban"sv, |
|
||||
"mban"sv, |
|
||||
"mineunban"sv, |
|
||||
"unmineban"sv, |
|
||||
"munban"sv, |
|
||||
"unmban"sv, |
|
||||
"mutateasnone"sv, |
|
||||
"mutateasplayer"sv, |
|
||||
"normalmode"sv, |
|
||||
"nmode"sv, |
|
||||
"pamsg"sv, |
|
||||
"recorddemo"sv, |
|
||||
"demorecord"sv, |
|
||||
"demorec"sv, |
|
||||
"removemap"sv, |
|
||||
"setcommander"sv, |
|
||||
"spectatemode"sv, |
|
||||
"smode"sv, |
|
||||
"swapteams"sv, |
|
||||
"teamswap"sv, |
|
||||
"team"sv, |
|
||||
"changeteam"sv, |
|
||||
"team2"sv, |
|
||||
"changeteam2"sv, |
|
||||
"textmute"sv, |
|
||||
"mute"sv, |
|
||||
"textunmute"sv, |
|
||||
"unmute"sv, |
|
||||
"togglebotvoice"sv, |
|
||||
"mutebot"sv, |
|
||||
"mutebots"sv, |
|
||||
"unmutebot"sv, |
|
||||
"unmutebots"sv, |
|
||||
"cheatbots"sv, |
|
||||
"togglecheatbots"sv, |
|
||||
"switchcheatbots"sv, |
|
||||
"forcebots"sv, |
|
||||
"toggleforcebots"sv, |
|
||||
"switchforcebots"sv, |
|
||||
"togglesuspect"sv, |
|
||||
"travel"sv, |
|
||||
"removemutator"sv, |
|
||||
"mutatorremove"sv, |
|
||||
"unloadmutator"sv, |
|
||||
"mutatorunload"sv, |
|
||||
"warn"sv |
|
||||
}; |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::RenX_OnRaw(RenX::Server &server, const Jupiter::ReadableString &line) { |
|
||||
// Not parsing any escape sequences, so data gets sent to devbot exactly as it's received here. Copy tokens where needed to process escape sequences.
|
|
||||
Jupiter::ReadableString::TokenizeResult<Jupiter::String_Strict> tokens = Jupiter::StringS::tokenize(line, RenX::DelimC); |
|
||||
bool required_sanitization = false; |
|
||||
|
|
||||
// Ensure valid message received
|
|
||||
if (tokens.token_count == 0) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// Check that we already have a session for this server
|
|
||||
Jupiter::TCPSocket* socket{}; |
|
||||
{ |
|
||||
auto pair_itr = m_server_info_map.find(&server); |
|
||||
if (pair_itr == m_server_info_map.end()) { |
|
||||
return; |
|
||||
} |
|
||||
socket = pair_itr->second.m_socket.get(); |
|
||||
if (!socket) { |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (m_suppress_chat_logs |
|
||||
&& tokens.getToken(0) == "lCHAT") { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
auto findPlayerByIP = [&server](const Jupiter::ReadableString& in_ip) -> const RenX::PlayerInfo* { |
|
||||
// Parse into integer so we're doing int comparisons instead of strings
|
|
||||
auto ip32 = Jupiter::Socket::pton4(static_cast<std::string>(in_ip).c_str()); |
|
||||
if (ip32 == 0) { |
|
||||
return nullptr; |
|
||||
} |
|
||||
|
|
||||
// Search players
|
|
||||
for (const auto& player : server.players) { |
|
||||
if (player.ip32 == ip32) { |
|
||||
return &player; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nullptr; |
|
||||
}; |
|
||||
|
|
||||
auto findPlayerByHWID = [&server](const Jupiter::ReadableString& in_hwid) -> const RenX::PlayerInfo* { |
|
||||
if (in_hwid.isEmpty()) { |
|
||||
return nullptr; |
|
||||
} |
|
||||
|
|
||||
for (const auto& player : server.players) { |
|
||||
if (player.hwid == in_hwid) { |
|
||||
return &player; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nullptr; |
|
||||
}; |
|
||||
|
|
||||
auto findPlayerBySteamID = [&server](const Jupiter::ReadableString& in_steamid) -> const RenX::PlayerInfo* { |
|
||||
uint64_t steamid = in_steamid.asUnsignedLongLong(); |
|
||||
if (steamid == 0) { |
|
||||
return nullptr; |
|
||||
} |
|
||||
|
|
||||
for (const auto& player : server.players) { |
|
||||
if (player.steamid == steamid) { |
|
||||
return &player; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nullptr; |
|
||||
}; |
|
||||
|
|
||||
if (m_sanitize_names) { |
|
||||
for (size_t index = 0; index != tokens.token_count; ++index) { |
|
||||
auto& token = tokens.tokens[index]; |
|
||||
if (is_player_token(token.ptr(), token.ptr() + token.size())) { |
|
||||
// Get token pieces
|
|
||||
Jupiter::ReferenceString teamToken = Jupiter::ReferenceString::getToken(token, 0, ','); |
|
||||
Jupiter::ReferenceString idToken = Jupiter::ReferenceString::getToken(token, 1, ','); |
|
||||
|
|
||||
Jupiter::StringS replacement_player = teamToken; |
|
||||
replacement_player += ','; |
|
||||
replacement_player += idToken; |
|
||||
replacement_player += ','; |
|
||||
if (idToken.isNotEmpty() && idToken.get(0) == 'b') { |
|
||||
idToken.shiftRight(1); |
|
||||
} |
|
||||
|
|
||||
// Name (sanitized)
|
|
||||
replacement_player += "Player"; |
|
||||
replacement_player += idToken; |
|
||||
|
|
||||
token = replacement_player; |
|
||||
required_sanitization = true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (m_sanitize_ips || m_sanitize_hwids) { |
|
||||
// It's way too much of a pain to check for command usages, and it's not full-proof either (an alias or similar could be added).
|
|
||||
// So instead, we'll just search all messages for any tokens containing any player's IP or HWID.
|
|
||||
// This isn't terribly efficient, but there's only up to 64 players, so not a huge concern
|
|
||||
for (size_t index = 0; index != tokens.token_count; ++index) { |
|
||||
auto& token = tokens.tokens[index]; |
|
||||
const RenX::PlayerInfo* player; |
|
||||
if (m_sanitize_ips) { |
|
||||
player = findPlayerByIP(token); |
|
||||
if (player != nullptr) { |
|
||||
// Initialize the engine here using the init time, so that player fake IPs will be consistent
|
|
||||
// Also include player ID so we get different IPs between players and for each match
|
|
||||
std::mt19937 randgen(m_init_time.time_since_epoch().count() + (player->id * 2)); |
|
||||
std::uniform_int_distribution<uint32_t> dist(10, 200); |
|
||||
|
|
||||
// Replace real IP with fake
|
|
||||
token.format("%u.%u.%u.%u", |
|
||||
static_cast<unsigned int>(dist(randgen)), |
|
||||
static_cast<unsigned int>(dist(randgen)), |
|
||||
static_cast<unsigned int>(dist(randgen)), |
|
||||
static_cast<unsigned int>(dist(randgen))); |
|
||||
|
|
||||
required_sanitization = true; |
|
||||
continue; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (m_sanitize_hwids) { |
|
||||
player = findPlayerByHWID(token); |
|
||||
if (player != nullptr) { |
|
||||
// Initialize the engine here using the init time, so that player fake HWIDs will be consistent
|
|
||||
// Also include player ID so we get different HWIDs between players and for each match
|
|
||||
std::mt19937 randgen(m_init_time.time_since_epoch().count() + (player->id * 2 + 69)); |
|
||||
std::uniform_int_distribution<uint64_t> dist(0, 0x0000FFFFFFFFFFFFULL); |
|
||||
|
|
||||
HWID hwid{}; |
|
||||
hwid.hwid = dist(randgen); |
|
||||
|
|
||||
token = "m"; |
|
||||
token += to_hex(hwid.left) + to_hex(hwid.right); |
|
||||
|
|
||||
required_sanitization = true; |
|
||||
continue; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (m_sanitize_steam_ids) { |
|
||||
player = findPlayerBySteamID(token); |
|
||||
if (player != nullptr) { |
|
||||
token = g_blank_steamid; |
|
||||
continue; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// More sanitization checks here...
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Jupiter::StringS line_sanitized; |
|
||||
if (required_sanitization) { |
|
||||
// Construct line to send and send it
|
|
||||
line_sanitized = tokens.tokens[0]; |
|
||||
for (size_t index = 1; index != tokens.token_count; ++index) { |
|
||||
line_sanitized += RenX::DelimC; |
|
||||
line_sanitized += tokens.tokens[index]; |
|
||||
} |
|
||||
} |
|
||||
else { |
|
||||
// Forward line without modification
|
|
||||
line_sanitized = line; |
|
||||
} |
|
||||
|
|
||||
line_sanitized += '\n'; |
|
||||
socket->send(line_sanitized); |
|
||||
} |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::devbot_connected(RenX::Server& in_server, ext_server_info& in_server_info) { |
|
||||
in_server_info.m_devbot_connected = true; |
|
||||
in_server_info.m_socket->setBlocking(false); |
|
||||
|
|
||||
// New format: 004 | Game Version Number | Game Version
|
|
||||
auto& version_str = in_server.getGameVersion(); |
|
||||
std::string version_message = "v004"; |
|
||||
version_message += RenX::DelimC; |
|
||||
version_message += std::to_string(in_server.getGameVersionNumber()); |
|
||||
version_message += RenX::DelimC; |
|
||||
version_message.append(version_str.ptr(), version_str.size()); |
|
||||
version_message += '\n'; |
|
||||
|
|
||||
// Tack on aDevBot
|
|
||||
version_message += "aDevBot\n"; |
|
||||
|
|
||||
in_server_info.m_socket->send(version_message.c_str(), version_message.size()); |
|
||||
} |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::devbot_disconnected(RenX::Server&, ext_server_info& in_server_info) { |
|
||||
in_server_info.m_devbot_connected = false; |
|
||||
|
|
||||
if (in_server_info.m_socket) { |
|
||||
in_server_info.m_socket->close(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void RenX_FuckCronusPlugin::process_devbot_message(RenX::Server* in_server, const Jupiter::ReadableString& in_line) { |
|
||||
if (in_line.isEmpty()) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// Sanitize unknown & blacklisted commands
|
|
||||
if (in_line[0] == 'c' && in_line.size() > 1) { |
|
||||
if (m_sanitize_unknown_commands || m_sanitize_blacklisted_commands) { |
|
||||
Jupiter::ReferenceString command = Jupiter::ReferenceString::getToken(in_line, 0, ' '); |
|
||||
command.shiftRight(1); |
|
||||
std::string_view command_view{ command.ptr(), command.size() }; |
|
||||
|
|
||||
if (m_sanitize_unknown_commands |
|
||||
&& g_known_commands.find(command_view) == g_known_commands.end()) { |
|
||||
// Command not in known commands list; ignore it
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (m_sanitize_blacklisted_commands |
|
||||
&& g_blacklist_commands.find(command_view) != g_blacklist_commands.end()) { |
|
||||
// Command is blacklisted; ignore it
|
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Send line to game server
|
|
||||
Jupiter::StringS sanitized_message = in_line; |
|
||||
sanitized_message += '\n'; |
|
||||
in_server->sendData(in_line); |
|
||||
} |
|
||||
|
|
||||
// Plugin instantiation and entry point.
|
|
||||
RenX_FuckCronusPlugin pluginInstance; |
|
||||
|
|
||||
extern "C" JUPITER_EXPORT Jupiter::Plugin *getPlugin() |
|
||||
{ |
|
||||
return &pluginInstance; |
|
||||
} |
|
@ -1,51 +0,0 @@ |
|||||
/**
|
|
||||
* Copyright (C) 2021 Jessica James. All rights reserved. |
|
||||
* Written by Jessica James <jessica.aj@outlook.com> |
|
||||
*/ |
|
||||
|
|
||||
#if !defined _FUCKCRONUS_H_HEADER |
|
||||
#define _FUCKCRONUS_H_HEADER |
|
||||
|
|
||||
#include "Jupiter/Plugin.h" |
|
||||
#include "Jupiter/Reference_String.h" |
|
||||
#include "Jupiter/TCPSocket.h" |
|
||||
#include "RenX_Plugin.h" |
|
||||
|
|
||||
class RenX_FuckCronusPlugin : public RenX::Plugin |
|
||||
{ |
|
||||
public: // Jupiter::Thinker
|
|
||||
int think() override; |
|
||||
|
|
||||
public: // Jupiter::Plugin
|
|
||||
bool initialize() override; |
|
||||
|
|
||||
public: // RenX::Plugin
|
|
||||
void RenX_OnServerCreate(RenX::Server &server) override; |
|
||||
void RenX_OnServerDisconnect(RenX::Server &server, RenX::DisconnectReason reason) override; |
|
||||
void RenX_OnRaw(RenX::Server &server, const Jupiter::ReadableString &raw) override; |
|
||||
|
|
||||
private: |
|
||||
struct ext_server_info { |
|
||||
std::unique_ptr<Jupiter::TCPSocket> m_socket; |
|
||||
bool m_devbot_connected{}; |
|
||||
std::chrono::steady_clock::time_point m_last_connect_attempt{}; |
|
||||
std::chrono::steady_clock::time_point m_last_activity{}; |
|
||||
Jupiter::StringL m_last_line; |
|
||||
}; |
|
||||
|
|
||||
void devbot_connected(RenX::Server& in_server, ext_server_info& in_server_info); |
|
||||
void devbot_disconnected(RenX::Server& in_server, ext_server_info& in_server_info); |
|
||||
void process_devbot_message(RenX::Server* in_server, const Jupiter::ReadableString& in_line); |
|
||||
|
|
||||
std::unordered_map<RenX::Server*, ext_server_info> m_server_info_map; |
|
||||
std::chrono::steady_clock::time_point m_init_time{}; |
|
||||
bool m_sanitize_names{}; |
|
||||
bool m_sanitize_ips{}; |
|
||||
bool m_sanitize_hwids{}; |
|
||||
bool m_sanitize_steam_ids{}; |
|
||||
bool m_sanitize_unknown_commands{}; |
|
||||
bool m_sanitize_blacklisted_commands{}; |
|
||||
bool m_suppress_chat_logs{}; |
|
||||
}; |
|
||||
|
|
||||
#endif // _FUCKCRONUS_H_HEADER
|
|
@ -1,36 +0,0 @@ |
|||||
# Setup |
|
||||
1) Setup Jupiter on the same server as your game server |
|
||||
2) Add `RenX.Listen` and `RenX.FuckCronus` to your root `Config.ini` |
|
||||
3) Configure `RenX-ListenServer` (see RenX.Listen.ini) server block in `RenX.Core.ini`. These are the settings for any servers which connect to your bot. |
|
||||
4) In UDKRenegadeX.ini on the game server, replace `devbot.ren-x.com` with `127.0.0.1` |
|
||||
5) Startup Jupiter Bot |
|
||||
6) Startup game server |
|
||||
7) Verify your server is listed on the RenX server list |
|
||||
|
|
||||
### Optional: |
|
||||
It's also possible to use this plugin as a general proxy for devbot traffic, so that you can change the listed IP address of your game server. To do this: |
|
||||
1) Setup Jupiter on the server (i.e: proxy node) you wish to have listed instead of your game server |
|
||||
2) Follow the same steps as above, except use your proxy node's IP address instead of `127.0.0.1` |
|
||||
|
|
||||
# Function |
|
||||
|
|
||||
Sanitizes logs going to the Renegade X devbot (server list), and sanitizes commands coming from the Renegade X devbot. |
|
||||
|
|
||||
When all sanitizations are disabled, this can also be used to change the listed IP address for a Renegade X server, |
|
||||
without relying on Cronus. This is done by hosting Jupiter on the IP address you wish to have listed, and routing |
|
||||
traffic from there. |
|
||||
|
|
||||
# Rationale |
|
||||
|
|
||||
*Short*: Thieves shouldn't be trusted with personally identifiable information. This prevents that. |
|
||||
|
|
||||
*Long* (copied from RenX.FuckCronus.ini): |
|
||||
|
|
||||
Certain server owners report great unease about a specific Totem Arts sysadmin who is entirely unrestrained, who has |
|
||||
entirely unrestrained access to player personal information such as IP addresses, Hardware IDs (MAC addresses), |
|
||||
SteamIDs, and usernames, particularly since it's known that this sysadmin logs all of that information every time any |
|
||||
player joins any Renegade X server. It's also known that this data is stored alongside C&C Renegade player information, |
|
||||
meaning that C&C Renegade players and Renegade X players can be tracked across each game. |
|
||||
|
|
||||
By sanitizing personal information going to the DevBot, players can be feel safe knowing that their data is not being |
|
||||
tracked by a sysadmin who is extremely well known to hoard, steal, and misuse data. |
|
Loading…
Reference in new issue