Fix NPE for skin apply in ProtocolLib mode (Related #182)

This commit is contained in:
games647
2017-10-15 17:57:24 +02:00
parent 4858049c2a
commit 57eff4b3ec
18 changed files with 48 additions and 39 deletions

View File

@ -62,6 +62,7 @@ public class BukkitLoginSession extends LoginSession {
return ArrayUtils.clone(verifyToken); return ArrayUtils.clone(verifyToken);
} }
//todo: this should be optional for players without a skin at all
public synchronized SkinProperties getSkinProperty() { public synchronized SkinProperties getSkinProperty() {
return skinProperty; return skinProperty;
} }

View File

@ -4,7 +4,7 @@ import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand; import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
import com.github.games647.fastlogin.bukkit.listener.BungeeListener; import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
import com.github.games647.fastlogin.bukkit.listener.JoinListener; import com.github.games647.fastlogin.bukkit.listener.JoinListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.LoginSkinApplyListener; import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener; import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener;
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener; import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook; import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
@ -70,7 +70,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
//check for incoming messages from the bungeecord version of this plugin //check for incoming messages from the bungeecord version of this plugin
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeListener(this)); getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeListener(this));
getServer().getMessenger().registerOutgoingPluginChannel(this, getName()); getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
//register listeners on success
} else { } else {
if (!core.setupDatabase()) { if (!core.setupDatabase()) {
setEnabled(false); setEnabled(false);
@ -81,11 +80,10 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this); getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) { } else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
//they will be created with a static builder, because otherwise it will throw a //they will be created with a static builder, because otherwise it will throw a
//java.lang.NoClassDefFoundError: com/comphenix/protocol/events/PacketListener if ProtocolSupport was //NoClassDefFoundError: com/comphenix/protocol/events/PacketListener if only ProtocolSupport was found
//only found
ProtocolLibListener.register(this); ProtocolLibListener.register(this);
getServer().getPluginManager().registerEvents(new LoginSkinApplyListener(this), this); getServer().getPluginManager().registerEvents(new SkinApplyListener(this), this);
} else { } else {
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord"); logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
} }

View File

@ -7,10 +7,12 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -36,7 +38,8 @@ public class MojangApiBukkit extends MojangApiConnector {
HttpURLConnection conn = getConnection(url); HttpURLConnection conn = getConnection(url);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
//validate parsing //validate parsing
//http://wiki.vg/Protocol_Encryption#Server //http://wiki.vg/Protocol_Encryption#Server
VerificationReply verification = gson.fromJson(reader, VerificationReply.class); VerificationReply verification = gson.fromJson(reader, VerificationReply.class);
@ -50,7 +53,7 @@ public class MojangApiBukkit extends MojangApiConnector {
return true; return true;
} }
} catch (Exception ex) { } catch (IOException ex) {
//catch not only io-exceptions also parse and NPE on unexpected json format //catch not only io-exceptions also parse and NPE on unexpected json format
logger.warn("Failed to verify session", ex); logger.warn("Failed to verify session", ex);
} }

View File

@ -36,7 +36,7 @@ public class CrackedCommand implements CommandExecutor {
plugin.getCore().sendLocaleMessage("remove-premium", sender); plugin.getCore().sendLocaleMessage("remove-premium", sender);
profile.setPremium(false); profile.setPremium(false);
profile.setUUID(null); profile.setUuid(null);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile); plugin.getCore().getStorage().save(profile);
}); });

View File

@ -14,6 +14,7 @@ import java.net.InetSocketAddress;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Random; import java.util.Random;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT; import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
@ -93,6 +94,6 @@ public class ProtocolLibLoginSource implements LoginSource {
} }
public byte[] getVerifyToken() { public byte[] getVerifyToken() {
return verifyToken; return ArrayUtils.clone(verifyToken);
} }
} }

View File

