reworked antiflood. it now has some logic in core to take care of loading order nastiness, and to fully prevent trigger spamming
--HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401931
This commit is contained in:
parent
b10bf9d31a
commit
aed775162c
@ -34,6 +34,8 @@
|
||||
#include "sm_stringutil.h"
|
||||
#include "ConCmdManager.h"
|
||||
#include "PlayerManager.h"
|
||||
#include "Translator.h"
|
||||
#include "HalfLife2.h"
|
||||
|
||||
/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does.
|
||||
* While the OB build only runs on MM:S 1.6.0+ (SH 5+), the older one
|
||||
@ -55,6 +57,7 @@ extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegat
|
||||
|
||||
ChatTriggers g_ChatTriggers;
|
||||
bool g_bSupressSilentFails = false;
|
||||
CPhraseFile *g_pFloodPhrases = NULL;
|
||||
|
||||
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false),
|
||||
m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE)
|
||||
@ -103,6 +106,20 @@ ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key,
|
||||
return ConfigResult_Ignore;
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSourceModAllInitialized()
|
||||
{
|
||||
m_pShouldFloodBlock = g_Forwards.CreateForward("OnClientFloodCheck", ET_Event, 1, NULL, Param_Cell);
|
||||
m_pDidFloodBlock = g_Forwards.CreateForward("OnClientFloodResult", ET_Event, 2, NULL, Param_Cell, Param_Cell);
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSourceModAllInitialized_Post()
|
||||
{
|
||||
unsigned int file_id;
|
||||
|
||||
file_id = g_Translator.FindOrAddPhraseFile("antiflood.phrases.txt");
|
||||
g_pFloodPhrases = g_Translator.GetFileByIndex(file_id);
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSourceModGameInitialized()
|
||||
{
|
||||
unsigned int total = 2;
|
||||
@ -155,6 +172,9 @@ void ChatTriggers::OnSourceModShutdown()
|
||||
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
||||
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
||||
}
|
||||
|
||||
g_Forwards.ReleaseForward(m_pShouldFloodBlock);
|
||||
g_Forwards.ReleaseForward(m_pDidFloodBlock);
|
||||
}
|
||||
|
||||
#if defined ORANGEBOX_BUILD
|
||||
@ -167,6 +187,7 @@ void ChatTriggers::OnSayCommand_Pre()
|
||||
#endif
|
||||
int client = g_ConCmds.GetCommandClient();
|
||||
m_bIsChatTrigger = false;
|
||||
m_bWasFloodedMessage = false;
|
||||
|
||||
/* The server console cannot do this */
|
||||
if (client == 0)
|
||||
@ -181,6 +202,35 @@ void ChatTriggers::OnSayCommand_Pre()
|
||||
RETURN_META(MRES_IGNORED);
|
||||
}
|
||||
|
||||
/* Check if we need to block this message from being sent */
|
||||
if (ClientIsFlooding(client))
|
||||
{
|
||||
char buffer[128];
|
||||
|
||||
/* :TODO: log an error? */
|
||||
if (g_Translator.CoreTransEx(g_pFloodPhrases,
|
||||
client,
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"Flooding the server",
|
||||
NULL,
|
||||
NULL)
|
||||
!= Trans_Okay)
|
||||
{
|
||||
UTIL_Format(buffer, sizeof(buffer), "You are flooding the server!");
|
||||
}
|
||||
|
||||
/* :TODO: we should probably kick people who spam too much. */
|
||||
|
||||
char fullbuffer[192];
|
||||
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s", buffer);
|
||||
g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
|
||||
|
||||
m_bWasFloodedMessage = true;
|
||||
|
||||
RETURN_META(MRES_SUPERCEDE);
|
||||
}
|
||||
|
||||
/* Handle quoted string sets */
|
||||
bool is_quoted = false;
|
||||
if (args[0] == '"')
|
||||
@ -197,7 +247,9 @@ void ChatTriggers::OnSayCommand_Pre()
|
||||
{
|
||||
is_trigger = true;
|
||||
args = &args[m_PubTriggerSize];
|
||||
} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
|
||||
}
|
||||
else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0)
|
||||
{
|
||||
is_trigger = true;
|
||||
is_silent = true;
|
||||
args = &args[m_PrivTriggerSize];
|
||||
@ -250,6 +302,7 @@ void ChatTriggers::OnSayCommand_Post()
|
||||
#endif
|
||||
{
|
||||
m_bIsChatTrigger = false;
|
||||
m_bWasFloodedMessage = false;
|
||||
if (m_bWillProcessInPost)
|
||||
{
|
||||
/* Reset this for re-entrancy */
|
||||
@ -355,3 +408,35 @@ bool ChatTriggers::IsChatTrigger()
|
||||
{
|
||||
return m_bIsChatTrigger;
|
||||
}
|
||||
|
||||
bool ChatTriggers::ClientIsFlooding(int client)
|
||||
{
|
||||
bool is_flooding = false;
|
||||
|
||||
if (m_pShouldFloodBlock->GetFunctionCount() != 0)
|
||||
{
|
||||
cell_t res = 0;
|
||||
|
||||
m_pShouldFloodBlock->PushCell(client);
|
||||
m_pShouldFloodBlock->Execute(&res);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
is_flooding = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pDidFloodBlock->GetFunctionCount() != 0)
|
||||
{
|
||||
m_pDidFloodBlock->PushCell(client);
|
||||
m_pDidFloodBlock->PushCell(is_flooding ? 1 : 0);
|
||||
m_pDidFloodBlock->Execute(NULL);
|
||||
}
|
||||
|
||||
return is_flooding;
|
||||
}
|
||||
|
||||
bool ChatTriggers::WasFloodedMessage()
|
||||
{
|
||||
return m_bWasFloodedMessage;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "sourcemm_api.h"
|
||||
#include <IGameHelpers.h>
|
||||
#include <compat_wrappers.h>
|
||||
#include <IForwardSys.h>
|
||||
|
||||
class ChatTriggers : public SMGlobalClass
|
||||
{
|
||||
@ -43,6 +44,8 @@ public:
|
||||
ChatTriggers();
|
||||
~ChatTriggers();
|
||||
public: //SMGlobalClass
|
||||
void OnSourceModAllInitialized();
|
||||
void OnSourceModAllInitialized_Post();
|
||||
void OnSourceModGameInitialized();
|
||||
void OnSourceModShutdown();
|
||||
ConfigResult OnSourceModConfigChanged(const char *key,
|
||||
@ -62,8 +65,10 @@ public:
|
||||
unsigned int GetReplyTo();
|
||||
unsigned int SetReplyTo(unsigned int reply);
|
||||
bool IsChatTrigger();
|
||||
bool WasFloodedMessage();
|
||||
private:
|
||||
bool PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted);
|
||||
bool ClientIsFlooding(int client);
|
||||
private:
|
||||
ConCommand *m_pSayCmd;
|
||||
ConCommand *m_pSayTeamCmd;
|
||||
@ -74,8 +79,11 @@ private:
|
||||
bool m_bWillProcessInPost;
|
||||
bool m_bTriggerWasSilent;
|
||||
bool m_bIsChatTrigger;
|
||||
bool m_bWasFloodedMessage;
|
||||
unsigned int m_ReplyTo;
|
||||
char m_ToExecute[300];
|
||||
IForward *m_pShouldFloodBlock;
|
||||
IForward *m_pDidFloodBlock;
|
||||
};
|
||||
|
||||
extern ChatTriggers g_ChatTriggers;
|
||||
|
@ -299,6 +299,16 @@ void ConCmdManager::InternalDispatch(const CCommand &command)
|
||||
return;
|
||||
}
|
||||
|
||||
/* This is a hack to prevent say triggers from firing on messages that were
|
||||
* blocked because of flooding. We won't remove this, but the hack will get
|
||||
* "nicer" when we expose explicit say hooks.
|
||||
*/
|
||||
if (META_RESULT_STATUS == MRES_SUPERCEDE
|
||||
&& g_ChatTriggers.WasFloodedMessage())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t result = Pl_Continue;
|
||||
int args = command.ArgC() - 1;
|
||||
|
||||
@ -456,7 +466,9 @@ bool ConCmdManager::CheckCommandAccess(int client, const char *cmd, FlagBits cmd
|
||||
if (rule == Command_Allow)
|
||||
{
|
||||
return true;
|
||||
} else if (rule == Command_Deny) {
|
||||
}
|
||||
else if (rule == Command_Deny)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -486,18 +498,20 @@ bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmi
|
||||
if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
|
||||
!= Trans_Okay)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "You do not have access to this command");
|
||||
UTIL_Format(buffer, sizeof(buffer), "You do not have access to this command");
|
||||
}
|
||||
|
||||
unsigned int replyto = g_ChatTriggers.GetReplyTo();
|
||||
if (replyto == SM_REPLY_CONSOLE)
|
||||
{
|
||||
char fullbuffer[192];
|
||||
snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
|
||||
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
|
||||
engine->ClientPrintf(pEdict, fullbuffer);
|
||||
} else if (replyto == SM_REPLY_CHAT) {
|
||||
}
|
||||
else if (replyto == SM_REPLY_CHAT)
|
||||
{
|
||||
char fullbuffer[192];
|
||||
snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
|
||||
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
|
||||
g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
|
||||
}
|
||||
|
||||
|
@ -949,25 +949,19 @@ size_t Translator::Translate(char *buffer, size_t maxlength, void **params, cons
|
||||
return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
|
||||
}
|
||||
|
||||
TransError Translator::CoreTrans(int client,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
const char *phrase,
|
||||
void **params,
|
||||
size_t *outlen)
|
||||
TransError Translator::CoreTransEx(CPhraseFile *pFile,
|
||||
int client,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
const char *phrase,
|
||||
void **params,
|
||||
size_t *outlen)
|
||||
{
|
||||
/* :TODO: do language stuff here */
|
||||
|
||||
if (!g_pCorePhrases)
|
||||
{
|
||||
return Trans_BadPhraseFile;
|
||||
}
|
||||
|
||||
Translation trans;
|
||||
TransError err;
|
||||
|
||||
|
||||
/* Using server lang temporarily until client lang stuff is implemented */
|
||||
if ((err=g_pCorePhrases->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
|
||||
if ((err = pFile->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
@ -982,6 +976,21 @@ TransError Translator::CoreTrans(int client,
|
||||
return Trans_Okay;
|
||||
}
|
||||
|
||||
TransError Translator::CoreTrans(int client,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
const char *phrase,
|
||||
void **params,
|
||||
size_t *outlen)
|
||||
{
|
||||
if (!g_pCorePhrases)
|
||||
{
|
||||
return Trans_BadPhraseFile;
|
||||
}
|
||||
|
||||
return CoreTransEx(g_pCorePhrases, client, buffer, maxlength, phrase, params, outlen);
|
||||
}
|
||||
|
||||
unsigned int Translator::GetServerLanguage()
|
||||
{
|
||||
return m_ServerLang;
|
||||
|
@ -140,6 +140,13 @@ public:
|
||||
bool GetLanguageByName(const char *name, unsigned int *index);
|
||||
size_t Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans);
|
||||
CPhraseFile *GetFileByIndex(unsigned int index);
|
||||
TransError CoreTransEx(CPhraseFile *pFile,
|
||||
int client,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
const char *phrase,
|
||||
void **params,
|
||||
size_t *outlen=NULL);
|
||||
TransError CoreTrans(int client,
|
||||
char *buffer,
|
||||
size_t maxlength,
|
||||
|
@ -51,10 +51,6 @@ new Handle:sm_flood_time; /* Handle to sm_flood_time convar */
|
||||
|
||||
public OnPluginStart()
|
||||
{
|
||||
LoadTranslations("antiflood.phrases");
|
||||
|
||||
RegConsoleCmd("say", CheckChatFlood);
|
||||
RegConsoleCmd("say_team", CheckChatFlood);
|
||||
sm_flood_time = CreateConVar("sm_flood_time", "0.75", "Amount of time allowed between chat messages");
|
||||
}
|
||||
|
||||
@ -64,41 +60,63 @@ public OnClientPutInServer(client)
|
||||
g_FloodTokens[client] = 0;
|
||||
}
|
||||
|
||||
public Action:CheckChatFlood(client, args)
|
||||
new Float:max_chat;
|
||||
|
||||
public bool:OnClientFloodCheck(client)
|
||||
{
|
||||
/* Chat from server console shouldn't be checked for flooding */
|
||||
if (client == 0)
|
||||
max_chat = GetConVarFloat(sm_flood_time);
|
||||
|
||||
if (max_chat <= 0.0
|
||||
|| (GetUserFlagBits(client) & ADMFLAG_ROOT) == ADMFLAG_ROOT)
|
||||
{
|
||||
return Plugin_Continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
new Float:maxChat = GetConVarFloat(sm_flood_time);
|
||||
PrintToServer("OCFC: %f %f %d", g_LastTime[client], GetGameTime(), g_FloodTokens[client]);
|
||||
|
||||
if (maxChat > 0.0)
|
||||
if (g_LastTime[client] > GetGameTime())
|
||||
{
|
||||
new Float:curTime = GetGameTime();
|
||||
|
||||
if (g_LastTime[client] > curTime)
|
||||
/* If player has 3 or more flood tokens, block their message */
|
||||
if (g_FloodTokens[client] >= 3)
|
||||
{
|
||||
/* If player has 3 or more flood tokens, block their message */
|
||||
if (g_FloodTokens[client] >= 3)
|
||||
{
|
||||
PrintToChat(client, "[SM] %t", "Flooding the server");
|
||||
g_LastTime[client] = curTime + maxChat + 3.0;
|
||||
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
/* Add one flood token when player goes over chat time limit */
|
||||
g_FloodTokens[client]++;
|
||||
} else if (g_FloodTokens[client]) {
|
||||
/* Remove one flood token when player chats within time limit (slow decay) */
|
||||
g_FloodTokens[client]--;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Store last time of chat usage */
|
||||
g_LastTime[client] = curTime + maxChat;
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
public OnClientFloodResult(client, bool:blocked)
|
||||
{
|
||||
if (max_chat <= 0.0
|
||||
|| (GetUserFlagBits(client) & ADMFLAG_ROOT) == ADMFLAG_ROOT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
new Float:curTime = GetGameTime();
|
||||
new Float:newTime = curTime + max_chat;
|
||||
|
||||
PrintToServer("OCFR: %f, %f", g_LastTime[client], GetGameTime());
|
||||
|
||||
if (g_LastTime[client] > curTime)
|
||||
{
|
||||
/* If the last message was blocked, update their time limit */
|
||||
if (blocked)
|
||||
{
|
||||
newTime += 3.0;
|
||||
}
|
||||
/* Add one flood token when player goes over chat time limit */
|
||||
else if (g_FloodTokens[client] < 3)
|
||||
{
|
||||
g_FloodTokens[client]++;
|
||||
}
|
||||
}
|
||||
else if (g_FloodTokens[client] > 0)
|
||||
{
|
||||
/* Remove one flood token when player chats within time limit (slow decay) */
|
||||
g_FloodTokens[client]--;
|
||||
}
|
||||
|
||||
g_LastTime[client] = newTime;
|
||||
}
|
||||
|
@ -505,6 +505,32 @@ native Handle:ReadMapList(Handle:array=INVALID_HANDLE,
|
||||
*/
|
||||
native SetMapListCompatBind(const String:name[], const String:file[]);
|
||||
|
||||
/**
|
||||
* Called when a client has sent chat text. This must return either true or
|
||||
* false to indicate that a client is or is not spamming the server.
|
||||
*
|
||||
* The return value is a hint only. Core or another plugin may decide
|
||||
* otherwise.
|
||||
*
|
||||
* @param client Client index. The server (0) will never be passed.
|
||||
* @return True if client is spamming the server, false otherwise.
|
||||
*/
|
||||
forward bool:OnClientFloodCheck(client);
|
||||
|
||||
/**
|
||||
* Called after a client's flood check has been computed. This can be used
|
||||
* by antiflood algorithms to decay/increase flooding weights.
|
||||
*
|
||||
* Since the result from "OnClientFloodCheck" isn't guaranteed to be the
|
||||
* final result, it is generally a good idea to use this to play with other
|
||||
* algorithms nicely.
|
||||
*
|
||||
* @param client Client index. The server (0) will never be passed.
|
||||
* @param blocked True if client flooded last "say", false otherwise.
|
||||
* @noreturn
|
||||
*/
|
||||
forward OnClientFloodResult(client, bool:blocked);
|
||||
|
||||
#include <helpers>
|
||||
#include <entity>
|
||||
#include <entity_prop_stocks>
|
||||
|
Loading…
Reference in New Issue
Block a user