mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2026-01-29 09:40:08 +01:00
196 lines
7.8 KiB
Java
196 lines
7.8 KiB
Java
package com.github.games647.fastlogin;
|
|
|
|
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;
|
|
|
|
import java.io.IOException;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.security.KeyPair;
|
|
import java.util.Set;
|
|
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.
|
|
*/
|
|
public class FastLogin extends JavaPlugin {
|
|
|
|
//http connection, read timeout and user agent for a connection to mojang api servers
|
|
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
|
|
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
|
|
|
//we need a thread-safe set because we access it async in the packet listener
|
|
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
|
|
|
//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)
|
|
//SafeCacheBuilder is used in order to be version independent
|
|
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)
|
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
|
//mapped by ip:port -> PlayerSession
|
|
.build(new CacheLoader<String, PlayerSession>() {
|
|
|
|
@Override
|
|
public PlayerSession load(String key) throws Exception {
|
|
//A key should be inserted manually on start packet
|
|
throw new UnsupportedOperationException("Not supported");
|
|
}
|
|
});
|
|
|
|
@Override
|
|
public void onLoad() {
|
|
//online mode is only changeable after a restart so check it here
|
|
if (getServer().getOnlineMode()) {
|
|
//we need to require offline to prevent a session request for a offline player
|
|
getLogger().severe("Server have to be in offline mode");
|
|
|
|
setEnabled(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onEnable() {
|
|
if (!isEnabled() || !registerHooks()) {
|
|
return;
|
|
}
|
|
|
|
//register packet listeners on success
|
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
|
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
|
|
public void onDisable() {
|
|
//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)
|
|
*
|
|
* @return a thread-safe session map
|
|
*/
|
|
public ConcurrentMap<String, PlayerSession> getSessions() {
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* 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<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
|
|
*/
|
|
public KeyPair getServerKey() {
|
|
return keyPair;
|
|
}
|
|
|
|
/**
|
|
* Gets a set of user who activated premium logins
|
|
*
|
|
* @return user who activated premium logins
|
|
*/
|
|
public Set<String> getEnabledPremium() {
|
|
return enabledPremium;
|
|
}
|
|
|
|
/**
|
|
* Prepares a Mojang API connection. The connection is not started in this method
|
|
*
|
|
* @param url the url connecting to
|
|
* @return the prepared connection
|
|
*
|
|
* @throws IOException on invalid url format or on {@link java.net.URL#openConnection() }
|
|
*/
|
|
public HttpURLConnection getConnection(String url) throws IOException {
|
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
|
connection.setConnectTimeout(TIMEOUT);
|
|
connection.setReadTimeout(TIMEOUT);
|
|
//the new Mojang API just uses json as response
|
|
connection.setRequestProperty("Content-Type", "application/json");
|
|
connection.setRequestProperty("User-Agent", USER_AGENT);
|
|
|
|
return connection;
|
|
}
|
|
|
|
private boolean registerHooks() {
|
|
AuthPlugin authPluginHook = null;
|
|
try {
|
|
String hooksPackage = this.getClass().getPackage().getName() + ".hooks";
|
|
//Look through all classes in the hooks package and look for supporting plugins on the server
|
|
for (ClassPath.ClassInfo clazzInfo : ClassPath.from(getClassLoader()).getTopLevelClasses(hooksPackage)) {
|
|
//remove the hook suffix
|
|
String pluginName = clazzInfo.getSimpleName().replace("Hook", "");
|
|
Class<?> clazz = clazzInfo.load();
|
|
//uses only member classes which uses AuthPlugin interface (skip interfaces)
|
|
if (AuthPlugin.class.isAssignableFrom(clazz)
|
|
//check only for enabled plugins. A single plugin could be disabled by plugin managers
|
|
&& getServer().getPluginManager().isPluginEnabled(pluginName)) {
|
|
authPluginHook = (AuthPlugin) clazz.newInstance();
|
|
getLogger().log(Level.INFO, "Hooking into auth plugin: {0}", pluginName);
|
|
break;
|
|
}
|
|
}
|
|
} catch (InstantiationException | IllegalAccessException | IOException ex) {
|
|
getLogger().log(Level.SEVERE, "Couldn't load the integration class", ex);
|
|
}
|
|
|
|
if (authPluginHook == null) {
|
|
//run this check for exceptions (errors) and not found plugins
|
|
getLogger().warning("No support offline Auth plugin found. ");
|
|
getLogger().warning("Disabling this plugin...");
|
|
|
|
setEnabled(false);
|
|
return false;
|
|
}
|
|
|
|
//We found a supporting plugin - we can now register a forwarding listener to skip authentication from them
|
|
getServer().getPluginManager().registerEvents(new BukkitJoinListener(this, authPluginHook), this);
|
|
return true;
|
|
}
|
|
}
|