forked from TuxCoding/FastLogin
Added BungeeCord support
This commit is contained in:
@ -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
|
######0.2.4
|
||||||
|
|
||||||
* Fixed NPE on invalid sessions
|
* Fixed NPE on invalid sessions
|
||||||
|
15
README.md
15
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)
|
* Automatically login paid accounts (premium)
|
||||||
* Support various of auth plugins
|
* Support various of auth plugins
|
||||||
* Experimental Cauldron support
|
* Experimental Cauldron support
|
||||||
|
* BungeeCord support
|
||||||
* No client modifications needed
|
* 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.
|
by buying the username.
|
||||||
|
|
||||||
####Does the plugin have BungeeCord support?
|
####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?
|
####Could premium players have a premium UUID and Skin?
|
||||||
Something like that is possible, but is not yet implemented.
|
Something like that is possible, but is not yet implemented.
|
||||||
|
|
||||||
####Is this plugin compatible with Cauldron?
|
####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:
|
###Useful Links:
|
||||||
* [Login Protocol](http://wiki.vg/Protocol#Login)
|
* [Login Protocol](http://wiki.vg/Protocol#Login)
|
||||||
* [Protocol Encryption](http://wiki.vg/Protocol_Encryption)
|
* [Protocol Encryption](http://wiki.vg/Protocol_Encryption)
|
||||||
|
|
||||||
|
###Donate
|
||||||
|
|
||||||
|
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8ZBULMAPN7MZC)
|
99
bungee/pom.xml
Normal file
99
bungee/pom.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.github.games647</groupId>
|
||||||
|
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||||
|
<artifactId>fastloginbungee</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>FastLogin</name>
|
||||||
|
<version>0.1</version>
|
||||||
|
<inceptionYear>2015</inceptionYear>
|
||||||
|
<url>https://github.com/games647/FastLogin</url>
|
||||||
|
<description>
|
||||||
|
Automatically logins premium (paid accounts) player on a offline mode server
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<!--Possibility to deploy directly to the plugins folder-->
|
||||||
|
<outputDir>${basedir}/target</outputDir>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<issueManagement>
|
||||||
|
<system>GitHub</system>
|
||||||
|
<url>https://github.com/games647/FastLogin/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>https://github.com/games647/FastLogin</url>
|
||||||
|
<connection>scm:git:git://github.com/games647/FastLogin.git</connection>
|
||||||
|
<developerConnection>scm:git:ssh://git@github.com:games647/FastLogin.git</developerConnection>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<defaultGoal>install</defaultGoal>
|
||||||
|
<!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->
|
||||||
|
<finalName>${project.name}</finalName>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.2</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.7</source>
|
||||||
|
<target>1.7</target>
|
||||||
|
<showWarnings>true</showWarnings>
|
||||||
|
<showDeprecation>true</showDeprecation>
|
||||||
|
<!--false means actual true http://jira.codehaus.org/browse/MCOMPILER-209-->
|
||||||
|
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${outputDir}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<!--Replace variables-->
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
|
||||||
|
<!--Add the license to jar in order to see it in the final jar-->
|
||||||
|
<resource>
|
||||||
|
<directory>${basedir}</directory>
|
||||||
|
<includes>
|
||||||
|
<include>LICENSE</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<!-- BungeeCord -->
|
||||||
|
<repository>
|
||||||
|
<id>bungeecord-repo</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-api</artifactId>
|
||||||
|
<version>1.8-SNAPSHOT</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -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<String> 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<String> getEnabledPremium() {
|
||||||
|
return enabledPremium;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
12
bungee/src/main/resources/bungee.yml
Normal file
12
bungee/src/main/resources/bungee.yml
Normal file
@ -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}
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>FastLogin</name>
|
<name>FastLogin</name>
|
||||||
<version>0.2.4</version>
|
<version>0.3</version>
|
||||||
<inceptionYear>2015</inceptionYear>
|
<inceptionYear>2015</inceptionYear>
|
||||||
<url>https://github.com/games647/FastLogin</url>
|
<url>https://github.com/games647/FastLogin</url>
|
||||||
<description>
|
<description>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package com.github.games647.fastlogin;
|
package com.github.games647.fastlogin;
|
||||||
|
|
||||||
import com.github.games647.fastlogin.listener.PlayerListener;
|
|
||||||
import com.comphenix.protocol.ProtocolLibrary;
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||||
import com.github.games647.fastlogin.hooks.AuthPlugin;
|
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.EncryptionPacketListener;
|
||||||
|
import com.github.games647.fastlogin.listener.HandshakePacketListener;
|
||||||
import com.github.games647.fastlogin.listener.StartPacketListener;
|
import com.github.games647.fastlogin.listener.StartPacketListener;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.reflect.ClassPath;
|
import com.google.common.reflect.ClassPath;
|
||||||
|
|
||||||
@ -20,16 +23,16 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This plugin checks if a player has a paid account and if so
|
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
|
||||||
* tries to skip offline mode authentication.
|
|
||||||
*/
|
*/
|
||||||
public class FastLogin extends JavaPlugin {
|
public class FastLogin extends JavaPlugin {
|
||||||
|
|
||||||
//http connection, read timeout and user agent for a connection to mojang api servers
|
//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";
|
private static final String USER_AGENT = "Premium-Checker";
|
||||||
|
|
||||||
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
|
//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
|
//we need a thread-safe set because we access it async in the packet listener
|
||||||
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
||||||
|
|
||||||
|
//player=fake player created by Protocollib | this mapmaker creates a concurrent map with weak keys
|
||||||
|
private final ConcurrentMap<Player, Object> bungeeCordUsers = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
//this map is thread-safe for async access (Packet Listener)
|
//this map is thread-safe for async access (Packet Listener)
|
||||||
//SafeCacheBuilder is used in order to be version independent
|
//SafeCacheBuilder is used in order to be version independent
|
||||||
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
||||||
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
//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
|
//mapped by ip:port -> PlayerSession
|
||||||
.build(new CacheLoader<String, PlayerSession>() {
|
.build(new CacheLoader<String, PlayerSession>() {
|
||||||
|
|
||||||
@ -72,11 +78,15 @@ public class FastLogin extends JavaPlugin {
|
|||||||
|
|
||||||
//register packet listeners on success
|
//register packet listeners on success
|
||||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
protocolManager.addPacketListener(new HandshakePacketListener(this));
|
||||||
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
||||||
|
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
||||||
|
|
||||||
//register commands using a unique name
|
//register commands using a unique name
|
||||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
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
|
@Override
|
||||||
@ -84,11 +94,12 @@ public class FastLogin extends JavaPlugin {
|
|||||||
//clean up
|
//clean up
|
||||||
session.clear();
|
session.clear();
|
||||||
enabledPremium.clear();
|
enabledPremium.clear();
|
||||||
|
bungeeCordUsers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a thread-safe map about players which are connecting to the server are being
|
* Gets a thread-safe map about players which are connecting to the server
|
||||||
* checked to be premium (paid account)
|
* are being checked to be premium (paid account)
|
||||||
*
|
*
|
||||||
* @return a thread-safe session map
|
* @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
|
* Gets a concurrent map with weak keys for all bungeecord users
|
||||||
* the client and server
|
* 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<Player, Object> getBungeeCordUsers() {
|
||||||
|
return bungeeCordUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the server KeyPair. This is used to encrypt or decrypt traffic between the client and server
|
||||||
*
|
*
|
||||||
* @return the server KeyPair
|
* @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
|
//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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.games647.fastlogin;
|
package com.github.games647.fastlogin;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.ArrayUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a client connecting to the server.
|
* Represents a client connecting to the server.
|
||||||
*
|
*
|
||||||
@ -15,7 +17,7 @@ public class PlayerSession {
|
|||||||
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.serverId = serverId;
|
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
|
* @return the verify token from the server
|
||||||
*/
|
*/
|
||||||
public byte[] getVerifyToken() {
|
public byte[] getVerifyToken() {
|
||||||
return verifyToken;
|
return ArrayUtils.clone(verifyToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -147,11 +147,11 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
//try to get the networkManager from ProtocolLib
|
//try to get the networkManager from ProtocolLib
|
||||||
private Object getNetworkManager(Player player)
|
private Object getNetworkManager(Player player)
|
||||||
throws IllegalAccessException, NoSuchFieldException {
|
throws IllegalAccessException, NoSuchFieldException {
|
||||||
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||||
Field injectorField = injector.getClass().getDeclaredField("injector");
|
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
|
||||||
injectorField.setAccessible(true);
|
injectorField.setAccessible(true);
|
||||||
|
|
||||||
Object rawInjector = injectorField.get(injector);
|
Object rawInjector = injectorField.get(socketInjector);
|
||||||
|
|
||||||
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||||
injectorField.setAccessible(true);
|
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
|
//fake a new login packet in order to let the server handle all the other stuff
|
||||||
private void receiveFakeStartPacket(String username, Player from) {
|
private void receiveFakeStartPacket(String username, Player from) {
|
||||||
//see StartPacketListener for packet information
|
//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
|
//uuid is ignored by the packet definition
|
||||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -68,7 +68,7 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
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
|
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
|
||||||
String sessionKey = player.getAddress().toString();
|
String sessionKey = player.getAddress().toString();
|
||||||
@ -81,7 +81,8 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
String username = packet.getGameProfiles().read(0).getName();
|
String username = packet.getGameProfiles().read(0).getName();
|
||||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
||||||
, new Object[]{sessionKey, username});
|
, 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
|
//minecraft server implementation
|
||||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||||
sentEncryptionRequest(sessionKey, username, player, packetEvent);
|
sentEncryptionRequest(sessionKey, username, player, packetEvent);
|
||||||
@ -117,7 +118,7 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
* key=public server key
|
* key=public server key
|
||||||
* verifyToken=random 4 byte array
|
* 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
|
//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/
|
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||||
|
Reference in New Issue
Block a user