mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-30 02:37:34 +02:00
Update description
This commit is contained in:
116
README.md
116
README.md
@ -1,21 +1,125 @@
|
|||||||
# FastLogin
|
# FastLogin
|
||||||
|
|
||||||
Checks if a minecraft player has a valid premium (paid account). If so, they can skip offline authentication.
|
Checks if a minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
|
||||||
|
So they don't need to enter passwords. This is also called auto login.
|
||||||
|
|
||||||
|
###Features:
|
||||||
|
* Detect paid accounts from others
|
||||||
|
* Automatically login paid accounts (premium)
|
||||||
|
* Support various of auth plugins
|
||||||
|
* Experimental Cauldron support
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
###Commands:
|
###Commands:
|
||||||
* /premium Marks the invoker as paid account
|
* /premium Label the invoker as paid account
|
||||||
* /premium [playername] Mark player specified as a paid account
|
* /premium [playername] Label specified player as a paid account
|
||||||
|
|
||||||
###Premissions:
|
###Permissions:
|
||||||
* fastlogin.command.premium
|
* fastlogin.command.premium
|
||||||
* fastlogin.command.premium.others
|
* fastlogin.command.premium.others
|
||||||
|
|
||||||
###Requirements:
|
###Requirements:
|
||||||
* [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
||||||
* Tested Bukkit 1.8.8 (could also work with other versions)
|
* Tested Bukkit/[Spigot](https://www.spigotmc.org) 1.8.8 (could also work with other versions)
|
||||||
* Java 7 or above
|
* Java 7 or above
|
||||||
|
* Run in offline mode (see server.properties)
|
||||||
* An auth plugin. Supported Plugins:
|
* An auth plugin. Supported Plugins:
|
||||||
* [AuthMe](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
* [AuthMe](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||||
* [xAuth](http://dev.bukkit.org/bukkit-plugins/xauth/)
|
* [xAuth](http://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||||
* [CrazyLogin](http://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
* [CrazyLogin](http://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||||
* [LoginSecurity](http://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
* [LoginSecurity](http://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||||
|
|
||||||
|
###Downloads
|
||||||
|
|
||||||
|
https://github.com/games647/FastLogin/releases
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
###FAQ
|
||||||
|
|
||||||
|
####Index
|
||||||
|
1. [How does minecraft logins work?](#how-does-minecraft-logins-work)
|
||||||
|
2. [How does this plugin work?](#how-does-this-plugin-work)
|
||||||
|
3. [Why does the plugin require offline mode?](#why-does-the-plugin-require-offline-mode)
|
||||||
|
4. [Can cracked player join with premium usernames?](#can-cracked-player-join-with-premium-usernames)
|
||||||
|
5. [Why do players have to invoke a command?](#why-do-players-have-to-invoke-a-command)
|
||||||
|
6. [What happens if a paid account joins with a used username?](#what-happens-if-a-paid-account-joins-with-a-used-username)
|
||||||
|
7. [Does the plugin have BungeeCord support?](#does-the-plugin-have-bungeecord-support)
|
||||||
|
8. [Could premium players have a premium UUID and Skin?](#could-premium-players-have-a-premium-uuid-and-skin)
|
||||||
|
9. [Is this plugin compatible with Cauldron?](#is-this-plugin-compatible-with-cauldron)
|
||||||
|
|
||||||
|
####How does minecraft logins work?
|
||||||
|
######Online Mode
|
||||||
|
1. Client -> Server: I want to login, here is my username
|
||||||
|
2. Server -> Client: Okay. I'm in online mode so here is my public key for encryption and my serverid
|
||||||
|
3. Client -> Mojang: I'm player "xyz". I want to join a server with that serverid
|
||||||
|
4. Mojang -> Client: Session data checked. You can continue
|
||||||
|
5. Client -> Server: I received a successful response from Mojang. Heres our shared secret key
|
||||||
|
6. Server -> Mojang: Does the player "xyz" with this shared secret key has a valid account to join me?
|
||||||
|
7. Client and Server: encrypt all following communication packet
|
||||||
|
8. Server -> Client: Everything checked you can play now
|
||||||
|
|
||||||
|
|
||||||
|
######Offline Mode
|
||||||
|
In offline mode step 2-7 is skipped. So a login request is directly followed by 8.
|
||||||
|
|
||||||
|
######More details
|
||||||
|
http://wiki.vg/Protocol#Login
|
||||||
|
|
||||||
|
####How does this plugin work?
|
||||||
|
By using ProtocolLib, this plugin works as a proxy between the client and server. This plugin will fake that the server
|
||||||
|
runs in online mode. It does everything an online mode server would do. This will be for example, generating keys or
|
||||||
|
checking for valid sessions. Because everything is the same compared to an offline mode login after an encrypted
|
||||||
|
connection, we will intercept only **login** packets of **premium** players.
|
||||||
|
|
||||||
|
1. Player is connecting to the server.
|
||||||
|
2. Plugin checks if the username we received activated the fast login method (i.e. using command)
|
||||||
|
3. Run a check if the username is currently used by a paid account.
|
||||||
|
(We don't know yet if the client connecting is premium)
|
||||||
|
4. Request an Mojang Session Server authentication
|
||||||
|
5. On response check if all data is correct
|
||||||
|
6. Encrypt the connection
|
||||||
|
7. On success intercept all related login packets and fake a new login packet as a normal offline login
|
||||||
|
|
||||||
|
####Why does the plugin require offline mode?
|
||||||
|
1. As you can see in the question "how does minecraft login works", offline mode is equivalent to online mode except of
|
||||||
|
the encryption and session checks on login. So we can intercept and cancel the first packets for premium players and
|
||||||
|
enable an encrypted connection. Then we send a new fake packet in order to pretend that this a new login request from
|
||||||
|
a offline mode player. The server will handle the rest.
|
||||||
|
2. Some plugins check if the server is in online mode. If so, they could process the real offline (cracked) accounts
|
||||||
|
incorrectly. For example, a plugin tries to fetch the UUID from Mojang, but the name of the player is not associated to
|
||||||
|
a paid account.
|
||||||
|
3. Servers, who allow cracked players and just speed up logins for premium players, are **already** in offline mode.
|
||||||
|
|
||||||
|
####Can cracked player join with premium usernames?
|
||||||
|
Yes, indeed. Therefore the command for toggling the fast login method exists.
|
||||||
|
|
||||||
|
####Why do players have to invoke a command?
|
||||||
|
1. It's a secure way to make sure a person with a paid account cannot steal the account
|
||||||
|
of a cracked player that has the same username.
|
||||||
|
2. We only receive the username from the player on login. We could check if that username is associated
|
||||||
|
to a paid account but if we request a online mode login from a cracked player (who uses a username from
|
||||||
|
a paid account), the player will disconnect with the reason bad login. There is no way to change that message
|
||||||
|
on the server side (without client modifications), because it's a connection between the Client and the Sessionserver.
|
||||||
|
|
||||||
|
###What happens if a paid account joins with a used username?
|
||||||
|
The player on the server have to activate the feature of this plugin by command. If a person buys the username
|
||||||
|
of his own account, it's still secured. A normal offline mode login makes sure he's the owner of the server account
|
||||||
|
and Mojang account. Then the command can be executed. So someone different cannot steal the account of cracked player
|
||||||
|
by buying the username.
|
||||||
|
|
||||||
|
####Does the plugin have BungeeCord support?
|
||||||
|
Not yet, but I'm planning this.
|
||||||
|
|
||||||
|
####Could premium players have a premium UUID and Skin?
|
||||||
|
Something like that is possible, but is not yet implemented.
|
||||||
|
|
||||||
|
####Is this plugin compatible with Cauldron?
|
||||||
|
It's not yet tested, but all needed methods also exists in Cauldron so it could work together
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
###Useful Links:
|
||||||
|
* [Login Protocol](http://wiki.vg/Protocol#Login)
|
||||||
|
* [Protocol Encryption](http://wiki.vg/Protocol_Encryption)
|
2
pom.xml
2
pom.xml
@ -126,7 +126,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>fr.xephi</groupId>
|
<groupId>fr.xephi</groupId>
|
||||||
<artifactId>authme</artifactId>
|
<artifactId>authme</artifactId>
|
||||||
<version>5.0-SNAPSHOT</version>
|
<version>5.1-SNAPSHOT</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@ -31,23 +31,23 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
*
|
*
|
||||||
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
||||||
*/
|
*/
|
||||||
public class Encryption {
|
public class EncryptionUtil {
|
||||||
|
|
||||||
public static KeyPair generateKeyPair() {
|
public static KeyPair generateKeyPair() {
|
||||||
try {
|
try {
|
||||||
KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("RSA");
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
|
||||||
keypairgenerator.initialize(1024);
|
keyPairGenerator.initialize(1024);
|
||||||
return keypairgenerator.generateKeyPair();
|
return keyPairGenerator.generateKeyPair();
|
||||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||||
//Should be existing in every vm
|
//Should be existing in every vm
|
||||||
return null;
|
throw new ExceptionInInitializerError(nosuchalgorithmexception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] getServerIdHash(String serverId, PublicKey publickey, SecretKey secretkey) {
|
public static byte[] getServerIdHash(String serverId, PublicKey publicKey, SecretKey secretKey) {
|
||||||
return digestOperation("SHA-1"
|
return digestOperation("SHA-1"
|
||||||
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretkey.getEncoded(), publickey.getEncoded()});
|
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded()});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] digestOperation(String algo, byte[]... content) {
|
private static byte[] digestOperation(String algo, byte[]... content) {
|
||||||
@ -66,24 +66,24 @@ public class Encryption {
|
|||||||
|
|
||||||
public static PublicKey decodePublicKey(byte[] encodedKey) {
|
public static PublicKey decodePublicKey(byte[] encodedKey) {
|
||||||
try {
|
try {
|
||||||
X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
|
|
||||||
KeyFactory keyfactory = KeyFactory.getInstance("RSA");
|
KeyFactory keyfactory = KeyFactory.getInstance("RSA");
|
||||||
|
|
||||||
|
X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
|
||||||
return keyfactory.generatePublic(x509encodedkeyspec);
|
return keyfactory.generatePublic(x509encodedkeyspec);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException nosuchalgorithmexception) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException nosuchalgorithmexception) {
|
||||||
;
|
//ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
System.err.println("Public key reconstitute failed!");
|
System.err.println("Public key reconstitute failed!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SecretKey decryptSharedKey(PrivateKey privatekey, byte[] encryptedSharedKey) {
|
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] encryptedSharedKey) {
|
||||||
return new SecretKeySpec(decryptData(privatekey, encryptedSharedKey), "AES");
|
return new SecretKeySpec(decryptData(privateKey, encryptedSharedKey), "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] decryptData(Key key, byte[] abyte) {
|
public static byte[] decryptData(Key key, byte[] data) {
|
||||||
return cipherOperation(Cipher.DECRYPT_MODE, key, abyte);
|
return cipherOperation(Cipher.DECRYPT_MODE, key, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
|
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
|
||||||
@ -122,7 +122,7 @@ public class Encryption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Encryption() {
|
private EncryptionUtil() {
|
||||||
//utility
|
//utility
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,7 +33,7 @@ public class FastLogin extends JavaPlugin {
|
|||||||
private static final String USER_AGENT = "Premium-Checker";
|
private static final String USER_AGENT = "Premium-Checker";
|
||||||
|
|
||||||
//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 = Encryption.generateKeyPair();
|
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||||
|
|
||||||
//we need a thread-safe set because we access it async in the packet listener
|
//we need a thread-safe set because we access it async in the packet listener
|
||||||
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
||||||
@ -102,7 +102,7 @@ public class FastLogin extends JavaPlugin {
|
|||||||
*
|
*
|
||||||
* @return the server KeyPair
|
* @return the server KeyPair
|
||||||
*/
|
*/
|
||||||
public KeyPair getKeyPair() {
|
public KeyPair getServerKey() {
|
||||||
return keyPair;
|
return keyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
|||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||||
import com.github.games647.fastlogin.Encryption;
|
import com.github.games647.fastlogin.EncryptionUtil;
|
||||||
import com.github.games647.fastlogin.FastLogin;
|
import com.github.games647.fastlogin.FastLogin;
|
||||||
import com.github.games647.fastlogin.PlayerSession;
|
import com.github.games647.fastlogin.PlayerSession;
|
||||||
|
|
||||||
@ -80,7 +80,6 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||||
PacketContainer packet = packetEvent.getPacket();
|
|
||||||
Player player = packetEvent.getPlayer();
|
Player player = packetEvent.getPlayer();
|
||||||
|
|
||||||
//the player name is unknown to ProtocolLib (so getName() doesn't work) - now uses ip:port as key
|
//the player name is unknown to ProtocolLib (so getName() doesn't work) - now uses ip:port as key
|
||||||
@ -88,48 +87,23 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
PlayerSession session = plugin.getSessions().get(uniqueSessionKey);
|
PlayerSession session = plugin.getSessions().get(uniqueSessionKey);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
disconnect(packetEvent, "Invalid request", Level.FINE
|
disconnect(packetEvent, "Invalid request", Level.FINE
|
||||||
, "Player {0} tried to send encryption response on an invalid connection state"
|
, "Player {0} tried to send encryption response at invalid state"
|
||||||
, player.getAddress());
|
, player.getAddress());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] sharedSecret = packet.getByteArrays().read(0);
|
PrivateKey privateKey = plugin.getServerKey().getPrivate();
|
||||||
//encrypted verify token
|
|
||||||
byte[] clientVerify = packet.getByteArrays().read(1);
|
|
||||||
|
|
||||||
PrivateKey privateKey = plugin.getKeyPair().getPrivate();
|
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||||
byte[] serverVerify = session.getVerifyToken();
|
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
|
||||||
if (!Arrays.equals(serverVerify, Encryption.decryptData(privateKey, clientVerify))) {
|
|
||||||
//check if the verify token are equal to the server sent one
|
|
||||||
disconnect(packetEvent, "Invalid token", Level.FINE
|
|
||||||
, "Player {0} ({1}) tried to login with an invalid verify token. "
|
|
||||||
+ "Server: {2} Client: {3}"
|
|
||||||
, session.getUsername(), player.getAddress(), serverVerify, clientVerify);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SecretKey loginKey = Encryption.decryptSharedKey(privateKey, sharedSecret);
|
|
||||||
try {
|
|
||||||
//get the NMS connection handle of this player
|
|
||||||
Object networkManager = getNetworkManager(player);
|
|
||||||
|
|
||||||
//try to detect the method by parameters
|
|
||||||
Method encryptConnectionMethod = FuzzyReflection.fromObject(networkManager)
|
|
||||||
.getMethodByParameters("a", SecretKey.class);
|
|
||||||
|
|
||||||
//encrypt/decrypt following packets
|
|
||||||
//the client expects this behaviour
|
|
||||||
encryptConnectionMethod.invoke(networkManager, loginKey);
|
|
||||||
} catch (ReflectiveOperationException ex) {
|
|
||||||
disconnect(packetEvent, "Error occurred", Level.SEVERE, "Couldn't enable encryption", ex);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L193
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L193
|
||||||
//generate the server id based on client and server data
|
//generate the server id based on client and server data
|
||||||
String serverId = (new BigInteger(Encryption.getServerIdHash("", plugin.getKeyPair().getPublic(), loginKey)))
|
byte[] serverIdHash = EncryptionUtil.getServerIdHash("", plugin.getServerKey().getPublic(), loginKey);
|
||||||
.toString(16);
|
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||||
|
|
||||||
String username = session.getUsername();
|
String username = session.getUsername();
|
||||||
if (hasJoinedServer(username, serverId)) {
|
if (hasJoinedServer(username, serverId)) {
|
||||||
@ -144,9 +118,63 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
, session.getUsername(), player.getAddress(), serverId);
|
, session.getUsername(), player.getAddress(), serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//this is a fake packet; it shouldn't be send to the server
|
||||||
packetEvent.setCancelled(true);
|
packetEvent.setCancelled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkVerifyToken(PlayerSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
||||||
|
byte[] requestVerify = session.getVerifyToken();
|
||||||
|
//encrypted verify token
|
||||||
|
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||||
|
|
||||||
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
||||||
|
if (!Arrays.equals(requestVerify, EncryptionUtil.decryptData(privateKey, responseVerify))) {
|
||||||
|
//check if the verify token are equal to the server sent one
|
||||||
|
disconnect(packetEvent, "Invalid token", Level.FINE
|
||||||
|
, "Player {0} ({1}) tried to login with an invalid verify token. "
|
||||||
|
+ "Server: {2} Client: {3}"
|
||||||
|
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//try to get the networkManager from ProtocolLib
|
||||||
|
private Object getNetworkManager(Player player)
|
||||||
|
throws IllegalAccessException, NoSuchFieldException {
|
||||||
|
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||||
|
Field injectorField = injector.getClass().getDeclaredField("injector");
|
||||||
|
injectorField.setAccessible(true);
|
||||||
|
|
||||||
|
Object rawInjector = injectorField.get(injector);
|
||||||
|
|
||||||
|
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||||
|
injectorField.setAccessible(true);
|
||||||
|
return injectorField.get(rawInjector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
//get the NMS connection handle of this player
|
||||||
|
Object networkManager = getNetworkManager(player);
|
||||||
|
|
||||||
|
//try to detect the method by parameters
|
||||||
|
Method encryptConnectionMethod = FuzzyReflection.fromObject(networkManager)
|
||||||
|
.getMethodByParameters("a", SecretKey.class);
|
||||||
|
|
||||||
|
//encrypt/decrypt following packets
|
||||||
|
//the client expects this behaviour
|
||||||
|
encryptConnectionMethod.invoke(networkManager, loginKey);
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
disconnect(packetEvent, "Error occurred", Level.SEVERE, "Couldn't enable encryption", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void disconnect(PacketEvent packetEvent, String kickReason, Level logLevel, String logMessage
|
private void disconnect(PacketEvent packetEvent, String kickReason, Level logLevel, String logMessage
|
||||||
, Object... arguments) {
|
, Object... arguments) {
|
||||||
plugin.getLogger().log(logLevel, logMessage, arguments);
|
plugin.getLogger().log(logLevel, logMessage, arguments);
|
||||||
@ -170,20 +198,6 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//try to get the networkManager from ProtocolLib
|
|
||||||
private Object getNetworkManager(Player player)
|
|
||||||
throws SecurityException, IllegalAccessException, NoSuchFieldException {
|
|
||||||
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
|
||||||
Field injectorField = injector.getClass().getDeclaredField("injector");
|
|
||||||
injectorField.setAccessible(true);
|
|
||||||
|
|
||||||
Object rawInjector = injectorField.get(injector);
|
|
||||||
|
|
||||||
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
|
||||||
injectorField.setAccessible(true);
|
|
||||||
return injectorField.get(rawInjector);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasJoinedServer(String username, String serverId) {
|
private boolean hasJoinedServer(String username, String serverId) {
|
||||||
try {
|
try {
|
||||||
String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
|
String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
|
||||||
@ -192,7 +206,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if (!line.equals("null")) {
|
if (!"null".equals(line)) {
|
||||||
//validate parsing
|
//validate parsing
|
||||||
//http://wiki.vg/Protocol_Encryption#Server
|
//http://wiki.vg/Protocol_Encryption#Server
|
||||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||||
@ -220,7 +234,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
//see StartPacketListener for packet information
|
//see StartPacketListener for packet information
|
||||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
||||||
|
|
||||||
//uuid is ignored
|
//uuid is ignored by the packet definition
|
||||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||||
try {
|
try {
|
||||||
|
@ -68,7 +68,6 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||||
PacketContainer packet = packetEvent.getPacket();
|
|
||||||
Player player = packetEvent.getPlayer();
|
Player player = packetEvent.getPlayer();
|
||||||
|
|
||||||
//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
|
||||||
@ -78,17 +77,18 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
plugin.getSessions().remove(sessionKey);
|
plugin.getSessions().remove(sessionKey);
|
||||||
|
|
||||||
//player.getName() won't work at this state
|
//player.getName() won't work at this state
|
||||||
|
PacketContainer packet = packetEvent.getPacket();
|
||||||
String username = packet.getGameProfiles().read(0).getName();
|
String username = packet.getGameProfiles().read(0).getName();
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
||||||
, new Object[]{sessionKey, username});
|
, new Object[]{sessionKey, username});
|
||||||
if (plugin.getEnabledPremium().contains(username) && isPremium(username)) {
|
if (plugin.getEnabledPremium().contains(username) && isPremiumName(username)) {
|
||||||
//minecraft server implementation
|
//minecraft server implementation
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||||
sentEncryptionRequest(sessionKey, username, player, packetEvent);
|
sentEncryptionRequest(sessionKey, username, player, packetEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPremium(String playerName) {
|
private boolean isPremiumName(String playerName) {
|
||||||
//check if it's a valid playername
|
//check if it's a valid playername
|
||||||
if (playernameMatcher.matcher(playerName).matches()) {
|
if (playernameMatcher.matcher(playerName).matches()) {
|
||||||
//only make a API call if the name is valid existing mojang account
|
//only make a API call if the name is valid existing mojang account
|
||||||
@ -120,12 +120,13 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
PacketContainer newPacket = protocolManager
|
PacketContainer newPacket = protocolManager
|
||||||
.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
|
.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
|
||||||
|
|
||||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getKeyPair().getPublic());
|
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
||||||
//generate a random token which should be the same when we receive it from the client
|
//generate a random token which should be the same when we receive it from the client
|
||||||
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||||
random.nextBytes(verifyToken);
|
random.nextBytes(verifyToken);
|
||||||
newPacket.getByteArrays().write(0, verifyToken);
|
newPacket.getByteArrays().write(0, verifyToken);
|
||||||
|
|
||||||
|
//serverId is a empty string
|
||||||
protocolManager.sendServerPacket(player, newPacket);
|
protocolManager.sendServerPacket(player, newPacket);
|
||||||
|
|
||||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||||
|
Reference in New Issue
Block a user