Skip to content

Commit d4c7625

Browse files
committedOct 4, 2016
Chat: new settings to prevent spam
Added the following chat coreside features * Chat messages length limit * Message rate limiting * Message rate kicking Note: * handleChat now takes RemotePlayer pointer instead of u16 to remove useless lookups
1 parent 1079aea commit d4c7625

8 files changed

+125
-11
lines changed
 

‎builtin/settingtypes.txt

+9
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,15 @@ time_speed (Time speed) int 72
774774
# Interval of saving important changes in the world, stated in seconds.
775775
server_map_save_interval (Map save interval) float 5.3
776776

777+
# Set the maximum character length of a chat message sent by clients.
778+
# chat_message_max_size int 500
779+
780+
# Limit a single player to send X messages per 10 seconds.
781+
# chat_message_limit_per_10sec float 10.0
782+
783+
# Kick player if send more than X messages per 10 seconds.
784+
# chat_message_limit_trigger_kick int 50
785+
777786
[**Physics]
778787

779788
movement_acceleration_default (Default acceleration) float 3

‎minetest.conf.example

+13-1
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,18 @@
933933
# type: float
934934
# server_map_save_interval = 5.3
935935

936+
# Set the maximum character length of a chat message sent by clients. (0 to disable)
937+
# type: integer
938+
# chat_message_max_size = 500
939+
940+
# Limit a single player to send X messages per 10 seconds. (0 to disable)
941+
# type: float
942+
# chat_message_limit_per_10sec = 8.0
943+
944+
# Kick player if send more than X messages per 10 seconds. (0 to disable)
945+
# type: integer
946+
# chat_message_limit_trigger_kick = 50
947+
936948
### Physics
937949

938950
# type: float
@@ -1523,7 +1535,7 @@
15231535
# profiler.default_report_format = txt
15241536

15251537
# The file path relative to your worldpath in which profiles will be saved to.
1526-
#
1538+
#
15271539
# type: string
15281540
# profiler.report_path = ""
15291541

‎src/defaultsettings.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ void set_default_settings(Settings *settings)
285285
settings->setDefault("server_unload_unused_data_timeout", "29");
286286
settings->setDefault("max_objects_per_block", "49");
287287
settings->setDefault("server_map_save_interval", "5.3");
288+
settings->setDefault("chat_message_max_size", "500");
289+
settings->setDefault("chat_message_limit_per_10sec", "8.0");
290+
settings->setDefault("chat_message_limit_trigger_kick", "50");
288291
settings->setDefault("sqlite_synchronous", "2");
289292
settings->setDefault("full_block_send_enable_min_time_from_building", "2.0");
290293
settings->setDefault("dedicated_server_step", "0.1");

‎src/network/serverpackethandler.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,7 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
10651065
std::wstring wname = narrow_to_wide(name);
10661066

10671067
std::wstring answer_to_sender = handleChat(name, wname, message,
1068-
true, pkt->getPeerId());
1068+
true, dynamic_cast<RemotePlayer *>(player));
10691069
if (!answer_to_sender.empty()) {
10701070
// Send the answer to sender
10711071
SendChatMessage(pkt->getPeerId(), answer_to_sender);
@@ -1656,16 +1656,16 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
16561656
}
16571657

16581658
} // action == 4
1659-
1659+
16601660
/*
16611661
5: rightclick air
16621662
*/
16631663
else if (action == 5) {
16641664
ItemStack item = playersao->getWieldedItem();
1665-
1666-
actionstream << player->getName() << " activates "
1665+
1666+
actionstream << player->getName() << " activates "
16671667
<< item.name << std::endl;
1668-
1668+
16691669
if (m_script->item_OnSecondaryUse(
16701670
item, playersao)) {
16711671
if( playersao->setWieldedItem(item)) {

‎src/player.cpp

+55-1
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,25 @@ void Player::clearHud()
227227
}
228228
}
229229

230+
// static config cache for remoteplayer
231+
bool RemotePlayer::m_setting_cache_loaded = false;
232+
float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f;
233+
u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0;
234+
230235
RemotePlayer::RemotePlayer(IGameDef *gamedef, const char *name):
231236
Player(gamedef, name),
232-
m_sao(NULL)
237+
m_sao(NULL),
238+
m_last_chat_message_sent(time(NULL)),
239+
m_chat_message_allowance(5.0f),
240+
m_message_rate_overhead(0)
233241
{
242+
if (!RemotePlayer::m_setting_cache_loaded) {
243+
RemotePlayer::m_setting_chat_message_limit_per_10sec =
244+
g_settings->getFloat("chat_message_limit_per_10sec");
245+
RemotePlayer::m_setting_chat_message_limit_trigger_kick =
246+
g_settings->getU16("chat_message_limit_trigger_kick");
247+
RemotePlayer::m_setting_cache_loaded = true;
248+
}
234249
movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS;
235250
movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS;
236251
movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS;
@@ -304,3 +319,42 @@ void RemotePlayer::setPosition(const v3f &position)
304319
m_sao->setBasePosition(position);
305320
}
306321

322+
const RemotePlayerChatResult RemotePlayer::canSendChatMessage()
323+
{
324+
// Rate limit messages
325+
u32 now = time(NULL);
326+
float time_passed = now - m_last_chat_message_sent;
327+
m_last_chat_message_sent = now;
328+
329+
// If this feature is disabled
330+
if (m_setting_chat_message_limit_per_10sec <= 0.0) {
331+
return RPLAYER_CHATRESULT_OK;
332+
}
333+
334+
m_chat_message_allowance += time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f);
335+
if (m_chat_message_allowance > m_setting_chat_message_limit_per_10sec) {
336+
m_chat_message_allowance = m_setting_chat_message_limit_per_10sec;
337+
}
338+
339+
if (m_chat_message_allowance < 1.0f) {
340+
infostream << "Player " << m_name
341+
<< " chat limited due to excessive message amount." << std::endl;
342+
343+
// Kick player if flooding is too intensive
344+
m_message_rate_overhead++;
345+
if (m_message_rate_overhead > RemotePlayer::m_setting_chat_message_limit_trigger_kick) {
346+
return RPLAYER_CHATRESULT_KICK;
347+
}
348+
349+
return RPLAYER_CHATRESULT_FLOODING;
350+
}
351+
352+
// Reinit message overhead
353+
if (m_message_rate_overhead > 0) {
354+
m_message_rate_overhead = 0;
355+
}
356+
357+
m_chat_message_allowance -= 1.0f;
358+
return RPLAYER_CHATRESULT_OK;
359+
}
360+

