mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-29 18:27:36 +02:00
Merge pull request #823 from games647/805-more-antibot-features
Add support for blocking incoming connection on AntiBot detection
This commit is contained in:
@ -114,9 +114,9 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
}
|
||||
|
||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getRateLimiter()), this);
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getAntiBot()), this);
|
||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibListener.register(this, core.getRateLimiter());
|
||||
ProtocolLibListener.register(this, core.getAntiBot());
|
||||
|
||||
if (isPluginInstalled("floodgate")) {
|
||||
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
||||
|
@ -30,9 +30,12 @@ import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@ -50,9 +53,9 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
private final RateLimiter rateLimiter;
|
||||
private final AntiBotService antiBotService;
|
||||
|
||||
public ProtocolLibListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
public ProtocolLibListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params()
|
||||
.plugin(plugin)
|
||||
@ -60,15 +63,15 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
.optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.antiBotService = antiBotService;
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
||||
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||
// TODO: make synchronous processing, but do web or database requests async
|
||||
ProtocolLibrary.getProtocolManager()
|
||||
.getAsynchronousManager()
|
||||
.registerAsyncHandler(new ProtocolLibListener(plugin, rateLimiter))
|
||||
.registerAsyncHandler(new ProtocolLibListener(plugin, antiBotService))
|
||||
.start();
|
||||
}
|
||||
|
||||
@ -88,12 +91,26 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = packetEvent.getPacketType();
|
||||
if (packetType == START) {
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", sender);
|
||||
return;
|
||||
}
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
onLogin(packetEvent, sender);
|
||||
InetSocketAddress address = sender.getAddress();
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
sender.kickPlayer(message);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
//player.getName() won't work at this state
|
||||
onLogin(packetEvent, sender, username);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
onEncryptionBegin(packetEvent, sender);
|
||||
}
|
||||
@ -108,23 +125,24 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
BukkitLoginSession session = plugin.getSession(sender.getAddress());
|
||||
if (session == null) {
|
||||
plugin.getLog().warn("GameProfile {} tried to send encryption response at invalid state", sender.getAddress());
|
||||
sender.kickPlayer(plugin.getCore().getMessage("invalid-request"));
|
||||
} else {
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLogin(PacketEvent packetEvent, Player player) {
|
||||
private void onLogin(PacketEvent packetEvent, Player player, String username) {
|
||||
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
|
||||
String sessionKey = player.getAddress().toString();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(player.getAddress());
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
|
||||
if (packetEvent.getPacket().getMeta("original_name").isPresent()) {
|
||||
//username has been injected by ManualNameChange.java
|
||||
username = (String) packetEvent.getPacket().getMeta("original_name").get();
|
||||
|
@ -79,16 +79,20 @@ public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final Player player;
|
||||
|
||||
private final BukkitLoginSession session;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
private static Method encryptMethod;
|
||||
private static Method cipherMethod;
|
||||
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent,
|
||||
Player player, BukkitLoginSession session,
|
||||
byte[] sharedSecret, KeyPair keyPair) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.session = session;
|
||||
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
|
||||
this.serverKey = keyPair;
|
||||
}
|
||||
@ -96,14 +100,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BukkitLoginSession session = plugin.getSession(player.getAddress());
|
||||
if (session == null) {
|
||||
disconnect("invalid-request",
|
||||
"GameProfile {0} tried to send encryption response at invalid state",
|
||||
player.getAddress());
|
||||
} else {
|
||||
verifyResponse(session);
|
||||
}
|
||||
verifyResponse(session);
|
||||
} finally {
|
||||
//this is a fake packet; it shouldn't be sent to the server
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
@ -143,25 +140,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
Optional<Verification> 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 {
|
||||
|
@ -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<Player, CommandSende
|
||||
implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final AntiBotService antiBotService;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.antiBotService = antiBotService;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -64,19 +65,28 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", loginStartEvent.getConnection());
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getConnection().getProfile().getName();
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, address);
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(address);
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
loginStartEvent.denyLogin(message);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(address);
|
||||
|
||||
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||
super.onLogin(username, source);
|
||||
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||
super.onLogin(username, source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -100,7 +100,7 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
//events
|
||||
PluginManager pluginManager = getProxy().getPluginManager();
|
||||
|
||||
ConnectListener connectListener = new ConnectListener(this, core.getRateLimiter());
|
||||
ConnectListener connectListener = new ConnectListener(this, core.getAntiBot());
|
||||
pluginManager.registerListener(this, connectListener);
|
||||
pluginManager.registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
|
@ -31,8 +31,9 @@ import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask;
|
||||
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
|
||||
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.hooks.bedrock.FloodgateService;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.google.common.base.Throwables;
|
||||
@ -41,8 +42,10 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
@ -92,12 +95,12 @@ public class ConnectListener implements Listener {
|
||||
}
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final AntiBotService antiBotService;
|
||||
private final Property[] emptyProperties = {};
|
||||
|
||||
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter) {
|
||||
public ConnectListener(FastLoginBungee plugin, AntiBotService antiBotService) {
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.antiBotService = antiBotService;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -107,17 +110,28 @@ public class ConnectListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
|
||||
return;
|
||||
}
|
||||
|
||||
InetSocketAddress address = preLoginEvent.getConnection().getAddress();
|
||||
String username = connection.getName();
|
||||
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
||||
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");
|
||||
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
|
||||
preLoginEvent.setCancelled(true);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@ -140,7 +154,7 @@ public class ConnectListener implements Listener {
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(verifiedUUID);
|
||||
|
||||
// bungeecord will do this automatically so override it on disabled option
|
||||
// BungeeCord will do this automatically so override it on disabled option
|
||||
if (uniqueIdSetter != null) {
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
|
||||
|
@ -23,3 +23,40 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class AntiBotService {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private final RateLimiter rateLimiter;
|
||||
private final Action limitReachedAction;
|
||||
|
||||
public AntiBotService(Logger logger, RateLimiter rateLimiter, Action limitReachedAction) {
|
||||
this.logger = logger;
|
||||
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.limitReachedAction = limitReachedAction;
|
||||
}
|
||||
|
||||
public Action onIncomingConnection(InetSocketAddress clientAddress, String username) {
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
logger.warn("Anti-Bot join limit - Ignoring {}", clientAddress);
|
||||
return limitReachedAction;
|
||||
}
|
||||
|
||||
return Action.Continue;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
Ignore,
|
||||
|
||||
Block,
|
||||
|
||||
Continue;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core;
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RateLimiter {
|
@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core;
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
|
||||
import com.google.common.base.Ticker;
|
||||
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
@ -28,8 +28,10 @@ package com.github.games647.fastlogin.core.shared;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.TickingRateLimiter;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.antibot.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.antibot.TickingRateLimiter;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
@ -88,7 +90,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
private Configuration config;
|
||||
private SQLStorage storage;
|
||||
private RateLimiter rateLimiter;
|
||||
private AntiBotService antiBot;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
@ -122,7 +124,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
// 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<Proxy> proxies = config.getStringList("proxies")
|
||||
.stream()
|
||||
.map(HostAndPort::fromString)
|
||||
@ -144,20 +146,34 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
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<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public RateLimiter getRateLimiter() {
|
||||
return rateLimiter;
|
||||
public AntiBotService getAntiBot() {
|
||||
return antiBot;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
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));
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user