# Interval of saving important changes in the world, stated in seconds.
server_map_save_interval (Map save interval) float 5.3
+# Set the maximum character length of a chat message sent by clients.
+# chat_message_max_size int 500
+
+# Limit a single player to send X messages per 10 seconds.
+# chat_message_limit_per_10sec float 10.0
+
+# Kick player if send more than X messages per 10 seconds.
+# chat_message_limit_trigger_kick int 50
+
[**Physics]
movement_acceleration_default (Default acceleration) float 3
# type: float
# server_map_save_interval = 5.3
+# Set the maximum character length of a chat message sent by clients. (0 to disable)
+# type: integer
+# chat_message_max_size = 500
+
+# Limit a single player to send X messages per 10 seconds. (0 to disable)
+# type: float
+# chat_message_limit_per_10sec = 8.0
+
+# Kick player if send more than X messages per 10 seconds. (0 to disable)
+# type: integer
+# chat_message_limit_trigger_kick = 50
+
### Physics
# type: float
# profiler.default_report_format = txt
# The file path relative to your worldpath in which profiles will be saved to.
-#
+#
# type: string
# profiler.report_path = ""
settings->setDefault("server_unload_unused_data_timeout", "29");
settings->setDefault("max_objects_per_block", "49");
settings->setDefault("server_map_save_interval", "5.3");
+ settings->setDefault("chat_message_max_size", "500");
+ settings->setDefault("chat_message_limit_per_10sec", "8.0");
+ settings->setDefault("chat_message_limit_trigger_kick", "50");
settings->setDefault("sqlite_synchronous", "2");
settings->setDefault("full_block_send_enable_min_time_from_building", "2.0");
settings->setDefault("dedicated_server_step", "0.1");
std::wstring wname = narrow_to_wide(name);
std::wstring answer_to_sender = handleChat(name, wname, message,
- true, pkt->getPeerId());
+ true, dynamic_cast<RemotePlayer *>(player));
if (!answer_to_sender.empty()) {
// Send the answer to sender
SendChatMessage(pkt->getPeerId(), answer_to_sender);
}
} // action == 4
-
+
/*
5: rightclick air
*/
else if (action == 5) {
ItemStack item = playersao->getWieldedItem();
-
- actionstream << player->getName() << " activates "
+
+ actionstream << player->getName() << " activates "
<< item.name << std::endl;
-
+
if (m_script->item_OnSecondaryUse(
item, playersao)) {
if( playersao->setWieldedItem(item)) {
}
}
+// static config cache for remoteplayer
+bool RemotePlayer::m_setting_cache_loaded = false;
+float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f;
+u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0;
+
RemotePlayer::RemotePlayer(IGameDef *gamedef, const char *name):
Player(gamedef, name),
- m_sao(NULL)
+ m_sao(NULL),
+ m_last_chat_message_sent(time(NULL)),
+ m_chat_message_allowance(5.0f),
+ m_message_rate_overhead(0)
{
+ if (!RemotePlayer::m_setting_cache_loaded) {
+ RemotePlayer::m_setting_chat_message_limit_per_10sec =
+ g_settings->getFloat("chat_message_limit_per_10sec");
+ RemotePlayer::m_setting_chat_message_limit_trigger_kick =
+ g_settings->getU16("chat_message_limit_trigger_kick");
+ RemotePlayer::m_setting_cache_loaded = true;
+ }
movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS;
movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS;
movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS;
m_sao->setBasePosition(position);
}
+const RemotePlayerChatResult RemotePlayer::canSendChatMessage()
+{
+ // Rate limit messages
+ u32 now = time(NULL);
+ float time_passed = now - m_last_chat_message_sent;
+ m_last_chat_message_sent = now;
+
+ // If this feature is disabled
+ if (m_setting_chat_message_limit_per_10sec <= 0.0) {
+ return RPLAYER_CHATRESULT_OK;
+ }
+
+ m_chat_message_allowance += time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f);
+ if (m_chat_message_allowance > m_setting_chat_message_limit_per_10sec) {
+ m_chat_message_allowance = m_setting_chat_message_limit_per_10sec;
+ }
+
+ if (m_chat_message_allowance < 1.0f) {
+ infostream << "Player " << m_name
+ << " chat limited due to excessive message amount." << std::endl;
+
+ // Kick player if flooding is too intensive
+ m_message_rate_overhead++;
+ if (m_message_rate_overhead > RemotePlayer::m_setting_chat_message_limit_trigger_kick) {
+ return RPLAYER_CHATRESULT_KICK;
+ }
+
+ return RPLAYER_CHATRESULT_FLOODING;
+ }
+
+ // Reinit message overhead
+ if (m_message_rate_overhead > 0) {
+ m_message_rate_overhead = 0;
+ }
+
+ m_chat_message_allowance -= 1.0f;
+ return RPLAYER_CHATRESULT_OK;
+}
+
Mutex m_mutex;
};
-
+enum RemotePlayerChatResult {
+ RPLAYER_CHATRESULT_OK,
+ RPLAYER_CHATRESULT_FLOODING,
+ RPLAYER_CHATRESULT_KICK,
+};
/*
Player on the server
*/
{ m_sao = sao; }
void setPosition(const v3f &position);
+ const RemotePlayerChatResult canSendChatMessage();
+
private:
PlayerSAO *m_sao;
+
+ static bool m_setting_cache_loaded;
+ static float m_setting_chat_message_limit_per_10sec;
+ static u16 m_setting_chat_message_limit_trigger_kick;
+
+ u32 m_last_chat_message_sent;
+ float m_chat_message_allowance;
+ u16 m_message_rate_overhead;
};
#endif
add_legacy_abms(m_env, m_nodedef);
m_liquid_transform_every = g_settings->getFloat("liquid_update");
+ m_max_chatmessage_length = g_settings->getU16("chat_message_max_size");
}
Server::~Server()
}
std::wstring Server::handleChat(const std::string &name, const std::wstring &wname,
- const std::wstring &wmessage, bool check_shout_priv,
- u16 peer_id_to_avoid_sending)
+ const std::wstring &wmessage, bool check_shout_priv, RemotePlayer *player)
{
// If something goes wrong, this player is to blame
RollbackScopeActor rollback_scope(m_rollback,
if (ate)
return L"";
+ switch (player->canSendChatMessage()) {
+ case RPLAYER_CHATRESULT_FLOODING: {
+ std::wstringstream ws;
+ ws << L"You cannot send more messages. You are limited to "
+ << g_settings->getFloat("chat_message_limit_per_10sec")
+ << " messages per 10 seconds.";
+ return ws.str();
+ }
+ case RPLAYER_CHATRESULT_KICK:
+ DenyAccess_Legacy(player->peer_id, L"You have been kicked due to message flooding.");
+ return L"";
+ case RPLAYER_CHATRESULT_OK: break;
+ default: FATAL_ERROR("Unhandled chat filtering result found.");
+ }
+
+ if (m_max_chatmessage_length > 0 && wmessage.length() > m_max_chatmessage_length) {
+ return L"Your message exceed the maximum chat message limit set on the server. "
+ "It was refused. Send a shorter message";
+ }
+
// Commands are implemented in Lua, so only catch invalid
// commands that were not "eaten" and send an error back
if (wmessage[0] == L'/') {
std::vector<u16> clients = m_clients.getClientIDs();
+ u16 peer_id_to_avoid_sending = (player ? player->peer_id : PEER_ID_INEXISTENT);
for (u16 i = 0; i < clients.size(); i++) {
u16 cid = clients[i];
if (cid != peer_id_to_avoid_sending)
std::wstring handleChat(const std::string &name, const std::wstring &wname,
const std::wstring &wmessage,
bool check_shout_priv = false,
- u16 peer_id_to_avoid_sending = PEER_ID_INEXISTENT);
+ RemotePlayer *player = NULL);
void handleAdminChat(const ChatEventChat *evt);
v3f findSpawnPos();
// If true, do not allow multiple players and hide some multiplayer
// functionality
bool m_simple_singleplayer_mode;
+ u16 m_max_chatmessage_length;
// Thread can set; step() will throw as ServerError
MutexedVariable<std::string> m_async_fatal_error;