From a3e6c0916673e4206dab71d161344de63da8af4f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 18 Jan 2022 16:57:37 +0100 Subject: [PATCH] Paper added an option to disable username validation for a connection Co-authored-by: Camotoy <20743703+camotoy@users.noreply.github.com> --- .../org/geysermc/floodgate/SpigotPlugin.java | 6 ++ .../addon/data/SpigotDataHandler.java | 33 ++++++--- .../geysermc/floodgate/util/ClassNames.java | 11 +++ .../geysermc/floodgate/util/ProxyUtils.java | 69 +++++++++++++++++++ .../util/SpigotHandshakeHandler.java | 56 +++++++++++++++ 5 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java create mode 100644 spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java index 1100262e..66bb38e7 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java +++ b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate; import com.google.inject.Guice; import com.google.inject.Injector; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.module.PluginMessageModule; import org.geysermc.floodgate.module.ServerCommonModule; @@ -35,6 +36,7 @@ import org.geysermc.floodgate.module.SpigotAddonModule; import org.geysermc.floodgate.module.SpigotCommandModule; import org.geysermc.floodgate.module.SpigotListenerModule; import org.geysermc.floodgate.module.SpigotPlatformModule; +import org.geysermc.floodgate.util.SpigotHandshakeHandler; import org.geysermc.floodgate.util.SpigotProtocolSupportHandler; import org.geysermc.floodgate.util.SpigotProtocolSupportListener; @@ -66,6 +68,10 @@ public final class SpigotPlugin extends JavaPlugin { new PluginMessageModule() ); + //todo add proper support for disabling things on shutdown and enabling this on enable + injector.getInstance(HandshakeHandlers.class) + .addHandshakeHandler(injector.getInstance(SpigotHandshakeHandler.class)); + // add ProtocolSupport support (hack) if (getServer().getPluginManager().getPlugin("ProtocolSupport") != null) { injector.getInstance(SpigotProtocolSupportHandler.class); diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java index 27f87cfb..6298f0ad 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java @@ -37,10 +37,12 @@ import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import org.geysermc.floodgate.util.ClassNames; +import org.geysermc.floodgate.util.ProxyUtils; public final class SpigotDataHandler extends CommonDataHandler { private Object networkManager; private FloodgatePlayer player; + private boolean proxyData; public SpigotDataHandler( FloodgateHandshakeHandler handshakeHandler, @@ -72,13 +74,18 @@ public final class SpigotDataHandler extends CommonDataHandler { return true; } - // Paper now has username validation, so we have to help Paper to bypass the check. - // The username is only validated in the login start packet, and that packet doesn't reach - // the server handler when we call our own login cycle. + // the server will do all the work if BungeeCord mode is enabled, + // otherwise we have to help the server. + proxyData = ProxyUtils.isProxyData(); - // After internal discussion, we've decided to require assistance for all Spigot platforms, - // in case Spigot decides to add username validation as well. - return false; + if (!proxyData) { + // Use a spoofedUUID for initUUID (just like Bungeecord) + setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId()); + } + + // we can only remove the handler if the data is proxy data and username validation doesn't + // exist. Otherwise, we need to wait and disable it. + return proxyData && ClassNames.PAPER_DISABLE_USERNAME_VALIDATION == null; } @Override @@ -131,16 +138,22 @@ public final class SpigotDataHandler extends CommonDataHandler { return true; } + if (ClassNames.PAPER_DISABLE_USERNAME_VALIDATION != null) { + // ensure that Paper will not be checking + setValue(packetListener, ClassNames.PAPER_DISABLE_USERNAME_VALIDATION, true); + if (proxyData) { + // the server will handle the rest if we have proxy data + ctx.pipeline().remove(this); + return false; + } + } + // set the player his GameProfile, we can't change the username without this GameProfile gameProfile = new GameProfile( player.getCorrectUniqueId(), player.getCorrectUsername() ); setValue(packetListener, ClassNames.LOGIN_PROFILE, gameProfile); - //todo we can remove this line + the initUUID line, but then if a skin has been sent - // from a proxy using bungee data, we won't be able to show it - setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId()); - // we have to fake the offline player (login) cycle // just like on Spigot: diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java index 3285b8b5..67f7edfb 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java @@ -25,6 +25,7 @@ package org.geysermc.floodgate.util; +import static org.geysermc.floodgate.util.ReflectionUtils.getField; import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType; import static org.geysermc.floodgate.util.ReflectionUtils.getMethod; @@ -37,6 +38,7 @@ import java.lang.reflect.Method; import java.net.SocketAddress; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("PMD.SystemPrintln") public class ClassNames { @@ -56,6 +58,8 @@ public class ClassNames { public static final Field HANDSHAKE_HOST; public static final Field LOGIN_PROFILE; public static final Field PACKET_LISTENER; + @Nullable + public static final Field PAPER_DISABLE_USERNAME_VALIDATION; public static final Method GET_PROFILE_METHOD; public static final Method LOGIN_DISCONNECT; @@ -157,6 +161,13 @@ public class ClassNames { FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); + + PAPER_DISABLE_USERNAME_VALIDATION = getField(LOGIN_LISTENER, + "iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation"); + if (Constants.DEBUG_MODE) { + System.out.println("Paper disable username validation field exists? " + + (PAPER_DISABLE_USERNAME_VALIDATION != null)); + } } private static Class getClassOrFallBack(String className, String fallbackName) { diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java b/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java new file mode 100644 index 00000000..ff0fc5b3 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.util; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.geysermc.floodgate.util.ReflectionUtils.getField; + +import java.lang.reflect.Field; + +@SuppressWarnings("ConstantConditions") +public final class ProxyUtils { + private static final Field IS_BUNGEE_DATA; + private static final Field IS_MODERN_FORWARDING; + + static { + Class spigotConfig = ReflectionUtils.getClass("org.spigotmc.SpigotConfig"); + IS_BUNGEE_DATA = getField(spigotConfig, "bungee"); + checkNotNull(IS_BUNGEE_DATA, "bungee field cannot be null. Are you using CraftBukkit?"); + + Field velocitySupport; + try { + Class paperConfig = Class.forName("com.destroystokyo.paper.PaperConfig"); + velocitySupport = getField(paperConfig, "velocitySupport"); + } catch (ClassNotFoundException e) { + // We're not on a platform that has modern forwarding + velocitySupport = null; // NOPMD - there's really not a better way around this unless you want to use an optional + } + IS_MODERN_FORWARDING = velocitySupport; + } + + public static boolean isProxyData() { + return isBungeeData() || isVelocitySupport(); + } + + private static boolean isBungeeData() { + return ReflectionUtils.getCastedValue(null, IS_BUNGEE_DATA); + } + + private static boolean isVelocitySupport() { + if (IS_MODERN_FORWARDING == null) { + return false; + } + + return ReflectionUtils.getCastedValue(null, IS_MODERN_FORWARDING); + } +} \ No newline at end of file diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java new file mode 100644 index 00000000..f14d569c --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.util; + +import com.google.inject.Inject; +import java.util.UUID; +import org.geysermc.floodgate.api.handshake.HandshakeData; +import org.geysermc.floodgate.api.handshake.HandshakeHandler; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public class SpigotHandshakeHandler implements HandshakeHandler { + @Inject + private FloodgateLogger logger; + + @Override + public void handle(HandshakeData data) { + BedrockData bedrockData = data.getBedrockData(); + UUID correctUuid = data.getCorrectUniqueId(); + + // replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid + String[] split = data.getHostname().split("\0"); + if (split.length >= 3) { + if (logger.isDebug()) { + logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'", + split[1], bedrockData.getIp(), split[2], correctUuid.toString() + ); + } + split[1] = bedrockData.getIp(); + split[2] = correctUuid.toString(); + } + data.setHostname(String.join("\0", split)); + } +}