diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java index ab4ceed1..9c2fa3a7 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java @@ -114,9 +114,9 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin response = resolver.hasJoined(requestedUsername, serverId, address); if (response.isPresent()) { - Verification verification = response.get(); - plugin.getLog().info("Profile {} has a verified premium account", requestedUsername); - String realUsername = verification.getName(); - if (realUsername == null) { - disconnect("invalid-session", "Username field null for {}", requestedUsername); - return; - } - - SkinProperty[] properties = verification.getProperties(); - if (properties.length > 0) { - session.setSkinProperty(properties[0]); - } - - session.setVerifiedUsername(realUsername); - session.setUuid(verification.getId()); - session.setVerified(true); - - setPremiumUUID(session.getUuid()); - receiveFakeStartPacket(realUsername); + encryptConnection(session, requestedUsername, response.get()); } else { //user tried to fake an authentication disconnect("invalid-session", "GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}", session.getRequestUsername(), socketAddress, serverId); @@ -171,6 +150,27 @@ public class VerifyResponseTask implements Runnable { } } + private void encryptConnection(BukkitLoginSession session, String requestedUsername, Verification verification) { + plugin.getLog().info("Profile {} has a verified premium account", requestedUsername); + String realUsername = verification.getName(); + if (realUsername == null) { + disconnect("invalid-session", "Username field null for {}", requestedUsername); + return; + } + + SkinProperty[] properties = verification.getProperties(); + if (properties.length > 0) { + session.setSkinProperty(properties[0]); + } + + session.setVerifiedUsername(realUsername); + session.setUuid(verification.getId()); + session.setVerified(true); + + setPremiumUUID(session.getUuid()); + receiveFakeStartPacket(realUsername); + } + private void setPremiumUUID(UUID premiumUUID) { if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) { try { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java index 608078c9..ce84824d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java @@ -29,8 +29,9 @@ import com.github.games647.craftapi.UUIDAdapter; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent; -import com.github.games647.fastlogin.core.RateLimiter; import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.antibot.AntiBotService; +import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; import com.github.games647.fastlogin.core.shared.JoinManagement; import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent; @@ -49,13 +50,13 @@ public class ProtocolSupportListener extends JoinManagement> { private Configuration config; private SQLStorage storage; - private RateLimiter rateLimiter; + private AntiBotService antiBot; private PasswordGenerator

passwordGenerator = new DefaultPasswordGenerator<>(); private AuthPlugin

authPlugin; @@ -122,7 +124,7 @@ public class FastLoginCore

> { // Initialize the resolver based on the config parameter this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false) ? new ProxyAgnosticMojangResolver() : new MojangResolver(); - rateLimiter = createRateLimiter(config.getSection("anti-bot")); + antiBot = createAntiBotService(config.getSection("anti-bot")); Set proxies = config.getStringList("proxies") .stream() .map(HostAndPort::fromString) @@ -144,20 +146,34 @@ public class FastLoginCore

> { resolver.setOutgoingAddresses(addresses); } - private RateLimiter createRateLimiter(Configuration botSection) { - boolean enabled = botSection.getBoolean("enabled", true); - if (!enabled) { + private AntiBotService createAntiBotService(Configuration botSection) { + RateLimiter rateLimiter; + if (botSection.getBoolean("enabled", true)) { + int maxCon = botSection.getInt("connections", 200); + long expireTime = botSection.getLong("expire", 5) * 60 * 1_000L; + if (expireTime > MAX_EXPIRE_RATE) { + expireTime = MAX_EXPIRE_RATE; + } + + rateLimiter = new TickingRateLimiter(Ticker.systemTicker(), maxCon, expireTime); + } else { // no-op rate limiter - return () -> true; + rateLimiter = () -> true; } - int maxCon = botSection.getInt("anti-bot.connections", 200); - long expireTime = botSection.getLong("anti-bot.expire", 5) * 60 * 1_000L; - if (expireTime > MAX_EXPIRE_RATE) { - expireTime = MAX_EXPIRE_RATE; + Action action = Action.Ignore; + switch (botSection.getString("action", "ignore")) { + case "ignore": + action = Action.Ignore; + break; + case "block": + action = Action.Block; + break; + default: + plugin.getLog().warn("Invalid anti bot action - defaulting to ignore"); } - return new TickingRateLimiter(Ticker.systemTicker(), maxCon, expireTime); + return new AntiBotService(plugin.getLog(), rateLimiter, action); } private Configuration loadFile(String fileName) throws IOException { @@ -285,8 +301,8 @@ public class FastLoginCore

> { return authPlugin; } - public RateLimiter getRateLimiter() { - return rateLimiter; + public AntiBotService getAntiBot() { + return antiBot; } public void setAuthPluginHook(AuthPlugin

authPlugin) { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 1b0905d6..3a2ab7a9 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -20,6 +20,9 @@ anti-bot: connections: 600 # Amount of minutes after the first connection got inserted will expire and made available expire: 10 + # Action - Which action should be performed when the bucket is full (too many connections) + # Allowed values are: 'ignore' (FastLogin drops handling the player) or 'block' (block this incoming connection) + action: 'ignore' # Request a premium login without forcing the player to type a command # diff --git a/core/src/main/resources/messages.yml b/core/src/main/resources/messages.yml index 8b315baa..33cadaad 100644 --- a/core/src/main/resources/messages.yml +++ b/core/src/main/resources/messages.yml @@ -48,6 +48,9 @@ not-premium-other: '&4Player is not in the premium list' # Admin wanted to change the premium of a user that isn't known to the plugin player-unknown: '&4Player not in the database' +# Player kicked from anti bot feature +kick-antibot: '&4Please wait a moment!' + # ========= Bukkit/Spigot ================ # The user skipped the authentication, because it was a premium player diff --git a/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 2c04b6e3..3f753cd4 100644 --- a/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java +++ b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java @@ -25,6 +25,8 @@ */ package com.github.games647.fastlogin.core; +import com.github.games647.fastlogin.core.antibot.TickingRateLimiter; + import java.time.Duration; import java.util.concurrent.TimeUnit; diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java index d57e6ad7..e15dd3ef 100644 --- a/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java @@ -96,7 +96,7 @@ public class FastLoginVelocity implements PlatformPlugin { return; } - server.getEventManager().register(this, new ConnectListener(this, core.getRateLimiter())); + server.getEventManager().register(this, new ConnectListener(this, core.getAntiBot())); server.getEventManager().register(this, new PluginMessageListener(this)); server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL)); server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL)); diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java index 4c03df77..33c8c311 100644 --- a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java @@ -26,8 +26,9 @@ package com.github.games647.fastlogin.velocity.listener; import com.github.games647.craftapi.UUIDAdapter; -import com.github.games647.fastlogin.core.RateLimiter; import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.antibot.AntiBotService; +import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; import com.github.games647.fastlogin.core.shared.LoginSession; import com.github.games647.fastlogin.velocity.FastLoginVelocity; import com.github.games647.fastlogin.velocity.VelocityLoginSession; @@ -37,6 +38,7 @@ import com.velocitypowered.api.event.Continuation; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.InboundConnection; @@ -44,19 +46,23 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + public class ConnectListener { private final FastLoginVelocity plugin; - private final RateLimiter rateLimiter; + private final AntiBotService antiBotService; - public ConnectListener(FastLoginVelocity plugin, RateLimiter rateLimiter) { + public ConnectListener(FastLoginVelocity plugin, AntiBotService antiBotService) { this.plugin = plugin; - this.rateLimiter = rateLimiter; + this.antiBotService = antiBotService; } @Subscribe @@ -66,16 +72,28 @@ public class ConnectListener { } InboundConnection connection = preLoginEvent.getConnection(); - if (!rateLimiter.tryAcquire()) { - plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection); - return; - } - String username = preLoginEvent.getUsername(); - plugin.getLog().info("Incoming login request for {} from {}", username, connection.getRemoteAddress()); + InetSocketAddress address = connection.getRemoteAddress(); + plugin.getLog().info("Incoming login request for {} from {}", username, address); - Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, connection, username, continuation, preLoginEvent); - plugin.getScheduler().runAsync(asyncPremiumCheck); + Action action = antiBotService.onIncomingConnection(address, username); + switch (action) { + case Ignore: + // just ignore + return; + case Block: + String message = plugin.getCore().getMessage("kick-antibot"); + TextComponent messageParsed = LegacyComponentSerializer.legacyAmpersand().deserialize(message); + + PreLoginComponentResult reason = PreLoginComponentResult.denied(messageParsed); + preLoginEvent.setResult(reason); + break; + case Continue: + default: + Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, connection, username, continuation, preLoginEvent); + plugin.getScheduler().runAsync(asyncPremiumCheck); + break; + } } @Subscribe