63 Commits
Author SHA1 Message Date
Dylan Keir cfbd6ffac5 Support Velocity's new LoginInboundConnection class. 2021-11-21 17:13:47 +00:00
Dylan Keir cf97e37632 Add try/catch to java version check 2021-10-31 15:43:08 +00:00
Dylan Keir 1629334e71 fix issue with signature verification due to erroneous event, 2.5.4 release 2021-10-27 22:17:57 +01:00
Dylan Keir 8cb66fc87a add description to plugin.yml 2021-10-11 13:30:30 +01:00
Dylan Keir e868bab503 Geyser support with BungeeCord & Velocity is now fully functional 2021-10-11 13:25:28 +01:00
Dylan Keir 2daad65b33 fix 2021-08-28 05:49:38 +01:00
Dylan Keir 846f4d0a6e 2.5.4: initial geyser/floodgate v2 support with only-allow-proxy-connections 2021-08-28 05:43:47 +01:00
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
Paul Zhang 44715187c4 fixes spelling mistake 2021-02-25 13:31:03 +01:00
Paul Zhang 75e30ec6b3 we're not in rust 2021-02-24 14:53:24 +01:00
Paul Zhang f0f1c53ba8 Lazily initialize the final field 2021-02-23 18:20:32 +01:00
Paul Zhang 5779306940 Merge pull request #33 from SlingSMP/master
Remove Legacy API
2021-01-19 13:59:58 +01:00
Ben Kerllenevich d031708308 Remove Legacy 2021-01-19 06:40:40 -05:00
Paul Zhang 46f7e9385b Merge pull request #32 from TCPShield/bugfix/event-priority
Adjusts event priority
2021-01-11 23:44:48 +01:00
Paul Zhang f100c0578a adjust event priority 2021-01-08 23:54:30 +01:00
Paul Zhang 15c0543114 removes two unused fields 2021-01-08 23:38:13 +01:00
Paul Zhang a1d6664906 removes second [TCPShield]-prefix on ip whitelist fetching 2020-12-27 00:34:52 +01:00
Paul Zhang 22b0b7065d Merge pull request #28 from TCPShield/feature-revise-gitignore
Update .gitignore
2020-12-24 17:13:02 +01:00
Paul Zhang e66f4df4ee Merge pull request #29 from RoboMWM/patch-1
contract an administrator
2020-12-23 20:04:36 +01:00
RoboMWM 74bc2b069c Well, contracts can be kewl too 2020-12-23 11:01:40 -08:00
Paul Zhang 637c64916b Update .gitignore 2020-12-23 17:14:43 +01:00
Paul Zhang 0a8d6295b2 Merge pull request #27 from TCPShield/bugfix-http-ok-check
Adds a check if the webserver returns 200 on pulling the IP list
2020-12-23 16:58:41 +01:00
Paul Zhang 1d7f2c368a changes line formatting 2020-12-23 16:55:05 +01:00
Paul Zhang 086bdd99d5 adds additional checks for validity of the returned string 2020-12-23 16:54:04 +01:00
Paul Zhang d01aa5efc4 Adds a check if the webserver returns 200 on pulling the IP list 2020-12-23 16:43:16 +01:00
Paul Zhang d67ece7a0d Create LICENSE 2020-12-19 20:07:06 +01:00
Paul Zhang 615e4b2289 Update README.md
Updates user link
2020-12-12 01:18:23 +01:00
Paul Zhang ddf722c069 Fixes testing 2020-12-06 20:25:18 +01:00
Fuzzlemann 1b716d5e18 Merge remote-tracking branch 'origin/master' 2020-12-04 15:21:56 +01:00
Fuzzlemann 839d625ce9 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:21:47 +01:00
Fuzzlemann 8a01e915b1 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:20:30 +01:00
Fuzzlemann 32d422a952 Fixes raw IP matching 2020-12-04 13:45:22 +01:00
Fuzzlemann c5cd43f2fa Merge remote-tracking branch 'origin/master' 2020-12-02 14:12:51 +01:00
Fuzzlemann d69d4f7ba8 Removes unused config keys 2020-12-02 14:12:35 +01:00
Paul Zhang 52325a8a2c Merge pull request #24 from TCPShield/ip-validation
TCPShield v2.4
2020-12-01 23:22:08 +01:00
Fuzzlemann b07fe80979 Future-proofs HandshakePacketHandler for possible proxy-side changes in the handshake hostname data 2020-12-01 23:13:39 +01:00
Fuzzlemann c6471d8e15 Fixes compatibility on some BungeeCord forks like FlameCord 2020-12-01 23:11:48 +01:00
Fuzzlemann 0dd3c25421 Removes the empty line on fetching the TCPShield IP list 2020-12-01 22:14:23 +01:00
Fuzzlemann 42530caa57 Reverts re-activation of native Paper support 2020-11-30 17:11:53 +01:00
Fuzzlemann e58319cabe Bumps version to v2.4 2020-11-30 17:11:23 +01:00
Fuzzlemann 1de480d575 Merge remote-tracking branch 'origin/ip-validation' into ip-validation 2020-11-29 19:34:30 +01:00
Fuzzlemann 1959106ac8 Fixes that the updated IPs are appended instead of replaced 2020-11-29 19:33:50 +01:00
Paul Zhang 5750391e43 Update README.md
Updated because of the re-introduction of native paper support
2020-11-29 15:30:00 +01:00
Fuzzlemann 00f50110fe Adds checks for the origin IP (if it's originating from TCPShield nodes) in replacement of signature checks + timestamp validation. Custom CIDR records can be added by creating a file in the "ip-whitelist" folder and adding one CIDR entry per line. 2020-11-29 15:16:11 +01:00
Paul Zhang e14d937a70 Merge pull request #16 from JasperJH/master
Fixes searchFieldByClass looping forever and incorrect cache keys
2020-11-05 23:19:10 +01:00
Fuzzlemann 3d5dab69bb Changes wrong static final field naming 2020-11-01 19:09:00 +01:00
JasperJH 78d22e627c Fixes searchFieldByClass looping forever and incorrect cache keys 2020-09-11 12:12:16 +02:00
102 changed files with 3104 additions and 1769 deletions

No files matched your search

+15 -3
View File
@@ -1,4 +1,16 @@
# Testing
ip-whitelist/
# IntelliJ
.idea/
*/target/
*/*/target/
*.iml
build/
*.iml
# Gradle
.gradle/
gradle/
gradlew
gradlew.bat
libs/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 TCPShield
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5 -3
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 cake. 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,11 +22,13 @@ 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
These wonderful contributors have helped TCPShield make this plugin better!
* [Fuzzlemann](https://github.com/Fuzzlemann)
* [Dylan Keir](https://github.com/DylanKeir)
* [Paul Zhang](https://github.com/paulzhng)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+89 -65
View File
@@ -5,13 +5,16 @@ 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,58 +23,23 @@ plugins {
id 'idea'
}
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
maven {
url = 'https://repo.dmulloy2.net/nexus/repository/public/'
}
maven {
url = 'https://oss.sonatype.org/content/repositories/snapshots'
}
maven {
url = 'https://papermc.io/repo/repository/maven-public/'
}
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
}
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'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.15.2-R0.1-SNAPSHOT'
// BungeeCord
compileOnly group: 'net.md-5', name: 'bungeecord-api', version: '1.14-SNAPSHOT'
// Velocity
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'
}
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.3'
// --
// Variables
// --
version = '2.5.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
// --
// Misc.
// --
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
sourceSets {
main {
java {
@@ -91,24 +59,80 @@ sourceSets {
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
sourceCompatibility = "8"
targetCompatibility = "8"
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
// --
// Dependencies
// --
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
maven {
url = 'https://repo.dmulloy2.net/nexus/repository/public/'
}
maven {
url = 'https://oss.sonatype.org/content/repositories/snapshots'
}
maven {
url = 'https://papermc.io/repo/repository/maven-public/'
}
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = "https://repo.opencollab.dev/maven-snapshots/"
}
}
void updateYaml() {
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'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.15.2-R0.1-SNAPSHOT'
// BungeeCord
compileOnly group: 'net.md-5', name: 'bungeecord-api', version: '1.14-SNAPSHOT'
// Velocity
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'
compileOnly 'org.geysermc.floodgate:api:2.0-SNAPSHOT'
}
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// --
// Build Tasks
// --
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
task updateVersion {
updateYamls()
updateJsons()
}
void updateYamls() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
@@ -132,7 +156,7 @@ void updateYaml() {
}
}
void updateJson() {
void updateJsons() {
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
@@ -151,4 +175,4 @@ void updateJson() {
}
}
build.dependsOn updateVersion
build.dependsOn updateVersion
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = 'TCPShield'
@@ -1,7 +0,0 @@
package net.tcpshield.tcpshield;
public class Constants {
public static long MAX_TIME_DIFFERENCE = 3; // in seconds
}
@@ -1,122 +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.*;
import net.tcpshield.tcpshield.validation.TimestampValidation;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final SignatureVerifier signatureVerifier;
private final TimestampValidation timestampValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.signatureVerifier = new SignatureVerifier();
this.timestampValidation = new TimestampValidation(config);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
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 != 4)
throw new MalformedPayloadException("payload.length != 4. Raw payload = \"" + rawPayload + "\"");
String hostname = payload[0];
String ipData = payload[1];
int timestamp;
try {
timestamp = Integer.parseInt(payload[2]);
} catch (NumberFormatException e) {
throw new MalformedPayloadException(e);
}
String signature = payload[3];
if (!timestampValidation.checkTimestamp(timestamp))
throw new InvalidTimestampException(timestamp, timestampValidation.getTime());
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!signatureVerifier.verify(reconstructedPayload, signature))
throw new SigningVerificationFailureException();
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidTimestampException e) {
handleInvalidTimestamp(player, e.getTimestamp(), e.getCurrentTime());
} catch (SigningVerificationFailureException e) {
handleSigningVerificationFailure(player, rawPayload);
} catch (ConnectionNotProxiedException e) {
handleNotProxiedConnection(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 handleInvalidTimestamp(IPlayer player, long timestamp, long currentTime) {
if (config.isDebug()) {
this.logger.warning(String.format("%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(), timestamp, currentTime, config.getTimestampValidationMode()));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
private void handleSigningVerificationFailure(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
private void handleNotProxiedConnection(IPlayer player, String rawPayload) {
if (!config.isOnlyProxy()) return;
if (config.isDebug()) {
this.logger.info(String.format("%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(), rawPayload));
}
player.disconnect();
}
}
@@ -1,109 +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 final Field MODIFIERS_FIELD;
static {
Field tempModifiersField;
try {
tempModifiersField = 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);
tempModifiersField = Arrays.stream(fields).filter(field -> field.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
MODIFIERS_FIELD = tempModifiersField;
MODIFIERS_FIELD.setAccessible(true);
}
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())) {
MODIFIERS_FIELD.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;
while (currentClass.getSuperclass() != null) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() != searchFor) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = clazz.getSuperclass();
}
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;
}
}
@@ -1,41 +0,0 @@
package net.tcpshield.tcpshield;
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;
public class SignatureVerifier {
private final PublicKey publicKey;
public SignatureVerifier() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(SignatureVerifier.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public boolean verify(String str, String signature) {
return verify(str.getBytes(StandardCharsets.UTF_8), signature);
}
private boolean verify(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) { // catching for illegal base64 encoding or invalid encoding for signatures
return false;
}
}
}
@@ -0,0 +1,205 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
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.exception.phase.InvalidSecretException;
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;
import java.util.Arrays;
/**
* 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[] payload = packet.getPayloadString().split("///");
if (payload.length != 4)
if (cidrValidator.validate(inetAddress))
return; // Allow connection with no processing
else
throw new InvalidPayloadException("length: " + payload.length + ", payload: " + Arrays.toString(payload) + ", raw payload: " + packet.getPayloadString());
int nullIndex;
if ((nullIndex = payload[3].indexOf('\0')) != -1) { // FML tagged payload
String originalData = payload[3];
payload[3] = originalData.substring(0, nullIndex);
extraData = originalData.substring(nullIndex);
}
String hostname = payload[0];
String ipData = payload[1];
int timestamp = Integer.parseInt(payload[2]);
String signature = payload[3];
String[] ipParts;
String host;
int port;
if (timestamp == 0 && GeyserUtils.GEYSER_SUPPORT_ENABLED) {
// Remap the altered layout
ipData = payload[0];
signature = payload[1];
hostname = payload[3];
// This is annoying having to have this in both blocks but w/e
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
if (!signature.equals(GeyserUtils.SESSION_SECRET)) {
throw new InvalidSecretException("Invalid secret: " + signature);
}
} else {
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!timestampValidator.validate(timestamp))
throw new TimestampValidationException(timestampValidator, 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 (InvalidSecretException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided an invalid session secret when authenticating a geyser connection.",
player.getName(), player.getUUID(), player.getIP());
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,58 @@
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");
try {
// 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);
} catch (Throwable t) {
this.getDebugger().error("Failed to check java version for string: " + 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,21 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected String timestampValidationMode;
protected boolean debug;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
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");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
} 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,35 +1,73 @@
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.geyser.GeyserUtils;
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.");
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Please install ProtocolLib in order to use our plugin.");
return;
}
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
new TCPShieldProtocolLib(this).load();
}
}
private BukkitImplProvider bukkitImpl;
private boolean isPaper() {
if (true) return false; // disabled for now
@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();
GeyserUtils.initGeyser(this, configProvider);
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;
}
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -1,18 +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;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
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,36 +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 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.HIGHEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
}
}
@@ -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 contract an administrator.");
handshakeEvent.setFailed(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.HIGHEST, 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,77 +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.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Field socketAddressField;
private static Field remoteAddressField;
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");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
} 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,58 @@ 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.geyser.GeyserUtils;
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));
GeyserUtils.initGeyser(this, configProvider);
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,47 @@
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());
this.plugin.getDebugger().warn("BungeeCord: Raw player hostname: " + packet.getPayloadString());
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,82 @@
package net.tcpshield.tcpshield.bungee.handler;
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 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(channel.getClass(), "remoteAddress"), ip);
try {
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(channel.getClass(), "localAddress"), ip);
} catch (Throwable t) {
// ChannelWrapper doesn't have a localAddress
}
} 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.timestampValidationMode = config.getString("timestamp-validation");
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,16 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class ConnectionNotProxiedException extends SecurityException {
public ConnectionNotProxiedException() {
super();
}
public ConnectionNotProxiedException(Throwable cause) {
super(cause);
}
public ConnectionNotProxiedException(String s) {
super(s);
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -1,20 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class InvalidTimestampException extends SigningVerificationFailureException {
private final long timestamp;
private final long currentTime;
public InvalidTimestampException(long timestamp, long currentTime) {
this.timestamp = timestamp;
this.currentTime = currentTime;
}
public long getTimestamp() {
return timestamp;
}
public long getCurrentTime() {
return currentTime;
}
}
@@ -1,15 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class MalformedPayloadException extends ConnectionNotProxiedException {
public MalformedPayloadException() {
}
public MalformedPayloadException(Throwable cause) {
super(cause);
}
public MalformedPayloadException(String s) {
super(s);
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class SigningVerificationFailureException extends MalformedPayloadException {
public SigningVerificationFailureException() {
super();
}
}
@@ -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,49 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import org.geysermc.floodgate.api.InstanceHolder;
import java.util.Arrays;
public class GeyserHandshakeHandler {
private final TCPShieldPlugin plugin;
public GeyserHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
public void init() {
InstanceHolder.getHandshakeHandlers().addHandshakeHandler(data -> {
if (data.getIp() == null) {
// not bedrock
this.plugin.getDebugger().warn("Connection with no bedrock data joined and ignored: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
return;
}
this.plugin.getDebugger().warn("Bedrock connection joined and validated: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
String oldPayload = data.getHostname();
if (oldPayload.contains("///")) {
oldPayload = oldPayload.split("///")[0];
}
String hostname = oldPayload;
String realIp = data.getIp();
String timestamp = "0";
String signature = GeyserUtils.SESSION_SECRET;
// so floodgate likes to append "\0Floodgate\0" or something stupid to the hostname
// which just completely breaks this, so we have to pass these in a different
// order to stop bungeecord detecting it as FML data, thanks floodgate.
String newHostname = realIp + ":0///" + signature + "///" + timestamp + "///" + hostname;
if (hostname.contains("\0")) { // this is usually fine because FML, providing the hostname is first
this.plugin.getDebugger().warn("Hostname contains null byte: " + Arrays.toString(hostname.toCharArray()));
}
this.plugin.getDebugger().warn("Setting hostname to %s - %s", newHostname, Arrays.toString(newHostname.toCharArray()));
data.setHostname(newHostname);
});
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.util.UUID;
public class GeyserUtils {
public static final String SESSION_SECRET = UUID.randomUUID().toString();
public static boolean GEYSER_SUPPORT_ENABLED = false;
public static void initGeyser(TCPShieldPlugin plugin, ConfigProvider config) {
if (!(GEYSER_SUPPORT_ENABLED = useGeyser(config))) {
return;
}
GeyserHandshakeHandler geyserHandler = new GeyserHandshakeHandler(plugin);
geyserHandler.init();
}
private static boolean useGeyser(ConfigProvider config) {
if (!config.isGeyser()) return false;
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,89 @@
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 boolean geyser = false;
protected File dataFolder;
protected File configFile;
public boolean isOnlyProxy() {
return onlyProxy;
}
public boolean isGeyser() {
return geyser;
}
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,26 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
public class InvalidSecretException extends TCPShieldException {
public InvalidSecretException(Throwable throwable) {
super("An invalid secret was provided during the geyser handshake process", throwable);
}
public InvalidSecretException(String message) {
super(message);
}
public InvalidSecretException(String message, Throwable throwable) {
super(message, throwable);
}
public InvalidSecretException() {
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,11 +0,0 @@
package net.tcpshield.tcpshield.validation;
public interface ITimestampValidator {
boolean validateTimestamp(long checkedTimestamp);
default long getTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -1,34 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.validation.impl.DisabledTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.HTPDateTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.SystemTimeTimestampValidatorImpl;
public class TimestampValidation {
private final ITimestampValidator timestampValidator;
public TimestampValidation(TCPShieldConfig config) {
switch (config.getTimestampValidationMode().toLowerCase()) {
case "system":
this.timestampValidator = new SystemTimeTimestampValidatorImpl();
break;
case "off":
this.timestampValidator = new DisabledTimestampValidatorImpl();
break;
case "htpdate":
default:
this.timestampValidator = new HTPDateTimestampValidatorImpl();
}
}
public boolean checkTimestamp(long timestamp) {
return timestampValidator.validateTimestamp(timestamp);
}
public long getTime() {
return timestampValidator.getTime();
}
}
@@ -1,13 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
/**
* @author Fuzzlemann
*/
public class DisabledTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
return true;
}
}
@@ -1,81 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
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;
/**
* inspired by https://github.com/iridium77/htpdate/blob/master/htpdate.c
*/
public class HTPDateTimestampValidatorImpl implements ITimestampValidator {
private volatile long timestampOffset = 0;
public HTPDateTimestampValidatorImpl() {
ForkJoinPool.commonPool().submit(() -> {
try {
syncTimestamp();
} catch (Exception e) {
e.printStackTrace();
}
}); // do not execute network requests on the main thread
}
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
private void syncTimestamp() throws IOException {
try (Socket socket = new Socket("google.com", 80)) {
// no-cache forces the server to return time
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 = extractDate(response);
timestampOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
}
private Date extractDate(List<String> response) {
for (String line : response) {
if (!line.startsWith("Date: ")) continue;
String dateString = line.substring("Date: ".length());
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
try {
return sdf.parse(dateString);
} catch (ParseException e) {
throw new IllegalStateException(e);
}
}
throw new IllegalArgumentException("no date line found - response: " + response);
}
@Override
public long getTime() {
return (System.currentTimeMillis() + timestampOffset) / 1000;
}
}
@@ -1,14 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
public class SystemTimeTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
}
@@ -6,29 +6,81 @@ 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.geyser.GeyserUtils;
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));
GeyserUtils.initGeyser(this, configProvider);
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");
this.geyser = loadedToml.getBoolean("enable-geyser-support");
} 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,62 @@
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;
}
// Turns out this event sometimes passes erroneous hostnames
// which have the null bytes terminated as FML data which causes
// issues with the verification process.
// @Subscribe(order = PostOrder.FIRST)
// public void onPreLogin(PreLoginEvent e) {
// InboundConnection connection = e.getConnection();
// handleEvent(connection, "onPreLogin");
// }
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection, "onHandshake");
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection, "onProxyPing");
}
private void handleEvent(InboundConnection connection, String debugSource) {
VelocityPlayer player = new VelocityPlayer(connection);
if (player.getConnectionType() == VelocityPlayer.ConnectionType.LEGACY) {
player.disconnect();
return;
}
VelocityPacket packet = new VelocityPacket(connection);
this.plugin.getDebugger().warn("Velocity: " + debugSource + " Raw player hostname: " + packet.getPayloadString());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
plugin.getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,109 @@
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;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_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));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacket(InboundConnection inboundConnection) {
// support new velocity connection type
if (inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
try {
inboundConnection = (InboundConnection) LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(inboundConnection);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
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,155 @@
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;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD;
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));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
private final InboundConnection inboundConnection;
// private final boolean legacy;
private final ConnectionType connectionType;
private String ip;
public VelocityPlayer(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
// this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS && inboundConnection.getClass() != LOGIN_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
if (this.inboundConnection.getClass() == INITIAL_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.INITIAL_INBOUND;
} else if (this.inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.LOGIN_INBOUND;
} else {
this.connectionType = ConnectionType.LEGACY;
}
}
/**
* 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;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object minecraftConnection = this.getMinecraftConnection();
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
// public boolean isLegacy() {
// return legacy;
// }
@Override
public void disconnect() {
try {
Object minecraftConnection = this.getMinecraftConnection();
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
private Object getMinecraftConnection() {
try {
switch (this.connectionType) {
case LEGACY:
return LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case INITIAL_INBOUND:
return MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case LOGIN_INBOUND: {
// starts as login_inbound, get delegate initial_inbound
Object initialInboundConnection = LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(this.inboundConnection);
return MINECRAFT_CONNECTION_FIELD.get(initialInboundConnection);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public ConnectionType getConnectionType() {
return connectionType;
}
enum ConnectionType {
LOGIN_INBOUND, INITIAL_INBOUND, LEGACY;
}
}
@@ -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.timestampValidationMode = toml.getString("timestamp-validation");
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<?> initialInboundConnectionClass;
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 {
initialInboundConnectionClass = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(initialInboundConnectionClass, "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() != initialInboundConnectionClass;
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
}
}
}
+3 -1
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: '2.3'
version: 2.5.5
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softdepends:
- floodgate
+4 -1
View File
@@ -5,4 +5,7 @@ only-allow-proxy-connections = true
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
debug-mode = false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support = false
+4 -1
View File
@@ -5,4 +5,7 @@ only-allow-proxy-connections: true
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
debug-mode: false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support: false
+4 -1
View File
@@ -1,6 +1,9 @@
name: TCPShield
version: '2.3'
version: 2.5.5
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
- floodgate
author: https://tcpshield.com
description: TCPShield IP parsing capabilities for Velocity
api-version: 1.16
+7 -2
View File
@@ -1,11 +1,16 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.3",
"version": "2.5.5",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [],
"dependencies": [
{
"id": "floodgate",
"optional": true
}
],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -1,74 +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.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl("system");
private final TCPShieldConfig skippingTimestampCheckTestConfig = new TestConfigImpl("off");
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
private final HandshakePacketHandler skippingTimestampCheckHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), skippingTimestampCheckTestConfig);
private TestPlayerImpl player;
@BeforeEach
public void setUp() {
player = new TestPlayerImpl();
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.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);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testUnsuccessfulHandshakeFailedTimestampCheck() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@Test
public void testUnsuccessfulHandshakeMalformedPayload() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@Test
public void testUnsuccessfulHandshakeInvalidSignature() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.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,60 +0,0 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SignatureObjectVerifierTest {
private static SignatureVerifier signatureVerifier;
@BeforeAll
public static void setUp() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureVerifier = new SignatureVerifier();
}
@Test
public void testVerificationSuccess() {
SignatureObject signatureObject = createSignature(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP);
assertTrue(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
@Test
public void testVerificationFailure() {
SignatureObject signatureObject = createSignature(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE);
assertFalse(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
private SignatureObject createSignature(String payload) {
int signatureIndex = payload.lastIndexOf("///");
String signature = payload.substring(signatureIndex + 3);
String reconstructedPayload = payload.substring(0, signatureIndex);
return new SignatureObject(signature, reconstructedPayload);
}
private static class SignatureObject {
private final String signature;
private final String reconstructedPayload;
private SignatureObject(String signature, String reconstructedPayload) {
this.signature = signature;
this.reconstructedPayload = reconstructedPayload;
}
public String getSignature() {
return signature;
}
public String getReconstructedPayload() {
return reconstructedPayload;
}
}
}
@@ -1,16 +0,0 @@
package net.tcpshield.tcpshield;
public class TestConstants {
public static final String STANDARD_HOSTNAME = "example.org";
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_INVALID_TIMESTAMP = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD_INVALID_TIMESTAMP + "\0FML\0";
public static final String INVALID_PAYLOAD_INVALID_SIGNATURE = VALID_PAYLOAD_INVALID_TIMESTAMP + "_INVALID_SIGNATURE";
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "MALFORMED_PAYLOAD";
}
@@ -1,12 +0,0 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl(String timestampValidationMode) {
this.timestampValidationMode = timestampValidationMode;
this.debug = true;
this.onlyProxy = true;
}
}
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more