diff --git a/src/main/java/ru/nanit/limbo/LimboConstants.java b/src/main/java/ru/nanit/limbo/LimboConstants.java new file mode 100644 index 0000000..3e1653f --- /dev/null +++ b/src/main/java/ru/nanit/limbo/LimboConstants.java @@ -0,0 +1,9 @@ +package ru.nanit.limbo; + +public final class LimboConstants { + + public static final String VELOCITY_INFO_CHANNEL = "velocity:player_info"; + + private LimboConstants(){} + +} diff --git a/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java b/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java index 2208773..9fe0096 100644 --- a/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java +++ b/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java @@ -35,7 +35,7 @@ public final class LimboConfig { public void load() throws Exception { Configuration conf = YamlConfiguration.builder() - .source(ConfigSources.resource("/limbo.yml", this).copyTo(root)) + .source(ConfigSources.resource("/settings.yml", this).copyTo(root)) .build(); conf.reload(); diff --git a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java index 6c7507f..3983130 100644 --- a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java +++ b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java @@ -1,9 +1,11 @@ package ru.nanit.limbo.connection; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import ru.nanit.limbo.LimboConstants; import ru.nanit.limbo.protocol.packets.login.*; import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.pipeline.PacketDecoder; @@ -17,7 +19,10 @@ import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.UuidUtil; +import ru.nanit.limbo.util.VelocityUtil; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -25,31 +30,38 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { private final LimboServer server; private final Channel channel; + private final GameProfile profile; private State state; private Version clientVersion; + private SocketAddress address; - private UUID uuid; - private String username; - - public UUID getUuid() { - return uuid; - } - - public String getUsername() { - return username; - } + private int velocityLoginMessageId = -1; public ClientConnection(Channel channel, LimboServer server){ this.server = server; this.channel = channel; + this.address = channel.remoteAddress(); + this.profile = new GameProfile(); + } + + public UUID getUuid() { + return profile.getUuid(); + } + + public String getUsername() { + return profile.getUsername(); + } + + private void setAddress(String host){ + this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (state.equals(State.PLAY)){ server.getConnections().removeConnection(this); - Logger.info("Player %s disconnected", this.username); + Logger.info("Player %s disconnected", getUsername()); } super.channelInactive(ctx); } @@ -69,9 +81,20 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { public void handlePacket(Object packet){ if (packet instanceof PacketHandshake){ PacketHandshake handshake = (PacketHandshake) packet; - updateState(State.getById(handshake.getNextState())); clientVersion = handshake.getVersion(); - Logger.debug("Pinged from " + handshake.getHost() + ":" + handshake.getPort()); + updateState(State.getById(handshake.getNextState())); + Logger.debug("Pinged from " + address); + + if (server.getConfig().getInfoForwarding().isLegacy()){ + String[] split = handshake.getHost().split("\00"); + + if (split.length == 3 || split.length == 4){ + setAddress(split[1]); + profile.setUuid(UuidUtil.fromString(split[2])); + } else { + disconnect("You've enabled player info forwarding. To join, enable it in your proxy too"); + } + } } if (packet instanceof PacketStatusRequest){ @@ -93,22 +116,66 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { return; } - this.username = ((PacketLoginStart) packet).getUsername(); - this.uuid = UuidUtil.getOfflineModeUuid(this.username); + if (server.getConfig().getInfoForwarding().isModern()){ + velocityLoginMessageId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE); + PacketLoginPluginRequest request = new PacketLoginPluginRequest(); + request.setMessageId(velocityLoginMessageId); + request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL); + request.setData(Unpooled.EMPTY_BUFFER); + sendPacket(request); + return; + } - PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); + if (server.getConfig().getInfoForwarding().isNone()){ + profile.setUsername(((PacketLoginStart) packet).getUsername()); + profile.setUuid(UuidUtil.getOfflineModeUuid(getUsername())); + } - loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(this.username)); - loginSuccess.setUsername(this.username); - - sendPacket(loginSuccess); - updateState(State.PLAY); - - server.getConnections().addConnection(this); - Logger.info("Player %s connected (%s)", this.username, channel.remoteAddress()); - - sendJoinPackets(); + fireLoginSuccess(); } + + if (packet instanceof PacketLoginPluginResponse){ + PacketLoginPluginResponse response = (PacketLoginPluginResponse) packet; + + if (server.getConfig().getInfoForwarding().isModern() + && response.getMessageId() == velocityLoginMessageId){ + + if (!response.isSuccessful() || response.getData() == null){ + disconnect("You need to connect with Velocity"); + return; + } + + if (!VelocityUtil.checkIntegrity(response.getData())) { + disconnect("Can't verify forwarded player info"); + return; + } + + setAddress(response.getData().readString()); + profile.setUuid(response.getData().readUuid()); + profile.setUsername(response.getData().readString()); + + fireLoginSuccess(); + } + } + } + + private void fireLoginSuccess(){ + if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1){ + disconnect("You need to connect with Velocity"); + return; + } + + PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); + loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(getUsername())); + loginSuccess.setUsername(getUsername()); + sendPacket(loginSuccess); + + updateState(State.PLAY); + server.getConnections().addConnection(this); + + Logger.info("Player %s connected (%s)", getUsername(), address); + + sendJoinPackets(); } private void sendJoinPackets(){ diff --git a/src/main/java/ru/nanit/limbo/connection/GameProfile.java b/src/main/java/ru/nanit/limbo/connection/GameProfile.java new file mode 100644 index 0000000..39da0f3 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/connection/GameProfile.java @@ -0,0 +1,25 @@ +package ru.nanit.limbo.connection; + +import java.util.UUID; + +public class GameProfile { + + private String username; + private UUID uuid; + + public String getUsername() { + return username; + } + + public UUID getUuid() { + return uuid; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java index 5619e56..7c5eba1 100644 --- a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java +++ b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java @@ -66,7 +66,7 @@ public class ByteMessage extends ByteBuf { return readString(readVarInt()); } - private String readString(int length) { + public String readString(int length) { String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); buf.skipBytes(length); return str; diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginRequest.java b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginRequest.java new file mode 100644 index 0000000..0ee58b1 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginRequest.java @@ -0,0 +1,32 @@ +package ru.nanit.limbo.protocol.packets.login; + +import io.netty.buffer.ByteBuf; +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.PacketOut; + +public class PacketLoginPluginRequest implements PacketOut { + + private int messageId; + private String channel; + private ByteBuf data; + + public void setMessageId(int messageId) { + this.messageId = messageId; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public void setData(ByteBuf data) { + this.data = data; + } + + @Override + public void encode(ByteMessage msg) { + msg.writeVarInt(messageId); + msg.writeString(channel); + msg.writeBytes(data); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginResponse.java b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginResponse.java new file mode 100644 index 0000000..a022e42 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginPluginResponse.java @@ -0,0 +1,35 @@ +package ru.nanit.limbo.protocol.packets.login; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.PacketIn; + +public class PacketLoginPluginResponse implements PacketIn { + + private int messageId; + private boolean successful; + private ByteMessage data; + + public int getMessageId() { + return messageId; + } + + public boolean isSuccessful() { + return successful; + } + + public ByteMessage getData() { + return data; + } + + @Override + public void decode(ByteMessage msg) { + messageId = msg.readVarInt(); + successful = msg.readBoolean(); + + if (msg.readableBytes() > 0){ + int i = msg.readableBytes(); + data = new ByteMessage(msg.readBytes(i)); + } + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java index 3b53112..191942c 100644 --- a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java @@ -41,5 +41,4 @@ public class PacketDecoder extends MessageToMessageDecoder { public void updateState(State state){ this.mappings = state.serverBound; } - } diff --git a/src/main/java/ru/nanit/limbo/protocol/registry/State.java b/src/main/java/ru/nanit/limbo/protocol/registry/State.java index 55e625a..4bc20d2 100644 --- a/src/main/java/ru/nanit/limbo/protocol/registry/State.java +++ b/src/main/java/ru/nanit/limbo/protocol/registry/State.java @@ -30,8 +30,10 @@ public enum State { LOGIN(2){ { serverBound.register(0x00, PacketLoginStart::new); + serverBound.register(0x02, PacketLoginPluginResponse::new); clientBound.register(0x00, PacketDisconnect::new); clientBound.register(0x02, PacketLoginSuccess::new); + clientBound.register(0x04, PacketLoginPluginRequest::new); } }, PLAY(3){ diff --git a/src/main/java/ru/nanit/limbo/server/LimboServer.java b/src/main/java/ru/nanit/limbo/server/LimboServer.java index c100a1d..2ee69b1 100644 --- a/src/main/java/ru/nanit/limbo/server/LimboServer.java +++ b/src/main/java/ru/nanit/limbo/server/LimboServer.java @@ -12,6 +12,7 @@ import ru.nanit.limbo.protocol.packets.play.PacketBossBar; import ru.nanit.limbo.protocol.packets.play.PacketChatMessage; import ru.nanit.limbo.server.data.*; import ru.nanit.limbo.util.Logger; +import ru.nanit.limbo.util.VelocityUtil; import ru.nanit.limbo.world.DimensionRegistry; import java.net.SocketAddress; @@ -64,6 +65,10 @@ public final class LimboServer { Logger.setLevel(config.getDebugLevel()); + if (config.getInfoForwarding().isModern()){ + VelocityUtil.init(config); + } + dimensionRegistry = new DimensionRegistry(); dimensionRegistry.load(config.getDimensionType()); diff --git a/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java b/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java index fcae3ee..c629f55 100644 --- a/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java +++ b/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java @@ -4,19 +4,31 @@ import napi.configurate.data.ConfigNode; import napi.configurate.serializing.NodeSerializer; import napi.configurate.serializing.NodeSerializingException; -import java.util.Optional; +import java.nio.charset.StandardCharsets; public class InfoForwarding { private Type type; - private String secret; + private byte[] secretKey; public Type getType() { return type; } - public Optional getSecret() { - return Optional.ofNullable(secret); + public byte[] getSecretKey() { + return secretKey; + } + + public boolean isNone(){ + return type == Type.NONE; + } + + public boolean isLegacy(){ + return type == Type.LEGACY; + } + + public boolean isModern(){ + return type == Type.MODERN; } public enum Type { @@ -38,14 +50,14 @@ public class InfoForwarding { } if (forwarding.type == Type.MODERN){ - forwarding.secret = node.getNode("secret").getString(); + forwarding.secretKey = node.getNode("secret").getString().getBytes(StandardCharsets.UTF_8); } return forwarding; } @Override - public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) throws NodeSerializingException { + public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) { } } diff --git a/src/main/java/ru/nanit/limbo/util/UuidUtil.java b/src/main/java/ru/nanit/limbo/util/UuidUtil.java index 586cd59..87b5b41 100644 --- a/src/main/java/ru/nanit/limbo/util/UuidUtil.java +++ b/src/main/java/ru/nanit/limbo/util/UuidUtil.java @@ -12,4 +12,9 @@ public final class UuidUtil { .getBytes(StandardCharsets.UTF_8)); } + public static UUID fromString(String str){ + if(str.contains("-")) return UUID.fromString(str); + return UUID.fromString(str.replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5")); + } + } diff --git a/src/main/java/ru/nanit/limbo/util/VelocityUtil.java b/src/main/java/ru/nanit/limbo/util/VelocityUtil.java new file mode 100644 index 0000000..c40a30b --- /dev/null +++ b/src/main/java/ru/nanit/limbo/util/VelocityUtil.java @@ -0,0 +1,42 @@ +package ru.nanit.limbo.util; + +import io.netty.buffer.ByteBuf; +import ru.nanit.limbo.configuration.LimboConfig; +import ru.nanit.limbo.protocol.ByteMessage; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.MessageDigest; + +public final class VelocityUtil { + + private static byte[] secretKey; + + private VelocityUtil(){} + + public static void init(LimboConfig config){ + secretKey = config.getInfoForwarding().getSecretKey(); + } + + public static boolean checkIntegrity(ByteMessage buf) { + byte[] signature = new byte[32]; + buf.readBytes(signature); + byte[] data = new byte[buf.readableBytes()]; + buf.getBytes(buf.readerIndex(), data); + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secretKey, "HmacSHA256")); + byte[] mySignature = mac.doFinal(data); + if (!MessageDigest.isEqual(signature, mySignature)) + return false; + } catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + int version = buf.readVarInt(); + if (version != 1) + throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001'); + return true; + } + +} diff --git a/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java b/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java index 77a6f89..3ddefc8 100644 --- a/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java +++ b/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java @@ -57,12 +57,12 @@ public final class DimensionRegistry { overWorld = CompoundBinaryTag.builder() .putString("name", "minecraft:overworld") .putByte("piglin_safe", (byte) 0) - .putByte("natural", (byte) 0) + .putByte("natural", (byte) 1) .putFloat("ambient_light", 0.0F) .putString("infiniburn", "minecraft:infiniburn_overworld") .putByte("respawn_anchor_works", (byte) 0) - .putByte("has_skylight", (byte) 0) - .putByte("bed_works", (byte) 0) + .putByte("has_skylight", (byte) 1) + .putByte("bed_works", (byte) 1) .putString("effects", "minecraft:overworld") .putLong("fixed_time", 6000L) .putByte("has_raids", (byte) 1) @@ -74,20 +74,20 @@ public final class DimensionRegistry { nether = CompoundBinaryTag.builder() .putString("name", "minecraft:the_nether") - .putByte("piglin_safe", (byte) 0) + .putByte("piglin_safe", (byte) 1) .putByte("natural", (byte) 0) - .putFloat("ambient_light", 0.0F) + .putFloat("ambient_light", 0.1F) .putString("infiniburn", "minecraft:infiniburn_nether") - .putByte("respawn_anchor_works", (byte) 0) + .putByte("respawn_anchor_works", (byte) 1) .putByte("has_skylight", (byte) 0) .putByte("bed_works", (byte) 0) .putString("effects", "minecraft:the_nether") .putLong("fixed_time", 18000L) - .putByte("has_raids", (byte) 1) + .putByte("has_raids", (byte) 0) .putInt("logical_height", 128) .putDouble("coordinate_scale", 1.0) - .putByte("ultrawarm", (byte) 0) - .putByte("has_ceiling", (byte) 0) + .putByte("ultrawarm", (byte) 1) + .putByte("has_ceiling", (byte) 1) .build(); theEnd = CompoundBinaryTag.builder() @@ -110,13 +110,13 @@ public final class DimensionRegistry { CompoundBinaryTag overWorldData = CompoundBinaryTag.builder() .putString("name", "minecraft:overworld") - .putInt("id", 2) + .putInt("id", 0) .put("element", overWorld) .build(); CompoundBinaryTag netherData = CompoundBinaryTag.builder() .putString("name", "minecraft:the_nether") - .putInt("id", 2) + .putInt("id", 1) .put("element", nether) .build(); diff --git a/src/main/resources/limbo.yml b/src/main/resources/settings.yml similarity index 99% rename from src/main/resources/limbo.yml rename to src/main/resources/settings.yml index c9332b0..b5cc77b 100644 --- a/src/main/resources/limbo.yml +++ b/src/main/resources/settings.yml @@ -21,7 +21,7 @@ dimension: THE_END # Spawn position in the world spawnPosition: x: 0.0 - y: 0.0 + y: 32.0 z: 0.0 yaw: 0.0 pitch: 0.0