From c3f8e59a9a41c2aba37b0ebfbf3fcf397b99e641 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 13 Nov 2015 22:46:38 +0100 Subject: [PATCH] Added BungeeCord support --- CHANGELOG.md | 6 ++ README.md | 15 ++- bungee/pom.xml | 99 +++++++++++++++++++ .../games647/fastloginbungee/FastLogin.java | 34 +++++++ .../PlayerConnectionListener.java | 78 +++++++++++++++ .../fastloginbungee/PremiumCommand.java | 38 +++++++ bungee/src/main/resources/bungee.yml | 12 +++ pom.xml | 2 +- .../github/games647/fastlogin/FastLogin.java | 45 ++++++--- .../games647/fastlogin/PlayerSession.java | 6 +- .../listener/BukkitJoinListener.java | 53 ++++++++++ .../listener/BungeeCordListener.java | 90 +++++++++++++++++ .../listener/EncryptionPacketListener.java | 8 +- .../listener/HandshakePacketListener.java | 58 +++++++++++ .../fastlogin/listener/PlayerListener.java | 54 ---------- .../listener/StartPacketListener.java | 7 +- 16 files changed, 527 insertions(+), 78 deletions(-) create mode 100644 bungee/pom.xml create mode 100644 bungee/src/main/java/com/github/games647/fastloginbungee/FastLogin.java create mode 100644 bungee/src/main/java/com/github/games647/fastloginbungee/PlayerConnectionListener.java create mode 100644 bungee/src/main/java/com/github/games647/fastloginbungee/PremiumCommand.java create mode 100644 bungee/src/main/resources/bungee.yml create mode 100644 src/main/java/com/github/games647/fastlogin/listener/BukkitJoinListener.java create mode 100644 src/main/java/com/github/games647/fastlogin/listener/BungeeCordListener.java create mode 100644 src/main/java/com/github/games647/fastlogin/listener/HandshakePacketListener.java delete mode 100644 src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2751bdcb..93f1d1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +#####0.3 + +* Added BungeeCord support +* Decrease timeout checks in order to fail faster on connection problems +* Code style improvements + ######0.2.4 * Fixed NPE on invalid sessions diff --git a/README.md b/README.md index d0fa3cfb..04fe9e5f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,10 @@ So they don't need to enter passwords. This is also called auto login. * Automatically login paid accounts (premium) * Support various of auth plugins * Experimental Cauldron support +* BungeeCord support * No client modifications needed +* Good performance by async non blocking operations +* Free *** @@ -114,16 +117,22 @@ and Mojang account. Then the command can be executed. So someone different canno by buying the username. ####Does the plugin have BungeeCord support? -Not yet, but I'm planning this. +Yes it has. Just activate ipForward in your BungeeCord config and place the plugin in the plugins folder of +Bukkit/Spigot and BungeeCord. This plugin will automatically detect if BungeeCord is running and so handle checks +there. ####Could premium players have a premium UUID and Skin? Something like that is possible, but is not yet implemented. ####Is this plugin compatible with Cauldron? -It's not yet tested, but all needed methods also exists in Cauldron so it could work together +It's not tested yet, but all needed methods also exists in Cauldron so it could work together. *** ###Useful Links: * [Login Protocol](http://wiki.vg/Protocol#Login) -* [Protocol Encryption](http://wiki.vg/Protocol_Encryption) \ No newline at end of file +* [Protocol Encryption](http://wiki.vg/Protocol_Encryption) + +###Donate + +[![Donate Button](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8ZBULMAPN7MZC) \ No newline at end of file diff --git a/bungee/pom.xml b/bungee/pom.xml new file mode 100644 index 00000000..c067e4e0 --- /dev/null +++ b/bungee/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + + com.github.games647 + + fastloginbungee + jar + + FastLogin + 0.1 + 2015 + https://github.com/games647/FastLogin + + Automatically logins premium (paid accounts) player on a offline mode server + + + + UTF-8 + + ${basedir}/target + + + + GitHub + https://github.com/games647/FastLogin/issues + + + + https://github.com/games647/FastLogin + scm:git:git://github.com/games647/FastLogin.git + scm:git:ssh://git@github.com:games647/FastLogin.git + + + + install + + ${project.name} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.7 + 1.7 + true + true + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + ${outputDir} + + + + + + + src/main/resources + + true + + + + + ${basedir} + + LICENSE + + + + + + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + net.md-5 + bungeecord-api + 1.8-SNAPSHOT + jar + provided + + + diff --git a/bungee/src/main/java/com/github/games647/fastloginbungee/FastLogin.java b/bungee/src/main/java/com/github/games647/fastloginbungee/FastLogin.java new file mode 100644 index 00000000..5384dd8b --- /dev/null +++ b/bungee/src/main/java/com/github/games647/fastloginbungee/FastLogin.java @@ -0,0 +1,34 @@ +package com.github.games647.fastloginbungee; + +import com.google.common.collect.Sets; + +import java.util.Set; + +import net.md_5.bungee.api.plugin.Plugin; + +/** + * BungeeCord version of FastLogin. This plugin keeps track + * on online mode connections. + */ +public class FastLogin extends Plugin { + + private final Set enabledPremium = Sets.newConcurrentHashSet(); + + @Override + public void onEnable() { + //events + getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this)); + + //commands + getProxy().getPluginManager().registerCommand(this, new PremiumCommand(this)); + } + + /** + * A set of players who want to use fastlogin + * + * @return all player which want to be in onlinemode + */ + public Set getEnabledPremium() { + return enabledPremium; + } +} diff --git a/bungee/src/main/java/com/github/games647/fastloginbungee/PlayerConnectionListener.java b/bungee/src/main/java/com/github/games647/fastloginbungee/PlayerConnectionListener.java new file mode 100644 index 00000000..ca77e532 --- /dev/null +++ b/bungee/src/main/java/com/github/games647/fastloginbungee/PlayerConnectionListener.java @@ -0,0 +1,78 @@ +package com.github.games647.fastloginbungee; + +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import java.util.UUID; + +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.PreLoginEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +/** + * Enables online mode logins for specified users and sends + * plugin message to the Bukkit version of this plugin in + * order to clear that the connection is online mode. + */ +public class PlayerConnectionListener implements Listener { + + private final FastLogin plugin; + + public PlayerConnectionListener(FastLogin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPreLogin(PreLoginEvent preLoginEvent) { + if (preLoginEvent.isCancelled()) { + return; + } + + PendingConnection connection = preLoginEvent.getConnection(); + String username = connection.getName(); + //just enable it for activated users + if (plugin.getEnabledPremium().contains(username)) { + connection.setOnlineMode(true); + } + } + + @EventHandler + public void onServerConnected(ServerConnectedEvent serverConnectedEvent) { + ProxiedPlayer player = serverConnectedEvent.getPlayer(); + //send message even when the online mode is activated by default + if (player.getPendingConnection().isOnlineMode()) { + Server server = serverConnectedEvent.getServer(); + + ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput(); + //subchannel name + dataOutput.writeUTF("Checked"); + + //proxy identifier to check if it's a acceptable proxy + UUID proxyId = UUID.fromString(plugin.getProxy().getConfig().getUuid()); + dataOutput.writeLong(proxyId.getMostSignificantBits()); + dataOutput.writeLong(proxyId.getLeastSignificantBits()); + + //Data is sent through a random player. We have to tell the Bukkit version of this plugin the target + dataOutput.writeUTF(player.getName()); + + server.sendData(plugin.getDescription().getName(), dataOutput.toByteArray()); + } + } + + @EventHandler + public void onPluginMessage(PluginMessageEvent pluginMessageEvent) { + String channel = pluginMessageEvent.getTag(); + if (pluginMessageEvent.isCancelled() || !plugin.getDescription().getName().equals(channel)) { + return; + } + + //the client shouldn't be able to read the messages in order to know something about server internal states + //moreover the client shouldn't be able fake a running premium check by sending the result message + pluginMessageEvent.setCancelled(true); + } +} diff --git a/bungee/src/main/java/com/github/games647/fastloginbungee/PremiumCommand.java b/bungee/src/main/java/com/github/games647/fastloginbungee/PremiumCommand.java new file mode 100644 index 00000000..3497a09c --- /dev/null +++ b/bungee/src/main/java/com/github/games647/fastloginbungee/PremiumCommand.java @@ -0,0 +1,38 @@ +package com.github.games647.fastloginbungee; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +/** + * Let players activate the fastlogin method on a BungeeCord instance. + */ +public class PremiumCommand extends Command { + + private final FastLogin plugin; + + public PremiumCommand(FastLogin plugin) { + super(plugin.getDescription().getName() + , plugin.getDescription().getName() + ".command." + "premium" + , "prem" , "premium", "loginfast"); + + this.plugin = plugin; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(new ComponentBuilder("Only player can invoke this command") + .color(ChatColor.DARK_RED) + .create()); + return; + } + + plugin.getEnabledPremium().add(sender.getName()); + sender.sendMessage(new ComponentBuilder("Added to the list of premium players") + .color(ChatColor.DARK_GREEN) + .create()); + } +} diff --git a/bungee/src/main/resources/bungee.yml b/bungee/src/main/resources/bungee.yml new file mode 100644 index 00000000..de451534 --- /dev/null +++ b/bungee/src/main/resources/bungee.yml @@ -0,0 +1,12 @@ +# project informations for BungeeCord +# This file will be prioritised over plugin.yml which can be also used for Bungee +# This make it easy to combine BungeeCord and Bukkit support in one plugin +name: ${project.name} +# ${...} will be automatically replaced by Maven +main: ${project.groupId}.${project.artifactId}.${project.name} + +version: ${project.version} +author: games647, http://github.com/games647/FastLogin/graphs/contributors + +description: | + ${project.description} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0e46ce33..a8f2c59e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ jar FastLogin - 0.2.4 + 0.3 2015 https://github.com/games647/FastLogin diff --git a/src/main/java/com/github/games647/fastlogin/FastLogin.java b/src/main/java/com/github/games647/fastlogin/FastLogin.java index d2c61c16..e304a924 100644 --- a/src/main/java/com/github/games647/fastlogin/FastLogin.java +++ b/src/main/java/com/github/games647/fastlogin/FastLogin.java @@ -1,13 +1,16 @@ package com.github.games647.fastlogin; -import com.github.games647.fastlogin.listener.PlayerListener; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.utility.SafeCacheBuilder; import com.github.games647.fastlogin.hooks.AuthPlugin; +import com.github.games647.fastlogin.listener.BukkitJoinListener; +import com.github.games647.fastlogin.listener.BungeeCordListener; import com.github.games647.fastlogin.listener.EncryptionPacketListener; +import com.github.games647.fastlogin.listener.HandshakePacketListener; import com.github.games647.fastlogin.listener.StartPacketListener; import com.google.common.cache.CacheLoader; +import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import com.google.common.reflect.ClassPath; @@ -20,16 +23,16 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; /** - * This plugin checks if a player has a paid account and if so - * tries to skip offline mode authentication. + * This plugin checks if a player has a paid account and if so tries to skip offline mode authentication. */ public class FastLogin extends JavaPlugin { //http connection, read timeout and user agent for a connection to mojang api servers - private static final int TIMEOUT = 10 * 1000; + 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 @@ -38,11 +41,14 @@ public class FastLogin extends JavaPlugin { //we need a thread-safe set because we access it async in the packet listener private final Set enabledPremium = Sets.newConcurrentHashSet(); + //player=fake player created by Protocollib | this mapmaker creates a concurrent map with weak keys + private final ConcurrentMap bungeeCordUsers = new MapMaker().weakKeys().makeMap(); + //this map is thread-safe for async access (Packet Listener) //SafeCacheBuilder is used in order to be version independent private final ConcurrentMap session = SafeCacheBuilder.newBuilder() //2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang) - .expireAfterWrite(2, TimeUnit.MINUTES) + .expireAfterWrite(1, TimeUnit.MINUTES) //mapped by ip:port -> PlayerSession .build(new CacheLoader() { @@ -72,11 +78,15 @@ public class FastLogin extends JavaPlugin { //register packet listeners on success ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); - protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager)); + protocolManager.addPacketListener(new HandshakePacketListener(this)); protocolManager.addPacketListener(new StartPacketListener(this, protocolManager)); + protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager)); //register commands using a unique name getCommand("premium").setExecutor(new PremiumCommand(this)); + + //check for incoming messages from the bungeecord version of this plugin + getServer().getMessenger().registerIncomingPluginChannel(this, this.getName(), new BungeeCordListener(this)); } @Override @@ -84,11 +94,12 @@ public class FastLogin extends JavaPlugin { //clean up session.clear(); enabledPremium.clear(); + bungeeCordUsers.clear(); } /** - * Gets a thread-safe map about players which are connecting to the server are being - * checked to be premium (paid account) + * Gets a thread-safe map about players which are connecting to the server + * are being checked to be premium (paid account) * * @return a thread-safe session map */ @@ -97,8 +108,20 @@ public class FastLogin extends JavaPlugin { } /** - * Gets the server KeyPair. This is used to encrypt or decrypt traffic between - * the client and server + * Gets a concurrent map with weak keys for all bungeecord users + * which could be detected. It's mapped by a fake instance of player + * created by Protocollib and a non-null raw object. + * + * Represents a similar set collection + * + * @return + */ + public ConcurrentMap getBungeeCordUsers() { + return bungeeCordUsers; + } + + /** + * Gets the server KeyPair. This is used to encrypt or decrypt traffic between the client and server * * @return the server KeyPair */ @@ -166,7 +189,7 @@ public class FastLogin extends JavaPlugin { } //We found a supporting plugin - we can now register a forwarding listener to skip authentication from them - getServer().getPluginManager().registerEvents(new PlayerListener(this, authPluginHook), this); + getServer().getPluginManager().registerEvents(new BukkitJoinListener(this, authPluginHook), this); return true; } } diff --git a/src/main/java/com/github/games647/fastlogin/PlayerSession.java b/src/main/java/com/github/games647/fastlogin/PlayerSession.java index eeee7d75..f76750f4 100644 --- a/src/main/java/com/github/games647/fastlogin/PlayerSession.java +++ b/src/main/java/com/github/games647/fastlogin/PlayerSession.java @@ -1,5 +1,7 @@ package com.github.games647.fastlogin; +import org.apache.commons.lang.ArrayUtils; + /** * Represents a client connecting to the server. * @@ -15,7 +17,7 @@ public class PlayerSession { public PlayerSession(String username, String serverId, byte[] verifyToken) { this.username = username; this.serverId = serverId; - this.verifyToken = verifyToken; + this.verifyToken = ArrayUtils.clone(verifyToken); } /** @@ -37,7 +39,7 @@ public class PlayerSession { * @return the verify token from the server */ public byte[] getVerifyToken() { - return verifyToken; + return ArrayUtils.clone(verifyToken); } /** diff --git a/src/main/java/com/github/games647/fastlogin/listener/BukkitJoinListener.java b/src/main/java/com/github/games647/fastlogin/listener/BukkitJoinListener.java new file mode 100644 index 00000000..51956633 --- /dev/null +++ b/src/main/java/com/github/games647/fastlogin/listener/BukkitJoinListener.java @@ -0,0 +1,53 @@ +package com.github.games647.fastlogin.listener; + +import com.github.games647.fastlogin.FastLogin; +import com.github.games647.fastlogin.PlayerSession; +import com.github.games647.fastlogin.hooks.AuthPlugin; + +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +/** + * This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the + * plugin can skip authentication. + */ +public class BukkitJoinListener implements Listener { + + private static final long DELAY_LOGIN = 2 * 20L; + + protected final FastLogin plugin; + protected final AuthPlugin authPlugin; + + public BukkitJoinListener(FastLogin plugin, AuthPlugin authPlugin) { + this.plugin = plugin; + this.authPlugin = authPlugin; + } + + @EventHandler(ignoreCancelled = true) + public void onJoin(PlayerJoinEvent joinEvent) { + final Player player = joinEvent.getPlayer(); + + Bukkit.getScheduler().runTaskLater(plugin, new Runnable() { + + @Override + public void run() { + String address = player.getAddress().toString(); + //removing the session because we now use it + PlayerSession session = plugin.getSessions().remove(address); + + //check if it's the same player as we checked before + if (player.isOnline() && session != null + && player.getName().equals(session.getUsername()) && session.isVerified()) { + plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName()); + authPlugin.forceLogin(player); + } + } + //Wait before auth plugin and we received a message from BungeeCord initializes the player + }, DELAY_LOGIN); + } +} diff --git a/src/main/java/com/github/games647/fastlogin/listener/BungeeCordListener.java b/src/main/java/com/github/games647/fastlogin/listener/BungeeCordListener.java new file mode 100644 index 00000000..d738a2d0 --- /dev/null +++ b/src/main/java/com/github/games647/fastlogin/listener/BungeeCordListener.java @@ -0,0 +1,90 @@ +package com.github.games647.fastlogin.listener; + +import com.github.games647.fastlogin.FastLogin; +import com.github.games647.fastlogin.PlayerSession; +import com.google.common.base.Charsets; +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.UUID; +import java.util.logging.Level; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +/** + * Responsible for receiving messages from a BungeeCord instance. + * + * This class also receives the plugin message from the bungeecord version of this plugin in order to + * get notified if the connection is in online mode. + */ +public class BungeeCordListener implements PluginMessageListener { + + private static final String FILE_NAME = "proxy-whitelist.txt"; + + private final FastLogin plugin; + //null if whitelist is empty so bungeecord support is disabled + private final UUID proxyId; + + public BungeeCordListener(FastLogin plugin) { + this.plugin = plugin; + this.proxyId = loadBungeeCordId(); + } + + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + if (!channel.equals(plugin.getName())) { + return; + } + + ByteArrayDataInput dataInput = ByteStreams.newDataInput(message); + String subchannel = dataInput.readUTF(); + plugin.getLogger().log(Level.FINEST, "Received plugin message for subchannel {0} from {1}" + , new Object[]{subchannel, player}); + if ("Checked".equalsIgnoreCase(subchannel)) { + //bungeecord UUID + long mostSignificantBits = dataInput.readLong(); + long leastSignificantBits = dataInput.readLong(); + UUID sourceId = new UUID(mostSignificantBits, leastSignificantBits); + //fails too if no proxy id is specified in the whitelist file + if (sourceId.equals(proxyId)) { + //make sure the proxy is allowed to transfer data to us + String playerName = dataInput.readUTF(); + //check if the player is still online or disconnected + Player checkedPlayer = plugin.getServer().getPlayerExact(playerName); + if (checkedPlayer != null && checkedPlayer.isOnline()) { + PlayerSession playerSession = new PlayerSession(playerName, null, null); + playerSession.setVerified(true); + //put it only if the user doesn't has a session open + //so that the player have to send the bungeecord packet and cannot skip the verification then + plugin.getSessions().putIfAbsent(checkedPlayer.getAddress().toString(), playerSession); + } + } + } + } + + public UUID loadBungeeCordId() { + File whitelistFile = new File(plugin.getDataFolder(), FILE_NAME); + //create a new folder if it doesn't exist. Fail silently otherwise + whitelistFile.getParentFile().mkdir(); + try { + if (!whitelistFile.exists()) { + whitelistFile.createNewFile(); + } + + String firstLine = Files.readFirstLine(whitelistFile, Charsets.UTF_8); + if (firstLine != null && !firstLine.isEmpty()) { + return UUID.fromString(firstLine.trim()); + } + } catch (IOException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex); + } catch (Exception ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to retrieve proxy Id. Disabling BungeeCord support", ex); + } + + return null; + } +} diff --git a/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java b/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java index 3e05fac7..bbd95d0d 100644 --- a/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java +++ b/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java @@ -147,11 +147,11 @@ public class EncryptionPacketListener extends PacketAdapter { //try to get the networkManager from ProtocolLib private Object getNetworkManager(Player player) throws IllegalAccessException, NoSuchFieldException { - Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player); - Field injectorField = injector.getClass().getDeclaredField("injector"); + Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player); + Field injectorField = socketInjector.getClass().getDeclaredField("injector"); injectorField.setAccessible(true); - Object rawInjector = injectorField.get(injector); + Object rawInjector = injectorField.get(socketInjector); injectorField = rawInjector.getClass().getDeclaredField("networkManager"); injectorField.setAccessible(true); @@ -236,7 +236,7 @@ public class EncryptionPacketListener extends PacketAdapter { //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 - PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true); + PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START); //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); diff --git a/src/main/java/com/github/games647/fastlogin/listener/HandshakePacketListener.java b/src/main/java/com/github/games647/fastlogin/listener/HandshakePacketListener.java new file mode 100644 index 00000000..e143f129 --- /dev/null +++ b/src/main/java/com/github/games647/fastlogin/listener/HandshakePacketListener.java @@ -0,0 +1,58 @@ +package com.github.games647.fastlogin.listener; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.github.games647.fastlogin.FastLogin; + +import java.util.logging.Level; + +/** + * Listens to incoming handshake packets. + * + * As BungeeCord sends additional information on the Handshake, + * we can detect it and check so if the player is coming from a + * BungeeCord instance. IpForward has to be activated in the + * BungeeCord config to send these extra information. + * + * Packet information: + * http://wiki.vg/Protocol#Handshake + * + * Int=Protocol version + * String=connecting server address (and additional information from BungeeCord) + * int=server port + * int=next state + */ +public class HandshakePacketListener extends PacketAdapter { + + //hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin + private final FastLogin plugin; + + public HandshakePacketListener(FastLogin plugin) { + //run async in order to not block the server, because we are making api calls to Mojang + super(params(plugin, PacketType.Handshake.Client.SET_PROTOCOL).optionAsync()); + + this.plugin = plugin; + } + + @Override + public void onPacketReceiving(PacketEvent packetEvent) { + PacketContainer packet = packetEvent.getPacket(); + PacketType.Protocol nextProtocol = packet.getProtocols().read(0); + + //we don't want to listen for server ping. + if (nextProtocol == PacketType.Protocol.LOGIN) { + //here are the information written separated by a space + String hostname = packet.getStrings().read(0); + //https://hub.spigotmc.org/stash/projects/SPIGOT/repos/spigot/browse/CraftBukkit-Patches/0055-BungeeCord-Support.patch + String[] split = hostname.split("\00"); + if (split.length == 3 || split.length == 4) { + plugin.getLogger().log(Level.FINER, "Detected BungeeCord for {0}", hostname); + + //object = because there are no concurrent sets with weak keys + plugin.getBungeeCordUsers().put(packetEvent.getPlayer(), new Object()); + } + } + } +} diff --git a/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java b/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java deleted file mode 100644 index 16a97ab3..00000000 --- a/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.github.games647.fastlogin.listener; - -import com.github.games647.fastlogin.FastLogin; -import com.github.games647.fastlogin.PlayerSession; -import com.github.games647.fastlogin.hooks.AuthPlugin; - -import java.util.logging.Level; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; - -/** - * This listener tells authentication plugins if the player - * has a premium account and we checked it successfully. So the - * plugin can skip authentication. - */ -public class PlayerListener implements Listener { - - private static final long DELAY_LOGIN = 1 * 20L; - - private final FastLogin plugin; - private final AuthPlugin authPlugin; - - public PlayerListener(FastLogin plugin, AuthPlugin authPlugin) { - this.plugin = plugin; - this.authPlugin = authPlugin; - } - - @EventHandler(ignoreCancelled = true) - public void onJoin(PlayerJoinEvent joinEvent) { - final Player player = joinEvent.getPlayer(); - String address = player.getAddress().toString(); - - //removing the session because we now use it - PlayerSession session = plugin.getSessions().remove(address); - //check if it's the same player as we checked before - if (session != null && player.getName().equals(session.getUsername()) && session.isVerified()) { - Bukkit.getScheduler().runTaskLater(plugin, new Runnable() { - - @Override - public void run() { - if (player.isOnline()) { - plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName()); - authPlugin.forceLogin(player); - } - } - //Wait before auth plugin initializes the player - }, DELAY_LOGIN); - } - } -} diff --git a/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java b/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java index a7b5009a..65171744 100644 --- a/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java +++ b/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java @@ -68,7 +68,7 @@ public class StartPacketListener extends PacketAdapter { */ @Override public void onPacketReceiving(PacketEvent packetEvent) { - Player player = packetEvent.getPlayer(); + final Player player = packetEvent.getPlayer(); //this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes String sessionKey = player.getAddress().toString(); @@ -81,7 +81,8 @@ 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.getEnabledPremium().contains(username) && isPremiumName(username)) { + if (!plugin.getBungeeCordUsers().containsKey(player) + && plugin.getEnabledPremium().contains(username) && isPremiumName(username)) { //minecraft server implementation //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161 sentEncryptionRequest(sessionKey, username, player, packetEvent); @@ -117,7 +118,7 @@ public class StartPacketListener extends PacketAdapter { * key=public server key * verifyToken=random 4 byte array */ - PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true); + 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/