17 Commits
Author SHA1 Message Date
Dylan Keir c07b71c5b1 re-introduce java 8 support due to high demand 2021-08-02 04:37:11 +01:00
Dylan Keir 4b13ac2ca1 2.5.2 & fix a java16/gradle7 build issue 2021-07-28 20:31:15 +01:00
Steven Ferguson 860fba8d6e Merge pull request #60 from xPlumpOrange/master
General Bug and Typo Fixes
2021-06-27 17:27:51 -06:00
Plump Orange 99cca8aa33 Merge branch 'TCPShield:master' into master 2021-06-27 19:15:48 -04:00
xPlumpOrange ad2a29de0f General Bug and Typo Fixes
Removed ReflectionUtil logic that caused issues. Added cancel call in Paper implementation to fix Paper issues. Upgraded minimum Java version to 11 and added checks to make sure the plugin is ran properly. Fixed old/invalid links in README. (Merged PR's of other users)
2021-06-27 19:14:58 -04:00
Steven Ferguson 17a28b1c57 Merge pull request #48 from xPlumpOrange/master
Fixed Bukkit Config Loading and ProtocolLib Implementation
2021-05-10 19:26:24 -06:00
xPlumpOrange eb8663f5b8 Fixed Bukkit Config Loading and ProtocolLib Implementation
Changed resource streaming for the Bukkit implementation of the config to Bukkit's resource streamer. Also fixed an issue with the ProtocolLib implementation where it throws an error instead of warning and disabling when ProtocolLib is not installed.
2021-05-10 21:01:26 -04:00
Steven Ferguson be2fe38744 Merge pull request #47 from xPlumpOrange/master
Added IP Whitelisting and Smarter Configs
2021-05-08 19:05:51 -06:00
xPlumpOrange e740a28f77 Removed Repeated Information in Exception
Removed payload information in InvalidPayloadException since the same information is displayed in the debugger.
2021-05-08 21:04:06 -04:00
xPlumpOrange d8a755035c Fixed logic issue with backwards support of old v4 system
Added a skip to the IP validation if the payload length is correct so users with old unsupported v4 ip whitelists dont have issues.
2021-05-08 20:48:17 -04:00
xPlumpOrange 188d541c4d Added Node Checking for Smarter Configs
Implemented node checks during config loading to stop the config api from providing null and causing other unrelated initialization errors and stopping auto config reset.
2021-05-08 20:22:42 -04:00
Plump Orange 40138413bb Merge branch 'TCPShield:master' into master 2021-05-08 18:58:59 -04:00
xPlumpOrange 2475bdfabd Added IP Whitelisting and Smarter Configs
Integrated a CIDR matching IP whitelist as seen in v2.4. Implemented forced config methods for resetting, loading, and reloading for auto resetting on errors and future support for reloading.
2021-05-08 18:58:05 -04:00
Steven Ferguson 71ba01d8e7 Merge pull request #46 from xPlumpOrange/master
2.5 Release
2021-05-07 15:00:56 -06:00
xPlumpOrange 8416dbe4e3 2.5 Release 2021-05-07 16:54:01 -04:00
Steven Ferguson 863fd644cf Merge pull request #42 from AoElite/preloginfix
Velocity PreLoginEvent Fix
2021-04-16 01:02:55 -06:00
AoElite f91a5c54b6 fix prelogin 2021-04-14 07:22:04 -04:00
95 changed files with 2804 additions and 1745 deletions

No files matched your search

+3 -2
View File
@@ -11,7 +11,7 @@ TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
### Setup
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/onboarding/tcpshield-plugin) guidelines.
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/panel/tcpshield-plugin) guidelines.
### Compiling
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
@@ -22,7 +22,7 @@ gradle build
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
See [Contact](https://docs.tcpshield.com/about-us)
See [Contact](https://tcpshield.com/#contact)
### Contributors
@@ -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/)
+68 -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,54 @@ plugins {
id 'idea'
}
// --
// Variables
// --
version = '2.5.3'
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 = "8"
targetCompatibility = "8"
options.encoding = 'UTF-8'
}
// --
// Dependencies
// --
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
@@ -42,6 +94,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 +109,17 @@ 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'
}
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// --
// Build Tasks
// --
test {
useJUnitPlatform()
}
@@ -68,47 +128,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 +157,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,178 @@
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.cidr.CIDRValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.impl.HTPDateTimestampValidator;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
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;
private CIDRValidator cidrValidator;
/**
* 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;
}
}
cidrValidator = new CIDRValidator(plugin);
}
/**
* 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 {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
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)
if (cidrValidator.validate(inetAddress))
return; // Allow connection with no processing
else
throw new InvalidPayloadException();
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 (UnknownHostException e) {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (Exception e) {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
if (!(e instanceof HandshakeException))
throw new HandshakeException(e);
else
throw e;
}
}
}
@@ -0,0 +1,51 @@
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();
/**
* Default initialization of TCPShield, called after interface defaults are set
*/
default void initialization() {
String jvmVersion = System.getProperty("java.version");
// Java 11 check/warning (Will eventually force Java 16 without warning)
String[] versionParts = jvmVersion.split("\\.");
int baseVersion = Integer.parseInt(versionParts[0]);
if (baseVersion < 11) // Java 8, and below, starts with 1, but since we are using Java 11 we can ignore sub values
this.getDebugger().warn("The Java version you are running is outdated for TCPShield and may cause issues. Update to atleast Java 11. Your version: Java %s", jvmVersion);
}
}
@@ -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,101 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Bukkit's configuration implementation
*/
public class BukkitConfig extends ConfigProvider {
private final JavaPlugin plugin;
private FileConfiguration loadedConfiguration;
public BukkitConfig(JavaPlugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = plugin.getDataFolder();
this.configFile = new File(dataFolder, "config.yml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedConfiguration.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedConfiguration = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = plugin.getResource("config.yml")) { // Have to use Bukkit's resource streaming or the resource wont be found
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
loadedConfiguration = YamlConfiguration.loadConfiguration(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(e);
}
}
}
@@ -1,34 +1,71 @@
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 {
if (getServer().getPluginManager().getPlugin("ProtocolLib") == null) {
getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
bukkitImpl = new BukkitProtocolLib(this);
}
bukkitImpl.load();
initialization();
} 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,65 @@
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.setCancelled(false); // Caused issues with newer versions of Paper (Thanks https://github.com/realDragonium)
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,23 @@
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() {
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,105 @@
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 net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import net.tcpshield.tcpshield.velocity.VelocityConfig;
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;
private Configuration loadedConfiguration;
public BungeeConfig(Plugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = plugin.getDataFolder();
this.configFile = new File(dataFolder, "config.yml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedConfiguration.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedConfiguration = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
loadedConfiguration = configProvider.load(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(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,57 @@ 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));
initialization();
} 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,86 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import java.io.File;
/**
* An abstract provider for TCPShield's options.
*/
public abstract class ConfigProvider {
/*
* Configuration options
*/
protected boolean onlyProxy = true;
protected String timestampValidationMode = "htpdate";
protected boolean doDebug = true; // Fail-safe default set to true
protected File dataFolder;
protected File configFile;
public boolean isOnlyProxy() {
return onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
public boolean doDebug() {
return doDebug;
}
public File getDataFolder() {
return dataFolder;
}
public File getConfigFile() {
return configFile;
}
/*
* Plugin Constants
*/
protected final long maxTimestampDifference = 3; // In Unix Timesteps (Seconds)
public long getMaxTimestampDifference() {
return maxTimestampDifference;
}
/*
* Required methods
*/
/**
* Deletes the current config saved to the disk and reinstalls the default config
* @throws ConfigResetException Thrown if resetting fails
*/
protected abstract void reset() throws ConfigResetException;
/**
* Trys to load the options from the config, if failed, throws ConfigLoadException
* @throws ConfigLoadException Thrown if loading fails, reset should be called if thrown
*/
protected abstract void load() throws ConfigLoadException;
/**
* Trys to reload the config, if failed, throws ConfigReloadException
* @throws ConfigReloadException Thrown if reloading fails
*/
public abstract void reload() throws ConfigReloadException;
/**
* Checks the provided nodes to see if they exist in the config
* @param nodes The nodes to check
* @throws ConfigException Thrown when a node isnt found
*/
protected abstract void checkNodes(String... nodes) throws ConfigException;
}
@@ -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,182 @@
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;
/**
* 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);
// Removed due to complications with Velocity (Thanks https://github.com/AoElite)
// 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.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the loading phase of the config process
*/
public class ConfigLoadException extends ConfigException {
public ConfigLoadException(Throwable throwable) {
super(throwable);
}
public ConfigLoadException(String message) {
super(message);
}
public ConfigLoadException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigLoadException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the reloading phase of the config process
*/
public class ConfigReloadException extends ConfigException {
public ConfigReloadException(Throwable throwable) {
super(throwable);
}
public ConfigReloadException(String message) {
super(message);
}
public ConfigReloadException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigReloadException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the resetting phase of the config process
*/
public class ConfigResetException extends ConfigException {
public ConfigResetException(Throwable throwable) {
super(throwable);
}
public ConfigResetException(String message) {
super(message);
}
public ConfigResetException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigResetException() {
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 CIDR process of TCPShield
*/
public class CIDRException extends TCPShieldException {
public CIDRException(Throwable throwable) {
super("An exception occured during the CIDR process", throwable);
}
public CIDRException(String message) {
super(message);
}
public CIDRException(String message, Throwable throwable) {
super(message, throwable);
}
public CIDRException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the config loading, reloeading, saving, etc. process of TCPShield
*/
public class ConfigException extends TCPShieldException {
public ConfigException(Throwable throwable) {
super("An exception occured during the config process", throwable);
}
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigException() {
super();
}
}
@@ -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,83 @@
package net.tcpshield.tcpshield.util.validation.cidr;
import net.tcpshield.tcpshield.util.exception.phase.CIDRException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* CIDR matcher for the CIDR validator
*/
public abstract class CIDRMatcher {
/**
* Creates a CIDRMatcher from a CIDR matching regex
* @param cidrMatchString The string containing the CIDR matching regex
* @return The corrosponding CIDRMatcher
*/
public static CIDRMatcher create(String cidrMatchString) {
return new CIDRMatcher(cidrMatchString) {
@Override
public boolean match(InetAddress inetAddress) {
return super.match(inetAddress);
}
};
}
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
private CIDRMatcher(String cidrMatchString) {
String[] split = cidrMatchString.split("/");
String parsedIPAddress;
if (split.length != 0) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = cidrMatchString;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new CIDRException(e);
}
}
/**
* Compares the provided InetAddress with the CIDR for a match
* @param inetAddress The InetAddress to match with the CIDR
* @return Boolean stating if the provided InetAddress matches the CIDR
*/
public boolean match(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;
}
}
@@ -0,0 +1,91 @@
package net.tcpshield.tcpshield.util.validation.cidr;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.CIDRException;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.util.*;
/**
* A CIDR validator for TCPShield
*/
public class CIDRValidator {
private final TCPShieldPlugin plugin;
private final File ipWhitelistFolder;
private final List<CIDRMatcher> cidrMatchers;
private final Set<String> cache = new HashSet<>(); // Max potential size is equilivent to the amount of whitelisted IP's
public CIDRValidator(TCPShieldPlugin plugin) throws CIDRException {
this.plugin = plugin;
ipWhitelistFolder = new File(plugin.getConfigProvider().getDataFolder(), "ip-whitelist");
if (!ipWhitelistFolder.exists())
ipWhitelistFolder.mkdir();
try {
List<String> whitelists = loadWhitelists();
cidrMatchers = loadCIDRMatchers(whitelists);
} catch (Exception e) {
throw new CIDRException(e);
}
}
private List<CIDRMatcher> loadCIDRMatchers(List<String> whitelists) {
List<CIDRMatcher> matchers = new ArrayList<>();
for (String whitelist : whitelists)
try {
matchers.add(CIDRMatcher.create(whitelist));
} catch (Exception e) {
plugin.getDebugger().warn("Exception occured while creating CIDRMatcher for \"%s\". Ignoring it.", whitelist);
plugin.getDebugger().exception(e);
}
return matchers;
}
private List<String> loadWhitelists() throws FileNotFoundException {
List<String> whitelists = new ArrayList<>();
for (File file : Objects.requireNonNull(ipWhitelistFolder.listFiles())) {
if (file.isDirectory())
continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
whitelists.add(cidrEntry);
}
}
}
return whitelists;
}
/**
* Validates an InetAddress with CIDR matchers
* @param inetAddress The InetAddress to validate
* @return Boolean stating if the InetAddress is validated with CIDR
*/
public boolean validate(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip))
return true;
for (CIDRMatcher cidrMatcher : cidrMatchers)
if (cidrMatcher.match(inetAddress)) {
cache.add(ip);
return true;
}
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,80 @@ 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(), this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
server.getEventManager().register(this, new VelocityHandshakeHandler(this));
initialization();
} 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,100 @@
package net.tcpshield.tcpshield.velocity;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Velocity's configuration implementation
*/
public class VelocityConfig extends ConfigProvider {
private final TCPShieldPlugin plugin;
private Toml loadedToml;
public VelocityConfig(File dataFolder, TCPShieldPlugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = dataFolder;
this.configFile = new File(this.dataFolder, "config.toml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedToml.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedToml = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = VelocityConfig.class.getResourceAsStream("/config.toml")) {
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
loadedToml = new Toml().read(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
this.onlyProxy = loadedToml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedToml.getString("timestamp-validation");
this.doDebug = loadedToml.getBoolean("debug-mode");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(e);
}
}
}
@@ -1,48 +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.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 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.3
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.3
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
Binary file not shown.
+1 -11
View File
@@ -1,11 +1 @@
{
"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.3","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;
}
}