mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-08-01 19:54:44 +02:00
Perform protocollib checks async/non-blocking
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
######1.6
|
######1.6
|
||||||
|
|
||||||
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
|
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
|
||||||
|
* Reduce the number of worker threads from 5 to 3 in ProtocolLib
|
||||||
|
* Process packets in ProtocolLib async/non-blocking -> better performance
|
||||||
|
* Fix error if forward skins is disabled
|
||||||
|
|
||||||
######1.5.2
|
######1.5.2
|
||||||
|
|
||||||
|
@@ -5,23 +5,18 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.google.common.base.Ticker;
|
import com.google.common.base.Ticker;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.RemovalListener;
|
import com.google.common.cache.RemovalListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Guava CacheBuilder that is compatible with both Guava 10 and 13
|
* Represents a Guava CacheBuilder that is compatible with both Guava 10 and 13
|
||||||
*/
|
*/
|
||||||
public class CacheBuilder<K, V> {
|
public class CompatibleCacheBuilder<K, V> {
|
||||||
|
|
||||||
private CacheBuilder<K, V> builder;
|
|
||||||
|
|
||||||
private static Method BUILD_METHOD;
|
private static Method BUILD_METHOD;
|
||||||
private static Method AS_MAP_METHOD;
|
private static Method AS_MAP_METHOD;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private CacheBuilder() {
|
|
||||||
builder = (CacheBuilder<K, V>) CacheBuilder.newBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new safe cache builder.
|
* Construct a new safe cache builder.
|
||||||
@@ -31,8 +26,15 @@ public class CacheBuilder<K, V> {
|
|||||||
*
|
*
|
||||||
* @return A new cache builder.
|
* @return A new cache builder.
|
||||||
*/
|
*/
|
||||||
public static <K, V> CacheBuilder<K, V> newBuilder() {
|
public static <K, V> CompatibleCacheBuilder<K, V> newBuilder() {
|
||||||
return new CacheBuilder<K, V>();
|
return new CompatibleCacheBuilder<K, V>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CacheBuilder<K, V> builder;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private CompatibleCacheBuilder() {
|
||||||
|
builder = (CacheBuilder<K, V>) CacheBuilder.newBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +58,7 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
|
* @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
|
||||||
* @throws IllegalStateException if a concurrency level was already set
|
* @throws IllegalStateException if a concurrency level was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
|
public CompatibleCacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
|
||||||
builder.concurrencyLevel(concurrencyLevel);
|
builder.concurrencyLevel(concurrencyLevel);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,7 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalArgumentException if {@code duration} is negative
|
* @throws IllegalArgumentException if {@code duration} is negative
|
||||||
* @throws IllegalStateException if the time to idle or time to live was already set
|
* @throws IllegalStateException if the time to idle or time to live was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
|
public CompatibleCacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
|
||||||
builder.expireAfterAccess(duration, unit);
|
builder.expireAfterAccess(duration, unit);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -110,7 +112,7 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalArgumentException if {@code duration} is negative
|
* @throws IllegalArgumentException if {@code duration} is negative
|
||||||
* @throws IllegalStateException if the time to live or time to idle was already set
|
* @throws IllegalStateException if the time to live or time to idle was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
|
public CompatibleCacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
|
||||||
builder.expireAfterWrite(duration, unit);
|
builder.expireAfterWrite(duration, unit);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -127,7 +129,7 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalArgumentException if {@code initialCapacity} is negative
|
* @throws IllegalArgumentException if {@code initialCapacity} is negative
|
||||||
* @throws IllegalStateException if an initial capacity was already set
|
* @throws IllegalStateException if an initial capacity was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> initialCapacity(int initialCapacity) {
|
public CompatibleCacheBuilder<K, V> initialCapacity(int initialCapacity) {
|
||||||
builder.initialCapacity(initialCapacity);
|
builder.initialCapacity(initialCapacity);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -149,7 +151,7 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalArgumentException if {@code size} is negative
|
* @throws IllegalArgumentException if {@code size} is negative
|
||||||
* @throws IllegalStateException if a maximum size was already set
|
* @throws IllegalStateException if a maximum size was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> maximumSize(int size) {
|
public CompatibleCacheBuilder<K, V> maximumSize(int size) {
|
||||||
builder.maximumSize(size);
|
builder.maximumSize(size);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -186,9 +188,9 @@ public class CacheBuilder<K, V> {
|
|||||||
* @throws IllegalStateException if a removal listener was already set
|
* @throws IllegalStateException if a removal listener was already set
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <K1 extends K, V1 extends V> CacheBuilder<K1, V1> removalListener(RemovalListener<? super K1, ? super V1> listener) {
|
public <K1 extends K, V1 extends V> CompatibleCacheBuilder<K1, V1> removalListener(RemovalListener<? super K1, ? super V1> listener) {
|
||||||
builder.removalListener(listener);
|
builder.removalListener(listener);
|
||||||
return (CacheBuilder<K1, V1>) this;
|
return (CompatibleCacheBuilder<K1, V1>) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,7 +206,7 @@ public class CacheBuilder<K, V> {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException if a ticker was already set
|
* @throws IllegalStateException if a ticker was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> ticker(Ticker ticker) {
|
public CompatibleCacheBuilder<K, V> ticker(Ticker ticker) {
|
||||||
builder.ticker(ticker);
|
builder.ticker(ticker);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -228,7 +230,7 @@ public class CacheBuilder<K, V> {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException if the value strength was already set
|
* @throws IllegalStateException if the value strength was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> softValues() {
|
public CompatibleCacheBuilder<K, V> softValues() {
|
||||||
builder.softValues();
|
builder.softValues();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -245,7 +247,7 @@ public class CacheBuilder<K, V> {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException if the key strength was already set
|
* @throws IllegalStateException if the key strength was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> weakKeys() {
|
public CompatibleCacheBuilder<K, V> weakKeys() {
|
||||||
builder.weakKeys();
|
builder.weakKeys();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -266,7 +268,7 @@ public class CacheBuilder<K, V> {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException if the value strength was already set
|
* @throws IllegalStateException if the value strength was already set
|
||||||
*/
|
*/
|
||||||
public CacheBuilder<K, V> weakValues() {
|
public CompatibleCacheBuilder<K, V> weakValues() {
|
||||||
builder.weakValues();
|
builder.weakValues();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
@@ -1,19 +1,18 @@
|
|||||||
package com.github.games647.fastlogin.bukkit;
|
package com.github.games647.fastlogin.bukkit;
|
||||||
|
|
||||||
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
|
||||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||||
import com.comphenix.protocol.AsynchronousManager;
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
import com.comphenix.protocol.ProtocolLibrary;
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
|
||||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||||
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
|
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
|
||||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
|
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
|
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.ProtocolSupportListener;
|
import com.github.games647.fastlogin.bukkit.listener.protocollib.EncryptionPacketListener;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.packet.LoginSkinApplyListener;
|
import com.github.games647.fastlogin.bukkit.listener.protocollib.LoginSkinApplyListener;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.packet.EncryptionPacketListener;
|
import com.github.games647.fastlogin.bukkit.listener.protocollib.StartPacketListener;
|
||||||
import com.github.games647.fastlogin.bukkit.listener.packet.StartPacketListener;
|
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
|
||||||
|
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
||||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
*/
|
*/
|
||||||
public class FastLoginBukkit extends JavaPlugin {
|
public class FastLoginBukkit extends JavaPlugin {
|
||||||
|
|
||||||
private static final int WORKER_THREADS = 5;
|
private static final int WORKER_THREADS = 3;
|
||||||
|
|
||||||
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
|
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
|
||||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||||
@@ -41,7 +40,8 @@ public class FastLoginBukkit extends JavaPlugin {
|
|||||||
|
|
||||||
//this map is thread-safe for async access (Packet Listener)
|
//this map is thread-safe for async access (Packet Listener)
|
||||||
//SafeCacheBuilder is used in order to be version independent
|
//SafeCacheBuilder is used in order to be version independent
|
||||||
private final ConcurrentMap<String, BukkitLoginSession> session = CacheBuilder.<String, BukkitLoginSession>newBuilder()
|
private final ConcurrentMap<String, BukkitLoginSession> session = CompatibleCacheBuilder
|
||||||
|
.<String, BukkitLoginSession>newBuilder()
|
||||||
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
//mapped by ip:port -> PlayerSession
|
//mapped by ip:port -> PlayerSession
|
||||||
@@ -104,13 +104,11 @@ public class FastLoginBukkit extends JavaPlugin {
|
|||||||
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
|
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
|
||||||
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
|
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
|
||||||
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
|
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
|
||||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
|
||||||
|
|
||||||
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
|
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
|
||||||
AsynchronousManager asynchronousManager = protocolManager.getAsynchronousManager();
|
AsynchronousManager asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager();
|
||||||
|
|
||||||
StartPacketListener startPacketListener = new StartPacketListener(this, protocolManager);
|
StartPacketListener startPacketListener = new StartPacketListener(this);
|
||||||
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this, protocolManager);
|
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this);
|
||||||
|
|
||||||
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
|
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
|
||||||
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
|
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
|
||||||
|
@@ -1,174 +0,0 @@
|
|||||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.PacketType;
|
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
|
||||||
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.bukkit.hooks.BukkitAuthPlugin;
|
|
||||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles incoming start packets from connecting clients. It
|
|
||||||
* checks if we can start checking if the player is premium and
|
|
||||||
* start a request to the client that it should start online mode
|
|
||||||
* login.
|
|
||||||
*
|
|
||||||
* Receiving packet information:
|
|
||||||
* http://wiki.vg/Protocol#Login_Start
|
|
||||||
*
|
|
||||||
* String=Username
|
|
||||||
*/
|
|
||||||
public class StartPacketListener extends PacketAdapter {
|
|
||||||
|
|
||||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
|
||||||
|
|
||||||
private final ProtocolManager protocolManager;
|
|
||||||
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
|
|
||||||
private final FastLoginBukkit plugin;
|
|
||||||
|
|
||||||
//just create a new once on plugin enable. This used for verify token generation
|
|
||||||
private final Random random = new Random();
|
|
||||||
|
|
||||||
public StartPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
|
||||||
//run async in order to not block the server, because we are making api calls to Mojang
|
|
||||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
|
||||||
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.protocolManager = protocolManger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* C->S : Handshake State=2
|
|
||||||
* C->S : Login Start
|
|
||||||
* S->C : Encryption Key Request
|
|
||||||
* (Client Auth)
|
|
||||||
* C->S : Encryption Key Response
|
|
||||||
* (Server Auth, Both enable encryption)
|
|
||||||
* S->C : Login Success (*)
|
|
||||||
*
|
|
||||||
* On offline logins is Login Start followed by Login Success
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
|
||||||
plugin.setServerStarted();
|
|
||||||
|
|
||||||
Player player = packetEvent.getPlayer();
|
|
||||||
|
|
||||||
//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.getSessions().remove(sessionKey);
|
|
||||||
|
|
||||||
//player.getName() won't work at this state
|
|
||||||
PacketContainer packet = packetEvent.getPacket();
|
|
||||||
|
|
||||||
String username = packet.getGameProfiles().read(0).getName();
|
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
|
||||||
, new Object[]{sessionKey, username});
|
|
||||||
|
|
||||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
|
||||||
if (authPlugin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
|
||||||
if (profile != null) {
|
|
||||||
if (profile.getUserId() == -1) {
|
|
||||||
UUID premiumUUID = null;
|
|
||||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
|
||||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
//user not exists in the db
|
|
||||||
try {
|
|
||||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
|
||||||
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
|
||||||
if (profile != null) {
|
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
|
||||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (premiumUUID != null
|
|
||||||
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
|
||||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//no premium check passed so we save it as a cracked player
|
|
||||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
|
||||||
plugin.getSessions().put(sessionKey, loginSession);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
|
||||||
}
|
|
||||||
} else if (profile.isPremium()) {
|
|
||||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, true);
|
|
||||||
} else {
|
|
||||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
|
||||||
plugin.getSessions().put(sessionKey, loginSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//minecraft server implementation
|
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
|
||||||
private void enablePremiumLogin(String username, PlayerProfile profile, String sessionKey, Player player
|
|
||||||
, PacketEvent packetEvent, boolean registered) {
|
|
||||||
//randomized server id to make sure the request is for our server
|
|
||||||
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
|
||||||
String serverId = Long.toString(random.nextLong(), 16);
|
|
||||||
|
|
||||||
//generate a random token which should be the same when we receive it from the client
|
|
||||||
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
|
||||||
random.nextBytes(verifyToken);
|
|
||||||
|
|
||||||
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
|
|
||||||
if (success) {
|
|
||||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId
|
|
||||||
, verifyToken, registered, profile);
|
|
||||||
plugin.getSessions().put(sessionKey, playerSession);
|
|
||||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
|
||||||
packetEvent.setCancelled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
|
||||||
*
|
|
||||||
* ServerID="" (String)
|
|
||||||
* key=public server key
|
|
||||||
* verifyToken=random 4 byte array
|
|
||||||
*/
|
|
||||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
|
||||||
|
|
||||||
newPacket.getStrings().write(0, serverId);
|
|
||||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
|
||||||
|
|
||||||
newPacket.getByteArrays().write(0, verifyToken);
|
|
||||||
|
|
||||||
//serverId is a empty string
|
|
||||||
protocolManager.sendServerPacket(player, newPacket);
|
|
||||||
return true;
|
|
||||||
} catch (InvocationTargetException ex) {
|
|
||||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,61 @@
|
|||||||
|
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming encryption responses from connecting clients.
|
||||||
|
* It prevents them from reaching the server because that cannot handle
|
||||||
|
* it in offline mode.
|
||||||
|
*
|
||||||
|
* Moreover this manages a started premium check from
|
||||||
|
* this plugin. So check if all data is correct and we can prove him as a
|
||||||
|
* owner of a paid minecraft account.
|
||||||
|
*
|
||||||
|
* Receiving packet information:
|
||||||
|
* http://wiki.vg/Protocol#Encryption_Response
|
||||||
|
*
|
||||||
|
* sharedSecret=encrypted byte array
|
||||||
|
* verify token=encrypted byte array
|
||||||
|
*/
|
||||||
|
public class EncryptionPacketListener extends PacketAdapter {
|
||||||
|
|
||||||
|
//hides the inherit Plugin plugin field, but we need this type
|
||||||
|
private final FastLoginBukkit plugin;
|
||||||
|
|
||||||
|
public EncryptionPacketListener(FastLoginBukkit plugin) {
|
||||||
|
//run async in order to not block the server, because we make api calls to Mojang
|
||||||
|
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||||
|
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C->S : Handshake State=2
|
||||||
|
* C->S : Login Start
|
||||||
|
* S->C : Encryption Key Request
|
||||||
|
* (Client Auth)
|
||||||
|
* C->S : Encryption Key Response
|
||||||
|
* (Server Auth, Both enable encryption)
|
||||||
|
* S->C : Login Success (*)
|
||||||
|
*
|
||||||
|
* On offline logins is Login Start followed by Login Success
|
||||||
|
*
|
||||||
|
* Minecraft Server implementation
|
||||||
|
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||||
|
Player sender = packetEvent.getPlayer();
|
||||||
|
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||||
|
|
||||||
|
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||||
|
VerifyResponseTask verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret);
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||||
|
|
||||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||||
@@ -9,7 +9,7 @@ import org.bukkit.entity.Player;
|
|||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerLoginEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
public class LoginSkinApplyListener implements Listener {
|
public class LoginSkinApplyListener implements Listener {
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ public class LoginSkinApplyListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOW)
|
@EventHandler(priority = EventPriority.LOW)
|
||||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
public void onPlayerLogin(PlayerJoinEvent joinEvent) {
|
||||||
Player player = loginEvent.getPlayer();
|
Player player = joinEvent.getPlayer();
|
||||||
|
|
||||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||||
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
|
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
|
@@ -0,0 +1,139 @@
|
|||||||
|
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
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.PlayerProfile;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class NameCheckTask implements Runnable {
|
||||||
|
|
||||||
|
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||||
|
|
||||||
|
private final FastLoginBukkit plugin;
|
||||||
|
private final PacketEvent packetEvent;
|
||||||
|
|
||||||
|
private final Random random;
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private final String username;
|
||||||
|
|
||||||
|
public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random, Player player, String username) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.packetEvent = packetEvent;
|
||||||
|
this.random = random;
|
||||||
|
this.player = player;
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
nameCheck();
|
||||||
|
} finally {
|
||||||
|
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nameCheck() {
|
||||||
|
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||||
|
if (profile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.getUserId() == -1) {
|
||||||
|
UUID premiumUUID = null;
|
||||||
|
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||||
|
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
//user not exists in the db
|
||||||
|
try {
|
||||||
|
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||||
|
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||||
|
if (profile != null) {
|
||||||
|
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||||
|
enablePremiumLogin(profile, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (premiumUUID != null && plugin.getConfig().getBoolean("autoRegister")
|
||||||
|
&& !plugin.getAuthPlugin().isRegistered(username)) {
|
||||||
|
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||||
|
enablePremiumLogin(profile, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//no premium check passed so we save it as a cracked player
|
||||||
|
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||||
|
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||||
|
}
|
||||||
|
} else if (profile.isPremium()) {
|
||||||
|
enablePremiumLogin(profile, true);
|
||||||
|
} else {
|
||||||
|
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||||
|
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//minecraft server implementation
|
||||||
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||||
|
private void enablePremiumLogin(PlayerProfile profile, boolean registered) {
|
||||||
|
//randomized server id to make sure the request is for our server
|
||||||
|
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||||
|
String serverId = Long.toString(random.nextLong(), 16);
|
||||||
|
|
||||||
|
//generate a random token which should be the same when we receive it from the client
|
||||||
|
byte[] verify = new byte[VERIFY_TOKEN_LENGTH];
|
||||||
|
random.nextBytes(verify);
|
||||||
|
|
||||||
|
boolean success = sentEncryptionRequest(player, serverId, verify);
|
||||||
|
if (success) {
|
||||||
|
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
|
||||||
|
plugin.getSessions().put(player.getAddress().toString(), playerSession);
|
||||||
|
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||||
|
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||||
|
packetEvent.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||||
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||||
|
*
|
||||||
|
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
|
||||||
|
*/
|
||||||
|
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
||||||
|
|
||||||
|
newPacket.getStrings().write(0, serverId);
|
||||||
|
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
||||||
|
|
||||||
|
newPacket.getByteArrays().write(0, verifyToken);
|
||||||
|
|
||||||
|
//serverId is a empty string
|
||||||
|
protocolManager.sendServerPacket(player, newPacket);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,80 @@
|
|||||||
|
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
|
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming start packets from connecting clients. It
|
||||||
|
* checks if we can start checking if the player is premium and
|
||||||
|
* start a request to the client that it should start online mode
|
||||||
|
* login.
|
||||||
|
*
|
||||||
|
* Receiving packet information:
|
||||||
|
* http://wiki.vg/Protocol#Login_Start
|
||||||
|
*
|
||||||
|
* String=Username
|
||||||
|
*/
|
||||||
|
public class StartPacketListener extends PacketAdapter {
|
||||||
|
|
||||||
|
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
|
||||||
|
private final FastLoginBukkit plugin;
|
||||||
|
|
||||||
|
//just create a new once on plugin enable. This used for verify token generation
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
public StartPacketListener(FastLoginBukkit plugin) {
|
||||||
|
//run async in order to not block the server, because we are making api calls to Mojang
|
||||||
|
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||||
|
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C->S : Handshake State=2
|
||||||
|
* C->S : Login Start
|
||||||
|
* S->C : Encryption Key Request
|
||||||
|
* (Client Auth)
|
||||||
|
* C->S : Encryption Key Response
|
||||||
|
* (Server Auth, Both enable encryption)
|
||||||
|
* S->C : Login Success (*)
|
||||||
|
*
|
||||||
|
* On offline logins is Login Start followed by Login Success
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||||
|
plugin.setServerStarted();
|
||||||
|
|
||||||
|
Player player = packetEvent.getPlayer();
|
||||||
|
|
||||||
|
//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.getSessions().remove(sessionKey);
|
||||||
|
|
||||||
|
//player.getName() won't work at this state
|
||||||
|
PacketContainer packet = packetEvent.getPacket();
|
||||||
|
|
||||||
|
String username = packet.getGameProfiles().read(0).getName();
|
||||||
|
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting", new Object[]{sessionKey, username});
|
||||||
|
|
||||||
|
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||||
|
if (authPlugin == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||||
|
NameCheckTask nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username);
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||||
|
|
||||||
import com.comphenix.protocol.PacketType;
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
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.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
@@ -26,65 +26,42 @@ import javax.crypto.SecretKey;
|
|||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
/**
|
public class VerifyResponseTask implements Runnable {
|
||||||
* Handles incoming encryption responses from connecting clients.
|
|
||||||
* It prevents them from reaching the server because that cannot handle
|
|
||||||
* it in offline mode.
|
|
||||||
*
|
|
||||||
* Moreover this manages a started premium check from
|
|
||||||
* this plugin. So check if all data is correct and we can prove him as a
|
|
||||||
* owner of a paid minecraft account.
|
|
||||||
*
|
|
||||||
* Receiving packet information:
|
|
||||||
* http://wiki.vg/Protocol#Encryption_Response
|
|
||||||
*
|
|
||||||
* sharedSecret=encrypted byte array
|
|
||||||
* verify token=encrypted byte array
|
|
||||||
*/
|
|
||||||
public class EncryptionPacketListener extends PacketAdapter {
|
|
||||||
|
|
||||||
private final ProtocolManager protocolManager;
|
|
||||||
//hides the inherit Plugin plugin field, but we need this type
|
|
||||||
private final FastLoginBukkit plugin;
|
private final FastLoginBukkit plugin;
|
||||||
|
private final PacketEvent packetEvent;
|
||||||
|
|
||||||
public EncryptionPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
private final Player fromPlayer;
|
||||||
//run async in order to not block the server, because we make api calls to Mojang
|
|
||||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
|
||||||
|
|
||||||
|
private final byte[] sharedSecret;
|
||||||
|
|
||||||
|
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player fromPlayer, byte[] sharedSecret) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.protocolManager = protocolManger;
|
this.packetEvent = packetEvent;
|
||||||
|
this.fromPlayer = fromPlayer;
|
||||||
|
this.sharedSecret = sharedSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* C->S : Handshake State=2
|
|
||||||
* C->S : Login Start
|
|
||||||
* S->C : Encryption Key Request
|
|
||||||
* (Client Auth)
|
|
||||||
* C->S : Encryption Key Response
|
|
||||||
* (Server Auth, Both enable encryption)
|
|
||||||
* S->C : Login Success (*)
|
|
||||||
*
|
|
||||||
* On offline logins is Login Start followed by Login Success
|
|
||||||
*
|
|
||||||
* Minecraft Server implementation
|
|
||||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
public void run() {
|
||||||
Player player = packetEvent.getPlayer();
|
try {
|
||||||
|
BukkitLoginSession session = plugin.getSessions().get(fromPlayer.getAddress().toString());
|
||||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
if (session == null) {
|
||||||
if (session == null) {
|
disconnect(plugin.getCore().getMessage("invalid-requst"), true
|
||||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-requst"), true
|
, "Player {0} tried to send encryption response at invalid state", fromPlayer.getAddress());
|
||||||
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
|
} else {
|
||||||
return;
|
verifyResponse(session);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyResponse(BukkitLoginSession session) {
|
||||||
PrivateKey privateKey = plugin.getServerKey().getPrivate();
|
PrivateKey privateKey = plugin.getServerKey().getPrivate();
|
||||||
|
|
||||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
|
||||||
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||||
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
|
if (!checkVerifyToken(session, privateKey) || !encryptConnection(loginKey)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,34 +79,35 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||||
|
|
||||||
session.setVerified(true);
|
session.setVerified(true);
|
||||||
setPremiumUUID(session, player);
|
setPremiumUUID(session.getUuid());
|
||||||
receiveFakeStartPacket(username, player);
|
receiveFakeStartPacket(username);
|
||||||
} else {
|
} else {
|
||||||
//user tried to fake a authentication
|
//user tried to fake a authentication
|
||||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-session"), true
|
disconnect(plugin.getCore().getMessage("invalid-session"), true
|
||||||
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||||
, session.getUsername(), player.getAddress(), serverId);
|
, session.getUsername(), fromPlayer.getAddress(), serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is a fake packet; it shouldn't be send to the server
|
//this is a fake packet; it shouldn't be send to the server
|
||||||
packetEvent.setCancelled(true);
|
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||||
|
packetEvent.setCancelled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPremiumUUID(BukkitLoginSession session, Player player) {
|
private void setPremiumUUID(UUID premiumUUID) {
|
||||||
UUID uuid = session.getUuid();
|
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||||
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
|
|
||||||
try {
|
try {
|
||||||
Object networkManager = getNetworkManager(player);
|
Object networkManager = getNetworkManager();
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
||||||
Field spoofField = FuzzyReflection.fromObject(networkManager).getFieldByType("spoofedUUID", UUID.class);
|
Field spoofField = FuzzyReflection.fromObject(networkManager).getFieldByType("spoofedUUID", UUID.class);
|
||||||
spoofField.set(networkManager, uuid);
|
spoofField.set(networkManager, premiumUUID);
|
||||||
} catch (ReflectiveOperationException reflectiveOperationException) {
|
} catch (ReflectiveOperationException reflectiveOperationException) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", reflectiveOperationException);
|
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", reflectiveOperationException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey) {
|
||||||
byte[] requestVerify = session.getVerifyToken();
|
byte[] requestVerify = session.getVerifyToken();
|
||||||
//encrypted verify token
|
//encrypted verify token
|
||||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||||
@@ -137,7 +115,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
||||||
if (!Arrays.equals(requestVerify, EncryptionUtil.decryptData(privateKey, responseVerify))) {
|
if (!Arrays.equals(requestVerify, EncryptionUtil.decryptData(privateKey, responseVerify))) {
|
||||||
//check if the verify token are equal to the server sent one
|
//check if the verify token are equal to the server sent one
|
||||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-verify-token"), true
|
disconnect(plugin.getCore().getMessage("invalid-verify-token"), true
|
||||||
, "Player {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
|
, "Player {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
|
||||||
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
||||||
return false;
|
return false;
|
||||||
@@ -147,9 +125,8 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//try to get the networkManager from ProtocolLib
|
//try to get the networkManager from ProtocolLib
|
||||||
private Object getNetworkManager(Player player)
|
private Object getNetworkManager() throws IllegalAccessException, NoSuchFieldException {
|
||||||
throws IllegalAccessException, NoSuchFieldException {
|
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(fromPlayer);
|
||||||
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
|
||||||
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
|
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
|
||||||
injectorField.setAccessible(true);
|
injectorField.setAccessible(true);
|
||||||
|
|
||||||
@@ -160,11 +137,10 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
return injectorField.get(rawInjector);
|
return injectorField.get(rawInjector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
|
private boolean encryptConnection(SecretKey loginKey) throws IllegalArgumentException {
|
||||||
throws IllegalArgumentException {
|
|
||||||
try {
|
try {
|
||||||
//get the NMS connection handle of this player
|
//get the NMS connection handle of this player
|
||||||
Object networkManager = getNetworkManager(player);
|
Object networkManager = getNetworkManager();
|
||||||
|
|
||||||
//try to detect the method by parameters
|
//try to detect the method by parameters
|
||||||
Method encryptConnectionMethod = FuzzyReflection
|
Method encryptConnectionMethod = FuzzyReflection
|
||||||
@@ -174,16 +150,15 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
//the client expects this behaviour
|
//the client expects this behaviour
|
||||||
encryptConnectionMethod.invoke(networkManager, loginKey);
|
encryptConnectionMethod.invoke(networkManager, loginKey);
|
||||||
} catch (ReflectiveOperationException ex) {
|
} catch (ReflectiveOperationException ex) {
|
||||||
disconnect(packetEvent, plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
disconnect(plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect(PacketEvent packetEvent, String kickReason, boolean debugLevel, String logMessage
|
private void disconnect(String kickReason, boolean debug, String logMessage, Object... arguments) {
|
||||||
, Object... arguments) {
|
if (debug) {
|
||||||
if (debugLevel) {
|
|
||||||
plugin.getLogger().log(Level.FINE, logMessage, arguments);
|
plugin.getLogger().log(Level.FINE, logMessage, arguments);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLogger().log(Level.SEVERE, logMessage, arguments);
|
plugin.getLogger().log(Level.SEVERE, logMessage, arguments);
|
||||||
@@ -191,10 +166,14 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
|
|
||||||
kickPlayer(packetEvent.getPlayer(), kickReason);
|
kickPlayer(packetEvent.getPlayer(), kickReason);
|
||||||
//cancel the event in order to prevent the server receiving an invalid packet
|
//cancel the event in order to prevent the server receiving an invalid packet
|
||||||
packetEvent.setCancelled(true);
|
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||||
|
packetEvent.setCancelled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void kickPlayer(Player player, String reason) {
|
private void kickPlayer(Player player, String reason) {
|
||||||
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
|
|
||||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
|
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
|
||||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
||||||
|
|
||||||
@@ -210,7 +189,9 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//fake a new login packet in order to let the server handle all the other stuff
|
//fake a new login packet in order to let the server handle all the other stuff
|
||||||
private void receiveFakeStartPacket(String username, Player from) {
|
private void receiveFakeStartPacket(String username) {
|
||||||
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
|
|
||||||
//see StartPacketListener for packet information
|
//see StartPacketListener for packet information
|
||||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START);
|
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START);
|
||||||
|
|
||||||
@@ -219,11 +200,11 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||||
try {
|
try {
|
||||||
//we don't want to handle our own packets so ignore filters
|
//we don't want to handle our own packets so ignore filters
|
||||||
protocolManager.recieveClientPacket(from, startPacket, false);
|
protocolManager.recieveClientPacket(fromPlayer, startPacket, false);
|
||||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||||
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
|
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
|
||||||
//cancel the event in order to prevent the server receiving an invalid packet
|
//cancel the event in order to prevent the server receiving an invalid packet
|
||||||
kickPlayer(from, plugin.getCore().getMessage("error-kick"));
|
kickPlayer(fromPlayer, plugin.getCore().getMessage("error-kick"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.github.games647.fastlogin.bukkit.listener;
|
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||||
|
|
||||||
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;
|
Reference in New Issue
Block a user