From 7b631092f5f859709483ce437a1afad1ade26d36 Mon Sep 17 00:00:00 2001 From: md_5 Date: Fri, 3 May 2013 20:57:19 +1000 Subject: [PATCH] Add experimental Forge support. This may cause issues when using Vanilla clients etc, so caution is advised. Please visit GitHub to report any issues you encounter. Thanks @LexManos for providing the basis for this implementation. --- .../net/md_5/bungee/ServerConnection.java | 3 +- .../java/net/md_5/bungee/ServerConnector.java | 80 +++++++++++++++++-- .../java/net/md_5/bungee/UserConnection.java | 3 +- .../bungee/connection/InitialHandler.java | 17 +++- 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java index d4796245..6caa2fea 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java @@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.netty.ChannelWrapper; -import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketFFKick; @@ -20,7 +19,7 @@ public class ServerConnection implements Server @Getter private final BungeeServerInfo info; @Getter - private final Packet1Login loginPacket; + private final boolean isForgeWrapper; @Getter @Setter private boolean isObsolete; diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index b1983be7..9958d921 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -1,10 +1,14 @@ package net.md_5.bungee; import com.google.common.base.Preconditions; +import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import java.security.PublicKey; import java.util.Objects; import java.util.Queue; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ProxyServer; @@ -18,6 +22,9 @@ import net.md_5.bungee.connection.CancelSendSignal; import net.md_5.bungee.connection.DownstreamBridge; import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.CipherDecoder; +import net.md_5.bungee.netty.CipherEncoder; +import net.md_5.bungee.netty.PacketDecoder; import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet9Respawn; @@ -25,9 +32,11 @@ import net.md_5.bungee.packet.PacketCDClientStatus; import net.md_5.bungee.packet.PacketCEScoreboardObjective; import net.md_5.bungee.packet.PacketD1Team; import net.md_5.bungee.packet.PacketFAPluginMessage; +import net.md_5.bungee.packet.PacketFCEncryptionResponse; import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketHandler; +import net.md_5.bungee.protocol.PacketDefinitions; @RequiredArgsConstructor public class ServerConnector extends PacketHandler @@ -38,11 +47,12 @@ public class ServerConnector extends PacketHandler private final UserConnection user; private final BungeeServerInfo target; private State thisState = State.ENCRYPT_REQUEST; + private SecretKey secretkey; private enum State { - ENCRYPT_REQUEST, LOGIN, FINISHED; + ENCRYPT_REQUEST, ENCRYPT_RESPONSE, LOGIN, FINISHED; } @Override @@ -57,7 +67,12 @@ public class ServerConnector extends PacketHandler channel.write( new PacketFAPluginMessage( "BungeeCord", out.toByteArray() ) ); channel.write( user.getPendingConnection().getHandshake() ); - channel.write( PacketCDClientStatus.CLIENT_LOGIN ); + + // Skip encryption if we are not using Forge + if ( user.getPendingConnection().getForgeLogin() == null ) + { + channel.write( PacketCDClientStatus.CLIENT_LOGIN ); + } } @Override @@ -71,7 +86,7 @@ public class ServerConnector extends PacketHandler { Preconditions.checkState( thisState == State.LOGIN, "Not exepcting LOGIN" ); - ServerConnection server = new ServerConnection( ch, target, login ); + ServerConnection server = new ServerConnection( ch, target, false ); ServerConnectedEvent event = new ServerConnectedEvent( user, server ); bungee.getPluginManager().callEvent( event ); @@ -96,7 +111,8 @@ public class ServerConnector extends PacketHandler synchronized ( user.getSwitchMutex() ) { - if ( user.getServer() == null ) + // TODO: This whole wrapper business is a hack + if ( user.getServer() == null || user.getServer().isForgeWrapper() ) { // Once again, first connection user.setClientEntityId( login.entityId ); @@ -110,7 +126,7 @@ public class ServerConnector extends PacketHandler login.difficulty, login.unused, (byte) user.getPendingConnection().getListener().getTabListSize(), - false ); + secretkey != null ); // If we are encrypting, we must be Forge user.sendPacket( modLogin ); } else { @@ -165,6 +181,40 @@ public class ServerConnector extends PacketHandler public void handle(PacketFDEncryptionRequest encryptRequest) throws Exception { Preconditions.checkState( thisState == State.ENCRYPT_REQUEST, "Not expecting ENCRYPT_REQUEST" ); + + // Only need to handle this if we want to use encryption + if ( user.getPendingConnection().getForgeLogin() != null ) + { + PublicKey publickey = EncryptionUtil.getPubkey( encryptRequest ); + this.secretkey = EncryptionUtil.getSecret(); + + byte[] shared = EncryptionUtil.encrypt( publickey, secretkey.getEncoded() ); + byte[] token = EncryptionUtil.encrypt( publickey, encryptRequest.verifyToken ); + + ch.write( new PacketFCEncryptionResponse( shared, token ) ); + + Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, secretkey ); + ch.getHandle().pipeline().addBefore( "decoder", "encrypt", new CipherEncoder( encrypt ) ); + + thisState = State.ENCRYPT_RESPONSE; + } else + { + thisState = State.LOGIN; + } + } + + @Override + public void handle(PacketFCEncryptionResponse encryptResponse) throws Exception + { + Preconditions.checkState( thisState == State.ENCRYPT_RESPONSE, "Not expecting ENCRYPT_RESPONSE" ); + + Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, secretkey ); + ch.getHandle().pipeline().addBefore( "decoder", "decrypt", new CipherDecoder( decrypt ) ); + + ch.write( user.getPendingConnection().getForgeLogin() ); + user.setServer( new ServerConnection( ch, target, true ) ); + + ch.write( PacketCDClientStatus.CLIENT_LOGIN ); thisState = State.LOGIN; } @@ -193,6 +243,26 @@ public class ServerConnector extends PacketHandler } } + @Override + public void handle(PacketFAPluginMessage pluginMessage) throws Exception + { + if ( ( pluginMessage.data[0] & 0xFF ) == 0 && pluginMessage.tag.equals( "FML" ) ) + { + ByteArrayDataInput in = ByteStreams.newDataInput( pluginMessage.data ); + in.readUnsignedByte(); + for ( int i = 0; i < in.readInt(); i++ ) + { + in.readUTF(); + } + if ( in.readByte() != 0 ) + { + ch.getHandle().pipeline().get( PacketDecoder.class ).setProtocol( PacketDefinitions.FORGE_PROTOCOL ); + } + } + + user.sendPacket( pluginMessage ); // We have to forward these to the user, especially with Forge as stuff might break + } + @Override public String toString() { diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index 1d2f990b..143df985 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -53,8 +53,9 @@ public final class UserConnection implements ProxiedPlayer private final InitialHandler pendingConnection; /*========================================================================*/ @Getter + @NonNull @Setter - private ServerConnection server; + private ServerConnection server = null; @Getter private final Object switchMutex = new Object(); @Getter diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 4ee67579..0dc15eda 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -33,6 +33,8 @@ import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.CipherDecoder; import net.md_5.bungee.netty.CipherEncoder; +import net.md_5.bungee.netty.PacketDecoder; +import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet2Handshake; import net.md_5.bungee.packet.PacketCDClientStatus; import net.md_5.bungee.packet.PacketFAPluginMessage; @@ -41,6 +43,7 @@ import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFEPing; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketHandler; +import net.md_5.bungee.protocol.PacketDefinitions; @RequiredArgsConstructor public class InitialHandler extends PacketHandler implements PendingConnection @@ -51,6 +54,8 @@ public class InitialHandler extends PacketHandler implements PendingConnection @Getter private final ListenerInfo listener; @Getter + private Packet1Login forgeLogin; + @Getter private Packet2Handshake handshake; private PacketFDEncryptionRequest request; @Getter @@ -100,6 +105,16 @@ public class InitialHandler extends PacketHandler implements PendingConnection disconnect( kickMessage ); } + @Override + public void handle(Packet1Login login) throws Exception + { + Preconditions.checkState( thisState == State.LOGIN, "Not expecting FORGE LOGIN" ); + Preconditions.checkState( forgeLogin == null, "Already received FORGE LOGIN" ); + forgeLogin = login; + + ch.getHandle().pipeline().get( PacketDecoder.class ).setProtocol( PacketDefinitions.FORGE_PROTOCOL ); + } + @Override public void handle(Packet2Handshake handshake) throws Exception { @@ -196,12 +211,12 @@ public class InitialHandler extends PacketHandler implements PendingConnection return; } + thisState = InitialHandler.State.LOGIN; ch.write( new PacketFCEncryptionResponse() ); try { Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, sharedKey ); ch.getHandle().pipeline().addBefore( "decoder", "encrypt", new CipherEncoder( encrypt ) ); - thisState = InitialHandler.State.LOGIN; } catch ( GeneralSecurityException ex ) { disconnect( "Cipher error: " + Util.exception( ex ) );