diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java index 87d5fb3d..a3af0226 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java @@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; public class GameClient { @@ -24,6 +25,7 @@ public class GameClient { private final LatencyTracker latencyTracker; private Habbo habbo; + private final AtomicBoolean disposed = new AtomicBoolean(false); private boolean handshakeFinished; private String machineId = ""; private String ssoTicket = ""; @@ -153,6 +155,10 @@ public class GameClient { } public void dispose(boolean allowSessionResume) { + if (!this.disposed.compareAndSet(false, true)) { + return; + } + try { this.channel.close(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java index 945e7b73..2055f126 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java @@ -73,11 +73,11 @@ public class SecureLoginEvent extends MessageHandler { return; } - String sso = this.packet.readString().replace(" ", ""); + String sso = SecureLoginInputGuard.normalizeSsoTicket(this.packet.readString()); - if (Emulator.getPluginManager().fireEvent(new SSOAuthenticationEvent(sso)).isCancelled()) { + if (!SecureLoginInputGuard.isValidSsoTicket(sso)) { Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - LOGGER.info("SSO Authentication is cancelled by a plugin. Closed connection..."); + LOGGER.debug("Client is trying to connect with an invalid SSO ticket! Closed connection..."); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuard.java new file mode 100644 index 00000000..b9a5a9b0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuard.java @@ -0,0 +1,20 @@ +package com.eu.habbo.messages.incoming.handshake; + +final class SecureLoginInputGuard { + static final int MAX_SSO_TICKET_LENGTH = 512; + + private SecureLoginInputGuard() { + } + + static String normalizeSsoTicket(String ticket) { + if (ticket == null) { + return ""; + } + + return ticket.replace(" ", ""); + } + + static boolean isValidSsoTicket(String ticket) { + return ticket != null && !ticket.isEmpty() && ticket.length() <= MAX_SSO_TICKET_LENGTH; + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/gameclients/GameClientManagerContractTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/gameclients/GameClientManagerContractTest.java index 4f35e668..27bc393f 100644 --- a/Emulator/src/test/java/com/eu/habbo/habbohotel/gameclients/GameClientManagerContractTest.java +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/gameclients/GameClientManagerContractTest.java @@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.gameclients; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; class GameClientManagerContractTest { @@ -19,4 +20,11 @@ class GameClientManagerContractTest { assertDoesNotThrow(() -> manager.disposeClient(null)); assertDoesNotThrow(() -> manager.forceDisposeClient(null)); } + + @Test + void gameClientDisposeIsExplicitlyIdempotent() throws Exception { + assertTrue(java.util.concurrent.atomic.AtomicBoolean.class.isAssignableFrom( + GameClient.class.getDeclaredField("disposed").getType() + )); + } } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuardTest.java new file mode 100644 index 00000000..28ec4504 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/handshake/SecureLoginInputGuardTest.java @@ -0,0 +1,27 @@ +package com.eu.habbo.messages.incoming.handshake; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SecureLoginInputGuardTest { + + @Test + void normalizesNullAndSpacesBeforeAuthentication() { + assertEquals("", SecureLoginInputGuard.normalizeSsoTicket(null)); + assertEquals("abc123", SecureLoginInputGuard.normalizeSsoTicket(" abc 123 ")); + } + + @Test + void rejectsMissingOrOversizedTickets() { + assertFalse(SecureLoginInputGuard.isValidSsoTicket("")); + assertFalse(SecureLoginInputGuard.isValidSsoTicket("x".repeat(SecureLoginInputGuard.MAX_SSO_TICKET_LENGTH + 1))); + } + + @Test + void acceptsTicketWithinBound() { + assertTrue(SecureLoginInputGuard.isValidSsoTicket("x".repeat(SecureLoginInputGuard.MAX_SSO_TICKET_LENGTH))); + } +}