mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-30 10:47:33 +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")) {
|
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")) {
|
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||||
ProtocolLibListener.register(this, core.getRateLimiter());
|
ProtocolLibListener.register(this, core.getAntiBot());
|
||||||
|
|
||||||
if (isPluginInstalled("floodgate")) {
|
if (isPluginInstalled("floodgate")) {
|
||||||
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
||||||
|
@ -30,9 +30,12 @@ import com.comphenix.protocol.ProtocolLibrary;
|
|||||||
import com.comphenix.protocol.events.PacketAdapter;
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
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.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.KeyPair;
|
||||||
import java.security.SecureRandom;
|
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
|
//just create a new once on plugin enable. This used for verify token generation
|
||||||
private final SecureRandom random = new SecureRandom();
|
private final SecureRandom random = new SecureRandom();
|
||||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
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
|
//run async in order to not block the server, because we are making api calls to Mojang
|
||||||
super(params()
|
super(params()
|
||||||
.plugin(plugin)
|
.plugin(plugin)
|
||||||
@ -60,15 +63,15 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
.optionAsync());
|
.optionAsync());
|
||||||
|
|
||||||
this.plugin = plugin;
|
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
|
// 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
|
// TODO: make synchronous processing, but do web or database requests async
|
||||||
ProtocolLibrary.getProtocolManager()
|
ProtocolLibrary.getProtocolManager()
|
||||||
.getAsynchronousManager()
|
.getAsynchronousManager()
|
||||||
.registerAsyncHandler(new ProtocolLibListener(plugin, rateLimiter))
|
.registerAsyncHandler(new ProtocolLibListener(plugin, antiBotService))
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,12 +91,26 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
Player sender = packetEvent.getPlayer();
|
Player sender = packetEvent.getPlayer();
|
||||||
PacketType packetType = packetEvent.getPacketType();
|
PacketType packetType = packetEvent.getPacketType();
|
||||||
if (packetType == START) {
|
if (packetType == START) {
|
||||||
if (!rateLimiter.tryAcquire()) {
|
PacketContainer packet = packetEvent.getPacket();
|
||||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", sender);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
onEncryptionBegin(packetEvent, sender);
|
onEncryptionBegin(packetEvent, sender);
|
||||||
}
|
}
|
||||||
@ -108,23 +125,24 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
|
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
|
||||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||||
|
|
||||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
BukkitLoginSession session = plugin.getSession(sender.getAddress());
|
||||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
|
if (session == null) {
|
||||||
plugin.getScheduler().runAsync(verifyTask);
|
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
|
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
|
||||||
String sessionKey = player.getAddress().toString();
|
String sessionKey = player.getAddress().toString();
|
||||||
|
|
||||||
//remove old data every time on a new login in order to keep the session only for one person
|
//remove old data every time on a new login in order to keep the session only for one person
|
||||||
plugin.removeSession(player.getAddress());
|
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()) {
|
if (packetEvent.getPacket().getMeta("original_name").isPresent()) {
|
||||||
//username has been injected by ManualNameChange.java
|
//username has been injected by ManualNameChange.java
|
||||||
username = (String) packetEvent.getPacket().getMeta("original_name").get();
|
username = (String) packetEvent.getPacket().getMeta("original_name").get();
|
||||||
|
@ -79,16 +79,20 @@ public class VerifyResponseTask implements Runnable {
|
|||||||
|
|
||||||
private final Player player;
|
private final Player player;
|
||||||
|
|
||||||
|
private final BukkitLoginSession session;
|
||||||
|
|
||||||
private final byte[] sharedSecret;
|
private final byte[] sharedSecret;
|
||||||
|
|
||||||
private static Method encryptMethod;
|
private static Method encryptMethod;
|
||||||
private static Method cipherMethod;
|
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) {
|
byte[] sharedSecret, KeyPair keyPair) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.packetEvent = packetEvent;
|
this.packetEvent = packetEvent;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
this.session = session;
|
||||||
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
|
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
|
||||||
this.serverKey = keyPair;
|
this.serverKey = keyPair;
|
||||||
}
|
}
|
||||||
@ -96,14 +100,7 @@ public class VerifyResponseTask implements Runnable {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
BukkitLoginSession session = plugin.getSession(player.getAddress());
|
verifyResponse(session);
|
||||||
if (session == null) {
|
|
||||||
disconnect("invalid-request",
|
|
||||||
"GameProfile {0} tried to send encryption response at invalid state",
|
|
||||||
player.getAddress());
|
|
||||||
} else {
|
|
||||||
verifyResponse(session);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
//this is a fake packet; it shouldn't be sent to the server
|
//this is a fake packet; it shouldn't be sent to the server
|
||||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||||
@ -143,25 +140,7 @@ public class VerifyResponseTask implements Runnable {
|
|||||||
InetAddress address = socketAddress.getAddress();
|
InetAddress address = socketAddress.getAddress();
|
||||||
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
|
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
|
||||||
if (response.isPresent()) {
|
if (response.isPresent()) {
|
||||||
Verification verification = response.get();
|
encryptConnection(session, requestedUsername, 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);
|
|
||||||
} else {
|
} else {
|
||||||
//user tried to fake an authentication
|
//user tried to fake an authentication
|
||||||
disconnect("invalid-session", "GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}", session.getRequestUsername(), socketAddress, serverId);
|
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) {
|
private void setPremiumUUID(UUID premiumUUID) {
|
||||||
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -29,8 +29,9 @@ import com.github.games647.craftapi.UUIDAdapter;
|
|||||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
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.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.JoinManagement;
|
||||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||||
|
|
||||||
@ -49,13 +50,13 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
|||||||
implements Listener {
|
implements Listener {
|
||||||
|
|
||||||
private final FastLoginBukkit plugin;
|
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());
|
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.rateLimiter = rateLimiter;
|
this.antiBotService = antiBotService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@ -64,19 +65,28 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rateLimiter.tryAcquire()) {
|
|
||||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", loginStartEvent.getConnection());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = loginStartEvent.getConnection().getProfile().getName();
|
String username = loginStartEvent.getConnection().getProfile().getName();
|
||||||
InetSocketAddress address = loginStartEvent.getAddress();
|
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
|
Action action = antiBotService.onIncomingConnection(address, username);
|
||||||
plugin.removeSession(address);
|
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);
|
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||||
super.onLogin(username, source);
|
super.onLogin(username, source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
@ -100,7 +100,7 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
|||||||
//events
|
//events
|
||||||
PluginManager pluginManager = getProxy().getPluginManager();
|
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, connectListener);
|
||||||
pluginManager.registerListener(this, new PluginMessageListener(this));
|
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.AsyncPremiumCheck;
|
||||||
import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask;
|
import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask;
|
||||||
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
|
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.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.hooks.bedrock.FloodgateService;
|
||||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||||
import com.google.common.base.Throwables;
|
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;
|
||||||
import java.lang.invoke.MethodHandles.Lookup;
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.util.UUID;
|
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.PendingConnection;
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
import net.md_5.bungee.api.connection.Server;
|
import net.md_5.bungee.api.connection.Server;
|
||||||
@ -92,12 +95,12 @@ public class ConnectListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final FastLoginBungee plugin;
|
private final FastLoginBungee plugin;
|
||||||
private final RateLimiter rateLimiter;
|
private final AntiBotService antiBotService;
|
||||||
private final Property[] emptyProperties = {};
|
private final Property[] emptyProperties = {};
|
||||||
|
|
||||||
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter) {
|
public ConnectListener(FastLoginBungee plugin, AntiBotService antiBotService) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.rateLimiter = rateLimiter;
|
this.antiBotService = antiBotService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@ -107,17 +110,28 @@ public class ConnectListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rateLimiter.tryAcquire()) {
|
InetSocketAddress address = preLoginEvent.getConnection().getAddress();
|
||||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = connection.getName();
|
String username = connection.getName();
|
||||||
|
|
||||||
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
|
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
|
||||||
|
|
||||||
preLoginEvent.registerIntent(plugin);
|
Action action = antiBotService.onIncomingConnection(address, username);
|
||||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
switch (action) {
|
||||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
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)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
@ -140,7 +154,7 @@ public class ConnectListener implements Listener {
|
|||||||
StoredProfile playerProfile = session.getProfile();
|
StoredProfile playerProfile = session.getProfile();
|
||||||
playerProfile.setId(verifiedUUID);
|
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) {
|
if (uniqueIdSetter != null) {
|
||||||
InitialHandler initialHandler = (InitialHandler) connection;
|
InitialHandler initialHandler = (InitialHandler) connection;
|
||||||
|
|
||||||
|
@ -23,3 +23,40 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* 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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.github.games647.fastlogin.core;
|
package com.github.games647.fastlogin.core.antibot;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface RateLimiter {
|
public interface RateLimiter {
|
@ -23,7 +23,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.github.games647.fastlogin.core;
|
package com.github.games647.fastlogin.core.antibot;
|
||||||
|
|
||||||
import com.google.common.base.Ticker;
|
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.MojangResolver;
|
||||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||||
import com.github.games647.fastlogin.core.CommonUtil;
|
import com.github.games647.fastlogin.core.CommonUtil;
|
||||||
import com.github.games647.fastlogin.core.RateLimiter;
|
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||||
import com.github.games647.fastlogin.core.TickingRateLimiter;
|
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.AuthPlugin;
|
||||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
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 Configuration config;
|
||||||
private SQLStorage storage;
|
private SQLStorage storage;
|
||||||
private RateLimiter rateLimiter;
|
private AntiBotService antiBot;
|
||||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||||
private AuthPlugin<P> authPlugin;
|
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
|
// Initialize the resolver based on the config parameter
|
||||||
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false) ? new ProxyAgnosticMojangResolver() : new MojangResolver();
|
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")
|
Set<Proxy> proxies = config.getStringList("proxies")
|
||||||
.stream()
|
.stream()
|
||||||
.map(HostAndPort::fromString)
|
.map(HostAndPort::fromString)
|
||||||
@ -144,20 +146,34 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
|||||||
resolver.setOutgoingAddresses(addresses);
|
resolver.setOutgoingAddresses(addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RateLimiter createRateLimiter(Configuration botSection) {
|
private AntiBotService createAntiBotService(Configuration botSection) {
|
||||||
boolean enabled = botSection.getBoolean("enabled", true);
|
RateLimiter rateLimiter;
|
||||||
if (!enabled) {
|
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
|
// no-op rate limiter
|
||||||
return () -> true;
|
rateLimiter = () -> true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxCon = botSection.getInt("anti-bot.connections", 200);
|
Action action = Action.Ignore;
|
||||||
long expireTime = botSection.getLong("anti-bot.expire", 5) * 60 * 1_000L;
|
switch (botSection.getString("action", "ignore")) {
|
||||||
if (expireTime > MAX_EXPIRE_RATE) {
|
case "ignore":
|
||||||
expireTime = MAX_EXPIRE_RATE;
|
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 {
|
private Configuration loadFile(String fileName) throws IOException {
|
||||||
@ -285,8 +301,8 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
|||||||
return authPlugin;
|
return authPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RateLimiter getRateLimiter() {
|
public AntiBotService getAntiBot() {
|
||||||
return rateLimiter;
|
return antiBot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||||
|
@ -20,6 +20,9 @@ anti-bot:
|
|||||||
connections: 600
|
connections: 600
|
||||||
# Amount of minutes after the first connection got inserted will expire and made available
|
# Amount of minutes after the first connection got inserted will expire and made available
|
||||||
expire: 10
|
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
|
# 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
|
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||||
player-unknown: '&4Player not in the database'
|
player-unknown: '&4Player not in the database'
|
||||||
|
|
||||||
|
# Player kicked from anti bot feature
|
||||||
|
kick-antibot: '&4Please wait a moment!'
|
||||||
|
|
||||||
# ========= Bukkit/Spigot ================
|
# ========= Bukkit/Spigot ================
|
||||||
|
|
||||||
# The user skipped the authentication, because it was a premium player
|
# The user skipped the authentication, because it was a premium player
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.github.games647.fastlogin.core;
|
package com.github.games647.fastlogin.core;
|
||||||
|
|
||||||
|
import com.github.games647.fastlogin.core.antibot.TickingRateLimiter;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
|||||||
return;
|
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.getEventManager().register(this, new PluginMessageListener(this));
|
||||||
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
||||||
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
||||||
|
@ -26,8 +26,9 @@
|
|||||||
package com.github.games647.fastlogin.velocity.listener;
|
package com.github.games647.fastlogin.velocity.listener;
|
||||||
|
|
||||||
import com.github.games647.craftapi.UUIDAdapter;
|
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.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.core.shared.LoginSession;
|
||||||
import com.github.games647.fastlogin.velocity.FastLoginVelocity;
|
import com.github.games647.fastlogin.velocity.FastLoginVelocity;
|
||||||
import com.github.games647.fastlogin.velocity.VelocityLoginSession;
|
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.Subscribe;
|
||||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
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.GameProfileRequestEvent;
|
||||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||||
import com.velocitypowered.api.proxy.InboundConnection;
|
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.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
|
||||||
public class ConnectListener {
|
public class ConnectListener {
|
||||||
|
|
||||||
private final FastLoginVelocity plugin;
|
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.plugin = plugin;
|
||||||
this.rateLimiter = rateLimiter;
|
this.antiBotService = antiBotService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@ -66,16 +72,28 @@ public class ConnectListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InboundConnection connection = preLoginEvent.getConnection();
|
InboundConnection connection = preLoginEvent.getConnection();
|
||||||
if (!rateLimiter.tryAcquire()) {
|
|
||||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = preLoginEvent.getUsername();
|
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);
|
Action action = antiBotService.onIncomingConnection(address, username);
|
||||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
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
|
@Subscribe
|
||||||
|
Reference in New Issue
Block a user