‎src/player.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ class Player
439439
Mutex m_mutex;
440440
};
441441

442-
442+
enum RemotePlayerChatResult {
443+
RPLAYER_CHATRESULT_OK,
444+
RPLAYER_CHATRESULT_FLOODING,
445+
RPLAYER_CHATRESULT_KICK,
446+
};
443447
/*
444448
Player on the server
445449
*/
@@ -457,8 +461,18 @@ class RemotePlayer : public Player
457461
{ m_sao = sao; }
458462
void setPosition(const v3f &position);
459463

464+
const RemotePlayerChatResult canSendChatMessage();
465+
460466
private:
461467
PlayerSAO *m_sao;
468+
469+
static bool m_setting_cache_loaded;
470+
static float m_setting_chat_message_limit_per_10sec;
471+
static u16 m_setting_chat_message_limit_trigger_kick;
472+
473+
u32 m_last_chat_message_sent;
474+
float m_chat_message_allowance;
475+
u16 m_message_rate_overhead;
462476
};
463477

464478
#endif

‎src/server.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ Server::Server(
358358
add_legacy_abms(m_env, m_nodedef);
359359

360360
m_liquid_transform_every = g_settings->getFloat("liquid_update");
361+
m_max_chatmessage_length = g_settings->getU16("chat_message_max_size");
361362
}
362363

363364
Server::~Server()
@@ -2734,8 +2735,7 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt)
27342735
}
27352736

27362737
std::wstring Server::handleChat(const std::string &name, const std::wstring &wname,
2737-
const std::wstring &wmessage, bool check_shout_priv,
2738-
u16 peer_id_to_avoid_sending)
2738+
const std::wstring &wmessage, bool check_shout_priv, RemotePlayer *player)
27392739
{
27402740
// If something goes wrong, this player is to blame
27412741
RollbackScopeActor rollback_scope(m_rollback,
@@ -2753,6 +2753,26 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
27532753
if (ate)
27542754
return L"";
27552755

2756+
switch (player->canSendChatMessage()) {
2757+
case RPLAYER_CHATRESULT_FLOODING: {
2758+
std::wstringstream ws;
2759+
ws << L"You cannot send more messages. You are limited to "
2760+
<< g_settings->getFloat("chat_message_limit_per_10sec")
2761+
<< " messages per 10 seconds.";
2762+
return ws.str();
2763+
}
2764+
case RPLAYER_CHATRESULT_KICK:
2765+
DenyAccess_Legacy(player->peer_id, L"You have been kicked due to message flooding.");
2766+
return L"";
2767+
case RPLAYER_CHATRESULT_OK: break;
2768+
default: FATAL_ERROR("Unhandled chat filtering result found.");
2769+
}
2770+
2771+
if (m_max_chatmessage_length > 0 && wmessage.length() > m_max_chatmessage_length) {
2772+
return L"Your message exceed the maximum chat message limit set on the server. "
2773+
"It was refused. Send a shorter message";
2774+
}
2775+
27562776
// Commands are implemented in Lua, so only catch invalid
27572777
// commands that were not "eaten" and send an error back
27582778
if (wmessage[0] == L'/') {
@@ -2787,6 +2807,7 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
27872807

27882808
std::vector<u16> clients = m_clients.getClientIDs();
27892809

2810+
u16 peer_id_to_avoid_sending = (player ? player->peer_id : PEER_ID_INEXISTENT);
27902811
for (u16 i = 0; i < clients.size(); i++) {
27912812
u16 cid = clients[i];
27922813
if (cid != peer_id_to_avoid_sending)

‎src/server.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
487487
std::wstring handleChat(const std::string &name, const std::wstring &wname,
488488
const std::wstring &wmessage,
489489
bool check_shout_priv = false,
490-
u16 peer_id_to_avoid_sending = PEER_ID_INEXISTENT);
490+
RemotePlayer *player = NULL);
491491
void handleAdminChat(const ChatEventChat *evt);
492492

493493
v3f findSpawnPos();
@@ -522,6 +522,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
522522
// If true, do not allow multiple players and hide some multiplayer
523523
// functionality
524524
bool m_simple_singleplayer_mode;
525+
u16 m_max_chatmessage_length;
525526

526527
// Thread can set; step() will throw as ServerError
527528
MutexedVariable<std::string> m_async_fatal_error;

0 commit comments

Comments
 (0)
Please sign in to comment.