diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index 96daa07041..2bd4bdb3eb 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -160,7 +160,7 @@ public class NetClient implements ApplicationListener{ throw new ValidateException(player, "Player has sent a message above the text limit."); } - String original = message; + Events.fire(new PlayerChatEvent(player, message)); //check if it's a command CommandResponse response = netServer.clientCommands.handleMessage(message, player); @@ -197,8 +197,6 @@ public class NetClient implements ApplicationListener{ player.sendMessage(text); } } - - Events.fire(new PlayerChatEvent(player, message, original)); } public static String colorizeName(int id, String name){ diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 774489a4a5..55a286b080 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -35,7 +35,7 @@ import static mindustry.Vars.*; public class NetServer implements ApplicationListener{ private final static int maxSnapshotSize = 430, timerBlockSync = 0; - private final static float serverSyncTime = 12, kickDuration = 30 * 1000, blockSyncTime = 60 * 8; + private final static float serverSyncTime = 12, blockSyncTime = 60 * 8; private final static Vec2 vector = new Vec2(); private final static Rect viewport = new Rect(); /** If a player goes away of their server-side coordinates by this distance, they get teleported back. */ @@ -115,7 +115,7 @@ public class NetServer implements ApplicationListener{ return; } - if(Time.millis() - info.lastKicked < kickDuration){ + if(Time.millis() < info.lastKicked){ con.kick(KickReason.recentKick); return; } diff --git a/core/src/mindustry/game/EventType.java b/core/src/mindustry/game/EventType.java index 5a9950b489..365e8f2b7c 100644 --- a/core/src/mindustry/game/EventType.java +++ b/core/src/mindustry/game/EventType.java @@ -64,13 +64,10 @@ public class EventType{ public static class PlayerChatEvent{ public final Player player; public final String message; - /** The original, unfiltered message. */ - public final String originalMessage; - public PlayerChatEvent(Player player, String message, String originalMessage){ + public PlayerChatEvent(Player player, String message){ this.player = player; this.message = message; - this.originalMessage = originalMessage; } } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index 9f068095d9..a5573afad8 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -2,6 +2,7 @@ package mindustry.net; import arc.*; import arc.struct.*; +import arc.util.*; import arc.util.ArcAnnotate.*; import mindustry.*; import mindustry.annotations.Annotations.*; @@ -19,6 +20,38 @@ public class Administration{ public Administration(){ load(); + + //anti-spam + addChatFilter((player, message) -> { + long resetTime = Config.messageRateLimit.num() * 1000; + if(Config.antiSpam.bool() && !player.isLocal && !player.isAdmin){ + //prevent people from spamming messages quickly + if(resetTime > 0 && Time.timeSinceMillis(player.getInfo().lastMessageTime) < resetTime){ + //supress message + player.sendMessage("[scarlet]You may only send messages every " + Config.messageRateLimit.num() + " seconds."); + player.getInfo().messageInfractions ++; + //kick player for spamming and prevent connection if they've done this several times + if(player.getInfo().messageInfractions >= Config.messageSpamKick.num() && Config.messageSpamKick.num() != 0){ + player.con.kick("You have been kicked for spamming.", 1000 * 60 * 2); + } + player.lastText = message; + return null; + }else{ + player.getInfo().messageInfractions = 0; + } + + //prevent players from sending the same message twice in the span of 50 seconds + if(message.equals(player.lastText) && Time.timeSinceMillis(player.getInfo().lastMessageTime) < 1000 * 50){ + player.sendMessage("[scarlet]You may not send the same message twice."); + return null; + } + + player.lastText = message; + player.getInfo().lastMessageTime = Time.millis(); + } + + return message; + }); } /** Adds a chat filter. This will transform the chat messages of every player. @@ -326,6 +359,9 @@ public class Administration{ crashReport("Whether to send crash reports.", false, "crashreport"), logging("Whether to log everything to files.", true), strict("Whether strict mode is on - corrects positions and prevents duplicate UUIDs.", true), + antiSpam("Whether spammers are automatically kicked and rate-limited.", true), + messageRateLimit("Message rate limit in seconds. 0 to disable.", 0), + messageSpamKick("How many times a player must send a message before the cooldown to get kicked. 0 to disable.", 3), socketInput("Allows a local application to control this server through a local TCP socket.", false, "socket", () -> Events.fire(Trigger.socketConfigChanged)), socketInputPort("The port for socket input.", 6859, () -> Events.fire(Trigger.socketConfigChanged)), socketInputAddress("The bind address for socket input.", "localhost", () -> Events.fire(Trigger.socketConfigChanged)), @@ -402,7 +438,11 @@ public class Administration{ public int timesKicked; public int timesJoined; public boolean banned, admin; - public long lastKicked; //last kicked timestamp + public long lastKicked; //last kicked time to expiration + + public transient long lastMessageTime; + public transient String lastSentMessage; + public transient int messageInfractions; PlayerInfo(String id){ this.id = id; diff --git a/core/src/mindustry/net/NetConnection.java b/core/src/mindustry/net/NetConnection.java index a09ab097ba..495b5b26f7 100644 --- a/core/src/mindustry/net/NetConnection.java +++ b/core/src/mindustry/net/NetConnection.java @@ -37,7 +37,7 @@ public abstract class NetConnection{ if(player != null && (reason == KickReason.kick || reason == KickReason.banned || reason == KickReason.vote) && player.uuid != null){ PlayerInfo info = netServer.admins.getInfo(player.uuid); info.timesKicked++; - info.lastKicked = Math.max(Time.millis(), info.lastKicked); + info.lastKicked = Math.max(Time.millis() + 30 * 1000, info.lastKicked); } Call.onKick(this, reason); @@ -49,12 +49,17 @@ public abstract class NetConnection{ /** Kick with an arbitrary reason. */ public void kick(String reason){ + kick(reason, 30 * 1000); + } + + /** Kick with an arbitrary reason, and a kick duration in milliseconds. */ + public void kick(String reason, int kickDuration){ Log.info("Kicking connection {0}; Reason: {1}", address, reason.replace("\n", " ")); if(player != null && player.uuid != null){ PlayerInfo info = netServer.admins.getInfo(player.uuid); info.timesKicked++; - info.lastKicked = Math.max(Time.millis(), info.lastKicked); + info.lastKicked = Math.max(Time.millis() + kickDuration, info.lastKicked); } Call.onKick(this, reason); diff --git a/gradle.properties b/gradle.properties index a60f51610b..052fc80569 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=c365173e586b84c7edd062d82675001c7b96b0b8 +archash=9f70d5a39a910bd855430d74e41d0b8753b47442