diff --git a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeDataAddon.java b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeDataAddon.java index 7bbc75f6..56d613bc 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeDataAddon.java +++ b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeDataAddon.java @@ -83,7 +83,6 @@ public class BungeeDataAddon implements InjectorAddon { public void onChannelClosed(Channel channel) { FloodgatePlayer player = channel.attr(playerAttribute).get(); if (player != null && api.removePlayer(player)) { - api.removeEncryptedData(player.getCorrectUniqueId()); logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername()); } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeProxyDataHandler.java b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeProxyDataHandler.java index 9d12ca92..88bd9e28 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeProxyDataHandler.java +++ b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeProxyDataHandler.java @@ -45,7 +45,7 @@ import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler.ResultType; +import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.ReflectionUtils; @SuppressWarnings("ConstantConditions") @@ -115,14 +115,20 @@ public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter { return; } - if (result.getResultType() == ResultType.EXCEPTION) { - ctx.channel().attr(kickMessageAttribute).set( - config.getDisconnect().getInvalidKey()); - } - - if (result.getResultType() == ResultType.INVALID_DATA_LENGTH) { - ctx.channel().attr(kickMessageAttribute) - .set(config.getDisconnect().getInvalidArgumentsLength()); + switch (result.getResultType()) { + case EXCEPTION: + ctx.channel().attr(kickMessageAttribute).set( + config.getDisconnect().getInvalidKey()); + break; + case INVALID_DATA_LENGTH: + ctx.channel().attr(kickMessageAttribute) + .set(config.getDisconnect().getInvalidArgumentsLength()); + break; + case TIMESTAMP_DENIED: + ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE); + break; + default: + break; } } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeServerDataHandler.java b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeServerDataHandler.java index c50f7ffb..af9d2771 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeServerDataHandler.java +++ b/bungee/src/main/java/org/geysermc/floodgate/addon/data/BungeeServerDataHandler.java @@ -42,6 +42,8 @@ import net.md_5.bungee.protocol.packet.Handshake; import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.player.FloodgatePlayerImpl; +import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.ReflectionUtils; @SuppressWarnings("ConstantConditions") @@ -92,7 +94,8 @@ public class BungeeServerDataHandler extends MessageToMessageEncoder { FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get(); if (player != null) { - String encryptedData = api.getEncryptedData(player.getCorrectUniqueId()); + BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData(); + String encryptedData = api.createEncryptedDataString(data); Handshake handshake = (Handshake) packet; String address = handshake.getHost(); diff --git a/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java b/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java index 4d12d6e0..ced575a0 100644 --- a/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java +++ b/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java @@ -26,15 +26,11 @@ package org.geysermc.floodgate.api; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.util.BedrockData; public final class ProxyFloodgateApi extends SimpleFloodgateApi { - private final Map encryptedData = new HashMap<>(); private final FloodgateCipher cipher; public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) { @@ -42,25 +38,16 @@ public final class ProxyFloodgateApi extends SimpleFloodgateApi { this.cipher = cipher; } - public String getEncryptedData(UUID uuid) { - return encryptedData.get(uuid); - } - - public void addEncryptedData(UUID uuid, String encryptedData) { - this.encryptedData.put(uuid, encryptedData); // just override already existing data I guess - } - - public void removeEncryptedData(UUID uuid) { - encryptedData.remove(uuid); - } - - public void updateEncryptedData(UUID uuid, BedrockData bedrockData) { + public byte[] createEncryptedData(BedrockData bedrockData) { try { - byte[] encryptedData = cipher.encryptFromString(bedrockData.toString()); - addEncryptedData(uuid, new String(encryptedData, StandardCharsets.UTF_8)); + return cipher.encryptFromString(bedrockData.toString()); } catch (Exception exception) { - throw new IllegalStateException("We failed to update the BedrockData, " + - "but we can't continue without the updated version!", exception); + throw new IllegalStateException("We failed to create the encrypted data, " + + "but creating encrypted data is mandatory!", exception); } } + + public String createEncryptedDataString(BedrockData bedrockData) { + return new String(createEncryptedData(bedrockData), StandardCharsets.UTF_8); + } } diff --git a/common/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java b/common/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java index 83d95d35..852d98a9 100644 --- a/common/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java +++ b/common/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java @@ -28,11 +28,14 @@ package org.geysermc.floodgate.player; import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH; import com.google.common.base.Charsets; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import io.netty.channel.Channel; import io.netty.util.AttributeKey; import java.net.InetSocketAddress; import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -55,6 +58,12 @@ import org.geysermc.floodgate.util.Utils; @RequiredArgsConstructor public final class FloodgateHandshakeHandler { + private final Cache handleCache = + CacheBuilder.newBuilder() + .maximumSize(500) + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + private final HandshakeHandlersImpl handshakeHandlers; private final SimpleFloodgateApi api; private final FloodgateCipher cipher; @@ -97,6 +106,29 @@ public final class FloodgateHandshakeHandler { channel, bedrockData, hostname); } + // timestamp checks + + long timeDifference = System.currentTimeMillis() - bedrockData.getTimestamp(); + if (timeDifference > 6000 || timeDifference < 0) { + return callHandlerAndReturnResult( + ResultType.TIMESTAMP_DENIED, + channel, bedrockData, hostname); + } + + Long cachedTimestamp = handleCache.getIfPresent(bedrockData.getXuid()); + if (cachedTimestamp != null) { + // the cached timestamp is newer than the gotten timestamp + // you also can't reuse the data (the timestamp is there to prevent that as well) + if (cachedTimestamp >= bedrockData.getTimestamp()) { + return callHandlerAndReturnResult( + ResultType.TIMESTAMP_DENIED, + channel, bedrockData, hostname); + } + } + + handleCache.put(bedrockData.getXuid(), bedrockData.getTimestamp()); + + LinkedPlayer linkedPlayer; // we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one) @@ -212,6 +244,7 @@ public final class FloodgateHandshakeHandler { EXCEPTION, NOT_FLOODGATE_DATA, INVALID_DATA_LENGTH, + TIMESTAMP_DENIED, SUCCESS } diff --git a/common/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java b/common/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java index 59859867..6882674c 100644 --- a/common/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java +++ b/common/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java @@ -34,9 +34,9 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; 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.SimpleFloodgateApi; import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.player.FloodgatePlayer; @@ -84,7 +84,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer { BedrockData data, HandshakeData handshakeData) { - FloodgateApi api = FloodgateApi.getInstance(); + SimpleFloodgateApi api = InstanceHolder.castApi(SimpleFloodgateApi.class); UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); @@ -94,18 +94,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer { LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer(); - FloodgatePlayerImpl player = new FloodgatePlayerImpl( + return new FloodgatePlayerImpl( data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(), javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile, inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi, linkedPlayer, data.getSubscribeId(), data.getVerifyCode()); - - // fromProxy and linked player might have to be changed - if (api instanceof ProxyFloodgateApi) { - InstanceHolder.castApi(ProxyFloodgateApi.class) - .updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData()); - } - return player; } /** diff --git a/common/src/main/java/org/geysermc/floodgate/util/Constants.java b/common/src/main/java/org/geysermc/floodgate/util/Constants.java index f2988c63..10edf4f1 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Constants.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Constants.java @@ -31,4 +31,8 @@ public final class Constants { public static final String WEBSOCKET_URL = "wss://api.geysermc.org/ws"; public static final boolean DEBUG_MODE = true; + + public static final String TIMESTAMP_DENIED_MESSAGE = + "Something isn't right with this data." + + " Try logging in again or contact a server administrator if the issue persists."; } 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 5ceb8171..ad666fcb 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 @@ -51,6 +51,7 @@ import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.SpigotUtils; @@ -188,9 +189,15 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter { return; } + //todo use kickMessageAttribute and let this be common logic + switch (result.getResultType()) { case SUCCESS: break; + case EXCEPTION: + logger.info(config.getDisconnect().getInvalidKey()); + ctx.close(); + return; case INVALID_DATA_LENGTH: int dataLength = result.getBedrockData().getDataLength(); logger.info( @@ -199,6 +206,10 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter { ); ctx.close(); return; + case TIMESTAMP_DENIED: + logger.info(Constants.TIMESTAMP_DENIED_MESSAGE); + ctx.close(); + return; default: // only continue when SUCCESS return; } diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java index 0c60f88f..1603c712 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java @@ -84,7 +84,6 @@ public final class VelocityDataAddon implements InjectorAddon { public void onChannelClosed(Channel channel) { FloodgatePlayer player = channel.attr(playerAttribute).get(); if (player != null && api.removePlayer(player)) { - api.removeEncryptedData(player.getCorrectUniqueId()); logger.translatedInfo("floodgate.ingame.disconnect_name", player.getUsername()); } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java index fe051e00..9d9842cf 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java @@ -44,6 +44,7 @@ import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.util.Constants; @RequiredArgsConstructor public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter { @@ -112,6 +113,9 @@ public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter ctx.channel().attr(kickMessageAttribute) .set(config.getDisconnect().getInvalidArgumentsLength()); return; + case TIMESTAMP_DENIED: + ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE); + return; default: // only continue when SUCCESS return; } diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java index 98b0a721..b4b1f8da 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java @@ -25,7 +25,6 @@ package org.geysermc.floodgate.addon.data; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke; import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue; @@ -46,6 +45,8 @@ import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.player.FloodgatePlayerImpl; +import org.geysermc.floodgate.util.BedrockData; @SuppressWarnings("ConstantConditions") @RequiredArgsConstructor @@ -116,8 +117,8 @@ public final class VelocityServerDataHandler extends MessageToMessageEncoder