@ -19,15 +19,14 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.player.PlayerLoginEvent.Result;
public class LoginSkinApplyListener implements Listener { public class SkinApplyListener implements Listener {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass(); private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties"); private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
private final FastLoginBukkit plugin; private final FastLoginBukkit plugin;
public LoginSkinApplyListener(FastLoginBukkit plugin) { public SkinApplyListener(FastLoginBukkit plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@ -46,7 +45,10 @@ public class LoginSkinApplyListener implements Listener {
for (BukkitLoginSession session : plugin.getLoginSessions().values()) { for (BukkitLoginSession session : plugin.getLoginSessions().values()) {
if (session.getUsername().equals(player.getName())) { if (session.getUsername().equals(player.getName())) {
SkinProperties skinProperty = session.getSkinProperty(); SkinProperties skinProperty = session.getSkinProperty();
applySkin(player, skinProperty.getValue(), skinProperty.getSignature()); if (skinProperty != null) {
applySkin(player, skinProperty.getValue(), skinProperty.getSignature());
}
break; break;
} }
} }
@ -56,13 +58,13 @@ public class LoginSkinApplyListener implements Listener {
private void applySkin(Player player, String skinData, String signature) { private void applySkin(Player player, String skinData, String signature) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player); WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
if (skinData != null && signature != null) { if (skinData != null && signature != null) {
WrappedSignedProperty skin = WrappedSignedProperty.fromValues("textures", skinData, signature); WrappedSignedProperty skin = WrappedSignedProperty.fromValues(SkinProperties.TEXTURE_KEY, skinData, signature);
try { try {
gameProfile.getProperties().put("textures", skin); gameProfile.getProperties().put(SkinProperties.TEXTURE_KEY, skin);
} catch (ClassCastException castException) { } catch (ClassCastException castException) {
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle()); Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
try { try {
MethodUtils.invokeMethod(map, "put", new Object[]{"textures", skin.getHandle()}); MethodUtils.invokeMethod(map, "put", new Object[]{SkinProperties.TEXTURE_KEY, skin.getHandle()});
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
plugin.getLog().error("Error setting premium skin", ex); plugin.getLog().error("Error setting premium skin", ex);
} }

View File

@ -41,7 +41,7 @@ public class VerifyResponseTask implements Runnable {
this.plugin = plugin; this.plugin = plugin;
this.packetEvent = packetEvent; this.packetEvent = packetEvent;
this.player = player; this.player = player;
this.sharedSecret = sharedSecret; this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
} }
@Override @Override

View File

@ -3,6 +3,7 @@ package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.PlayerProfile; import com.github.games647.fastlogin.core.PlayerProfile;
import com.github.games647.fastlogin.core.mojang.SkinProperties;
import com.github.games647.fastlogin.core.shared.JoinManagement; import com.github.games647.fastlogin.core.shared.JoinManagement;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -46,7 +47,7 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
BukkitLoginSession session = plugin.getLoginSessions().get(address.toString()); BukkitLoginSession session = plugin.getLoginSessions().get(address.toString());
//skin was resolved -> premium player //skin was resolved -> premium player
if (propertiesResolveEvent.hasProperty("textures") && session != null) { if (propertiesResolveEvent.hasProperty(SkinProperties.TEXTURE_KEY) && session != null) {
session.setVerified(true); session.setVerified(true);
} }
} }

View File

@ -18,15 +18,17 @@ import org.bukkit.metadata.FixedMetadataValue;
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> { public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player) { public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player) {
super(core, player); super(core, player, getSession(core.getPlugin(), player));
}
private static BukkitLoginSession getSession(FastLoginBukkit plugin, Player player) {
//remove the bungeecord identifier if there is ones
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
return plugin.getLoginSessions().remove(id);
} }
@Override @Override
public void run() { public void run() {
//remove the bungeecord identifier if there is ones
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
session = core.getPlugin().getLoginSessions().remove(id);
//blacklist this target player for BungeeCord Id brute force attacks //blacklist this target player for BungeeCord Id brute force attacks
FastLoginBukkit plugin = core.getPlugin(); FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true)); player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));

View File

@ -69,7 +69,7 @@ public class ConnectListener implements Listener {
session.setUuid(connection.getUniqueId()); session.setUuid(connection.getUniqueId());
PlayerProfile playerProfile = session.getProfile(); PlayerProfile playerProfile = session.getProfile();
playerProfile.setUUID(connection.getUniqueId()); playerProfile.setUuid(connection.getUniqueId());
//bungeecord will do this automatically so override it on disabled option //bungeecord will do this automatically so override it on disabled option
if (!plugin.getCore().getConfig().get("premiumUuid", true)) { if (!plugin.getCore().getConfig().get("premiumUuid", true)) {

View File

@ -44,7 +44,7 @@ public class AsyncToggleMessage implements Runnable {
} }
playerProfile.setPremium(false); playerProfile.setPremium(false);
playerProfile.setUUID(null); playerProfile.setUuid(null);
core.getStorage().save(playerProfile); core.getStorage().save(playerProfile);
sendMessage("remove-premium"); sendMessage("remove-premium");
} }

View File

@ -11,7 +11,6 @@ import com.google.common.io.ByteStreams;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
@ -22,15 +21,13 @@ public class ForceLoginTask
public ForceLoginTask(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core, public ForceLoginTask(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
ProxiedPlayer player, Server server) { ProxiedPlayer player, Server server) {
super(core, player); super(core, player, core.getPlugin().getSession().get(player.getPendingConnection()));
this.server = server; this.server = server;
} }
@Override @Override
public void run() { public void run() {
PendingConnection pendingConnection = player.getPendingConnection();
session = core.getPlugin().getSession().get(pendingConnection);
if (session == null) { if (session == null) {
return; return;
} }

View File

@ -138,7 +138,7 @@ public class AuthStorage {
public void save(PlayerProfile playerProfile) { public void save(PlayerProfile playerProfile) {
try (Connection con = dataSource.getConnection()) { try (Connection con = dataSource.getConnection()) {
UUID uuid = playerProfile.getUUID(); UUID uuid = playerProfile.getUuid();
if (playerProfile.getUserId() == -1) { if (playerProfile.getUserId() == -1) {
try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) { try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) {

View File

@ -44,11 +44,11 @@ public class PlayerProfile {
} }
//todo: this should be optional //todo: this should be optional
public synchronized UUID getUUID() { public synchronized UUID getUuid() {
return uuid; return uuid;
} }
public synchronized void setUUID(UUID uuid) { public synchronized void setUuid(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }

View File

@ -51,8 +51,9 @@ public class MojangApiConnector {
private Instant lastRateLimit = Instant.now().minus(10, ChronoUnit.MINUTES); private Instant lastRateLimit = Instant.now().minus(10, ChronoUnit.MINUTES);
protected final Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
protected final Logger logger; protected final Logger logger;
protected final Gson gson = new GsonBuilder()
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
public MojangApiConnector(Logger logger, Collection<String> localAddresses, int rateLimit public MojangApiConnector(Logger logger, Collection<String> localAddresses, int rateLimit
, Iterable<HostAndPort> proxies) { , Iterable<HostAndPort> proxies) {

View File

@ -2,6 +2,8 @@ package com.github.games647.fastlogin.core.mojang;
public class SkinProperties { public class SkinProperties {
public static final String TEXTURE_KEY = "textures";
private final String name = "textures"; private final String name = "textures";
private String value; private String value;

View File

@ -1,5 +1,6 @@
package com.github.games647.fastlogin.core.mojang; package com.github.games647.fastlogin.core.mojang;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
public class VerificationReply { public class VerificationReply {
@ -17,6 +18,6 @@ public class VerificationReply {
} }
public SkinProperties[] getProperties() { public SkinProperties[] getProperties() {
return properties; return Arrays.copyOf(properties, properties.length);
} }
} }

View File

@ -9,12 +9,12 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
protected final FastLoginCore<P, C, T> core; protected final FastLoginCore<P, C, T> core;
protected final P player; protected final P player;
protected final L session;
protected L session; public ForceLoginManagement(FastLoginCore<P, C, T> core, P player, L session) {
public ForceLoginManagement(FastLoginCore<P, C, T> core, P player) {
this.core = core; this.core = core;
this.player = player; this.player = player;
this.session = session;
} }
@Override @Override
@ -49,7 +49,7 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
if (success) { if (success) {
//update only on success to prevent corrupt data //update only on success to prevent corrupt data
if (playerProfile != null) { if (playerProfile != null) {
playerProfile.setUUID(session.getUuid()); playerProfile.setUuid(session.getUuid());
playerProfile.setPremium(true); playerProfile.setPremium(true);
storage.save(playerProfile); storage.save(playerProfile);
} }
@ -59,7 +59,7 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
} }
} else if (playerProfile != null) { } else if (playerProfile != null) {
//cracked player //cracked player
playerProfile.setUUID(null); playerProfile.setUuid(null);
playerProfile.setPremium(false); playerProfile.setPremium(false);
storage.save(playerProfile); storage.save(playerProfile);
} }