forked from TuxCoding/FastLogin
Added auto login without commands (Fixes #2)
This commit is contained in:
@ -1,9 +1,13 @@
|
|||||||
######0.5
|
######0.5
|
||||||
|
|
||||||
|
* Added autologin - See config
|
||||||
|
* Added config
|
||||||
* Added isRegistered API method
|
* Added isRegistered API method
|
||||||
* Added forceRegister API method
|
* Added forceRegister API method
|
||||||
* Fixed CrazyLogin player data restore -> Fixes memory leaks with this plugin
|
* Fixed CrazyLogin player data restore -> Fixes memory leaks with this plugin
|
||||||
|
|
||||||
|
* Fixed premium name check to protocolsupport
|
||||||
|
|
||||||
######0.4
|
######0.4
|
||||||
|
|
||||||
* Added forward premium skin
|
* Added forward premium skin
|
||||||
|
@ -37,7 +37,7 @@ public class EncryptionUtil {
|
|||||||
try {
|
try {
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
|
||||||
keyPairGenerator.initialize(1024);
|
keyPairGenerator.initialize(1_024);
|
||||||
return keyPairGenerator.generateKeyPair();
|
return keyPairGenerator.generateKeyPair();
|
||||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||||
//Should be existing in every vm
|
//Should be existing in every vm
|
||||||
|
@ -17,13 +17,12 @@ import com.google.common.collect.Sets;
|
|||||||
import com.google.common.reflect.ClassPath;
|
import com.google.common.reflect.ClassPath;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
@ -33,10 +32,6 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
*/
|
*/
|
||||||
public class FastLoginBukkit extends JavaPlugin {
|
public class FastLoginBukkit extends JavaPlugin {
|
||||||
|
|
||||||
//http connection, read timeout and user agent for a connection to mojang api servers
|
|
||||||
private static final int TIMEOUT = 1 * 1000;
|
|
||||||
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 = EncryptionUtil.generateKeyPair();
|
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||||
|
|
||||||
@ -62,9 +57,11 @@ public class FastLoginBukkit extends JavaPlugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
private AuthPlugin authPlugin;
|
private AuthPlugin authPlugin;
|
||||||
|
private final MojangApiConnector mojangApiConnector = new MojangApiConnector(this);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
saveDefaultConfig();
|
||||||
if (getServer().getOnlineMode() || !registerHooks()) {
|
if (getServer().getOnlineMode() || !registerHooks()) {
|
||||||
//we need to require offline to prevent a session request for a offline player
|
//we need to require offline to prevent a session request for a offline player
|
||||||
getLogger().severe("Server have to be in offline mode and have an auth plugin installed");
|
getLogger().severe("Server have to be in offline mode and have an auth plugin installed");
|
||||||
@ -108,6 +105,10 @@ public class FastLoginBukkit extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateStringPassword() {
|
||||||
|
return RandomStringUtils.random(8, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
||||||
* account)
|
* account)
|
||||||
@ -158,22 +159,13 @@ public class FastLoginBukkit extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a Mojang API connection. The connection is not started in this method
|
* Gets the a connection in order to access important
|
||||||
|
* features from the Mojang API.
|
||||||
*
|
*
|
||||||
* @param url the url connecting to
|
* @return the connector instance
|
||||||
* @return the prepared connection
|
|
||||||
*
|
|
||||||
* @throws IOException on invalid url format or on {@link java.net.URL#openConnection() }
|
|
||||||
*/
|
*/
|
||||||
public HttpURLConnection getConnection(String url) throws IOException {
|
public MojangApiConnector getApiConnector() {
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
return mojangApiConnector;
|
||||||
connection.setConnectTimeout(TIMEOUT);
|
|
||||||
connection.setReadTimeout(TIMEOUT);
|
|
||||||
//the new Mojang API just uses json as response
|
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
|
||||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean registerHooks() {
|
private boolean registerHooks() {
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.github.games647.fastlogin.bukkit;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.JSONValue;
|
||||||
|
|
||||||
|
public class MojangApiConnector {
|
||||||
|
|
||||||
|
//http connection, read timeout and user agent for a connection to mojang api servers
|
||||||
|
private static final int TIMEOUT = 1 * 1_000;
|
||||||
|
private static final String USER_AGENT = "Premium-Checker";
|
||||||
|
|
||||||
|
//mojang api check to prove a player is logged in minecraft and made a join server request
|
||||||
|
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
||||||
|
|
||||||
|
//only premium (paid account) users have a uuid from here
|
||||||
|
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||||
|
//this includes a-zA-Z1-9_
|
||||||
|
private static final String VALID_PLAYERNAME = "^\\w{2,16}$";
|
||||||
|
|
||||||
|
//compile the pattern only on plugin enable -> and this have to be threadsafe
|
||||||
|
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
||||||
|
|
||||||
|
private final FastLoginBukkit plugin;
|
||||||
|
|
||||||
|
public MojangApiConnector(FastLoginBukkit plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPremiumName(String playerName) {
|
||||||
|
//check if it's a valid playername
|
||||||
|
if (playernameMatcher.matcher(playerName).matches()) {
|
||||||
|
//only make a API call if the name is valid existing mojang account
|
||||||
|
try {
|
||||||
|
HttpURLConnection connection = getConnection(UUID_LINK + playerName);
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
|
||||||
|
return responseCode == HttpURLConnection.HTTP_OK;
|
||||||
|
//204 - no content for not found
|
||||||
|
} catch (IOException ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Failed to check if player has a paid account", ex);
|
||||||
|
}
|
||||||
|
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasJoinedServer(PlayerSession session, String serverId) {
|
||||||
|
try {
|
||||||
|
String url = HAS_JOINED_URL + "username=" + session.getUsername() + "&serverId=" + serverId;
|
||||||
|
HttpURLConnection conn = getConnection(url);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line != null && !line.equals("null")) {
|
||||||
|
//validate parsing
|
||||||
|
//http://wiki.vg/Protocol_Encryption#Server
|
||||||
|
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||||
|
String uuid = (String) userData.get("id");
|
||||||
|
|
||||||
|
JSONArray properties = (JSONArray) userData.get("properties");
|
||||||
|
JSONObject skinProperty = (JSONObject) properties.get(0);
|
||||||
|
|
||||||
|
String propertyName = (String) skinProperty.get("name");
|
||||||
|
if (propertyName.equals("textures")) {
|
||||||
|
String skinValue = (String) skinProperty.get("value");
|
||||||
|
String signature = (String) skinProperty.get("signature");
|
||||||
|
session.setSkin(WrappedSignedProperty.fromValues(propertyName, skinValue, signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
//catch not only ioexceptions also parse and NPE on unexpected json format
|
||||||
|
plugin.getLogger().log(Level.WARNING, "Failed to verify session", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpURLConnection getConnection(String url) throws IOException {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
connection.setConnectTimeout(TIMEOUT);
|
||||||
|
connection.setReadTimeout(TIMEOUT);
|
||||||
|
//the new Mojang API just uses json as response
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ public class PlayerSession {
|
|||||||
|
|
||||||
private WrappedSignedProperty skinProperty;
|
private WrappedSignedProperty skinProperty;
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
|
private boolean registered;
|
||||||
|
|
||||||
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
@ -73,6 +74,24 @@ public class PlayerSession {
|
|||||||
this.skinProperty = skinProperty;
|
this.skinProperty = skinProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the account of this player already exists
|
||||||
|
*
|
||||||
|
* @param registered whether the account exists
|
||||||
|
*/
|
||||||
|
public synchronized void setRegistered(boolean registered) {
|
||||||
|
this.registered = registered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether the account of this player already exists.
|
||||||
|
*
|
||||||
|
* @return whether the account exists
|
||||||
|
*/
|
||||||
|
public synchronized boolean needsRegistration() {
|
||||||
|
return !registered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the player has a premium (paid account) account
|
* Sets whether the player has a premium (paid account) account
|
||||||
* and valid session
|
* and valid session
|
||||||
|
@ -16,6 +16,7 @@ public class AuthMeHook implements AuthPlugin {
|
|||||||
NewAPI.getInstance().forceLogin(player);
|
NewAPI.getInstance().forceLogin(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isRegistered(String playerName) {
|
public boolean isRegistered(String playerName) {
|
||||||
return NewAPI.getInstance().isRegistered(playerName);
|
return NewAPI.getInstance().isRegistered(playerName);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import com.github.games647.fastlogin.bukkit.PlayerSession;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@ -55,8 +56,19 @@ public class BukkitJoinListener implements Listener {
|
|||||||
player.setMetadata(plugin.getName(), new FixedMetadataValue(plugin, true));
|
player.setMetadata(plugin.getName(), new FixedMetadataValue(plugin, true));
|
||||||
//check if it's the same player as we checked before
|
//check if it's the same player as we checked before
|
||||||
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
||||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
if (session.needsRegistration()) {
|
||||||
plugin.getAuthPlugin().forceLogin(player);
|
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
|
||||||
|
|
||||||
|
String generatedPassword = plugin.generateStringPassword();
|
||||||
|
plugin.getAuthPlugin().forceRegister(player, generatedPassword);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "Auto registered with password: "
|
||||||
|
+ generatedPassword);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "You may want change it?");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||||
|
plugin.getAuthPlugin().forceLogin(player);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "Auto logged in");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,14 @@ 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.comphenix.protocol.wrappers.WrappedSignedProperty;
|
|
||||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -29,9 +25,6 @@ import java.util.logging.Level;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.json.simple.JSONArray;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.JSONValue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles incoming encryption responses from connecting clients.
|
* Handles incoming encryption responses from connecting clients.
|
||||||
@ -50,9 +43,6 @@ import org.json.simple.JSONValue;
|
|||||||
*/
|
*/
|
||||||
public class EncryptionPacketListener extends PacketAdapter {
|
public class EncryptionPacketListener extends PacketAdapter {
|
||||||
|
|
||||||
//mojang api check to prove a player is logged in minecraft and made a join server request
|
|
||||||
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
|
||||||
|
|
||||||
private final ProtocolManager protocolManager;
|
private final ProtocolManager protocolManager;
|
||||||
//hides the inherit Plugin plugin field, but we need this type
|
//hides the inherit Plugin plugin field, but we need this type
|
||||||
private final FastLoginBukkit plugin;
|
private final FastLoginBukkit plugin;
|
||||||
@ -111,7 +101,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||||
|
|
||||||
String username = session.getUsername();
|
String username = session.getUsername();
|
||||||
if (hasJoinedServer(session, serverId)) {
|
if (plugin.getApiConnector().hasJoinedServer(session, serverId)) {
|
||||||
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);
|
||||||
@ -203,40 +193,6 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasJoinedServer(PlayerSession session, String serverId) {
|
|
||||||
try {
|
|
||||||
String url = HAS_JOINED_URL + "username=" + session.getUsername() + "&serverId=" + serverId;
|
|
||||||
HttpURLConnection conn = plugin.getConnection(url);
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
|
||||||
String line = reader.readLine();
|
|
||||||
if (line != null && !line.equals("null")) {
|
|
||||||
//validate parsing
|
|
||||||
//http://wiki.vg/Protocol_Encryption#Server
|
|
||||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
|
||||||
String uuid = (String) userData.get("id");
|
|
||||||
|
|
||||||
JSONArray properties = (JSONArray) userData.get("properties");
|
|
||||||
JSONObject skinProperty = (JSONObject) properties.get(0);
|
|
||||||
|
|
||||||
String propertyName = (String) skinProperty.get("name");
|
|
||||||
if (propertyName.equals("textures")) {
|
|
||||||
String skinValue = (String) skinProperty.get("value");
|
|
||||||
String signature = (String) skinProperty.get("signature");
|
|
||||||
session.setSkin(WrappedSignedProperty.fromValues(propertyName, skinValue, signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
//catch not only ioexceptions also parse and NPE on unexpected json format
|
|
||||||
plugin.getLogger().log(Level.WARNING, "Failed to verify session", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//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, Player from) {
|
||||||
//see StartPacketListener for packet information
|
//see StartPacketListener for packet information
|
||||||
|
@ -8,6 +8,7 @@ import java.net.InetSocketAddress;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@ -15,6 +16,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
|||||||
|
|
||||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
||||||
|
import protocolsupport.api.events.PlayerPropertiesResolveEvent.ProfileProperty;
|
||||||
|
|
||||||
public class ProtcolSupportListener implements Listener {
|
public class ProtcolSupportListener implements Listener {
|
||||||
|
|
||||||
@ -34,12 +36,11 @@ public class ProtcolSupportListener implements Listener {
|
|||||||
|
|
||||||
String playerName = loginStartEvent.getName();
|
String playerName = loginStartEvent.getName();
|
||||||
if (plugin.getEnabledPremium().contains(playerName)) {
|
if (plugin.getEnabledPremium().contains(playerName)) {
|
||||||
loginStartEvent.setOnlineMode(true);
|
//the player have to be registered in order to invoke the command
|
||||||
InetSocketAddress address = loginStartEvent.getAddress();
|
startPremiumSession(playerName, loginStartEvent, true);
|
||||||
|
} else if (plugin.getConfig().getBoolean("autologin") && !plugin.getAuthPlugin().isRegistered(playerName)) {
|
||||||
PlayerSession playerSession = new PlayerSession(playerName, null, null);
|
startPremiumSession(playerName, loginStartEvent, false);
|
||||||
plugin.getSessions().put(address.toString(), playerSession);
|
plugin.getEnabledPremium().add(playerName);
|
||||||
// loginStartEvent.setUseOnlineModeUUID(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +51,7 @@ public class ProtcolSupportListener implements Listener {
|
|||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.setVerified(true);
|
session.setVerified(true);
|
||||||
|
|
||||||
PlayerPropertiesResolveEvent.ProfileProperty skinProperty = propertiesResolveEvent.getProperties()
|
ProfileProperty skinProperty = propertiesResolveEvent.getProperties().get("textures");
|
||||||
.get("textures");
|
|
||||||
if (skinProperty != null) {
|
if (skinProperty != null) {
|
||||||
WrappedSignedProperty signedProperty = WrappedSignedProperty
|
WrappedSignedProperty signedProperty = WrappedSignedProperty
|
||||||
.fromValues(skinProperty.getName(), skinProperty.getValue(), skinProperty.getSignature());
|
.fromValues(skinProperty.getName(), skinProperty.getValue(), skinProperty.getSignature());
|
||||||
@ -74,12 +74,35 @@ public class ProtcolSupportListener implements Listener {
|
|||||||
if (player.isOnline()) {
|
if (player.isOnline()) {
|
||||||
//check if it's the same player as we checked before
|
//check if it's the same player as we checked before
|
||||||
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
||||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
if (session.needsRegistration()) {
|
||||||
plugin.getAuthPlugin().forceLogin(player);
|
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
|
||||||
|
|
||||||
|
String generatedPassword = plugin.generateStringPassword();
|
||||||
|
plugin.getAuthPlugin().forceRegister(player, generatedPassword);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "Auto registered with password: "
|
||||||
|
+ generatedPassword);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "You may want change it?");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||||
|
plugin.getAuthPlugin().forceLogin(player);
|
||||||
|
player.sendMessage(ChatColor.DARK_GREEN + "Auto logged in");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Wait before auth plugin and we received a message from BungeeCord initializes the player
|
//Wait before auth plugin and we received a message from BungeeCord initializes the player
|
||||||
}, DELAY_LOGIN);
|
}, DELAY_LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered) {
|
||||||
|
if (plugin.getApiConnector().isPremiumName(playerName)) {
|
||||||
|
loginStartEvent.setOnlineMode(true);
|
||||||
|
InetSocketAddress address = loginStartEvent.getAddress();
|
||||||
|
|
||||||
|
PlayerSession playerSession = new PlayerSession(playerName, null, null);
|
||||||
|
playerSession.setRegistered(registered);
|
||||||
|
plugin.getSessions().put(address.toString(), playerSession);
|
||||||
|
// loginStartEvent.setUseOnlineModeUUID(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,10 @@ import com.comphenix.protocol.events.PacketEvent;
|
|||||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -31,10 +28,6 @@ import org.bukkit.entity.Player;
|
|||||||
*/
|
*/
|
||||||
public class StartPacketListener extends PacketAdapter {
|
public class StartPacketListener extends PacketAdapter {
|
||||||
|
|
||||||
//only premium (paid account) users have a uuid from here
|
|
||||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
|
||||||
//this includes a-zA-Z1-9_
|
|
||||||
private static final String VALID_PLAYERNAME = "^\\w{2,16}$";
|
|
||||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||||
|
|
||||||
private final ProtocolManager protocolManager;
|
private final ProtocolManager protocolManager;
|
||||||
@ -43,8 +36,6 @@ public class StartPacketListener 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 Random random = new Random();
|
private final Random random = new Random();
|
||||||
//compile the pattern on plugin enable
|
|
||||||
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
|
||||||
|
|
||||||
public StartPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
public StartPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
||||||
//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
|
||||||
@ -80,35 +71,43 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
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.getBungeeCordUsers().containsKey(player)
|
if (!plugin.getBungeeCordUsers().containsKey(player)) {
|
||||||
&& plugin.getEnabledPremium().contains(username) && isPremiumName(username)) {
|
if (plugin.getEnabledPremium().contains(username)) {
|
||||||
|
enablePremiumLogin(username, sessionKey, player, packetEvent, true);
|
||||||
|
} else if (plugin.getConfig().getBoolean("autologin") && !plugin.getAuthPlugin().isRegistered(username)) {
|
||||||
|
enablePremiumLogin(username, sessionKey, player, packetEvent, false);
|
||||||
|
plugin.getEnabledPremium().add(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enablePremiumLogin(String username, String sessionKey, Player player, PacketEvent packetEvent
|
||||||
|
, boolean registered) {
|
||||||
|
if (plugin.getApiConnector().isPremiumName(username)) {
|
||||||
|
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPremiumName(String playerName) {
|
//randomized server id to make sure the request is for our server
|
||||||
//check if it's a valid playername
|
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||||
if (playernameMatcher.matcher(playerName).matches()) {
|
String serverId = Long.toString(random.nextLong(), 16);
|
||||||
//only make a API call if the name is valid existing mojang account
|
|
||||||
try {
|
|
||||||
HttpURLConnection connection = plugin.getConnection(UUID_LINK + playerName);
|
|
||||||
int responseCode = connection.getResponseCode();
|
|
||||||
|
|
||||||
return responseCode == HttpURLConnection.HTTP_OK;
|
//generate a random token which should be the same when we receive it from the client
|
||||||
//204 - no content for not found
|
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||||
} catch (IOException ex) {
|
random.nextBytes(verifyToken);
|
||||||
plugin.getLogger().log(Level.SEVERE, "Failed to check if player has a paid account", ex);
|
|
||||||
|
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
|
||||||
|
if (success) {
|
||||||
|
PlayerSession playerSession = new PlayerSession(username, serverId, verifyToken);
|
||||||
|
playerSession.setRegistered(registered);
|
||||||
|
plugin.getSessions().put(sessionKey, playerSession);
|
||||||
|
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||||
|
packetEvent.setCancelled(true);
|
||||||
}
|
}
|
||||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sentEncryptionRequest(String sessionKey, String username, Player player, PacketEvent packetEvent) {
|
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||||
@ -119,24 +118,18 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
*/
|
*/
|
||||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
||||||
|
|
||||||
//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);
|
|
||||||
newPacket.getStrings().write(0, serverId);
|
newPacket.getStrings().write(0, serverId);
|
||||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().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
|
|
||||||
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
|
||||||
random.nextBytes(verifyToken);
|
|
||||||
newPacket.getByteArrays().write(0, verifyToken);
|
newPacket.getByteArrays().write(0, verifyToken);
|
||||||
|
|
||||||
//serverId is a empty string
|
//serverId is a empty string
|
||||||
protocolManager.sendServerPacket(player, newPacket);
|
protocolManager.sendServerPacket(player, newPacket);
|
||||||
|
return true;
|
||||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
|
||||||
plugin.getSessions().put(sessionKey, new PlayerSession(username, serverId, verifyToken));
|
|
||||||
packetEvent.setCancelled(true);
|
|
||||||
} catch (InvocationTargetException ex) {
|
} catch (InvocationTargetException ex) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
bukkit/src/main/resources/config.yml
Normal file
19
bukkit/src/main/resources/config.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# FastLogin config
|
||||||
|
# You can access the newest config here:
|
||||||
|
# https://github.com/games647/FastLogin/blob/master/bukkit/src/main/resources/config.yml
|
||||||
|
|
||||||
|
# Request a premium login without forcing the player to type a command
|
||||||
|
#
|
||||||
|
# If you activate autologin, this plugin will check/do these points on login:
|
||||||
|
# 1. An existing cracked account shouldn't exist
|
||||||
|
# -> paid accounts cannot steal the existing account of cracked players
|
||||||
|
# - (Already registered players could still use the /premium command to activate premium checks)
|
||||||
|
# 2. Automatically registers an account with a strong random generated password
|
||||||
|
# -> cracked player cannot register an account for the premium player and so cannot the steal the account
|
||||||
|
#
|
||||||
|
# Furthermore the premium player check have to be made based on the player name
|
||||||
|
# This means if a cracked player connects to the server and we request a paid account login from this player
|
||||||
|
# the player just disconnect and sees the message: 'bad login'
|
||||||
|
# There is no way to change this message
|
||||||
|
# For more information: https://github.com/games647/FastLogin#why-do-players-have-to-invoke-a-command
|
||||||
|
autologin: false
|
Reference in New Issue
Block a user