Add support for multiple bungeecords (Fixes #19)

This commit is contained in:
games647 committed 2016-05-28 17:21:08 +02:00
1 parent 3e844be65d
commit cb876a52bd
24 files changed
+187 -217

No files matched your search

@@ -1,15 +1,10 @@
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class BukkitCore extends FastLoginCore {
@@ -30,21 +25,6 @@ public class BukkitCore extends FastLoginCore {
return plugin.getLogger();
}
@Override
public ConcurrentMap<String, PlayerProfile> buildCache() {
return SafeCacheBuilder
.<String, PlayerProfile>newBuilder()
.concurrencyLevel(20)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build(new CacheLoader<String, PlayerProfile>() {
@Override
public PlayerProfile load(String key) throws Exception {
//should be fetched manually
throw new UnsupportedOperationException("Not supported yet.");
}
});
}
@Override
public ThreadFactory getThreadFactory() {
String pluginName = plugin.getName();
@@ -1,6 +1,8 @@
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.fastlogin.core.LoginSession;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.util.UUID;
@@ -11,33 +13,32 @@ import org.apache.commons.lang.ArrayUtils;
*
* This session is invalid if the player disconnects or the login was successful
*/
public class PlayerSession {
public class BukkitLoginSession extends LoginSession {
private final String username;
private final String serverId;
private final byte[] verifyToken;
private UUID uuid;
private WrappedSignedProperty skinProperty;
private boolean verified;
private boolean registered;
public PlayerSession(String username, String serverId, byte[] verifyToken) {
this.username = username;
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
, PlayerProfile profile) {
super(username, registered, profile);
this.serverId = serverId;
this.verifyToken = ArrayUtils.clone(verifyToken);
}
public PlayerSession(String username) {
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY);
//available for bungeecord
public BukkitLoginSession(String username, boolean registered) {
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, registered, null);
}
/**
* Gets the random generated server id. This makes sure the request
* sent from the client is just for this server.
* Gets the random generated server id. This makes sure the request sent from the client is just for this server.
*
* See this for details
* http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
* See this for details http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
*
* Empty if it's a BungeeCord connection
*
@@ -58,15 +59,6 @@ public class PlayerSession {
return ArrayUtils.clone(verifyToken);
}
/**
* Gets the username the player sent to the server
*
* @return the client sent username
*/
public String getUsername() {
return username;
}
/**
* Gets the premium skin of this player
*
@@ -86,26 +78,7 @@ public class PlayerSession {
}
/**
* 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
* Sets whether the player has a premium (paid account) account and valid session
*
* @param verified whether the player has valid session
*/
@@ -122,7 +95,6 @@ public class PlayerSession {
return uuid;
}
/**
* Set the online UUID if it's fetched
*
@@ -133,8 +105,7 @@ public class PlayerSession {
}
/**
* Get whether the player has a premium (paid account) account
* and valid session
* Get whether the player has a premium (paid account) account and valid session
*
* @return whether the player has a valid session
*/
@@ -40,14 +40,14 @@ public class FastLoginBukkit extends JavaPlugin {
//this map is thread-safe for async access (Packet Listener)
//SafeCacheBuilder is used in order to be version independent
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
private final ConcurrentMap<String, BukkitLoginSession> session = SafeCacheBuilder.<String, BukkitLoginSession>newBuilder()
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
.expireAfterWrite(1, TimeUnit.MINUTES)
//mapped by ip:port -> PlayerSession
.build(new CacheLoader<String, PlayerSession>() {
.build(new CacheLoader<String, BukkitLoginSession>() {
@Override
public PlayerSession load(String key) throws Exception {
public BukkitLoginSession load(String key) throws Exception {
//A key should be inserted manually on start packet
throw new UnsupportedOperationException("Not supported");
}
@@ -158,7 +158,7 @@ public class FastLoginBukkit extends JavaPlugin {
*
* @return a thread-safe session map
*/
public ConcurrentMap<String, PlayerSession> getSessions() {
public ConcurrentMap<String, BukkitLoginSession> getSessions() {
return session;
}
@@ -17,7 +17,7 @@ import org.bukkit.entity.Player;
public class ForceLoginTask implements Runnable {
protected final FastLoginBukkit plugin;
private final FastLoginBukkit plugin;
protected final Player player;
public ForceLoginTask(FastLoginBukkit plugin, Player player) {
@@ -33,14 +33,12 @@ public class ForceLoginTask implements Runnable {
//remove the bungeecord identifier if there is ones
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
PlayerSession session = plugin.getSessions().get(id);
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
BukkitLoginSession session = plugin.getSessions().remove(id);
Storage storage = plugin.getCore().getStorage();
PlayerProfile playerProfile = null;
if (storage != null) {
playerProfile = storage.getProfile(player.getName(), false);
if (session != null) {
playerProfile = session.getProfile();
}
if (session == null) {
@@ -53,6 +51,7 @@ public class ForceLoginTask implements Runnable {
//check if it's the same player as we checked before
} else if (player.getName().equals(session.getUsername())) {
//premium player
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (authPlugin == null) {
//maybe only bungeecord plugin
sendSuccessNotification();
@@ -25,11 +25,11 @@ public class MojangApiBukkit extends MojangApiConnector {
@Override
public boolean hasJoinedServer(Object session, String serverId) {
if (!(session instanceof PlayerSession)) {
if (!(session instanceof BukkitLoginSession)) {
return false;
}
PlayerSession playerSession = (PlayerSession) session;
BukkitLoginSession playerSession = (BukkitLoginSession) session;
try {
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
HttpURLConnection conn = getConnection(url);
@@ -34,7 +34,7 @@ public class CrackedCommand implements CommandExecutor {
sender.sendMessage(ChatColor.YELLOW + "Sending request...");
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().getProfile(sender.getName(), true);
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
profile.setPremium(false);
@@ -62,7 +62,7 @@ public class CrackedCommand implements CommandExecutor {
sender.sendMessage(ChatColor.YELLOW + "Sending request for player " + args[0] + "...");
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().getProfile(args[0], true);
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
sender.sendMessage(ChatColor.DARK_RED + "Player not in the database");
return true;
@@ -39,7 +39,7 @@ public class PremiumCommand implements CommandExecutor {
sender.sendMessage(ChatColor.YELLOW + "Sending request...");
} else {
// //todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().getProfile(sender.getName(), true);
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
} else {
@@ -68,7 +68,7 @@ public class PremiumCommand implements CommandExecutor {
sender.sendMessage(ChatColor.YELLOW + "Sending request...");
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().getProfile(args[0], true);
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
sender.sendMessage(ChatColor.DARK_RED + "Player not in the database");
return true;
@@ -4,7 +4,7 @@ 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.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -14,7 +14,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
@@ -41,7 +40,7 @@ public class BukkitJoinListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
PlayerSession session = plugin.getSessions().get(player.getAddress().toString());
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = session.getSkin();
@@ -55,12 +54,4 @@ public class BukkitJoinListener implements Listener {
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin, player), DELAY_LOGIN);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
//prevent memory leaks
player.removeMetadata(plugin.getName(), plugin);
}
}
@@ -2,15 +2,18 @@ package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
@@ -31,11 +34,11 @@ public class BungeeCordListener implements PluginMessageListener {
protected final FastLoginBukkit plugin;
//null if whitelist is empty so bungeecord support is disabled
private final UUID proxyId;
private final Set<UUID> proxyIds;
public BungeeCordListener(FastLoginBukkit plugin) {
this.plugin = plugin;
this.proxyId = loadBungeeCordId();
this.proxyIds = loadBungeeCordIds();
}
@Override
@@ -65,17 +68,14 @@ public class BungeeCordListener implements PluginMessageListener {
plugin.getLogger().log(Level.FINEST, "Received proxy id {0} from {1}", new Object[]{sourceId, player});
//fail if BungeeCord support is disabled (id = null)
if (sourceId.equals(proxyId)) {
final PlayerSession playerSession = new PlayerSession(playerName);
if (proxyIds.contains(sourceId)) {
final String id = '/' + checkedPlayer.getAddress().getAddress().getHostAddress() + ':'
+ checkedPlayer.getAddress().getPort();
if ("AUTO_LOGIN".equalsIgnoreCase(subchannel)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
playerSession.setVerified(true);
playerSession.setRegistered(true);
plugin.getSessions().put(id, playerSession);
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
playerSession.setVerified(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
@@ -83,6 +83,8 @@ public class BungeeCordListener implements PluginMessageListener {
try {
//we need to check if the player is registered on Bukkit too
if (authPlugin != null && !authPlugin.isRegistered(playerName)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
playerSession.setVerified(true);
plugin.getSessions().put(id, playerSession);
}
} catch (Exception ex) {
@@ -97,7 +99,7 @@ public class BungeeCordListener implements PluginMessageListener {
}
}
public UUID loadBungeeCordId() {
public Set<UUID> loadBungeeCordIds() {
File whitelistFile = new File(plugin.getDataFolder(), FILE_NAME);
//create a new folder if it doesn't exist. Fail silently otherwise
whitelistFile.getParentFile().mkdir();
@@ -106,9 +108,16 @@ public class BungeeCordListener implements PluginMessageListener {
whitelistFile.createNewFile();
}
String firstLine = Files.readFirstLine(whitelistFile, Charsets.UTF_8);
if (firstLine != null && !firstLine.isEmpty()) {
return UUID.fromString(firstLine.trim());
Set<UUID> ids = Sets.newHashSet();
List<String> lines = Files.readLines(whitelistFile, Charsets.UTF_8);
for (String line : lines) {
if (line == null || line.trim().isEmpty()) {
continue;
}
UUID uuid = UUID.fromString(line.trim());
ids.add(uuid);
}
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex);
@@ -1,7 +1,7 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.core.PlayerProfile;
@@ -33,18 +33,18 @@ public class ProtocolSupportListener implements Listener {
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(username);
plugin.getSessions().remove(loginStartEvent.getAddress().toString());
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (authPlugin == null) {
return;
}
PlayerProfile playerProfile = plugin.getCore().getStorage().getProfile(username, true);
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(username);
if (playerProfile != null) {
if (playerProfile.isPremium()) {
if (playerProfile.getUserId() != -1) {
startPremiumSession(username, loginStartEvent, true);
startPremiumSession(username, loginStartEvent, true, playerProfile);
}
} else if (playerProfile.getUserId() == -1) {
//user not exists in the db
@@ -53,7 +53,7 @@ public class ProtocolSupportListener implements Listener {
UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
if (premiumUUID != null) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
startPremiumSession(username, loginStartEvent, false);
startPremiumSession(username, loginStartEvent, false, playerProfile);
}
}
} catch (Exception ex) {
@@ -68,19 +68,19 @@ public class ProtocolSupportListener implements Listener {
//skin was resolved -> premium player
if (propertiesResolveEvent.hasProperty("textures")) {
InetSocketAddress address = propertiesResolveEvent.getAddress();
PlayerSession session = plugin.getSessions().get(address.toString());
BukkitLoginSession session = plugin.getSessions().get(address.toString());
if (session != null) {
session.setVerified(true);
}
}
}
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered) {
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered
, PlayerProfile playerProfile) {
loginStartEvent.setOnlineMode(true);
InetSocketAddress address = loginStartEvent.getAddress();
PlayerSession playerSession = new PlayerSession(playerName, null, null);
playerSession.setRegistered(registered);
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, null, null, registered, playerProfile);
plugin.getSessions().put(address.toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
loginStartEvent.setUseOnlineModeUUID(true);
@@ -11,7 +11,7 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -73,13 +73,10 @@ public class EncryptionPacketListener extends PacketAdapter {
public void onPacketReceiving(PacketEvent packetEvent) {
Player player = packetEvent.getPlayer();
//the player name is unknown to ProtocolLib (so getName() doesn't work) - now uses ip:port as key
String uniqueSessionKey = player.getAddress().toString();
PlayerSession session = plugin.getSessions().get(uniqueSessionKey);
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
if (session == null) {
disconnect(packetEvent, "Invalid request", Level.FINE
, "Player {0} tried to send encryption response at invalid state"
, player.getAddress());
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
return;
}
@@ -118,7 +115,7 @@ public class EncryptionPacketListener extends PacketAdapter {
packetEvent.setCancelled(true);
}
private void setPremiumUUID(PlayerSession session, Player player) {
private void setPremiumUUID(BukkitLoginSession session, Player player) {
UUID uuid = session.getUuid();
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
try {
@@ -132,7 +129,7 @@ public class EncryptionPacketListener extends PacketAdapter {
}
}
private boolean checkVerifyToken(PlayerSession session, PrivateKey privateKey, PacketEvent packetEvent) {
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
byte[] requestVerify = session.getVerifyToken();
//encrypted verify token
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
@@ -6,9 +6,9 @@ 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.core.PlayerProfile;
import com.github.games647.fastlogin.bukkit.PlayerSession;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.lang.reflect.InvocationTargetException;
import java.security.PublicKey;
@@ -83,20 +83,20 @@ public class StartPacketListener extends PacketAdapter {
return;
}
PlayerProfile playerProfile = plugin.getCore().getStorage().getProfile(username, true);
if (playerProfile != null) {
if (playerProfile.isPremium()) {
if (playerProfile.getUserId() != -1) {
enablePremiumLogin(username, sessionKey, player, packetEvent, true);
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
if (profile != null) {
if (profile.isPremium()) {
if (profile.getUserId() != -1) {
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, true);
}
} else if (playerProfile.getUserId() == -1) {
} else if (profile.getUserId() == -1) {
//user not exists in the db
try {
if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
if (premiumUUID != null) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
enablePremiumLogin(username, sessionKey, player, packetEvent, false);
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
}
}
} catch (Exception ex) {
@@ -108,8 +108,8 @@ public class StartPacketListener extends PacketAdapter {
//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) {
private void enablePremiumLogin(String username, PlayerProfile profile, String sessionKey, Player player
, PacketEvent packetEvent, boolean registered) {
//randomized server id to make sure the request is for our server
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
String serverId = Long.toString(random.nextLong(), 16);
@@ -120,8 +120,8 @@ public class StartPacketListener extends PacketAdapter {
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
if (success) {
PlayerSession playerSession = new PlayerSession(username, serverId, verifyToken);
playerSession.setRegistered(registered);
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId
, verifyToken, registered, profile);
plugin.getSessions().put(sessionKey, playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
packetEvent.setCancelled(true);