diff --git a/Database Updates/001_auth_ticket_ttl.sql b/Database Updates/001_auth_ticket_ttl.sql index ac4d1fd8..ae8a669f 100644 --- a/Database Updates/001_auth_ticket_ttl.sql +++ b/Database Updates/001_auth_ticket_ttl.sql @@ -34,3 +34,10 @@ SET @ddl = IF(@col_exists = 0, PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; + + +UPDATE emulator_settings SET `key`='ws.whitelist' WHERE `key`='websockets.whitelist'; +UPDATE emulator_settings SET `key`='ws.host' WHERE `key`='ws.nitro.host'; +UPDATE emulator_settings SET `key`='ws.port' WHERE `key`='ws.nitro.port'; +INSERT emulator_settings (`key`, `value`) VALUES ('ws.ip.header', 'X-Forwarded-For'); +INSERT emulator_settings (`key`, `value`) VALUES ('ws.enabled', 'true'); \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/handlers/WebSocketHttpHandler.java b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/handlers/WebSocketHttpHandler.java index d4753f6a..572e9488 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/handlers/WebSocketHttpHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/handlers/WebSocketHttpHandler.java @@ -8,10 +8,13 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import io.netty.util.ReferenceCountUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.URI; public class WebSocketHttpHandler extends ChannelInboundHandlerAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketHttpHandler.class); private static final String ORIGIN_HEADER = "Origin"; @Override @@ -27,6 +30,12 @@ public class WebSocketHttpHandler extends ChannelInboundHandlerAdapter { } private boolean handleHttpRequest(ChannelHandlerContext ctx, HttpMessage req) { + captureForwardedIp(ctx, req); + + if (!isWebSocketUpgrade(req)) { + return true; + } + String origin = "error"; try { @@ -38,27 +47,47 @@ public class WebSocketHttpHandler extends ChannelInboundHandlerAdapter { String whitelist = Emulator.getConfig().getValue("ws.whitelist", "localhost"); if (!isWhitelisted(origin, whitelist.split(","))) { + LOGGER.warn("WebSocket upgrade rejected — origin '{}' not in ws.whitelist='{}'", + req.headers().get(ORIGIN_HEADER), whitelist); + FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN, Unpooled.wrappedBuffer("Origin forbidden".getBytes()) ); + response.headers().set("Vary", "Origin"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); return false; } - String ipHeader = Emulator.getConfig().getValue("ws.ip.header", ""); - if (!ipHeader.isEmpty() && req.headers().contains(ipHeader)) { - String ip = req.headers().get(ipHeader); - ctx.channel().attr(GameServerAttributes.WS_IP).set(ip); - } - return true; } + private static void captureForwardedIp(ChannelHandlerContext ctx, HttpMessage req) { + String ipHeader = Emulator.getConfig().getValue("ws.ip.header", ""); + if (!ipHeader.isEmpty() && req.headers().contains(ipHeader)) { + String ip = req.headers().get(ipHeader); + ctx.channel().attr(GameServerAttributes.WS_IP).set(ip); + } + } + + private static boolean isWebSocketUpgrade(HttpMessage req) { + String upgrade = req.headers().get(HttpHeaderNames.UPGRADE); + if (upgrade == null || !"websocket".equalsIgnoreCase(upgrade)) return false; + + String connection = req.headers().get(HttpHeaderNames.CONNECTION); + if (connection == null) return false; + + for (String token : connection.split(",")) { + if ("upgrade".equalsIgnoreCase(token.trim())) return true; + } + return false; + } + private static String getDomainNameFromUrl(String url) throws Exception { URI uri = new URI(url); String domain = uri.getHost(); + if (domain == null) return "error"; return domain.startsWith("www.") ? domain.substring(4) : domain; } diff --git a/Latest_Compiled_Version/config.ini.example b/Latest_Compiled_Version/config.ini.example index cd8bd750..547083ea 100644 --- a/Latest_Compiled_Version/config.ini.example +++ b/Latest_Compiled_Version/config.ini.example @@ -27,16 +27,6 @@ rcon.host=127.0.0.1 rcon.port=3001 rcon.allowed=127.0.0.1;127.0.0.2 -#WebSocket Configuration (for Nitro) -#Set ws.enabled to true to enable WebSocket connections. -ws.enabled=false -ws.host=0.0.0.0 -ws.port=2096 -#Comma-separated whitelist of allowed origins. Supports wildcards: *.example.com, * (allow all) -ws.whitelist=localhost -#Header name for real client IP when behind a proxy (e.g., X-Forwarded-For, CF-Connecting-IP). Leave empty if not using a proxy. -ws.ip.header= - # Databse configuration db.pool.connection_timeout_ms = 10000 db.pool.idle_timeout_ms = 600000 @@ -69,3 +59,12 @@ login.remember.jwt.secret= # Login news API. login.news.limit=5 + + +#WebSocket Configuration (for Nitro) +#Please adjust this setting in the Database !!!! +### ws.enabled=false +### ws.host=0.0.0.0 +### ws.port=2096 +### ws.whitelist=localhost #Comma-separated whitelist of allowed origins. Supports wildcards: *.example.com, * (allow all) +### ws.ip.header=X-Forwarded-For #Header name for real client IP when behind a proxy (e.g., X-Forwarded-For, CF-Connecting-IP). Leave empty if not using a proxy.