diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java index ab879248b2..5a267f2b5d 100644 --- a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java +++ b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -3,6 +3,7 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.CommandSender; @@ -339,4 +340,26 @@ public enum MainHand */ @Deprecated Scoreboard getScoreboard(); + + /** + * Retrieves a cookie from this player. + * + * @param cookie the resource location of the cookie, for example "bungeecord:my_cookie" + * + * @return a {@link CompletableFuture} that will be completed when the Cookie response is received + * @apiNote Only useable for 1.20.5 clients or newer, + * if the cookie is not set in the client, the {@link CompletableFuture} will complete with a null value + */ + CompletableFuture retrieveCookie(String cookie); + + + /** + * Stores a cookie in this player's client. + * + * @param cookie the resource location of the cookie, for example "bungeecord:my_cookie" + * @param data the data to store in the cookie + * + * @apiNote Only useable for 1.20.5 clients or newer + */ + void storeCookie(String cookie, byte[] data); } 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 04143a4689..0aa8517ad2 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -20,6 +20,7 @@ import java.util.Objects; import java.util.Queue; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import lombok.Getter; @@ -771,4 +772,16 @@ public Scoreboard getScoreboard() { return serverSentScoreboard; } + + @Override + public CompletableFuture retrieveCookie(String cookie) + { + return pendingConnection.retrieveCookie( cookie ); + } + + @Override + public void storeCookie(String cookie, byte[] data) + { + pendingConnection.storeCookie( cookie, data ); + } } 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 fc5abf837b..de6b91109e 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 @@ -11,12 +11,19 @@ import java.security.MessageDigest; import java.time.Instant; import java.util.HashSet; +import java.util.Queue; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import javax.crypto.SecretKey; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeServerInfo; import net.md_5.bungee.EncryptionUtil; @@ -51,6 +58,8 @@ import net.md_5.bungee.protocol.PlayerPublicKey; import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; +import net.md_5.bungee.protocol.packet.CookieRequest; +import net.md_5.bungee.protocol.packet.CookieResponse; import net.md_5.bungee.protocol.packet.EncryptionRequest; import net.md_5.bungee.protocol.packet.EncryptionResponse; import net.md_5.bungee.protocol.packet.Handshake; @@ -64,6 +73,7 @@ import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.StatusRequest; import net.md_5.bungee.protocol.packet.StatusResponse; +import net.md_5.bungee.protocol.packet.StoreCookie; import net.md_5.bungee.util.AllowedCharacters; import net.md_5.bungee.util.BufUtil; import net.md_5.bungee.util.QuietException; @@ -86,6 +96,18 @@ public class InitialHandler extends PacketHandler implements PendingConnection @Getter private final Set registeredChannels = new HashSet<>(); private State thisState = State.HANDSHAKE; + private final Queue requestedCookies = new ConcurrentLinkedQueue<>(); + + @Data + @ToString + @EqualsAndHashCode + @AllArgsConstructor + public static class CookieFuture + { + private String cookie; + private CompletableFuture future; + } + private final Unsafe unsafe = new Unsafe() { @Override @@ -653,6 +675,19 @@ public void handle(LoginPayloadResponse response) throws Exception disconnect( "Unexpected custom LoginPayloadResponse" ); } + @Override + public void handle(CookieResponse cookieResponse) + { + // be careful spigot could also make the client send a cookie response + CookieFuture future = requestedCookies.peek(); + if ( future != null && future.cookie.equals( cookieResponse.getCookie() ) ) + { + requestedCookies.remove(); + future.getFuture().complete( cookieResponse.getData() ); + throw CancelSendSignal.INSTANCE; + } + } + @Override public void disconnect(String reason) { @@ -785,4 +820,26 @@ public void relayMessage(PluginMessage input) throws Exception brandMessage = input; } } + + public CompletableFuture retrieveCookie(String cookie) + { + Preconditions.checkState( getVersion() >= ProtocolConstants.MINECRAFT_1_20_5, "Cookies are only supported in 1.20.5 and above" ); + Preconditions.checkState( loginRequest != null, "Cannot retrieve cookies for Status or legacy connections" ); + if ( cookie.indexOf( ':' ) == -1 ) + { + // if we request an invalid resource location (no prefix) the client will respond with minecraft: prefix + cookie = "minecraft:" + cookie; + } + CompletableFuture future = new CompletableFuture<>(); + requestedCookies.add( new CookieFuture( cookie, future ) ); + unsafe.sendPacket( new CookieRequest( cookie ) ); + return future; + } + + public void storeCookie(String cookie, byte[] data) + { + Preconditions.checkState( getVersion() >= ProtocolConstants.MINECRAFT_1_20_5, "Cookies are only supported in 1.20.5 and above" ); + Preconditions.checkState( loginRequest != null, "Cannot retrieve cookies for Status or legacy connections" ); + unsafe().sendPacket( new StoreCookie( cookie, data ) ); + } } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java index 460aa49159..95c7f3c4e8 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -32,6 +32,7 @@ import net.md_5.bungee.protocol.packet.ClientChat; import net.md_5.bungee.protocol.packet.ClientCommand; import net.md_5.bungee.protocol.packet.ClientSettings; +import net.md_5.bungee.protocol.packet.CookieResponse; import net.md_5.bungee.protocol.packet.FinishConfiguration; import net.md_5.bungee.protocol.packet.KeepAlive; import net.md_5.bungee.protocol.packet.LoginAcknowledged; @@ -363,6 +364,12 @@ public void handle(FinishConfiguration finishConfiguration) throws Exception con.sendQueuedPackets(); } + @Override + public void handle(CookieResponse cookieResponse) throws Exception + { + con.getPendingConnection().handle( cookieResponse ); + } + @Override public String toString() {