forked from TuxCoding/FastLogin
Added auto login without commands (Fixes #2)
This commit is contained in:
@ -1,9 +1,13 @@
|
||||
######0.5
|
||||
|
||||
* Added autologin - See config
|
||||
* Added config
|
||||
* Added isRegistered API method
|
||||
* Added forceRegister API method
|
||||
* Fixed CrazyLogin player data restore -> Fixes memory leaks with this plugin
|
||||
|
||||
* Fixed premium name check to protocolsupport
|
||||
|
||||
######0.4
|
||||
|
||||
* Added forward premium skin
|
||||
|
@ -37,7 +37,7 @@ public class EncryptionUtil {
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
|
||||
keyPairGenerator.initialize(1024);
|
||||
keyPairGenerator.initialize(1_024);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
//Should be existing in every vm
|
||||
|
@ -17,13 +17,12 @@ import com.google.common.collect.Sets;
|
||||
import com.google.common.reflect.ClassPath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
@ -33,10 +32,6 @@ import org.bukkit.plugin.java.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
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
|
||||
@ -62,9 +57,11 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
});
|
||||
|
||||
private AuthPlugin authPlugin;
|
||||
private final MojangApiConnector mojangApiConnector = new MojangApiConnector(this);
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
if (getServer().getOnlineMode() || !registerHooks()) {
|
||||
//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");
|
||||
@ -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
|
||||
* 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 prepared connection
|
||||
*
|
||||
* @throws IOException on invalid url format or on {@link java.net.URL#openConnection() }
|
||||
* @return the connector instance
|
||||
*/
|
||||
public 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;
|
||||
public MojangApiConnector getApiConnector() {
|
||||
return mojangApiConnector;
|
||||
}
|
||||
|
||||
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 boolean verified;
|
||||
private boolean registered;
|
||||
|
||||
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
||||
this.username = username;
|
||||
@ -73,6 +74,24 @@ public class PlayerSession {
|
||||
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
|
||||
* and valid session
|
||||
|
@ -16,6 +16,7 @@ public class AuthMeHook implements AuthPlugin {
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return NewAPI.getInstance().isRegistered(playerName);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@ -55,8 +56,19 @@ public class BukkitJoinListener implements Listener {
|
||||
player.setMetadata(plugin.getName(), new FixedMetadataValue(plugin, true));
|
||||
//check if it's the same player as we checked before
|
||||
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||
plugin.getAuthPlugin().forceLogin(player);
|
||||
if (session.needsRegistration()) {
|
||||
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.wrappers.WrappedChatComponent;
|
||||
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.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
@ -29,9 +25,6 @@ import java.util.logging.Level;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
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.
|
||||
@ -50,9 +43,6 @@ import org.json.simple.JSONValue;
|
||||
*/
|
||||
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;
|
||||
//hides the inherit Plugin plugin field, but we need this type
|
||||
private final FastLoginBukkit plugin;
|
||||
@ -111,7 +101,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
private void receiveFakeStartPacket(String username, Player from) {
|
||||
//see StartPacketListener for packet information
|
||||
|
@ -8,6 +8,7 @@ import java.net.InetSocketAddress;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@ -15,6 +16,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent.ProfileProperty;
|
||||
|
||||
public class ProtcolSupportListener implements Listener {
|
||||
|
||||
@ -34,12 +36,11 @@ public class ProtcolSupportListener implements Listener {
|
||||
|
||||
String playerName = loginStartEvent.getName();
|
||||
if (plugin.getEnabledPremium().contains(playerName)) {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
PlayerSession playerSession = new PlayerSession(playerName, null, null);
|
||||
plugin.getSessions().put(address.toString(), playerSession);
|
||||
// loginStartEvent.setUseOnlineModeUUID(true);
|
||||
//the player have to be registered in order to invoke the command
|
||||
startPremiumSession(playerName, loginStartEvent, true);
|
||||
} else if (plugin.getConfig().getBoolean("autologin") && !plugin.getAuthPlugin().isRegistered(playerName)) {
|
||||
startPremiumSession(playerName, loginStartEvent, false);
|
||||
plugin.getEnabledPremium().add(playerName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,8 +51,7 @@ public class ProtcolSupportListener implements Listener {
|
||||
if (session != null) {
|
||||
session.setVerified(true);
|
||||
|
||||
PlayerPropertiesResolveEvent.ProfileProperty skinProperty = propertiesResolveEvent.getProperties()
|
||||
.get("textures");
|
||||
ProfileProperty skinProperty = propertiesResolveEvent.getProperties().get("textures");
|
||||
if (skinProperty != null) {
|
||||
WrappedSignedProperty signedProperty = WrappedSignedProperty
|
||||
.fromValues(skinProperty.getName(), skinProperty.getValue(), skinProperty.getSignature());
|
||||
@ -74,12 +74,35 @@ public class ProtcolSupportListener implements Listener {
|
||||
if (player.isOnline()) {
|
||||
//check if it's the same player as we checked before
|
||||
if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) {
|
||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||
plugin.getAuthPlugin().forceLogin(player);
|
||||
if (session.needsRegistration()) {
|
||||
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
|
||||
}, 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.PlayerSession;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@ -31,10 +28,6 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
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 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
|
||||
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) {
|
||||
//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();
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
||||
, new Object[]{sessionKey, username});
|
||||
if (!plugin.getBungeeCordUsers().containsKey(player)
|
||||
&& plugin.getEnabledPremium().contains(username) && isPremiumName(username)) {
|
||||
if (!plugin.getBungeeCordUsers().containsKey(player)) {
|
||||
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
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
sentEncryptionRequest(sessionKey, username, player, packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private 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 = plugin.getConnection(UUID_LINK + playerName);
|
||||
int responseCode = connection.getResponseCode();
|
||||
//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);
|
||||
|
||||
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);
|
||||
//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) {
|
||||
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) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||
try {
|
||||
/**
|
||||
* 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);
|
||||
|
||||
//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.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);
|
||||
|
||||
//serverId is a empty string
|
||||
protocolManager.sendServerPacket(player, newPacket);
|
||||
|
||||
//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);
|
||||
return true;
|
||||
} catch (InvocationTargetException 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