feature: fail2ban native integration
This commit is contained in:
parent
984c63532e
commit
4059bf4f82
@ -144,6 +144,7 @@ namespace Components
|
||||
Proto::Auth::Connect connectData;
|
||||
if (msg->cursize <= 12 || !connectData.ParseFromString(std::string(reinterpret_cast<char*>(&msg->data[12]), msg->cursize - 12)))
|
||||
{
|
||||
Logger::PrintFail2Ban("Failed connect attempt from IP address: {}\n", Network::AdrToString(address));
|
||||
Network::Send(address, "error\nInvalid connect packet!");
|
||||
return;
|
||||
}
|
||||
@ -161,6 +162,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::PrintFail2Ban("Failed connect attempt from IP address: {}\n", Network::AdrToString(address));
|
||||
Network::Send(address, "error\nInvalid infostring data!");
|
||||
}
|
||||
}
|
||||
@ -170,6 +172,7 @@ namespace Components
|
||||
// Validate proto data
|
||||
if (connectData.signature().empty() || connectData.publickey().empty() || connectData.token().empty() || connectData.infostring().empty())
|
||||
{
|
||||
Logger::PrintFail2Ban("Failed connect attempt from IP address: {}\n", Network::AdrToString(address));
|
||||
Network::Send(address, "error\nInvalid connect data!");
|
||||
return;
|
||||
}
|
||||
@ -184,6 +187,7 @@ namespace Components
|
||||
// Ensure there are enough params
|
||||
if (params.size() < 3)
|
||||
{
|
||||
Logger::PrintFail2Ban("Failed connect attempt from IP address: {}\n", Network::AdrToString(address));
|
||||
Network::Send(address, "error\nInvalid connect string!");
|
||||
return;
|
||||
}
|
||||
@ -197,6 +201,7 @@ namespace Components
|
||||
|
||||
if (steamId.empty() || challenge.empty())
|
||||
{
|
||||
Logger::PrintFail2Ban("Failed connect attempt from IP address: {}\n", Network::AdrToString(address));
|
||||
Network::Send(address, "error\nInvalid connect data!");
|
||||
return;
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ namespace Components
|
||||
std::recursive_mutex Logger::LoggingMutex;
|
||||
std::vector<Network::Address> Logger::LoggingAddresses[2];
|
||||
|
||||
Dvar::Var Logger::IW4x_oneLog;
|
||||
Dvar::Var Logger::IW4x_one_log;
|
||||
Dvar::Var Logger::IW4x_fail2ban_location;
|
||||
|
||||
void(*Logger::PipeCallback)(const std::string&) = nullptr;;
|
||||
|
||||
@ -43,8 +44,8 @@ namespace Components
|
||||
|
||||
if (shouldPrint)
|
||||
{
|
||||
std::printf("%s", msg.data());
|
||||
std::fflush(stdout);
|
||||
(void)std::fputs(msg.data(), stdout);
|
||||
(void)std::fflush(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -116,6 +117,38 @@ namespace Components
|
||||
MessagePrint(channel, msg);
|
||||
}
|
||||
|
||||
void Logger::PrintFail2BanInternal(const std::string_view& fmt, std::format_args&& args)
|
||||
{
|
||||
static const auto shouldPrint = []() -> bool
|
||||
{
|
||||
return Flags::HasFlag("fail2ban");
|
||||
}();
|
||||
|
||||
if (!shouldPrint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = std::vformat(fmt, args);
|
||||
|
||||
static auto log_next_time_stamp = true;
|
||||
if (log_next_time_stamp)
|
||||
{
|
||||
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
// Convert to local time
|
||||
std::tm timeInfo = *std::localtime(&now);
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << std::put_time(&timeInfo, "%Y-%m-%d %H:%M:%S ");
|
||||
|
||||
msg.insert(0, ss.str());
|
||||
}
|
||||
|
||||
log_next_time_stamp = (msg.find('\n') != std::string::npos);
|
||||
|
||||
Utils::IO::WriteFile(IW4x_fail2ban_location.get<std::string>(), msg, true);
|
||||
}
|
||||
|
||||
void Logger::Frame()
|
||||
{
|
||||
std::unique_lock _(MessageMutex);
|
||||
@ -233,7 +266,7 @@ namespace Components
|
||||
{
|
||||
if (std::strcmp(folder, "userraw") != 0)
|
||||
{
|
||||
if (IW4x_oneLog.get<bool>())
|
||||
if (IW4x_one_log.get<bool>())
|
||||
{
|
||||
strncpy_s(folder, 256, "userraw", _TRUNCATE);
|
||||
}
|
||||
@ -388,7 +421,6 @@ namespace Components
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
IW4x_oneLog = Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH, "Only write the game log to the 'userraw' OS folder");
|
||||
Utils::Hook(0x642139, BuildOSPath_Stub, HOOK_JUMP).install()->quick();
|
||||
|
||||
Scheduler::Loop(Frame, Scheduler::Pipeline::SERVER);
|
||||
@ -405,6 +437,11 @@ namespace Components
|
||||
}
|
||||
|
||||
Events::OnSVInit(AddServerCommands);
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
IW4x_one_log = Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH, "Only write the game log to the 'userraw' OS folder");
|
||||
IW4x_fail2ban_location = Dvar::Register<const char*>("iw4x_fail2ban_location", "/var/log/iw4x.log", Game::DVAR_NONE, "Fail2Ban logfile location");
|
||||
});
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
|
@ -18,6 +18,7 @@ namespace Components
|
||||
static void ErrorInternal(Game::errorParm_t error, const std::string_view& fmt, std::format_args&& args);
|
||||
static void PrintErrorInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args);
|
||||
static void WarningInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args);
|
||||
static void PrintFail2BanInternal(const std::string_view& fmt, std::format_args&& args);
|
||||
static void DebugInternal(const std::string_view& fmt, std::format_args&& args, const std::source_location& loc);
|
||||
|
||||
static void Print(const std::string_view& fmt)
|
||||
@ -80,6 +81,18 @@ namespace Components
|
||||
PrintErrorInternal(channel, fmt, std::make_format_args(args...));
|
||||
}
|
||||
|
||||
static void PrintFail2Ban(const std::string_view& fmt)
|
||||
{
|
||||
PrintFail2BanInternal(fmt, std::make_format_args(0));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static void PrintFail2Ban(const std::string_view& fmt, Args&&... args)
|
||||
{
|
||||
(Utils::String::SanitizeFormatArgs(args), ...);
|
||||
PrintFail2BanInternal(fmt, std::make_format_args(args...));
|
||||
}
|
||||
|
||||
struct FormatWithLocation
|
||||
{
|
||||
std::string_view format;
|
||||
@ -114,7 +127,8 @@ namespace Components
|
||||
static std::recursive_mutex LoggingMutex;
|
||||
static std::vector<Network::Address> LoggingAddresses[2];
|
||||
|
||||
static Dvar::Var IW4x_oneLog;
|
||||
static Dvar::Var IW4x_one_log;
|
||||
static Dvar::Var IW4x_fail2ban_location;
|
||||
|
||||
static void(*PipeCallback)(const std::string&);
|
||||
|
||||
|
@ -39,6 +39,11 @@ namespace Components
|
||||
return ntohs(this->address.port);
|
||||
}
|
||||
|
||||
unsigned short Network::Address::getPortRaw() const
|
||||
{
|
||||
return this->address.port;
|
||||
}
|
||||
|
||||
void Network::Address::setIP(DWORD ip)
|
||||
{
|
||||
this->address.ip.full = ip;
|
||||
@ -151,6 +156,31 @@ namespace Components
|
||||
return (this->getType() != Game::NA_BAD && this->getType() >= Game::NA_BOT && this->getType() <= Game::NA_IP);
|
||||
}
|
||||
|
||||
const char* Network::AdrToString(const Address& a, const bool port)
|
||||
{
|
||||
if (a.getType() == Game::netadrtype_t::NA_LOOPBACK)
|
||||
{
|
||||
return "loopback";
|
||||
}
|
||||
|
||||
if (a.getType() == Game::netadrtype_t::NA_BOT)
|
||||
{
|
||||
return "bot";
|
||||
}
|
||||
|
||||
if (a.getType() == Game::netadrtype_t::NA_IP || a.getType() == Game::netadrtype_t::NA_BROADCAST)
|
||||
{
|
||||
if (a.getPort() && port)
|
||||
{
|
||||
return Utils::String::VA("%u.%u.%u.%u:%u", a.getIP().bytes[0], a.getIP().bytes[1], a.getIP().bytes[2], a.getIP().bytes[3], htons(a.getPortRaw()));
|
||||
}
|
||||
|
||||
return Utils::String::VA("%u.%u.%u.%u", a.getIP().bytes[0], a.getIP().bytes[1], a.getIP().bytes[2], a.getIP().bytes[3]);
|
||||
}
|
||||
|
||||
return "bad";
|
||||
}
|
||||
|
||||
void Network::Send(Game::netsrc_t type, const Address& target, const std::string& data)
|
||||
{
|
||||
// Do not use NET_OutOfBandPrint. It only supports non-binary data!
|
||||
|
@ -22,6 +22,7 @@ namespace Components
|
||||
|
||||
void setPort(unsigned short port);
|
||||
[[nodiscard]] unsigned short getPort() const;
|
||||
[[nodiscard]] unsigned short getPortRaw() const;
|
||||
|
||||
void setIP(DWORD ip);
|
||||
void setIP(Game::netIP_t ip);
|
||||
@ -51,6 +52,8 @@ namespace Components
|
||||
|
||||
Network();
|
||||
|
||||
static const char* AdrToString(const Address& a, bool port = false);
|
||||
|
||||
static std::uint16_t GetPort();
|
||||
|
||||
// Send quake-styled binary data
|
||||
|
@ -180,7 +180,8 @@ namespace Components
|
||||
const auto pos = data.find_first_of(' ');
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString());
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -203,7 +204,8 @@ namespace Components
|
||||
|
||||
if (svPassword != password)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -213,7 +215,7 @@ namespace Components
|
||||
if (RConLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", Network::AdrToString(address), command);
|
||||
}
|
||||
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
@ -318,6 +320,7 @@ namespace Components
|
||||
const auto time = Game::Sys_Milliseconds();
|
||||
if (!IsRateLimitCheckDisabled() && !RateLimitCheck(address, time))
|
||||
{
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -341,6 +344,7 @@ namespace Components
|
||||
const auto time = Game::Sys_Milliseconds();
|
||||
if (!IsRateLimitCheckDisabled() && !RateLimitCheck(address, time))
|
||||
{
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -360,13 +364,15 @@ namespace Components
|
||||
Proto::RCon::Command directive;
|
||||
if (!directive.ParseFromString(data))
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "Unable to parse secure command from {}\n", address.getString());
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "Unable to parse secure command from {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils::Cryptography::RSA::VerifyMessage(key, directive.command(), directive.signature()))
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA signature verification failed for message from {}\n", address.getString());
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(address));
|
||||
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA signature verification failed for message from {}\n", Network::AdrToString(address));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,7 @@ namespace Components
|
||||
{
|
||||
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
|
||||
{
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(client->header.netchan.remoteAddress));
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n",
|
||||
client->name, client->reliableSequence, client->reliableAcknowledge);
|
||||
client->reliableAcknowledge = client->reliableSequence;
|
||||
|
@ -168,6 +168,7 @@ namespace Components
|
||||
voicePacket.dataSize = Game::MSG_ReadByte(msg);
|
||||
if (voicePacket.dataSize <= 0 || voicePacket.dataSize > MAX_VOICE_PACKET_DATA)
|
||||
{
|
||||
Logger::PrintFail2Ban("Invalid packet from IP address: {}\n", Network::AdrToString(cl->header.netchan.remoteAddress));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Received invalid voice packet of size {} from {}\n", voicePacket.dataSize, cl->name);
|
||||
return;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user