Finish basic bukkit support

This commit is contained in:
games647
2016-04-26 21:01:48 +02:00
parent 0f85674ec1
commit 53e02d5457
9 changed files with 203 additions and 99 deletions

View File

@ -13,13 +13,11 @@ import com.github.games647.fastlogin.bukkit.listener.EncryptionPacketListener;
import com.github.games647.fastlogin.bukkit.listener.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.listener.StartPacketListener;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.Sets;
import com.google.common.reflect.ClassPath;
import java.io.IOException;
import java.security.KeyPair;
import java.sql.SQLException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@ -46,8 +44,7 @@ public class FastLoginBukkit extends JavaPlugin {
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
//we need a thread-safe set because we access it async in the packet listener
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
private static final int WORKER_THREADS = 5;
private boolean bungeeCord;
private Storage storage;
@ -122,8 +119,12 @@ public class FastLoginBukkit extends JavaPlugin {
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
AsynchronousManager asynchronousManager = protocolManager.getAsynchronousManager();
asynchronousManager.registerAsyncHandler(new StartPacketListener(this, protocolManager)).start();
asynchronousManager.registerAsyncHandler(new EncryptionPacketListener(this, protocolManager)).start();
StartPacketListener startPacketListener = new StartPacketListener(this, protocolManager);
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this, protocolManager);
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
}
}
@ -172,13 +173,8 @@ public class FastLoginBukkit extends JavaPlugin {
return keyPair;
}
/**
* Gets a set of user who activated premium logins
*
* @return user who activated premium logins
*/
public Set<String> getEnabledPremium() {
return enabledPremium;
public Storage getStorage() {
return storage;
}
/**
@ -231,8 +227,4 @@ public class FastLoginBukkit extends JavaPlugin {
authPlugin = authPluginHook;
return true;
}
public boolean isBungee() {
return bungeeCord;
}
}

View File

@ -38,23 +38,34 @@ public class MojangApiConnector {
this.plugin = plugin;
}
public boolean isPremiumName(String playerName) {
/**
*
* @param playerName
* @return null on non-premium
*/
public UUID getPremiumUUID(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;
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = reader.readLine();
if (line != null && !line.equals("null")) {
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
String uuid = (String) userData.get("id");
return parseId(uuid);
}
}
//204 - no content for not found
} catch (IOException ex) {
} catch (Exception 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;
return null;
}
public boolean hasJoinedServer(PlayerSession session, String serverId) {

View File

@ -4,11 +4,11 @@ import java.util.UUID;
public class PlayerProfile {
private final UUID uuid;
private final String playerName;
private long userId;
private UUID uuid;
private boolean premium;
private String lastIp;
private long lastLogin;
@ -32,6 +32,10 @@ public class PlayerProfile {
this.lastIp = lastIp;
}
public String getPlayerName() {
return playerName;
}
public synchronized long getUserId() {
return userId;
}
@ -40,27 +44,27 @@ public class PlayerProfile {
this.userId = generatedId;
}
public UUID getUuid() {
public synchronized UUID getUuid() {
return uuid;
}
public String getPlayerName() {
return playerName;
public synchronized void setUuid(UUID uuid) {
this.uuid = uuid;
}
public boolean isPremium() {
public synchronized boolean isPremium() {
return premium;
}
public void setPremium(boolean premium) {
public synchronized void setPremium(boolean premium) {
this.premium = premium;
}
public String getLastIp() {
public synchronized String getLastIp() {
return lastIp;
}
public void setLastIp(String lastIp) {
public synchronized void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
@ -68,7 +72,7 @@ public class PlayerProfile {
return lastLogin;
}
public void setLastLogin(long lastLogin) {
public synchronized void setLastLogin(long lastLogin) {
this.lastLogin = lastLogin;
}
}

View File

@ -65,7 +65,7 @@ public class Storage {
Statement statement = con.createStatement();
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
+ "`UUID` CHAR(36) NOT NULL, "
+ "`UUID` CHAR(36), "
+ "`Name` VARCHAR(16) NOT NULL, "
+ "`Premium` BOOLEAN NOT NULL, "
+ "`LastIp` VARCHAR(255) NOT NULL, "
@ -107,6 +107,10 @@ public class Storage {
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
profileCache.put(name, playerProfile);
return playerProfile;
} else {
PlayerProfile crackedProfile = new PlayerProfile(null, name, false, "");
profileCache.put(name, crackedProfile);
return crackedProfile;
}
} catch (SQLException sqlEx) {
plugin.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
@ -117,6 +121,40 @@ public class Storage {
return null;
}
// public PlayerProfile getProfile(UUID uuid, boolean fetch) {
// if (profileCache.containsKey(name)) {
// return profileCache.get(name);
// } else if (fetch) {
// Connection con = null;
// try {
// con = dataSource.getConnection();
// PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE
// + " WHERE `Name`=? LIMIT 1");
// loadStatement.setString(1, name);
//
// ResultSet resultSet = loadStatement.executeQuery();
// if (resultSet.next()) {
// long userId = resultSet.getInt(1);
// UUID uuid = FastLoginBukkit.parseId(resultSet.getString(2));
//// String name = resultSet.getString(3);
// boolean premium = resultSet.getBoolean(4);
// String lastIp = resultSet.getString(5);
// long lastLogin = resultSet.getTimestamp(6).getTime();
// PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
// profileCache.put(name, playerProfile);
// return playerProfile;
// }
//
// //todo: result on failure
// } catch (SQLException sqlEx) {
// plugin.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
// } finally {
// closeQuietly(con);
// }
// }
//
// return null;
// }
public boolean save(PlayerProfile playerProfile) {
Connection con = null;

View File

@ -1,8 +1,10 @@
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerProfile;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
@ -20,6 +22,11 @@ public class CrackedCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (plugin.getStorage() == null) {
sender.sendMessage(ChatColor.DARK_RED + "This command is disabled on the backend server");
return true;
}
if (args.length == 0) {
if (!(sender instanceof Player)) {
//console or command block
@ -27,10 +34,22 @@ public class CrackedCommand implements CommandExecutor {
return true;
}
String playerName = sender.getName();
boolean existed = plugin.getEnabledPremium().remove(playerName);
if (existed) {
Player player = (Player) sender;
// UUID uuid = player.getUniqueId();
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getStorage().getProfile(player.getName(), false);
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
profile.setPremium(false);
profile.setUuid(null);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
plugin.getStorage().save(profile);
}
});
notifiyBungeeCord((Player) sender);
} else {
sender.sendMessage(ChatColor.DARK_RED + "You are not in the premium list");
@ -38,14 +57,16 @@ public class CrackedCommand implements CommandExecutor {
return true;
} else {
String playerName = args[0];
boolean existed = plugin.getEnabledPremium().remove(playerName);
if (existed) {
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
sender.sendMessage(ChatColor.DARK_RED + "NOT IMPLEMENTED YET");
//todo:
// String playerName = args[0];
// boolean existed = plugin.getEnabledPremium().remove(playerName);
// if (existed) {
// sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
// notifiyBungeeCord((Player) sender);
} else {
sender.sendMessage(ChatColor.DARK_RED + "User is not in the premium list");
}
// } else {
// sender.sendMessage(ChatColor.DARK_RED + "User is not in the premium list");
// }
}
return true;

View File

@ -1,6 +1,7 @@
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerProfile;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
@ -25,6 +26,11 @@ public class PremiumCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (plugin.getStorage() == null) {
sender.sendMessage(ChatColor.DARK_RED + "This command is disabled on the backend server");
return true;
}
if (args.length == 0) {
if (!(sender instanceof Player)) {
//console or command block
@ -32,24 +38,31 @@ public class PremiumCommand implements CommandExecutor {
return true;
}
String playerName = sender.getName();
boolean didntexist = plugin.getEnabledPremium().add(playerName);
if (!didntexist) {
Player player = (Player) sender;
// UUID uuid = player.getUniqueId();
//todo: load async if it's not in the cache anymore
PlayerProfile profile = plugin.getStorage().getProfile(player.getName(), false);
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
} else {
//todo: resolve uuid
profile.setPremium(true);
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
}
notifiyBungeeCord((Player) sender);
return true;
} else {
String playerName = args[0];
boolean didntexist = plugin.getEnabledPremium().add(playerName);
if (!didntexist) {
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
} else {
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
}
sender.sendMessage(ChatColor.DARK_RED + "NOT IMPLEMENTED YET");
//todo: async load
// String playerName = args[0];
// boolean didntexist = plugin.getEnabledPremium().add(playerName);
// if (!didntexist) {
// sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
// } else {
// sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
// }
// notifiyBungeeCord();
}

View File

@ -3,7 +3,9 @@ package com.github.games647.fastlogin.bukkit.listener;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerProfile;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.Storage;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import java.util.logging.Level;
@ -65,7 +67,17 @@ public class BukkitJoinListener implements Listener {
if (session.needsRegistration()) {
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
plugin.getEnabledPremium().add(session.getUsername());
final Storage storage = plugin.getStorage();
if (storage != null) {
final PlayerProfile playerProfile = storage.getProfile(session.getUsername(), false);
playerProfile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
storage.save(playerProfile);
}
});
}
String generatedPassword = plugin.generateStringPassword();
authPlugin.forceRegister(player, generatedPassword);

View File

@ -1,10 +1,13 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerProfile;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -26,19 +29,26 @@ public class ProtocolSupportListener implements Listener {
return;
}
String playerName = loginStartEvent.getName();
String username = loginStartEvent.getName();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessions().remove(playerName);
plugin.getSessions().remove(username);
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (plugin.getEnabledPremium().contains(playerName)) {
//the player have to be registered in order to invoke the command
startPremiumSession(playerName, loginStartEvent, true);
} else if (plugin.getConfig().getBoolean("autoRegister")
&& authPlugin != null && !plugin.getAuthPlugin().isRegistered(playerName)) {
startPremiumSession(playerName, loginStartEvent, false);
plugin.getEnabledPremium().add(playerName);
PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true);
if (playerProfile != null) {
//user not exists in the db
if (playerProfile.getUserId() == -1) {
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username);
if (premiumUUID != null) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
startPremiumSession(username, loginStartEvent, false);
}
}
} else if (playerProfile.isPremium()) {
startPremiumSession(username, loginStartEvent, true);
}
}
}
@ -55,16 +65,14 @@ public class ProtocolSupportListener implements Listener {
}
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered) {
if (plugin.getApiConnector().isPremiumName(playerName)) {
loginStartEvent.setOnlineMode(true);
InetSocketAddress address = loginStartEvent.getAddress();
loginStartEvent.setOnlineMode(true);
InetSocketAddress address = loginStartEvent.getAddress();
PlayerSession playerSession = new PlayerSession(playerName, null, null);
playerSession.setRegistered(registered);
plugin.getSessions().put(address.toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
loginStartEvent.setUseOnlineModeUUID(true);
}
PlayerSession playerSession = new PlayerSession(playerName, null, null);
playerSession.setRegistered(registered);
plugin.getSessions().put(address.toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
loginStartEvent.setUseOnlineModeUUID(true);
}
}
}

View File

@ -6,12 +6,14 @@ import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerProfile;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import java.lang.reflect.InvocationTargetException;
import java.security.PublicKey;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.entity.Player;
@ -72,41 +74,44 @@ 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.isBungee()) {
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (plugin.getEnabledPremium().contains(username)) {
PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true);
if (playerProfile != null) {
//user not exists in the db
if (playerProfile.getUserId() == -1) {
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username);
if (premiumUUID != null) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
enablePremiumLogin(username, sessionKey, player, packetEvent, false);
}
}
} else if (playerProfile.isPremium()) {
enablePremiumLogin(username, sessionKey, player, packetEvent, true);
} else if (plugin.getConfig().getBoolean("autoRegister")
&& authPlugin != null && !plugin.getAuthPlugin().isRegistered(username)) {
enablePremiumLogin(username, sessionKey, player, packetEvent, false);
plugin.getEnabledPremium().add(username);
}
}
}
//minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
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
//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);
//randomized server id to make sure the request is for our server
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
String serverId = Long.toString(random.nextLong(), 16);
//generate a random token which should be the same when we receive it from the client
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(verifyToken);
//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);
}
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);
}
}