1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-06 15:42:03 +00:00

Developers can now change some Floodgate related data during handshake

This commit is contained in:
Tim203
2021-01-12 20:49:24 +01:00
parent dd4a12a5e5
commit 9b0cbd5cdd
21 changed files with 612 additions and 124 deletions

View File

@@ -34,6 +34,7 @@ import java.nio.file.Path;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
@@ -64,8 +65,12 @@ public class FloodgatePlatform {
}
@Inject
public void init(@Named("dataDirectory") Path dataDirectory, ConfigLoader configLoader,
FloodgateConfigHolder configHolder) {
public void init(
@Named("dataDirectory") Path dataDirectory,
ConfigLoader configLoader,
FloodgateConfigHolder configHolder,
HandshakeHandlers handshakeHandlers
) {
if (!Files.isDirectory(dataDirectory)) {
try {
@@ -85,7 +90,7 @@ public class FloodgatePlatform {
guice = guice.createChildInjector(new ConfigLoadedModule(config));
PlayerLink link = guice.getInstance(PlayerLinkLoader.class).load();
InstanceHolder.setInstance(api, link, this.injector, KEY);
InstanceHolder.setInstance(api, link, this.injector, handshakeHandlers, KEY);
}
public boolean enable(Module... postInitializeModules) {

View File

@@ -0,0 +1,50 @@
/*
* 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.addon.data;
import io.netty.channel.Channel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public class HandshakeDataImpl implements HandshakeData {
private final Channel channel;
private final boolean floodgatePlayer;
private final BedrockData bedrockData;
@Setter private LinkedPlayer linkedPlayer;
@Setter private RawSkin rawSkin;
@Setter private String hostname;
@Setter private String disconnectReason;
}

View File

@@ -0,0 +1,77 @@
/*
* 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.addon.data;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import java.util.Random;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.handshake.HandshakeHandler;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
public class HandshakeHandlersImpl implements HandshakeHandlers {
private final Random random = new Random();
private final IntObjectMap<HandshakeHandler> handshakeHandlers = new IntObjectHashMap<>();
@Override
public int addHandshakeHandler(HandshakeHandler handshakeHandler) {
if (handshakeHandler == null) {
return -1;
}
int key;
do {
key = random.nextInt(Integer.MAX_VALUE);
} while (handshakeHandlers.putIfAbsent(key, handshakeHandler) != null);
return key;
}
@Override
public void removeHandshakeHandler(int handshakeHandlerId) {
// key is always positive
if (handshakeHandlerId <= 0) {
return;
}
handshakeHandlers.remove(handshakeHandlerId);
}
@Override
public void removeHandshakeHandler(Class<? extends HandshakeHandler> handshakeHandler) {
if (HandshakeHandler.class == handshakeHandler) {
return;
}
handshakeHandlers.values().removeIf(handler -> handler.getClass() == handshakeHandler);
}
public void callHandshakeHandlers(HandshakeData handshakeData) {
for (HandshakeHandler handshakeHandler : handshakeHandlers.values()) {
handshakeHandler.handle(handshakeData);
}
}
}

View File

@@ -35,6 +35,7 @@ import org.geysermc.cumulus.util.FormBuilder;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor
public class SimpleFloodgateApi implements FloodgateApi {
@@ -66,7 +67,7 @@ public class SimpleFloodgateApi implements FloodgateApi {
@Override
public UUID createJavaPlayerId(long xuid) {
return new UUID(0, xuid);
return Utils.getJavaUuid(xuid);
}
@Override

View File

@@ -26,52 +26,50 @@
package org.geysermc.floodgate.link;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.Utils;
public class GlobalPlayerLinking extends CommonPlayerLink {
private static final String GET_BEDROCK_LINK = "http://localhost:4000/api/link/bedrock?xuid=";
@Inject private FloodgateApi api;
@Override
public void load() {}
public void load() {
}
@Override
public CompletableFuture<LinkedPlayer> getLinkedPlayer(UUID bedrockId) {
return CompletableFuture.supplyAsync(
() -> {
HttpResponse response =
HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
() -> {
HttpResponse response =
HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
// both on code != 200 and fails with 200 'success' will be false
if (!response.getResponse().get("success").getAsBoolean()) {
getLogger().error(
"Failed to request link for {}: {}",
bedrockId.getLeastSignificantBits(),
response.getResponse().get("message").getAsString());
return null;
}
// both on code != 200 and fails with 200 'success' will be false
if (!response.getResponse().get("success").getAsBoolean()) {
getLogger().error(
"Failed to request link for {}: {}",
bedrockId.getLeastSignificantBits(),
response.getResponse().get("message").getAsString());
return null;
}
JsonObject data = response.getResponse().getAsJsonObject("data");
JsonObject data = response.getResponse().getAsJsonObject("data");
// no link if data is empty
if (data.size() == 0) {
return null;
}
// no link if data is empty
if (data.size() == 0) {
return null;
}
return LinkedPlayer.of(
data.get("javaName").getAsString(),
UUID.fromString(data.get("javaId").getAsString()),
api.createJavaPlayerId(data.get("bedrockId").getAsLong()));
},
getExecutorService());
return LinkedPlayer.of(
data.get("javaName").getAsString(),
UUID.fromString(data.get("javaId").getAsString()),
Utils.getJavaUuid(data.get("bedrockId").getAsLong()));
},
getExecutorService());
}
@Override
@@ -117,8 +115,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
public CompletableFuture<?> createLinkRequest(
UUID javaId,
String javaUsername,
String bedrockUsername
) {
String bedrockUsername) {
return null;
}
@@ -127,8 +124,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
UUID bedrockId,
String javaUsername,
String bedrockUsername,
String code
) {
String code) {
return null;
}
}

View File

@@ -86,4 +86,9 @@ public final class JavaUtilFloodgateLogger implements FloodgateLogger {
logger.setLevel(originLevel);
}
}
@Override
public boolean isDebug() {
return logger.getLevel() == Level.ALL;
}
}

View File

@@ -32,8 +32,10 @@ import com.google.inject.name.Named;
import io.netty.util.AttributeKey;
import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -60,6 +62,7 @@ public class CommonModule extends AbstractModule {
protected void configure() {
bind(FloodgateApi.class).to(SimpleFloodgateApi.class);
bind(PlatformInjector.class).to(CommonPlatformInjector.class);
bind(HandshakeHandlers.class).to(HandshakeHandlersImpl.class);
}
@Provides
@@ -120,15 +123,30 @@ public class CommonModule extends AbstractModule {
return new LanguageManager(configHolder, logger);
}
@Provides
@Singleton
public HandshakeHandlersImpl handshakeHandlers() {
return new HandshakeHandlersImpl();
}
@Provides
@Singleton
public HandshakeHandler handshakeHandler(
HandshakeHandlersImpl handshakeHandlers,
SimpleFloodgateApi api,
FloodgateCipher cipher,
FloodgateConfigHolder configHolder,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger
) {
return new HandshakeHandler(api, cipher, configHolder, playerAttribute);
return new HandshakeHandler(
handshakeHandlers,
api,
cipher,
configHolder,
playerAttribute,
logger
);
}
@Provides

View File

@@ -37,6 +37,7 @@ import lombok.Setter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
@@ -49,6 +50,7 @@ import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile;
import org.geysermc.floodgate.util.Utils;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -79,8 +81,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
*/
@Setter private boolean login = true;
protected static FloodgatePlayerImpl from(BedrockData data, RawSkin skin,
FloodgateConfigHolder configHolder) {
protected static FloodgatePlayerImpl from(
BedrockData data,
HandshakeData handshakeData,
FloodgateConfigHolder configHolder) {
FloodgateApi api = FloodgateApi.getInstance();
FloodgateConfig config = configHolder.get();
@@ -91,22 +96,14 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
javaUsername = javaUsername.replaceAll(" ", "_");
}
UUID javaUniqueId = api.createJavaPlayerId(Long.parseLong(data.getXuid()));
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
DeviceOs deviceOs = DeviceOs.getById(data.getDeviceOs());
UiProfile uiProfile = UiProfile.getById(data.getUiProfile());
InputMode inputMode = InputMode.getById(data.getInputMode());
LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (data.hasPlayerLink()) {
linkedPlayer = data.getLinkedPlayer();
} else {
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(api.getPlayerLink(), javaUniqueId);
}
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
RawSkin skin = handshakeData.getRawSkin();
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
data.getVersion(), data.getUsername(), javaUsername, javaUniqueId, data.getXuid(),
@@ -149,8 +146,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
* isn't enabled
* @see #fetchLinkedPlayer(PlayerLink, UUID) for the sync version
*/
public static CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(PlayerLink link,
UUID javaUniqueId) {
public static CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(
PlayerLink link,
UUID javaUniqueId) {
return link.isEnabled() ?
link.getLinkedPlayer(javaUniqueId) :
CompletableFuture.completedFuture(null);

View File

@@ -31,12 +31,18 @@ import com.google.common.base.Charsets;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.addon.data.HandshakeDataImpl;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
@@ -45,29 +51,39 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.Base64Utils;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.InvalidFormatException;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor
public final class HandshakeHandler {
private final HandshakeHandlersImpl handshakeHandlers;
private final SimpleFloodgateApi api;
private final FloodgateCipher cipher;
private final FloodgateConfigHolder configHolder;
private final AttributeKey<FloodgatePlayer> playerAttribute;
private final FloodgateLogger logger;
public HandshakeResult handle(Channel channel, @NonNull String handshakeData) {
public HandshakeResult handle(Channel channel, @NonNull String hostname) {
try {
String[] dataArray = handshakeData.split("\0");
String[] split = hostname.split("\0");
String data = null;
for (String value : dataArray) {
if (FloodgateCipher.hasHeader(value)) {
StringBuilder hostnameBuilder = new StringBuilder();
for (String value : split) {
if (data == null && FloodgateCipher.hasHeader(value)) {
data = value;
break;
continue;
}
hostnameBuilder.append(value);
}
// hostname now doesn't have Floodgate data anymore if it had
hostname = hostnameBuilder.toString();
if (data == null) {
return ResultType.NOT_FLOODGATE_DATA.getCachedResult();
return callHandlerAndReturnResult(
ResultType.NOT_FLOODGATE_DATA,
channel, null, hostname);
}
// header + base64 iv - 0x21 - encrypted data - 0x21 - RawSkin
@@ -91,7 +107,9 @@ public final class HandshakeHandler {
BedrockData bedrockData = BedrockData.fromString(decrypted);
if (bedrockData.getDataLength() != EXPECTED_LENGTH) {
return ResultType.INVALID_DATA_LENGTH.getCachedResult();
return callHandlerAndReturnResult(
ResultType.INVALID_DATA_LENGTH,
channel, bedrockData, hostname);
}
RawSkin rawSkin = null;
@@ -102,7 +120,29 @@ public final class HandshakeHandler {
rawSkin = RawSkin.decode(rawSkinData);
}
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, rawSkin, configHolder);
LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (bedrockData.hasPlayerLink()) {
linkedPlayer = bedrockData.getLinkedPlayer();
} else {
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(Utils.getJavaUuid(bedrockData.getXuid()));
}
HandshakeData handshakeData = new HandshakeDataImpl(channel, true, bedrockData.clone(),
linkedPlayer != null ? linkedPlayer.clone() : null, rawSkin, hostname, null);
handshakeHandlers.callHandshakeHandlers(handshakeData);
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
handshakeData.setHostname(correctHostname(
handshakeData.getHostname(), bedrockData, javaUuid
));
FloodgatePlayer player =
FloodgatePlayerImpl.from(bedrockData, handshakeData, configHolder);
api.addPlayer(player.getJavaUniqueId(), player);
channel.attr(playerAttribute).set(player);
@@ -111,19 +151,76 @@ public final class HandshakeHandler {
InetSocketAddress socketAddress = new InetSocketAddress(bedrockData.getIp(), port);
player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress);
return new HandshakeResult(ResultType.SUCCESS, dataArray, bedrockData, player);
return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player);
} catch (InvalidFormatException formatException) {
// only header exceptions should return 'not floodgate data',
// all the other format exceptions are because of invalid/tempered Floodgate data
if (formatException.isHeader()) {
return ResultType.NOT_FLOODGATE_DATA.getCachedResult();
return callHandlerAndReturnResult(
ResultType.NOT_FLOODGATE_DATA,
channel, null, hostname);
}
formatException.printStackTrace();
return ResultType.EXCEPTION.getCachedResult();
return callHandlerAndReturnResult(
ResultType.EXCEPTION,
channel, null, hostname);
} catch (Exception exception) {
exception.printStackTrace();
return ResultType.EXCEPTION.getCachedResult();
return callHandlerAndReturnResult(
ResultType.EXCEPTION,
channel, null, hostname);
}
}
private HandshakeResult callHandlerAndReturnResult(
ResultType resultType,
Channel channel,
BedrockData bedrockData,
String hostname
) {
HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null,
bedrockData, null, null, hostname, null);
handshakeHandlers.callHandshakeHandlers(handshakeData);
if (bedrockData != null) {
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
handshakeData.setHostname(correctHostname(
handshakeData.getHostname(), bedrockData, javaUuid
));
}
return new HandshakeResult(resultType, handshakeData, bedrockData, null);
}
private String correctHostname(String hostname, BedrockData data, UUID correctUuid) {
// replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid
String[] split = hostname.split("\0");
if (split.length >= 3) {
if (logger.isDebug()) {
logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'",
split[1], data.getIp(), split[2], correctUuid.toString());
}
split[1] = data.getIp();
split[2] = correctUuid.toString();
}
return String.join("\0", split);
}
private LinkedPlayer fetchLinkedPlayer(UUID javaUniqueId) {
if (!api.getPlayerLink().isEnabled()) {
return null;
}
try {
return api.getPlayerLink().getLinkedPlayer(javaUniqueId).get();
} catch (InterruptedException | ExecutionException exception) {
exception.printStackTrace();
return null;
}
}
@@ -131,21 +228,14 @@ public final class HandshakeHandler {
EXCEPTION,
NOT_FLOODGATE_DATA,
INVALID_DATA_LENGTH,
SUCCESS;
@Getter
private final HandshakeResult cachedResult;
ResultType() {
cachedResult = new HandshakeResult(this, null, null, null);
}
SUCCESS
}
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class HandshakeResult {
private final ResultType resultType;
private final String[] handshakeData;
private final HandshakeData handshakeData;
private final BedrockData bedrockData;
private final FloodgatePlayer floodgatePlayer;
}

View File

@@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public class Utils {
/**
@@ -62,8 +63,9 @@ public class Utils {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
if (line == null) {
break;
}
result.add(line);
}
return result;
@@ -79,4 +81,12 @@ public class Utils {
public static String getLocale(Locale locale) {
return locale.getLanguage() + "_" + locale.getCountry();
}
public static UUID getJavaUuid(long xuid) {
return new UUID(0, xuid);
}
public static UUID getJavaUuid(String xuid) {
return getJavaUuid(Long.parseLong(xuid));
}
}