15 Commits
Author SHA1 Message Date
Draylar 740e648b92 TCPShield Fabric support (#40)
Adds Fabric support (huge, huge thanks to @Draylar)
2021-03-09 18:35:31 +01:00
Paul Zhang dd51276790 Updates outdated docs link 2021-02-27 16:21:33 +01:00
Paul Zhang 8f88b928e8 Update README.md 2021-02-27 16:20:44 +01:00
Paul Zhang 5b76160f78 Finish Paper native support 2021-02-27 16:12:57 +01:00
Paul Zhang d872880ede Remove IPlayer#getUUID and IPlayer#getName and their corresponding implementations because these informations aren't available at this state yet 2021-02-25 19:18:26 +01:00
Paul Zhang 07b80c0cfa remove debug message 2021-02-25 19:14:47 +01:00
Paul Zhang f567b6add3 Version bump yml + toml changes + the floodgate 2.0 jar 2021-02-25 19:13:13 +01:00
Paul Zhang a1654ea9af Bump version to 2.5 2021-02-25 18:47:49 +01:00
Paul Zhang 7b4543e744 Update Floodgate 2021-02-25 18:47:16 +01:00
Paul Zhang c9d088c0d0 Rough implementation of Geyser support. It only supports Floodgate 2.0 in conjunction with @mikroskreem's PROXY v2 PR.
!It's not yet working properly!
2021-02-25 18:45:21 +01:00
Paul Zhang b215435e03 Finish Geyser support 2021-02-25 18:13:32 +01:00
Paul Zhang 10d78caa56 Paper x Geyser 2021-02-25 14:48:56 +01:00
Paul Zhang 4b6aaf573f Revert "Removes Paper native support"
This reverts commit d3dd1d7a20.
2021-02-25 14:22:21 +01:00
Paul Zhang d3dd1d7a20 Removes Paper native support 2021-02-25 14:21:10 +01:00
Paul Zhang b80369562e Rough implementation of Geyser support. It only supports Floodgate 2.0 in conjunction with @mikroskreem's PROXY v2 PR.
!It's not yet working properly!
2021-02-25 14:21:04 +01:00
118 changed files with 2267 additions and 3547 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
.gitea/CODEOWNERS linguist-detectable=false
-3
View File
@@ -1,3 +0,0 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+4 -9
View File
@@ -3,16 +3,11 @@ ip-whitelist/
# IntelliJ
.idea/
build/
*.iml
# Eclipse
.classpath
.project
.settings/
# Gradle
.gradle/
# Build
libs/
build/
gradle/
gradlew
gradlew.bat
+5 -5
View File
@@ -6,12 +6,13 @@ It also parses passed IP addresses so the server is aware of the real player IP
### Compatibility
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
This does not apply when using Paper 1.16 build #503 or higher.
### Setup
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/panel/tcpshield-plugin) guidelines.
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/config/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,13 +23,12 @@ 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://tcpshield.com/#contact)
See [Contact](https://docs.tcpshield.com/about-us)
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Dylan Keir](https://github.com/DylanKeir)
* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+170
View File
@@ -0,0 +1,170 @@
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
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'
}
}
plugins {
id 'java'
id 'idea'
id 'fabric-loom' version '0.6-SNAPSHOT'
}
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://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
}
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.16.5-R0.1-20210227.144841-85'
// 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'
// Fabric
minecraft "com.mojang:minecraft:1.16.5"
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
modImplementation group: 'net.fabricmc', name: 'fabric-loader', version: '0.11.2'
// 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.geyser:floodgate-spigot:2.0'
}
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
}
void updateYaml() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
options.setPrettyFlow(true)
def yaml = new Yaml(options)
def bukkitYamlFile = new File('src/main/resources/plugin.yml')
def bungeeYamlFile = new File('src/main/resources/bungee.yml')
def files = [bukkitYamlFile, bungeeYamlFile]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
def cfg = yaml.load(inputStream)
cfg.put("version", version)
new FileWriter(file).withCloseable { writer ->
yaml.dump(cfg, writer)
}
}
}
}
void updateJson() {
def gson = new GsonBuilder().setPrettyPrinting().create();
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
new InputStreamReader(inputStream, StandardCharsets.UTF_8).withCloseable { inputStreamReader ->
JsonObject object = JsonParser.parseReader(inputStreamReader).getAsJsonObject()
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(gson.toJson(object))
}
}
}
}
}
build.dependsOn updateVersion
-138
View File
@@ -1,138 +0,0 @@
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.io.File
import java.io.FileWriter
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.*
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.yaml:snakeyaml:1.26")
classpath("com.google.code.gson:gson:2.8.6")
}
}
plugins {
id("java")
id("eclipse")
id("idea")
}
version = "2.8.0"
group = "net.tcpshield.tcpshield"
base {
archivesName.set("TCPShield")
}
idea {
module {
isDownloadJavadoc = true
isDownloadSources = true
}
}
sourceSets {
named("main") {
java.srcDir("src/main/java")
resources.srcDir("src/main/resources")
}
named("test") {
java.srcDir("src/test/java")
resources.srcDir("src/test/resources")
}
}
tasks.withType<JavaCompile> {
sourceCompatibility = "17"
targetCompatibility = "17"
options.encoding = "UTF-8"
}
repositories {
maven { url = uri("https://repo1.maven.org/maven2/") }
maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") }
maven { url = uri("https://repo.dmulloy2.net/nexus/repository/public/") }
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("https://repo.opencollab.dev/maven-snapshots/") }
}
dependencies {
compileOnly(files("libs/ProtocolLib-5.0.jar")) // Import Legacy ProtocolLib API.
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0-M1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0-M1")
compileOnly("org.geysermc.floodgate:api:2.1.1-SNAPSHOT")
}
tasks.named<ProcessResources>("processResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
configurations {
named("testImplementation") {
extendsFrom(configurations.named("compileOnly").get())
}
}
fun updateYamls() {
val options = DumperOptions().apply {
defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
setSplitLines(false)
setPrettyFlow(true)
}
val yaml = Yaml(options)
val files = listOf(
File("src/main/resources/plugin.yml"),
File("src/main/resources/bungee.yml")
)
files.forEach { file ->
file.inputStream().use { inputStream ->
@Suppress("UNCHECKED_CAST")
val cfg = yaml.load<MutableMap<String, Any?>>(inputStream)
cfg["version"] = project.version.toString()
FileWriter(file).use { writer ->
yaml.dump(cfg, writer)
}
}
}
}
fun updateJsons() {
val file = File("src/main/resources/velocity-plugin.json")
file.inputStream().use { inputStream ->
InputStreamReader(inputStream, StandardCharsets.UTF_8).use { reader ->
val obj: JsonObject = JsonParser.parseReader(reader).asJsonObject
obj.addProperty("version", project.version.toString())
FileWriter(file).use { writer ->
writer.write(obj.toString())
}
}
}
}
tasks.register("updateVersion") {
doLast {
updateYamls()
updateJsons()
}
}
tasks.named("build") {
dependsOn("updateVersion")
}
Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
-251
View File
@@ -1,251 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
-94
View File
@@ -1,94 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-339
View File
@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Binary file not shown.
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
-2
View File
@@ -1,2 +0,0 @@
rootProject.name = "TCPShield"
@@ -0,0 +1,93 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.InvalidIPException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.IPValidation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
// raw payload is passed when it's not available -> e.g. Paper server list ping events
if (rawPayload == null) return;
String extraData = null;
// fix for e.g. incoming FML tagged packets
String cleanedPayload;
int nullIndex = rawPayload.indexOf('\0');
if (nullIndex != -1) { // FML tagged
cleanedPayload = rawPayload.substring(0, nullIndex);
extraData = rawPayload.substring(nullIndex);
} else { // standard
cleanedPayload = rawPayload;
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length < 2) return; // is nonetheless allowed as it passed the IP check
String hostname = payload[0];
String ipData = payload[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidIPException e) {
handleInvalidIPException(player, rawPayload);
} catch (IPModificationFailureException e) {
this.logger.warning(String.format("%s's IP failed to be modified. Raw payload = \"%s\"", player.getIP(), rawPayload));
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvalidIPException(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s was disconnected because the client failed the ip check and only-allowed-proxy-connections is enabled. Raw payload = \"%s\"", player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
public TCPShieldConfig getConfig() {
return config;
}
}
@@ -0,0 +1,107 @@
package net.tcpshield.tcpshield;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ReflectionUtils {
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static Field modifiersField;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
if (modifiersField == null) {
try {
modifiersField = getDeclaredField(Field.class, "modifiers");
} catch (NoSuchFieldException e) { // workaround for when searching for the modifiers field on Java 12 or higher
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
modifiersField = Arrays.stream(fields).filter(modifier -> modifier.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
modifiersField.setAccessible(true);
}
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
setField(object, field, value);
}
public static void setField(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setField(Object object, Field field, Object value) throws IllegalAccessException {
field.set(object, value);
}
public static Object getObjectInPrivateField(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException {
Field field = getPrivateField(object.getClass(), fieldName);
return field.get(object);
}
public static Field getPrivateField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw e;
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -1,206 +0,0 @@
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.lang.reflect.InvocationTargetException;
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;
}
}
}
@@ -1,58 +0,0 @@
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);
}
}
}
@@ -0,0 +1,9 @@
package net.tcpshield.tcpshield.abstraction;
public interface IPacket {
String getRawPayload();
void modifyOriginalPacket(String hostname) throws Exception;
}
@@ -0,0 +1,15 @@
package net.tcpshield.tcpshield.abstraction;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.net.InetSocketAddress;
public interface IPlayer {
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
void disconnect();
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected File ipWhitelistFolder;
protected boolean debug;
protected boolean geyser;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
return this.debug;
}
public boolean isGeyser() {
return this.geyser;
}
}
@@ -1,102 +0,0 @@
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", "enable-geyser-support", "prefer-protocollib");
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");
this.preferProtocolLib = loadedConfiguration.getBoolean("prefer-protocollib");
} 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,100 +1,38 @@
package net.tcpshield.tcpshield.bukkit;
import java.util.logging.Logger;
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 net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The entry point for Bukkit servers
*/
public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
public class TCPShieldBukkit extends JavaPlugin {
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@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 (1.16 build #503 or higher) to enable native compatibility or install ProtocolLib.");
return;
}
private BukkitImplProvider bukkitImpl;
@Override
public void onEnable() {
try {
configProvider = new BukkitConfig(this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
// check force plib option -> paper -> plib -> error
if (this.configProvider.preferProtocolLib() && getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
try {
String[] protocolLibVersion = getServer().getPluginManager().getPlugin("ProtocolLib").getDescription().getVersion().split("-")[0].split("\\.");
int major = Integer.parseInt(protocolLibVersion[0]);
int minor = Integer.parseInt(protocolLibVersion[1]);
int patch = Integer.parseInt(protocolLibVersion[2]);
String paperVersion = Bukkit.getServer().getMinecraftVersion();
if (major <= 5 && minor <= 2 && patch <= 1 && (paperVersion.equals("1.20.5") || paperVersion.equals("1.20.6"))) {
getLogger().severe("TCPShield is incompatible with ProtocolLib <= 5.2.1 on Paper 1.20.5/1.20.6 due to lack of support from ProtocolLib. Reverting to default Paper handler to prevent issues. This error can be avoided by disabling 'prefer-protocollib' in the config.");
bukkitImpl = new BukkitPaper(this);
} else {
bukkitImpl = new BukkitProtocolLib(this);
}
} catch (Exception t) {
getLogger().warning("Failed to check Paper or ProtocolLib version. This is not a critical error unless you are running Paper 1.20.5/1.20.6 with ProtocolLib version 5.2.1 or below.");
getDebugger().exception(t);
bukkitImpl = new BukkitProtocolLib(this);
}
} else if (BukkitImplProvider.hasPaperEvent()) {
bukkitImpl = new BukkitPaper(this);
} else if (getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
bukkitImpl = new BukkitProtocolLib(this);
} else {
getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
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;
}
@Override
public Logger getLogger() {
return super.getLogger();
}
new TCPShieldProtocolLib(this).load();
}
}
/**
* Checks if the server version supports native Paper support (Paper 1.16.5 build #503 or higher)
*
* @return if the server version supports native Paper support
*/
private boolean isPaper() {
try {
Class<?> handshakeEvent = Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
handshakeEvent.getMethod("getOriginalSocketAddressHostname");
return true;
} catch (ClassNotFoundException | NoSuchMethodException e) {
return false;
}
}
}
@@ -0,0 +1,21 @@
package net.tcpshield.tcpshield.bukkit.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -1,22 +0,0 @@
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());
}
}
@@ -0,0 +1,43 @@
package net.tcpshield.tcpshield.bukkit.paper;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class PaperHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public PaperHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
IPacket packet = new PaperServerListPacketImpl(e);
IPlayer player = new PaperServerListPingPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.bukkit.paper;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserHandshakePacketHandler;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.velocity.VelocityHandshakePacketHandler;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
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() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(handshakePacketHandler);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
});
}
}
@@ -1,42 +0,0 @@
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);
}
}
@@ -1,33 +0,0 @@
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);
}
}
}
@@ -1,66 +0,0 @@
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)
// Paper requires a non-empty fail message (Guava checkArgument), so use a single space
// to avoid the plugin echoing a visible kick reason back into the console.
handshakeEvent.setFailMessage(" ");
handshakeEvent.setFailed(true);
}
}
@@ -0,0 +1,24 @@
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);
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.kyori.adventure.text.Component;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperPlayerImpl implements IPlayer {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayerImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getIP() {
return handshakeEvent.getOriginalSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.failMessage(Component.text("Connection failed. Please try again or contact an administrator."));
handshakeEvent.setFailed(true);
}
}
@@ -0,0 +1,23 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperServerListPacketImpl implements IPacket {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPacketImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getRawPayload() {
return null;
}
@Override
public void modifyOriginalPacket(String hostname) {
pingEvent.setCancelled(false);
}
}
@@ -0,0 +1,30 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperServerListPingPlayerImpl implements IPlayer {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPingPlayerImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getIP() {
return pingEvent.getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
// not necessary since this class is only for server list pings
}
@Override
public void disconnect() {
pingEvent.setCancelled(true);
}
}
@@ -1,22 +0,0 @@
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);
}
}
@@ -0,0 +1,30 @@
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.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, HandshakePacketHandler handshakePacketHandler) {
super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = handshakePacketHandler;
}
@Override
public void onPacketReceiving(PacketEvent e) {
IPacket packet = new ProtocolLibPacketImpl(e.getPacket());
IPlayer player = new ProtocolLibPlayerImpl(e.getPlayer());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
private final JavaPlugin plugin;
public TCPShieldProtocolLib(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin, handshakePacketHandler);
ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
});
}
}
@@ -1,34 +0,0 @@
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);
}
}
}
@@ -1,34 +0,0 @@
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);
}
}
}
@@ -1,92 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.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();
MinimalInjector 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, "wrappedChannel");
ReflectionUtil.setFinalField(channel, ReflectionUtil.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -0,0 +1,25 @@
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);
}
}
@@ -0,0 +1,64 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String 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("");
}
}
@@ -1,40 +0,0 @@
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,38 @@
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.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.BungeePacketImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePlayerImpl;
public class BungeeHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public BungeeHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@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);
}
}
@@ -0,0 +1,21 @@
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.HandshakePacketHandler;
import net.tcpshield.tcpshield.bungee.impl.BungeeConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
public class TCPShieldBungee extends Plugin implements Listener {
@Override
public void onEnable() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.getLogger(), new BungeeConfigImpl(this));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(handshakePacketHandler));
});
}
}
@@ -0,0 +1,58 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class BungeeConfigImpl extends TCPShieldConfig {
private final Plugin plugin;
public BungeeConfigImpl(Plugin plugin) {
this.plugin = plugin;
saveDefaultConfig();
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
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);
}
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,54 @@
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;
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 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();
}
}
@@ -0,0 +1,8 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,4 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends SecurityException {
}
@@ -0,0 +1,16 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldIPListUpdaterException extends RuntimeException {
public TCPShieldIPListUpdaterException(Throwable cause) {
super(cause);
}
public TCPShieldIPListUpdaterException(String message, Throwable cause) {
super(message, cause);
}
public TCPShieldIPListUpdaterException(String message) {
super(message);
}
}
@@ -0,0 +1,12 @@
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,18 @@
package net.tcpshield.tcpshield.fabric;
import net.fabricmc.api.ModInitializer;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.fabric.impl.FabricConfigImpl;
import java.util.logging.Logger;
public class TCPShieldFabric implements ModInitializer {
public static final Logger LOGGER = Logger.getLogger("TCPShield");
public static final HandshakePacketHandler PACKET_HANDLER = new HandshakePacketHandler(LOGGER, new FabricConfigImpl());
@Override
public void onInitialize() {
LOGGER.info("TCPShield has been loaded.");
}
}
@@ -0,0 +1,105 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.fabricmc.loader.api.FabricLoader;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class FabricConfigImpl extends TCPShieldConfig {
private final Path configLocation = new File(FabricLoader.getInstance().getConfigDir().toString(), "tcpshield.yml").toPath();
public FabricConfigImpl() {
if(!configExists()) {
createDefaultConfig();
}
ConfigData config = loadConfig();
this.onlyProxy = config.onlyAllowProxyConnections;
this.ipWhitelistFolder = new File(FabricLoader.getInstance().getGameDirectory(), "ip-whitelist");
this.geyser = false;
this.debug = config.debugMode;
}
/**
* @return whether the file specified in {@link FabricConfigImpl#configLocation} exists.
*/
private boolean configExists() {
return Files.exists(configLocation);
}
/**
* Creates the default configuration file at the location specified in {@link FabricConfigImpl#configLocation}.
*
* <p>
* This operation will always override an existing configuration file if it exists.
* Callers should check {@link FabricConfigImpl#configExists()} if they wish to avoid this behavior.
*/
private void createDefaultConfig() {
// Ensure the parent directory of our config file exists.
// If it does not exist, attempt to create it now.
Path parentDirectory = configLocation.getParent();
if (!Files.exists(parentDirectory) || !Files.isDirectory(parentDirectory)) {
parentDirectory.toFile().mkdirs();
}
// Copy the config.yml data from our mod jar to the loader config folder.
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
Files.copy(in, configLocation);
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
private ConfigData loadConfig() {
try {
List<String> strings = Files.readAllLines(configLocation);
boolean onlyAllowProxyConnections = true;
boolean debugMode = false;
// Rudimentary config parsing
for (String line : strings) {
String[] keyValue = line.split(": ");
// A config option will only be valid if it is in the format 'a: b'.
// Ensure that is the case now.
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
switch (key) {
case "only-allow-proxy-connections":
onlyAllowProxyConnections = Boolean.parseBoolean(value);
break;
case "debug-mode":
debugMode = Boolean.parseBoolean(value);
break;
}
}
}
return new ConfigData(onlyAllowProxyConnections, debugMode);
} catch (Exception e) {
throw new TCPShieldInitializationException("Couldn't load config in config/tclshield.yml!");
}
}
private static class ConfigData {
protected boolean onlyAllowProxyConnections;
protected boolean debugMode;
public ConfigData(boolean onlyAllowProxyConnections, boolean debugMode) {
this.onlyAllowProxyConnections = onlyAllowProxyConnections;
this.debugMode = debugMode;
}
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.fabric.mixin.HandshakeC2SPacketAccessor;
public class FabricPacketImpl implements IPacket {
private final HandshakeC2SPacket handshake;
public FabricPacketImpl(HandshakeC2SPacket handshake) {
this.handshake = handshake;
}
@Override
public String getRawPayload() {
return ((HandshakeC2SPacketAccessor) handshake).getAddress();
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
// NO OPERATION
}
}
@@ -0,0 +1,40 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
import net.minecraft.text.LiteralText;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.fabric.mixin.ClientConnectionAccessor;
import java.net.InetSocketAddress;
public class FabricPlayerImpl implements IPlayer {
private final ClientConnection connection;
private String ip;
public FabricPlayerImpl(HandshakeC2SPacket packet, ClientConnection connection) {
this.connection = connection;
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
// At this point, the IP/connection believe the player has the IP of TCPShield.
// The ip passed into this method contains their CORRECT data, which we have to assign to the player network connection.
((ClientConnectionAccessor) connection).setAddress(ip);
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public void disconnect() {
connection.disconnect(new LiteralText("Connection failed. Please try again or contact an administrator."));
}
}
@@ -0,0 +1,20 @@
package net.tcpshield.tcpshield.fabric.mixin;
import io.netty.channel.Channel;
import net.minecraft.network.ClientConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.net.SocketAddress;
@Mixin(ClientConnection.class)
public interface ClientConnectionAccessor {
@Accessor
void setAddress(SocketAddress address);
@Accessor
Channel getChannel();
@Accessor
SocketAddress getAddress();
}
@@ -0,0 +1,11 @@
package net.tcpshield.tcpshield.fabric.mixin;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(HandshakeC2SPacket.class)
public interface HandshakeC2SPacketAccessor {
@Accessor
String getAddress();
}
@@ -0,0 +1,32 @@
package net.tcpshield.tcpshield.fabric.mixin;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.server.network.ServerHandshakeNetworkHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.fabric.TCPShieldFabric;
import net.tcpshield.tcpshield.fabric.impl.FabricPacketImpl;
import net.tcpshield.tcpshield.fabric.impl.FabricPlayerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerHandshakeNetworkHandler.class)
public class ServerHandshakeMixin {
@Shadow @Final private ClientConnection connection;
@Inject(
method = "onHandshake",
at = @At("HEAD"))
private void onHandshake(HandshakeC2SPacket handshake, CallbackInfo ci) {
IPacket packet = new FabricPacketImpl(handshake);
IPlayer player = new FabricPlayerImpl(handshake, connection);
TCPShieldFabric.PACKET_HANDLER.onHandshake(packet, player);
}
}
@@ -1,49 +0,0 @@
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,29 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.geyser.impl.GeyserPacketImpl;
import net.tcpshield.tcpshield.geyser.impl.GeyserPlayerImpl;
import org.geysermc.floodgate.api.InstanceHolder;
public class GeyserHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public GeyserHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
public void init() {
InstanceHolder.getHandshakeHandlers().addHandshakeHandler(data -> {
// In case the connection is a Bedrock connection, we don't bother
if (data.getBedrockData() != null) return;
IPlayer player = new GeyserPlayerImpl(data);
IPacket packet = new GeyserPacketImpl(data);
handshakePacketHandler.onHandshake(packet, player);
});
}
}
@@ -1,33 +1,27 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.util.UUID;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
public class GeyserUtils {
public static final String SESSION_SECRET = UUID.randomUUID().toString();
public static boolean GEYSER_SUPPORT_ENABLED = false;
public static void initGeyserOrDefault(HandshakePacketHandler handshakePacketHandler, Runnable registrar) {
if (!useGeyser(handshakePacketHandler.getConfig())) {
registrar.run();
} else {
GeyserHandshakePacketHandler geyserHandler = new GeyserHandshakePacketHandler(handshakePacketHandler);
geyserHandler.init();
}
}
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;
}
}
private static boolean useGeyser(TCPShieldConfig config) {
if (!config.isGeyser()) return false;
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,23 @@
package net.tcpshield.tcpshield.geyser.impl;
import net.tcpshield.tcpshield.abstraction.IPacket;
import org.geysermc.floodgate.api.handshake.HandshakeData;
public class GeyserPacketImpl implements IPacket {
private final HandshakeData handshakeData;
public GeyserPacketImpl(HandshakeData handshakeData) {
this.handshakeData = handshakeData;
}
@Override
public String getRawPayload() {
return handshakeData.getHostname();
}
@Override
public void modifyOriginalPacket(String hostname) {
handshakeData.setHostname(hostname);
}
}
@@ -0,0 +1,30 @@
package net.tcpshield.tcpshield.geyser.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import java.net.InetSocketAddress;
public class GeyserPlayerImpl implements IPlayer {
private final HandshakeData handshakeData;
public GeyserPlayerImpl(HandshakeData handshakeData) {
this.handshakeData = handshakeData;
}
@Override
public String getIP() {
return ((InetSocketAddress) handshakeData.getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeData.setIp(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeData.getChannel().close();
}
}
@@ -1,101 +0,0 @@
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 boolean velocityHandlePreLoginEvent = true;
// spigot/paper only option
protected boolean preferProtocolLib;
protected File dataFolder;
protected File configFile;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public boolean isGeyser() {
return this.geyser;
}
public boolean handlePreLoginEvent() {
return this.velocityHandlePreLoginEvent;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
public boolean doDebug() {
return this.doDebug;
}
public boolean preferProtocolLib() {
return this.preferProtocolLib;
}
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;
}
@@ -1,22 +0,0 @@
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;
}
@@ -1,27 +0,0 @@
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();
}
@@ -1,114 +0,0 @@
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);
}
@@ -1,182 +0,0 @@
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;
}
}
@@ -1,27 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,14 +0,0 @@
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();
}
}
@@ -1,30 +0,0 @@
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;
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,26 +0,0 @@
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();
}
}
@@ -1,29 +0,0 @@
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();
}
}
@@ -1,58 +0,0 @@
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;
}
}
}
@@ -1,83 +0,0 @@
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;
}
}
@@ -1,91 +0,0 @@
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;
}
}
@@ -1,61 +0,0 @@
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;
}
}
@@ -1,80 +0,0 @@
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;
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.validation;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class IPListAutoUpdater {
private final IPListUpdater ipListUpdater;
public IPListAutoUpdater(IPListUpdater ipListUpdater) {
this.ipListUpdater = ipListUpdater;
}
public void init() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
ipListUpdater.update();
}
}, TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(12));
}
}
@@ -0,0 +1,80 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldIPListUpdaterException;
import net.tcpshield.tcpshield.validation.cidr.CIDRMatcher;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class IPListUpdater {
private final TCPShieldConfig config;
private final IPValidation ipValidation;
private final Logger logger;
public IPListUpdater(TCPShieldConfig config, IPValidation ipValidation, Logger logger) {
this.config = config;
this.ipValidation = ipValidation;
this.logger = logger;
}
public void update() {
try {
String ipList = getIPList();
File ipWhitelistFolder = config.getIPWhitelistFolder();
File tcpShieldIPList = new File(ipWhitelistFolder, "tcpshield-ips.list");
if (tcpShieldIPList.createNewFile()) {
logger.info("Fetched updated IP Whitelist from https://tcpshield.com/v4/");
}
try (FileWriter writer = new FileWriter(tcpShieldIPList, false)) {
writer.write(ipList);
}
ipValidation.readIPWhitelist();
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException(e);
}
}
public String getIPList() throws IOException {
URL url = new URL("https://tcpshield.com/v4/");
HttpsURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
int responseCode = connection.getResponseCode();
if (responseCode != 200)
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ did not return status code 200: " + responseCode);
String data = bufferedReader.lines()
.filter(str -> !str.isEmpty())
.peek(cidr -> { // checks for validity of each CIDR entry
try {
new CIDRMatcher(cidr);
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ returned a faulty CIDR entry: " + cidr, e);
}
})
.collect(Collectors.joining("\n"));
if (data.isEmpty())
throw new TCPShieldIPListUpdaterException("Response from https://tcpshield.com/v4/ empty");
return data;
}
} finally {
if (connection != null)
connection.disconnect();
}
}
}
@@ -0,0 +1,69 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.cidr.CIDRChecker;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.logging.Logger;
public class IPValidation {
private final TCPShieldConfig config;
private final IPListUpdater ipListUpdater;
private final CIDRChecker cidrChecker;
private List<String> ipWhitelist = new ArrayList<>();
public IPValidation(TCPShieldConfig config, Logger logger) {
this.config = config;
this.ipListUpdater = new IPListUpdater(config, this, logger);
new IPListAutoUpdater(ipListUpdater).init();
checkIPList();
try {
readIPWhitelist();
this.cidrChecker = new CIDRChecker(ipWhitelist);
} catch (IOException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean validateIP(InetAddress inetAddress) {
return cidrChecker.check(inetAddress);
}
private List<String> getIPWhitelist() throws IOException {
List<String> ipWhitelist = new ArrayList<>();
for (File file : Objects.requireNonNull(config.getIPWhitelistFolder().listFiles())) {
if (file.isDirectory()) continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
ipWhitelist.add(cidrEntry);
}
}
}
return ipWhitelist;
}
public void readIPWhitelist() throws IOException {
this.ipWhitelist = getIPWhitelist();
}
private void checkIPList() {
if (config.getIPWhitelistFolder().mkdirs()) {
ipListUpdater.update();
}
}
}
@@ -0,0 +1,38 @@
package net.tcpshield.tcpshield.validation.cidr;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CIDRChecker {
private final List<CIDRMatcher> cidrMatchers = new ArrayList<>();
private final Set<String> cache = new HashSet<>(); // quick cache for already verified IPs; HashSet is O(1) on HashSet#contains() operations
public CIDRChecker(List<String> cidrList) {
for (String cidr : cidrList) {
CIDRMatcher cidrMatcher = new CIDRMatcher(cidr);
cidrMatchers.add(cidrMatcher);
}
}
public boolean check(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip)) return true;
if (!checkUncached(inetAddress)) return false;
cache.add(ip);
return true;
}
private boolean checkUncached(InetAddress inetAddress) {
for (CIDRMatcher cidrMatcher : cidrMatchers) {
if (cidrMatcher.matches(inetAddress)) return true;
}
return false;
}
}
@@ -0,0 +1,59 @@
package net.tcpshield.tcpshield.validation.cidr;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CIDRMatcher {
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
public CIDRMatcher(String ipAddress) {
String[] split = ipAddress.split("/");
String parsedIPAddress;
if (split.length >= 2) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = ipAddress;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean matches(InetAddress inetAddress) {
if (!cidrAddress.getClass().equals(inetAddress.getClass())) return false; // check if IP is IPv4 or IPv6
if (simpleCIDR) return inetAddress.equals(cidrAddress); // check for equality if it's a simple CIDR
byte[] inetAddressBytes = inetAddress.getAddress();
byte[] requiredAddressBytes = cidrAddress.getAddress();
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
for (int i = 0; i < maskBytes; i++) {
if (inetAddressBytes[i] != requiredAddressBytes[i]) return false;
}
if (finalByte != 0)
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
return true;
}
}
@@ -6,81 +6,34 @@ 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.HandshakePacketHandler;
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 net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
import java.nio.file.Path;
import java.util.logging.Logger;
/**
* The entry point for Velocity servers
*/
@Plugin(
id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
@Plugin(id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity implements TCPShieldPlugin {
public class TCPShieldVelocity {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
@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) {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.logger, new VelocityConfigImpl(dataFolder.toFile()));
@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;
}
}
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> server.getEventManager().register(this, new VelocityHandshakePacketHandler(handshakePacketHandler)));
}
}
@@ -1,101 +0,0 @@
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", "enable-geyser-support", "pre-login-event");
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");
this.velocityHandlePreLoginEvent = loadedToml.getBoolean("pre-login-event");
} 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);
}
}
}
@@ -0,0 +1,44 @@
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.VelocityPacketImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPlayerImpl;
public class VelocityHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public VelocityHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@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);
}
}
@@ -1,83 +0,0 @@
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;
private static Class<?> CONNECTED_PLAYER_CONNECTION_CLASS;
static {
try {
CONNECTED_PLAYER_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.ConnectedPlayer");
} catch (Exception e) {
// ignore for old velocity versions
}
}
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) {
if (!this.plugin.getConfigProvider().handlePreLoginEvent()) {
return;
}
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();
if (connection.getClass() == CONNECTED_PLAYER_CONNECTION_CLASS) {
// new ServerData (0x42) packet on connect, we don't care about it
return;
}
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);
}
}
}
@@ -1,117 +0,0 @@
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");
Field hostnameField;
try {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.HandshakePacket"), "serverAddress");
} catch (Exception e) {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
}
HOSTNAME_FIELD = hostnameField;
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;
}
}
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more