2.5 Release

This commit is contained in:
xPlumpOrange committed 2021-05-07 16:54:01 -04:00
1 parent 863fd644cf
commit 8416dbe4e3
88 files changed
+2235 -1748

No files matched your search

+1
View File
@@ -30,3 +30,4 @@ These wonderful contributors have helped TCPShield make this plugin better!
* [Paul Zhang](https://github.com/paulzhng)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+64 -43
View File
@@ -5,13 +5,17 @@ import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
// --
// Building
// --
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.yaml', name: 'snakeyaml', version: '1.26'
classpath group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
classpath(group: 'org.yaml', name: 'snakeyaml', version: '1.26')
classpath(group: 'com.google.code.gson', name: 'gson', version: '2.8.6')
}
}
@@ -20,6 +24,53 @@ plugins {
id 'idea'
}
// --
// Variables
// --
version = '2.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
// --
// Misc.
// --
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
options.encoding = 'UTF-8'
}
// --
// Dependencies
// --
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
@@ -42,6 +93,7 @@ repositories {
}
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '4.4.0'
@@ -56,10 +108,14 @@ dependencies {
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'
// Testing
testImplementation group: 'org.junit.jupiter', name:'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version:'5.7.0-M1'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'
}
// --
// Build Tasks
// --
test {
useJUnitPlatform()
}
@@ -68,47 +124,12 @@ configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.4'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
updateYamls()
updateJsons()
}
void updateYaml() {
void updateYamls() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
@@ -132,7 +153,7 @@ void updateYaml() {
}
}
void updateJson() {
void updateJsons() {
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = 'TCPShield'
@@ -1,88 +0,0 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.InvalidIPException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.IPValidation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
if (rawPayload == null)
return; // raw payload is passed when it's not available -> e.g. Paper server list ping events
String extraData = null;
// fix for e.g. incoming FML tagged packets
String cleanedPayload;
int nullIndex = rawPayload.indexOf('\0');
if (nullIndex != -1) { // FML tagged
cleanedPayload = rawPayload.substring(0, nullIndex);
extraData = rawPayload.substring(nullIndex);
} else { // standard
cleanedPayload = rawPayload;
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length < 2) return; // is nonetheless allowed as it passed the IP check
String hostname = payload[0];
String ipData = payload[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidIPException e) {
handleInvalidIPException(player, rawPayload);
} catch (IPModificationFailureException e) {
this.logger.warning(String.format("%s[%s/%s]'s IP failed to be modified. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvalidIPException(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s[%s/%s] was disconnected because the client failed the ip check and only-allowed-proxy-connections is enabled. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
}
@@ -1,107 +0,0 @@
package net.tcpshield.tcpshield;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ReflectionUtils {
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static Field modifiersField;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
if (modifiersField == null) {
try {
modifiersField = getDeclaredField(Field.class, "modifiers");
} catch (NoSuchFieldException e) { // workaround for when searching for the modifiers field on Java 12 or higher
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
modifiersField = Arrays.stream(fields).filter(modifier -> modifier.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
modifiersField.setAccessible(true);
}
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
setField(object, field, value);
}
public static void setField(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setField(Object object, Field field, Object value) throws IllegalAccessException {
field.set(object, value);
}
public static Object getObjectInPrivateField(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException {
Field field = getPrivateField(object.getClass(), fieldName);
return field.get(object);
}
public static Field getPrivateField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw e;
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,159 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.exception.parse.InvalidPayloadException;
import net.tcpshield.tcpshield.util.exception.parse.SignatureValidationException;
import net.tcpshield.tcpshield.util.exception.parse.TimestampValidationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.validation.SignatureValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.impl.HTPDateTimestampValidator;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
/**
* Packet handler for TCPShield's plugins
*/
public class TCPShieldPacketHandler {
private final TCPShieldPlugin plugin;
private TimestampValidator timestampValidator;
private SignatureValidator signatureValidator;
/**
* Creates an instance of the TCPShieldPacketHandler
* @param plugin The TCPShield plugin
*/
public TCPShieldPacketHandler(TCPShieldPlugin plugin) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
this.plugin = plugin;
initValidators();
}
/**
* Initiates the validators
* @throws NoSuchAlgorithmException SignatureValidator exception
* @throws IOException SignatureValidator exception
* @throws InvalidKeySpecException SignatureValidator exception
*/
private void initValidators() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureValidator = new SignatureValidator();
switch (plugin.getConfigProvider().getTimestampValidationMode().toLowerCase()) {
case "system": {
timestampValidator = TimestampValidator.createDefault(plugin);
break;
}
case "htpdate": {
timestampValidator = new HTPDateTimestampValidator(plugin);
break;
}
case "off": {
timestampValidator = TimestampValidator.createEmpty(plugin);
break;
}
default: {
plugin.getDebugger().warn(
"Unknown timestamp validation mode \"%s\"! Disabling timestamp validation.",
plugin.getConfigProvider().getTimestampValidationMode());
timestampValidator = TimestampValidator.createEmpty(plugin);
break;
}
}
}
/**
* Handles the packet and player manipulation
* for the initial handshake from TCPShield
* servers
* @param packet The handshake packet
* @param player The involved player
*/
public void handleHandshake(PacketProvider packet, PlayerProvider player) throws HandshakeException {
try {
String extraData = null;
String cleanedPayload;
int nullIndex;
if ((nullIndex = packet.getPayloadString().indexOf('\0')) != -1) { // FML tagged payload
cleanedPayload = packet.getPayloadString().substring(0, nullIndex);
extraData = packet.getPayloadString().substring(nullIndex);
} else // Standard payload
cleanedPayload = packet.getPayloadString();
String[] payload = cleanedPayload.split("///", 4);
if (payload.length != 4)
throw new InvalidPayloadException("payload.length != 4. Raw payload = \"" + packet.getPayloadString() + "\"");
String hostname = payload[0];
String ipData = payload[1];
int timestamp = Integer.parseInt(payload[2]);
String signature = payload[3];
if (!timestampValidator.validate(timestamp))
throw new TimestampValidationException(timestampValidator, timestamp);
String[] ipParts = ipData.split(":");
String host = ipParts[0];
int port = Integer.parseInt(ipParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!signatureValidator.validate(reconstructedPayload, signature))
throw new SignatureValidationException();
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.setPacketHostname(hostname);
} catch (NumberFormatException | InvalidPayloadException e) {
plugin.getDebugger().warn(
"%s[%s/%s] was disconnected because no proxy info was received and only-allow-proxy-connections is enabled. Raw payload = \"%s\"",
player.getName(), player.getUUID(), player.getIP(), packet.getPayloadString());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
if (!(e instanceof InvalidPayloadException))
throw new HandshakeException(new InvalidPayloadException(e));
else
throw new HandshakeException(e);
} catch (TimestampValidationException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided valid handshake information, but timestamp was not valid. " +
"Provided timestamp: %d vs. system timestamp: %d. Please check your machine time. Timestamp validation mode: %s",
player.getName(), player.getUUID(), player.getIP(), e.getTimestamp(), e.getTimestampValidator().getUnixTime(), plugin.getConfigProvider().getTimestampValidationMode());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (SignatureValidationException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"",
player.getName(), player.getUUID(), player.getIP(), packet.getPayloadString());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (Exception e) {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw e;
}
}
}
@@ -0,0 +1,37 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import java.util.logging.Logger;
/**
* The base/provider for all entry points/main classes
*/
public interface TCPShieldPlugin {
/**
* Gets the plugin's config provider
* @return The plugin's config provider
*/
ConfigProvider getConfigProvider();
/**
* Gets the plugin's logger
* @return The plugin's logger
*/
Logger getLogger();
/**
* Gets the packet handler
* @return The packet handler
*/
TCPShieldPacketHandler getPacketHandler();
/**
* Gets the plugin's debugger
* @return The plugin's debugger
*/
Debugger getDebugger();
}
@@ -1,9 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
public interface IPacket {
String getRawPayload();
void modifyOriginalPacket(String hostname) throws Exception;
}
@@ -1,19 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.net.InetSocketAddress;
public interface IPlayer {
String getUUID();
String getName();
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
void disconnect();
}
@@ -1,23 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected File ipWhitelistFolder;
protected boolean debug;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
return this.debug;
}
}
@@ -0,0 +1,22 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Bukkit's configuration implementation
*/
public class BukkitConfig extends ConfigProvider {
public BukkitConfig(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.doDebug = config.getBoolean("debug-mode");
}
}
@@ -1,34 +1,63 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.Bukkit;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bukkit.paper.BukkitPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.BukkitProtocolLib;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit extends JavaPlugin {
/**
* The entry point for Bukkit servers
*/
public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
@Override
public void onEnable() {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
new TCPShieldProtocolLib(this).load();
}
}
private BukkitImplProvider bukkitImpl;
private boolean isPaper() {
if (1 == 1) return false;
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public void onEnable() {
try {
configProvider = new BukkitConfig(this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
if (BukkitImplProvider.hasPaperEvent())
bukkitImpl = new BukkitPaper(this);
else
bukkitImpl = new BukkitProtocolLib(this);
bukkitImpl.load();
} catch (Exception e) {
throw new InitializationException(e);
}
}
/*
* The provider's base methods
*/
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
}
@@ -1,20 +0,0 @@
package net.tcpshield.tcpshield.bukkit.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -0,0 +1,23 @@
package net.tcpshield.tcpshield.bukkit.paper;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
import net.tcpshield.tcpshield.bukkit.paper.handler.PaperHandshakeHandler;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
/**
* Bukkit provider for the PaperSpigot implementation of TCPShield
*/
public class BukkitPaper extends BukkitImplProvider {
public BukkitPaper(TCPShieldBukkit bukkitPlugin) {
super(bukkitPlugin);
}
@Override
public void load() {
PaperHandshakeHandler packetHandler = new PaperHandshakeHandler(this);
getPlugin().getServer().getPluginManager().registerEvents(packetHandler, getPlugin());
}
}
@@ -1,43 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class PaperHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public PaperHandshakePacketHandler(JavaPlugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
IPacket packet = new PaperServerListPacketImpl(e);
IPlayer player = new PaperServerListPingPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -1,18 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldPaper {
private final JavaPlugin plugin;
public TCPShieldPaper(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(plugin);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
}
}
@@ -0,0 +1,41 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
/**
* The handshake handler for PaperSpigot
*/
public class PaperHandshakeHandler implements Listener {
private final BukkitImplProvider bukkitProvider;
public PaperHandshakeHandler(BukkitImplProvider bukkitProvider) {
this.bukkitProvider = bukkitProvider;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
PaperPacket packet = new PaperPacket(e);
PaperPlayer player = new PaperPlayer(e);
try {
bukkitProvider.getPlugin().getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
bukkitProvider.getPlugin().getDebugger().exception(exception);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy())
e.setCancelled(true);
}
}
@@ -0,0 +1,34 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet wrapper for PaperSpigot
*/
public class PaperPacket implements PacketProvider {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPacket(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getPayloadString() {
return handshakeEvent.getOriginalHandshake();
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
handshakeEvent.setCancelled(false);
handshakeEvent.setServerHostname(hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,64 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import java.net.InetSocketAddress;
import java.util.UUID;
/**
* A player wrapper for PaperSpigot
*/
public class PaperPlayer implements PlayerProvider {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayer(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
/**
* Trys to grab the UUID of the handshake
* @return If found, the corrosponding uuid, if not, unknown
*/
@Override
public String getUUID() {
UUID uuid = handshakeEvent.getUniqueId();
if (uuid == null)
return "unknown";
return uuid.toString();
}
/**
* Unsupported with Paper handshakes
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return handshakeEvent.getSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
handshakeEvent.setFailMessage("Connection failed. Please try again or contract an administrator.");
handshakeEvent.setFailed(true);
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperPacketImpl implements IPacket {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPacketImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getRawPayload() {
return handshakeEvent.getOriginalHandshake();
}
@Override
public void modifyOriginalPacket(String hostname) {
handshakeEvent.setCancelled(false);
handshakeEvent.setServerHostname(hostname);
}
}
@@ -1,46 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
import java.util.UUID;
public class PaperPlayerImpl implements IPlayer {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayerImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getUUID() {
UUID uuid = handshakeEvent.getUniqueId();
if (uuid == null) return "unknown";
return uuid.toString();
}
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return handshakeEvent.getSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.setFailMessage("Connection failed. Please try again or contact an administrator.");
handshakeEvent.setFailed(true);
}
}
@@ -1,23 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperServerListPacketImpl implements IPacket {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPacketImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getRawPayload() {
return null;
}
@Override
public void modifyOriginalPacket(String hostname) {
pingEvent.setCancelled(false);
}
}
@@ -1,40 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperServerListPingPlayerImpl implements IPlayer {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPingPlayerImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getUUID() {
return "unknown";
}
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return pingEvent.getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
// not necessary since this class is only for server list pings
}
@Override
public void disconnect() {
pingEvent.setCancelled(true);
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
import net.tcpshield.tcpshield.bukkit.protocollib.handler.ProtocolLibHandshakeHandler;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
/**
* Bukkit provider for the ProtocolLib implementation of TCPShield
*/
public class BukkitProtocolLib extends BukkitImplProvider {
public BukkitProtocolLib(TCPShieldBukkit bukkitPlugin) {
super(bukkitPlugin);
}
@Override
public void load() {
if (getPlugin().getServer().getPluginManager().getPlugin("ProtocolLib") == null) {
getPlugin().getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
ProtocolLibHandshakeHandler packetHandler = new ProtocolLibHandshakeHandler(this);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
}
}
@@ -1,31 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPacketImpl;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPlayerImpl;
import org.bukkit.plugin.java.JavaPlugin;
public class ProtocolLibHandshakePacketHandler extends PacketAdapter {
private final HandshakePacketHandler handshakePacketHandler;
public ProtocolLibHandshakePacketHandler(JavaPlugin plugin) {
super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
}
@Override
public void onPacketReceiving(PacketEvent e) {
IPacket packet = new ProtocolLibPacketImpl(e.getPacket());
IPlayer player = new ProtocolLibPlayerImpl(e.getPlayer());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -1,17 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
private final JavaPlugin plugin;
public TCPShieldProtocolLib(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
}
}
@@ -0,0 +1,35 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for ProtocolLib
*/
public class ProtocolLibHandshakeHandler extends PacketAdapter {
private final BukkitImplProvider bukkitProvider;
public ProtocolLibHandshakeHandler(BukkitImplProvider bukkitProvider) {
super(bukkitProvider.getPlugin(), ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.bukkitProvider = bukkitProvider;
}
@Override
public void onPacketReceiving(PacketEvent e) {
ProtocolLibPacket packet = new ProtocolLibPacket(e.getPacket());
ProtocolLibPlayer player = new ProtocolLibPlayer(e.getPlayer());
try {
bukkitProvider.getPlugin().getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
bukkitProvider.getPlugin().getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,35 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.events.PacketContainer;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet wrapper for ProtocolLib
*/
public class ProtocolLibPacket implements PacketProvider {
private final PacketContainer packetContainer;
private final String rawPayload;
public ProtocolLibPacket(PacketContainer packetContainer) {
this.packetContainer = packetContainer;
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getPayloadString() {
return rawPayload;
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
packetContainer.getStrings().write(0, hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,93 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
/**
* A player wrapper for ProtocolLib
*/
public class ProtocolLibPlayer implements PlayerProvider {
/*
* Reflection objects needed for manipulation
*/
private static Class<?> abstractChannelClass;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new InitializationException(new ReflectionException(e2));
}
}
}
private final Player player;
private String ip;
public ProtocolLibPlayer(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
/**
* Unsupported with ProtocolLib handshakes
* @return unknown
*/
@Override
public String getUUID() {
return "unknown";
}
/**
* Unsupported with ProtocolLib handshakes
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtil.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtil.getObjectInPrivateField(injector, "networkManager");
ReflectionUtil.setFinalField(networkManager, ReflectionUtil.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtil.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtil.setFinalField(channel, ReflectionUtil.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -1,25 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.events.PacketContainer;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class ProtocolLibPacketImpl implements IPacket {
private final PacketContainer packetContainer;
private final String rawPayload;
public ProtocolLibPacketImpl(PacketContainer packetContainer) {
this.packetContainer = packetContainer;
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) {
packetContainer.getStrings().write(0, hostname);
}
}
@@ -1,74 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
return "unknown"; // not supported with temporary players
}
@Override
public String getName() {
return "unknown"; // not supported with temporary players
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtils.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtils.getObjectInPrivateField(injector, "networkManager");
ReflectionUtils.setFinalField(networkManager, ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -0,0 +1,40 @@
package net.tcpshield.tcpshield.bukkit.provider;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
/**
* A provider for Bukkit implementations of TCPShield
*/
public abstract class BukkitImplProvider {
private final TCPShieldBukkit bukkitPlugin;
public BukkitImplProvider(TCPShieldBukkit bukkitPlugin) {
this.bukkitPlugin = bukkitPlugin;
}
public TCPShieldBukkit getPlugin() {
return bukkitPlugin;
}
/**
* Loads the Bukkit provider
*/
public abstract void load();
/**
* Checks for the Paper handshake event class for the Paper implementation
* @return Boolean stating if the runtime has the proper Paper event
*/
public static boolean hasPaperEvent() {
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,65 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Bungee's configuration implementation
*/
public class BungeeConfig extends ConfigProvider {
private final Plugin plugin;
public BungeeConfig(Plugin plugin) {
this.plugin = plugin;
saveDefaultConfig();
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.doDebug = config.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
File dataFolder = plugin.getDataFolder();
if (!dataFolder.exists())
dataFolder.mkdir();
File file = getConfigFile();
if (file.exists())
return;
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
File dataFolder = plugin.getDataFolder();
return new File(dataFolder, "config.yml");
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
}
@@ -1,40 +0,0 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bungee.impl.BungeeConfigImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePacketImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePlayerImpl;
public class BungeeHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public BungeeHandshakePacketHandler(Plugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BungeeConfigImpl(plugin));
}
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy()) return;
connection.disconnect();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -3,12 +3,55 @@ package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bungee.handler.BungeeHandshakeHandler;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
public class TCPShieldBungee extends Plugin implements Listener {
/**
* The entry point for Bungee servers
*/
public class TCPShieldBungee extends Plugin implements TCPShieldPlugin, Listener {
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Override
public void onEnable() {
try {
configProvider = new BungeeConfig(this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakeHandler(this));
} catch (Exception e) {
throw new InitializationException(e);
}
}
/*
* The provider's base methods
*/
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
@Override
public void onEnable() {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(this));
}
}
@@ -0,0 +1,45 @@
package net.tcpshield.tcpshield.bungee.handler;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for Bungee
*/
public class BungeeHandshakeHandler implements Listener {
private final TCPShieldPlugin plugin;
public BungeeHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy())
return;
connection.disconnect();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
BungeePacket packet = new BungeePacket(e.getHandshake(), e.getConnection());
BungeePlayer player = new BungeePlayer(e.getConnection());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
plugin.getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,46 @@
package net.tcpshield.tcpshield.bungee.handler;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.packet.Handshake;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
import java.net.InetSocketAddress;
/**
* A packet wrapper for Bungee
*/
public class BungeePacket implements PacketProvider {
private final Handshake handshake;
private final PendingConnection pendingConnection;
public BungeePacket(Handshake handshake, PendingConnection pendingConnection) {
this.handshake = handshake;
this.pendingConnection = pendingConnection;
}
@Override
public String getPayloadString() {
return handshake.getHost();
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
InetSocketAddress virtualHost = InetSocketAddress.createUnresolved(hostname, handshake.getPort());
try {
ReflectionUtil.setFinalField(pendingConnection, "virtualHost", virtualHost);
} catch (Exception ex) {
ReflectionUtil.setFinalField(pendingConnection, "vHost", virtualHost);
}
ReflectionUtil.setField(handshake, "host", hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,79 @@
package net.tcpshield.tcpshield.bungee.handler;
import io.netty.channel.AbstractChannel;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
/**
* A player wrapper for Bungee
*/
public class BungeePlayer implements PlayerProvider {
private final PendingConnection pendingConnection;
private String ip;
public BungeePlayer(PendingConnection pendingConnection) {
this.pendingConnection = pendingConnection;
this.ip = pendingConnection.getAddress().getAddress().getHostAddress();
}
/**
* Trys to grab the UUID of the handshake
* @return If found, the corrosponding uuid, if not, unknown
*/
@Override
public String getUUID() {
UUID uuid = pendingConnection.getUniqueId();
if (uuid == null)
return "unknown";
return uuid.toString();
}
@Override
public String getName() {
return pendingConnection.getName();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object channelWrapper = ReflectionUtil.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtil.getObjectInPrivateField(channelWrapper, "ch");
try {
Field socketAddressField = ReflectionUtil.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtil.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
pendingConnection.disconnect();
}
}
@@ -1,57 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class BungeeConfigImpl extends TCPShieldConfig {
private final Plugin plugin;
public BungeeConfigImpl(Plugin plugin) {
this.plugin = plugin;
saveDefaultConfig();
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.debug = config.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
File dataFolder = plugin.getDataFolder();
if (!dataFolder.exists()) dataFolder.mkdir();
File file = getConfigFile();
if (file.exists()) return;
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
File dataFolder = plugin.getDataFolder();
return new File(dataFolder, "config.yml");
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
}
@@ -1,36 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.packet.Handshake;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import java.net.InetSocketAddress;
public class BungeePacketImpl implements IPacket {
private final Handshake handshake;
private final PendingConnection pendingConnection;
public BungeePacketImpl(Handshake handshake, PendingConnection pendingConnection) {
this.handshake = handshake;
this.pendingConnection = pendingConnection;
}
@Override
public String getRawPayload() {
return handshake.getHost();
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
InetSocketAddress virtualHost = InetSocketAddress.createUnresolved(hostname, handshake.getPort());
try {
ReflectionUtils.setFinalField(pendingConnection, "virtualHost", virtualHost);
} catch (Exception ex) {
ReflectionUtils.setFinalField(pendingConnection, "vHost", virtualHost);
}
ReflectionUtils.setField(handshake, "host", hostname);
}
}
@@ -1,68 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
import io.netty.channel.AbstractChannel;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
public class BungeePlayerImpl implements IPlayer {
private final PendingConnection pendingConnection;
private String ip;
public BungeePlayerImpl(PendingConnection pendingConnection) {
this.pendingConnection = pendingConnection;
this.ip = pendingConnection.getAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
UUID uuid = pendingConnection.getUniqueId();
if (uuid == null) return "Unknown";
return uuid.toString();
}
@Override
public String getName() {
return pendingConnection.getName();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
try {
Field socketAddressField = ReflectionUtils.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtils.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
pendingConnection.disconnect();
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -1,4 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends SecurityException {
}
@@ -1,16 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldIPListUpdaterException extends RuntimeException {
public TCPShieldIPListUpdaterException(Throwable cause) {
super(cause);
}
public TCPShieldIPListUpdaterException(String message, Throwable cause) {
super(message, cause);
}
public TCPShieldIPListUpdaterException(String message) {
super(message);
}
}
@@ -1,12 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(String message) {
super(message);
}
}
@@ -0,0 +1,39 @@
package net.tcpshield.tcpshield.provider;
/**
* An abstract provider for TCPShield's options.
*/
public abstract class ConfigProvider {
/*
* Configuration options
*/
protected boolean onlyProxy;
protected String timestampValidationMode;
protected boolean doDebug;
public boolean isOnlyProxy() {
return onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
public boolean doDebug() {
return doDebug;
}
/*
* Plugin Constants
*/
protected final long maxTimestampDifference = 3; // In Unix Timesteps (Seconds)
public long getMaxTimestampDifference() {
return maxTimestampDifference;
}
}
@@ -0,0 +1,22 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet provider for TCPShield handling
*/
public interface PacketProvider {
/**
* Gets the raw payload from the packet
* @return The raw payload in the form of a String
*/
String getPayloadString();
/**
* Sets the hostname within the packet
* @param hostname The new hostname
*/
void setPacketHostname(String hostname) throws PacketManipulationException;
}
@@ -0,0 +1,27 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import java.net.InetSocketAddress;
/**
* A player provider for TCPShield handling
*/
public interface PlayerProvider {
String getUUID();
String getName();
String getIP();
/**
* Sets the players IP address directly to the server
*
* @param ip The new IP address to pass to the proxy/server
*/
void setIP(InetSocketAddress ip) throws PlayerManipulationException;
void disconnect();
}
@@ -0,0 +1,114 @@
package net.tcpshield.tcpshield.util;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import java.util.logging.Logger;
/**
* A util for debugging
*/
public abstract class Debugger {
/**
* Creates a debugger instance according to
* the plugin's configuration options
* @param plugin The TCPShield plugin
* @return A debugger instance from plugin's configuration
*/
public static Debugger createDebugger(TCPShieldPlugin plugin) {
if (plugin.getConfigProvider().doDebug())
/*
* A non-empty debugger using the plugin's logger
*/
return new Debugger(plugin.getLogger()) {
@Override
public void info(String format, Object... formats) {
this.logger.info("Debug : " + String.format(format, formats));
}
@Override
public void warn(String format, Object... formats) {
this.logger.warning("Debug : " + String.format(format, formats));
}
@Override
public void error(String format, Object... formats) {
this.logger.severe("Debug : " + String.format(format, formats));
}
@Override
public void exception(Exception exception) {
exception.printStackTrace();
}
};
else
/*
* An empty debugger
*/
return new Debugger(null) {
@Override
public void info(String format, Object... formats) {
}
@Override
public void warn(String format, Object... formats) {
}
@Override
public void error(String format, Object... formats) {
}
@Override
public void exception(Exception exception) {
}
};
}
protected final Logger logger;
/**
* Non-accessable constructor for creating a debugger
* @param logger The plugin's logger
*/
private Debugger(Logger logger) {
this.logger = logger;
}
/**
* Outputs debug information with log level "INFO"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void info(String format, Object... formats);
/**
* Outputs debug information with log level "WARNING"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void warn(String format, Object... formats);
/**
* Outputs debug information with log level "SEVERE"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void error(String format, Object... formats);
/**
* Outputs the exception through the debugger
* @param exception The exception to be outputted
*/
public abstract void exception(Exception exception);
}
@@ -0,0 +1,184 @@
package net.tcpshield.tcpshield.util;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* A util for reflection and manipulation
*/
public class ReflectionUtil {
/**
* Cached information for Fields
*/
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static Field modifiersField;
/**
* Sets a final field, with a field name, inside an object
* @param object The object
* @param fieldName The field name
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setFinalField(Object object, String fieldName, Object value) throws ReflectionException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
/**
* Sets a final field, with a field object, inside an object
* @param object The object
* @param field The field
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setFinalField(Object object, Field field, Object value) throws ReflectionException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
if (modifiersField == null) {
try {
modifiersField = getDeclaredField(Field.class, "modifiers");
} catch (ReflectionException e) { // workaround for when searching for the modifiers field on Java 12 or higher
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
modifiersField = Arrays.stream(fields).filter(modifier -> modifier.getName().equals("modifiers")).findFirst().orElseThrow(() -> new ReflectionException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new ReflectionException(e2);
}
}
modifiersField.setAccessible(true);
}
try {
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
setField(object, field, value);
}
/**
* Sets a field, with a field name, inside an object
* @param object The object
* @param fieldName The field name
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setField(Object object, String fieldName, Object value) throws ReflectionException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
/**
* Sets a field, with a field object, inside an object
* @param object The object
* @param field The field
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setField(Object object, Field field, Object value) throws ReflectionException {
try {
field.set(object, value);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
/**
* Gets the object inside a private field, with a field name, in an object
* @param object The object
* @param fieldName The field name
* @return The grabbed object
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Object getObjectInPrivateField(Object object, String fieldName) throws ReflectionException {
Field field = getPrivateField(object.getClass(), fieldName);
try {
return field.get(object);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
/**
* Gets a private field, with a field name, inside an object
* @param clazz The clazz
* @param fieldName The field name
* @return The grabbed field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field getPrivateField(Class<?> clazz, String fieldName) throws ReflectionException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
/**
* Searches for a field, with the type of the provided class, inside a class
* @param clazz The class to search through
* @param searchFor The class type to search for
* @return The found field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) throws ReflectionException {
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new ReflectionException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
/**
* Gets a declared field, with a field name, inside a class
* @param clazz The clazz
* @param fieldName The field name
* @return The declared field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws ReflectionException {
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw new ReflectionException(e);
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,27 @@
package net.tcpshield.tcpshield.util.exception;
/**
* Outline for TCPShield exceptions
*/
public abstract class TCPShieldException extends RuntimeException {
public TCPShieldException(Throwable throwable) {
super(throwable);
}
public TCPShieldException(String message) {
super(message);
}
public TCPShieldException(String message, Throwable throwable) {
super(message, throwable);
}
public TCPShieldException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.manipulate;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown during packet manipulation
*/
public class PacketManipulationException extends HandshakeException {
public PacketManipulationException(Throwable throwable) {
super(throwable);
}
public PacketManipulationException(String message) {
super(message);
}
public PacketManipulationException(String message, Throwable throwable) {
super(message, throwable);
}
public PacketManipulationException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.manipulate;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown during player manipulation
*/
public class PlayerManipulationException extends HandshakeException {
public PlayerManipulationException(Throwable throwable) {
super(throwable);
}
public PlayerManipulationException(String message) {
super(message);
}
public PlayerManipulationException(String message, Throwable throwable) {
super(message, throwable);
}
public PlayerManipulationException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown when a handshake packet has invalid payload data
*/
public class InvalidPayloadException extends HandshakeException {
public InvalidPayloadException(Throwable throwable) {
super(throwable);
}
public InvalidPayloadException(String message) {
super(message);
}
public InvalidPayloadException(String message, Throwable throwable) {
super(message, throwable);
}
public InvalidPayloadException() {
super();
}
}
@@ -0,0 +1,14 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown when a handshake packet failed signature validation
*/
public class SignatureValidationException extends HandshakeException {
public SignatureValidationException() {
super();
}
}
@@ -0,0 +1,30 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
/**
* An exception thrown when a handshake packet failed timestamp validation
*/
public class TimestampValidationException extends HandshakeException {
private final TimestampValidator timestampValidator;
private final long timestamp;
public TimestampValidationException(TimestampValidator timestampValidator, long timestamp) {
super();
this.timestampValidator = timestampValidator;
this.timestamp = timestamp;
}
public TimestampValidator getTimestampValidator() {
return timestampValidator;
}
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the handshake handling of TCPShield
*/
public class HandshakeException extends TCPShieldException {
public HandshakeException(Throwable throwable) {
super("An exception occured during the handshake process", throwable);
}
public HandshakeException(String message) {
super(message);
}
public HandshakeException(String message, Throwable throwable) {
super(message, throwable);
}
public HandshakeException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the initialization phase of TCPShield
*/
public class InitializationException extends TCPShieldException {
public InitializationException(Throwable throwable) {
super("An exception occured during the initalization process", throwable);
}
public InitializationException(String message) {
super(message);
}
public InitializationException(String message, Throwable throwable) {
super(message, throwable);
}
public InitializationException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during reflection operations for TCPShield
*/
public class ReflectionException extends TCPShieldException {
public ReflectionException(Throwable throwable) {
super("An exception occured during the reflection process", throwable);
}
public ReflectionException(String message) {
super(message);
}
public ReflectionException(String message, Throwable throwable) {
super(message, throwable);
}
public ReflectionException() {
super();
}
}
@@ -0,0 +1,58 @@
package net.tcpshield.tcpshield.util.validation;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* A signature validator using the TCPShield public key
*/
public class SignatureValidator {
private final PublicKey publicKey;
public SignatureValidator() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(SignatureValidator.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
/**
* Validates a String and Signature pair
* @param str The data in the form of a string
* @param signature The provided signature
* @return Boolean stating if it's a valid signature
*/
public boolean validate(String str, String signature) {
return validate(str.getBytes(StandardCharsets.UTF_8), signature);
}
/**
* Validates a byte[] and Signature pair
* @param data The data in the form of a byte array
* @param signature The provided signature
* @return Boolean stating if it's a valid signature
*/
private boolean validate(byte[] data, String signature) {
try {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
} catch (IllegalArgumentException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) {
return false;
}
}
}
@@ -0,0 +1,61 @@
package net.tcpshield.tcpshield.util.validation.timestamp;
import net.tcpshield.tcpshield.TCPShieldPlugin;
/**
* Base for timestamp validators
*/
public abstract class TimestampValidator {
/**
* Creates an empty validator that always returns true
* @return An empty validator always returning true
*/
public static TimestampValidator createEmpty(TCPShieldPlugin plugin) {
return new TimestampValidator(plugin) {
@Override
public boolean validate(long timestamp) {
return true;
}
};
}
/**
* Creates a system validator that uses default validation
* @return A system validator
*/
public static TimestampValidator createDefault(TCPShieldPlugin plugin) {
return new TimestampValidator(plugin) {
// Uses default methods
};
}
protected final TCPShieldPlugin plugin;
public TimestampValidator(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
/**
* Validates given timestamp to see if it's within a reasonable
* time range
* @param timestamp The timestamp
* @return Boolean stating if its valid
*/
public boolean validate(long timestamp) {
return Math.abs(getUnixTime() - timestamp) <= plugin.getConfigProvider().getMaxTimestampDifference();
}
/**
* Returns the validator's current Unix time
* @return The current time in Unix format
* @default Returns the system time in Unix format
*/
public long getUnixTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -0,0 +1,80 @@
package net.tcpshield.tcpshield.util.validation.timestamp.impl;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/**
* A timestamp validator that offsets system time with Google's to sync
* tiemstamps
*/
public class HTPDateTimestampValidator extends TimestampValidator {
private volatile long htpDateOffset = 0;
public HTPDateTimestampValidator(TCPShieldPlugin plugin) {
super(plugin);
ForkJoinPool.commonPool().execute(() -> {
try {
updateHTPDateOffset();
} catch (Exception e) {
throw new InitializationException(e);
}
});
}
private void updateHTPDateOffset() throws IOException {
Socket socket = new Socket("google.com", 80);
String payload = "HEAD http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: tcpshield/1.0\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n";
socket.getOutputStream().write(payload.getBytes());
long readTime = System.currentTimeMillis(); // assuming server -> client time is negligible
List<String> response = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toList());
Date serverDate = parseDate(response);
htpDateOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
private Date parseDate(List<String> response) {
for (String line : response) {
if (!line.startsWith("Date: "))
continue;
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
try {
return sdf.parse(line.substring("Date: ".length()));
} catch (ParseException e) {
throw new IllegalStateException(e);
}
}
throw new IllegalArgumentException("no date line found - response: " + response);
}
@Override
public long getUnixTime() {
return (System.currentTimeMillis() + htpDateOffset) / 1000;
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.validation;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class IPListAutoUpdater {
private final IPListUpdater ipListUpdater;
public IPListAutoUpdater(IPListUpdater ipListUpdater) {
this.ipListUpdater = ipListUpdater;
}
public void init() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
ipListUpdater.update();
}
}, TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(12));
}
}
@@ -1,80 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldIPListUpdaterException;
import net.tcpshield.tcpshield.validation.cidr.CIDRMatcher;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class IPListUpdater {
private final TCPShieldConfig config;
private final IPValidation ipValidation;
private final Logger logger;
public IPListUpdater(TCPShieldConfig config, IPValidation ipValidation, Logger logger) {
this.config = config;
this.ipValidation = ipValidation;
this.logger = logger;
}
public void update() {
try {
String ipList = getIPList();
File ipWhitelistFolder = config.getIPWhitelistFolder();
File tcpShieldIPList = new File(ipWhitelistFolder, "tcpshield-ips.list");
if (tcpShieldIPList.createNewFile()) {
logger.info("Fetched updated IP Whitelist from https://tcpshield.com/v4/");
}
try (FileWriter writer = new FileWriter(tcpShieldIPList, false)) {
writer.write(ipList);
}
ipValidation.readIPWhitelist();
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException(e);
}
}
public String getIPList() throws IOException {
URL url = new URL("https://tcpshield.com/v4/");
HttpsURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
int responseCode = connection.getResponseCode();
if (responseCode != 200)
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ did not return status code 200: " + responseCode);
String data = bufferedReader.lines()
.filter(str -> !str.isEmpty())
.peek(cidr -> { // checks for validity of each CIDR entry
try {
new CIDRMatcher(cidr);
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ returned a faulty CIDR entry: " + cidr, e);
}
})
.collect(Collectors.joining("\n"));
if (data.isEmpty())
throw new TCPShieldIPListUpdaterException("Response from https://tcpshield.com/v4/ empty");
return data;
}
} finally {
if (connection != null)
connection.disconnect();
}
}
}
@@ -1,69 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.cidr.CIDRChecker;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.logging.Logger;
public class IPValidation {
private final TCPShieldConfig config;
private final IPListUpdater ipListUpdater;
private final CIDRChecker cidrChecker;
private List<String> ipWhitelist = new ArrayList<>();
public IPValidation(TCPShieldConfig config, Logger logger) {
this.config = config;
this.ipListUpdater = new IPListUpdater(config, this, logger);
new IPListAutoUpdater(ipListUpdater).init();
checkIPList();
try {
readIPWhitelist();
this.cidrChecker = new CIDRChecker(ipWhitelist);
} catch (IOException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean validateIP(InetAddress inetAddress) {
return cidrChecker.check(inetAddress);
}
private List<String> getIPWhitelist() throws IOException {
List<String> ipWhitelist = new ArrayList<>();
for (File file : Objects.requireNonNull(config.getIPWhitelistFolder().listFiles())) {
if (file.isDirectory()) continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
ipWhitelist.add(cidrEntry);
}
}
}
return ipWhitelist;
}
public void readIPWhitelist() throws IOException {
this.ipWhitelist = getIPWhitelist();
}
private void checkIPList() {
if (config.getIPWhitelistFolder().mkdirs()) {
ipListUpdater.update();
}
}
}
@@ -1,38 +0,0 @@
package net.tcpshield.tcpshield.validation.cidr;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CIDRChecker {
private final List<CIDRMatcher> cidrMatchers = new ArrayList<>();
private final Set<String> cache = new HashSet<>(); // quick cache for already verified IPs; HashSet is O(1) on HashSet#contains() operations
public CIDRChecker(List<String> cidrList) {
for (String cidr : cidrList) {
CIDRMatcher cidrMatcher = new CIDRMatcher(cidr);
cidrMatchers.add(cidrMatcher);
}
}
public boolean check(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip)) return true;
if (!checkUncached(inetAddress)) return false;
cache.add(ip);
return true;
}
private boolean checkUncached(InetAddress inetAddress) {
for (CIDRMatcher cidrMatcher : cidrMatchers) {
if (cidrMatcher.matches(inetAddress)) return true;
}
return false;
}
}
@@ -1,59 +0,0 @@
package net.tcpshield.tcpshield.validation.cidr;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CIDRMatcher {
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
public CIDRMatcher(String ipAddress) {
String[] split = ipAddress.split("/");
String parsedIPAddress;
if (split.length >= 2) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = ipAddress;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean matches(InetAddress inetAddress) {
if (!cidrAddress.getClass().equals(inetAddress.getClass())) return false; // check if IP is IPv4 or IPv6
if (simpleCIDR) return inetAddress.equals(cidrAddress); // check for equality if it's a simple CIDR
byte[] inetAddressBytes = inetAddress.getAddress();
byte[] requiredAddressBytes = cidrAddress.getAddress();
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
for (int i = 0; i < maskBytes; i++) {
if (inetAddressBytes[i] != requiredAddressBytes[i]) return false;
}
if (finalByte != 0)
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
return true;
}
}
@@ -6,29 +6,78 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.velocity.handler.VelocityHandshakeHandler;
import java.nio.file.Path;
import java.util.logging.Logger;
@Plugin(id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
/**
* The entry point for Velocity servers
*/
@Plugin(
id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity {
public class TCPShieldVelocity implements TCPShieldPlugin {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
server.getEventManager().register(this, new VelocityHandshakePacketHandler(logger, dataFolder.toFile()));
}
}
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
try {
configProvider = new VelocityConfig(dataFolder.toFile());
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
server.getEventManager().register(this, new VelocityHandshakeHandler(this));
} catch (Exception exception) {
throw new InitializationException(exception);
}
}
/*
* The provider's base methods
*/
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
}
@@ -0,0 +1,51 @@
package net.tcpshield.tcpshield.velocity;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Velocity's configuration implementation
*/
public class VelocityConfig extends ConfigProvider {
private final File dataFolder;
private final File file;
public VelocityConfig(File dataFolder) throws IOException {
this.dataFolder = dataFolder;
this.file = new File(this.dataFolder, "config.toml");
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = toml.getString("timestamp-validation");
this.doDebug = toml.getBoolean("debug-mode");
}
private void saveDefaultConfig() throws IOException {
if (!dataFolder.exists()) dataFolder.mkdir();
if (file.exists()) return;
try (InputStream in = VelocityConfig.class.getResourceAsStream("/config.toml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw e;
}
}
private File getConfigFile() {
return new File(dataFolder, "config.toml");
}
private Toml loadConfig() {
return new Toml().read(getConfigFile());
}
}
@@ -1,55 +0,0 @@
package net.tcpshield.tcpshield.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPacketImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPlayerImpl;
import java.io.File;
import java.util.logging.Logger;
public class VelocityHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public VelocityHandshakePacketHandler(Logger logger, File dataFolder) {
this.handshakePacketHandler = new HandshakePacketHandler(logger, new VelocityConfigImpl(dataFolder));
}
@Subscribe(order = PostOrder.FIRST)
public void onPreLogin(PreLoginEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
private void handleEvent(InboundConnection connection) {
VelocityPlayerImpl player = new VelocityPlayerImpl(connection);
if (player.isLegacy()) {
player.disconnect();
return;
}
IPacket packet = new VelocityPacketImpl(connection);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,59 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for Velocity
*/
public class VelocityHandshakeHandler {
private final TCPShieldPlugin plugin;
public VelocityHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
@Subscribe(order = PostOrder.FIRST)
public void onPreLogin(PreLoginEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
private void handleEvent(InboundConnection connection) {
VelocityPlayer player = new VelocityPlayer(connection);
if (player.isLegacy()) {
player.disconnect();
return;
}
VelocityPacket packet = new VelocityPacket(connection);
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
plugin.getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,88 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
/**
* A packet wrapper for Velocity
*/
public class VelocityPacket implements PacketProvider {
/*
* Reflection objects needed for manipulation
*/
private static final Field HANDSHAKE_FIELD;
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "handshake");
HOSTNAME_FIELD = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
CLEANED_ADDRESS_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "cleanedAddress");
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacket(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
} catch (IllegalAccessException e) {
throw new PacketManipulationException(e);
}
}
@Override
public String getPayloadString() {
return rawPayload;
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
ReflectionUtil.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
/**
* Adapted from https://github.com/VelocityPowered/Velocity/blob/17e6944daea8130e03903ccdfbf63f111c573849/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java
*/
private String cleanAddress(String hostname) {
// Clean out any anything after any zero bytes (this includes BungeeCord forwarding and the
// legacy Forge handshake indicator).
String cleaned = hostname;
int zeroIdx = cleaned.indexOf('\0');
if (zeroIdx > -1) {
cleaned = hostname.substring(0, zeroIdx);
}
// If we connect through an SRV record, there will be a period at the end (DNS usually elides
// this ending octet).
if (!cleaned.isEmpty() && cleaned.charAt(cleaned.length() - 1) == '.') {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
@@ -0,0 +1,105 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
/**
* A player wrapper for Velocity
*/
public class VelocityPlayer implements PlayerProvider {
/*
* Reflection objects needed for manipulation
*/
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtil.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
REMOTE_ADDRESS_FIELD = ReflectionUtil.getPrivateField(minecraftConnection, "remoteAddress");
CLOSE_CHANNEL_METHOD = minecraftConnection.getMethod("close");
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
}
private final InboundConnection inboundConnection;
private final boolean legacy;
private String ip;
public VelocityPlayer(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
/**
* Unsupported with Velocity handshakes
* @return unknown
*/
@Override
public String getUUID() {
return "unknown";
}
/**
* Unsupported with Velocity handshakes
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return ip;
}
public boolean isLegacy() {
return legacy;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
try {
Object minecraftConnection = legacy ? LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection) : MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
}
@@ -1,45 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class VelocityConfigImpl extends TCPShieldConfig {
private final File dataFolder;
private final File file;
public VelocityConfigImpl(File dataFolder) {
this.dataFolder = dataFolder;
this.file = new File(this.dataFolder, "config.toml");
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
this.debug = toml.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
if (!dataFolder.exists()) dataFolder.mkdir();
if (file.exists()) return;
try (InputStream in = VelocityConfigImpl.class.getResourceAsStream("/config.toml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
return new File(dataFolder, "config.toml");
}
private Toml loadConfig() {
return new Toml().read(getConfigFile());
}
}
@@ -1,72 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
public class VelocityPacketImpl implements IPacket {
private static final Field HANDSHAKE_FIELD;
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "handshake");
HOSTNAME_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
CLEANED_ADDRESS_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "cleanedAddress");
} catch (NoSuchFieldException | ClassNotFoundException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacketImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // pass on
}
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
ReflectionUtils.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
}
/**
* Adapted from https://github.com/VelocityPowered/Velocity/blob/17e6944daea8130e03903ccdfbf63f111c573849/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java
*/
private String cleanAddress(String hostname) {
// Clean out any anything after any zero bytes (this includes BungeeCord forwarding and the
// legacy Forge handshake indicator).
String cleaned = hostname;
int zeroIdx = cleaned.indexOf('\0');
if (zeroIdx > -1) {
cleaned = hostname.substring(0, zeroIdx);
}
// If we connect through an SRV record, there will be a period at the end (DNS usually elides
// this ending octet).
if (!cleaned.isEmpty() && cleaned.charAt(cleaned.length() - 1) == '.') {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
@@ -1,86 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
REMOTE_ADDRESS_FIELD = ReflectionUtils.getPrivateField(minecraftConnection, "remoteAddress");
CLOSE_CHANNEL_METHOD = minecraftConnection.getMethod("close");
} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final boolean legacy;
private String ip;
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
return "unknown"; // not supported
}
@Override
public String getName() {
return "unknown"; // not supported
}
@Override
public String getIP() {
return ip;
}
public boolean isLegacy() {
return legacy;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
try {
Object minecraftConnection = legacy ? LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection) : MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new RuntimeException(e); // pass exception on
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
name: TCPShield
version: '2.4'
version: '2.5'
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
+3
View File
@@ -1,5 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
+3
View File
@@ -1,5 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections: true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
+1 -1
View File
@@ -1,5 +1,5 @@
name: TCPShield
version: '2.4'
version: '2.5'
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
Binary file not shown.
+11 -9
View File
@@ -1,11 +1,13 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.4",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
"id":"tcpshield",
"name":"TCPShield",
"version":"2.5",
"description":"TCPShield IP parsing capabilities for Velocity",
"authors":[
"TCPShield"
],
"dependencies":[
],
"main":"net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -1,55 +0,0 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.impl.TestConfigImpl;
import net.tcpshield.tcpshield.impl.TestPacketImpl;
import net.tcpshield.tcpshield.impl.TestPlayerImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl();
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
private TestPlayerImpl player;
@BeforeEach
public void setUp() {
player = new TestPlayerImpl(TestConstants.VALID_IP);
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testSuccessfulFMLTaggedHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testUnsuccessfulHandshakeInvalidIP() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
TestPlayerImpl player = new TestPlayerImpl(TestConstants.INVALID_IP);
standardHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
}
@@ -1,102 +0,0 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReflectionUtilsTest {
private static final String MODIFIED = "modified";
private static final String ORIGINAL = "original";
private ReflectionUtilsTestObject testObject;
@BeforeEach
public void resetTestObject() {
testObject = new ReflectionUtilsTestObject(ORIGINAL);
}
@Test
public void testSetPublicField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "publicString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicString());
}
@Test
public void testSetPrivateField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "privateString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateString());
}
@Test
public void testSetPublicFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "publicFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicFinalString());
}
@Test
public void testSetPrivateFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "privateFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateFinalString());
}
@Test
public void testSearchFieldByClass() {
Field field = ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, ReflectionUtilsTestObject.class);
assertEquals("testObject", field.getName());
}
@Test
public void testSearchFieldByNonExistentClass() {
Assertions.assertThrows(IllegalArgumentException.class, () -> ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, byte.class));
}
@Test
public void testGetObjectInPrivateField() throws NoSuchFieldException, IllegalAccessException {
String privateField = (String) ReflectionUtils.getObjectInPrivateField(testObject, "privateString");
assertEquals(ORIGINAL, privateField);
}
public static class ReflectionUtilsTestObject {
public final String publicFinalString;
private final String privateFinalString;
public String publicString;
private String privateString;
public ReflectionUtilsTestObject(String str) {
this.publicFinalString = str;
this.privateFinalString = str;
this.publicString = str;
this.privateString = str;
}
public String getPublicFinalString() {
return publicFinalString;
}
public String getPrivateFinalString() {
return privateFinalString;
}
public String getPublicString() {
return publicString;
}
public void setPublicString(String publicString) {
this.publicString = publicString;
}
public String getPrivateString() {
return privateString;
}
public void setPrivateString(String privateString) {
this.privateString = privateString;
}
}
}
@@ -1,17 +0,0 @@
package net.tcpshield.tcpshield;
public class TestConstants {
public static final String STANDARD_HOSTNAME = "example.org";
public static final String VALID_IP = "51.161.19.224";
public static final String INVALID_IP = "1.1.1.1";
public static final String PAYLOAD_HOSTNAME = "homeserver.fuzzlemann.de";
public static final String PAYLOAD_FML_HOSTNAME = "homeserver.fuzzlemann.de\0FML\0";
public static final String PAYLOAD_IP = "79.227.33.179";
public static final String VALID_PAYLOAD = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD + "\0FML\0";
}
@@ -1,14 +0,0 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl() {
this.ipWhitelistFolder = new File("ip-whitelist");
this.debug = true;
this.onlyProxy = true;
}
}
@@ -1,28 +0,0 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class TestPacketImpl implements IPacket {
private final String payload;
private String hostname;
public TestPacketImpl(String payload, String hostname) {
this.payload = payload;
this.hostname = hostname;
}
@Override
public String getRawPayload() {
return payload;
}
@Override
public void modifyOriginalPacket(String hostname) {
this.hostname = hostname;
}
public String getHostname() {
return hostname;
}
}
@@ -1,53 +0,0 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class TestPlayerImpl implements IPlayer {
private InetAddress ip;
private boolean connected;
public TestPlayerImpl(String ip) {
this.connected = true;
try {
this.ip = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public String getUUID() {
return "uuid";
}
@Override
public String getName() {
return "name";
}
@Override
public String getIP() {
if (ip == null) return "ip";
return ip.getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
this.ip = ip.getAddress();
}
@Override
public void disconnect() {
connected = false;
}
public boolean isConnected() {
return connected;
}
}