Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -409,6 +409,7 @@ xpartamupp = "wfgbot23" ; Name of the server-side xmpp client that manage games buddies = "," ; Comma separated list of playernames that the current user has marked as buddies rememberpassword = true ; Whether to store the encrypted password in the user config +secureauth = true ; Secure Lobby Authentication: This prevents the impersonation of other players. The lobby server confirms the identity of the player before they join. [lobby.columns] gamerating = false ; Show the average rating of the participating players in a column of the gamelist Index: binaries/data/mods/public/gui/common/network.js =================================================================== --- binaries/data/mods/public/gui/common/network.js +++ binaries/data/mods/public/gui/common/network.js @@ -73,6 +73,7 @@ case 6: return translate("You have been banned"); case 7: return translate("Playername in use. If you were disconnected, retry in few seconds"); case 8: return translate("Server full"); + case 9: return translate("Secure lobby authentication failed. Join via lobby"); default: warn("Unknown disconnect-reason ID received: " + id); return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id }); Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -48,6 +48,7 @@ case "host": { Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = !Engine.HasXmppClient(); + Engine.GetGUIObjectByName("hostLobbyAuthWrapper").hidden = !Engine.HasXmppClient(); if (Engine.HasXmppClient()) { Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name; @@ -55,6 +56,7 @@ sprintf(translate("%(name)s's game"), { "name": attribs.name }); Engine.GetGUIObjectByName("useSTUN").checked = Engine.ConfigDB_GetValue("user", "lobby.stun.enabled") == "true"; + Engine.GetGUIObjectByName("useLobbyAuth").checked = Engine.ConfigDB_GetValue("user", "lobby.secureauth") == "true"; } switchSetupPage("pageHost"); @@ -260,10 +262,20 @@ function switchSetupPage(newPage) { - for (let page of Engine.GetGUIObjectByName("multiplayerPages").children) - if (page.name.substr(0, 4) == "page") + let multiplayerPages = Engine.GetGUIObjectByName("multiplayerPages"); + for (let page of multiplayerPages.children) + if (page.name.startsWith("page")) page.hidden = true; + if (newPage == "pageJoin" || newPage == "pageHost") + { + let pageSize = multiplayerPages.size; + let halfHeight = newPage == "pageJoin" ? 130 : Engine.HasXmppClient() ? 145 : 110; + pageSize.top = -halfHeight; + pageSize.bottom = halfHeight; + multiplayerPages.size = pageSize; + } + Engine.GetGUIObjectByName(newPage).hidden = false; Engine.GetGUIObjectByName("hostPlayerNameWrapper").hidden = Engine.HasXmppClient(); @@ -302,12 +314,10 @@ } } + let useLobbyAuth = Engine.HasXmppClient() && Engine.GetGUIObjectByName("useLobbyAuth").checked; try { - if (g_UserRating) - Engine.StartNetworkHost(playername + " (" + g_UserRating + ")", port); - else - Engine.StartNetworkHost(playername, port); + Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useLobbyAuth); } catch (e) { Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml @@ -106,17 +106,27 @@ - - + + saveSettingAndWriteToUserConfig("lobby.stun.enabled", String(this.checked)); - + Use STUN to work around firewalls + + + + saveSettingAndWriteToUserConfig("lobby.secureauth", String(this.checked)); + + + Require Lobby Authentication + This prevents the impersonation of other players. The lobby server confirms the identity of the player before they join. + + - + Continue Index: source/lobby/IXmppClient.h =================================================================== --- source/lobby/IXmppClient.h +++ source/lobby/IXmppClient.h @@ -41,6 +41,7 @@ virtual void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0; virtual void SendIqUnregisterGame() = 0; virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0; + virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0; virtual void SetNick(const std::string& nick) = 0; virtual void GetNick(std::string& nick) = 0; virtual void kick(const std::string& nick, const std::string& reason) = 0; Index: source/lobby/StanzaExtensions.h =================================================================== --- source/lobby/StanzaExtensions.h +++ source/lobby/StanzaExtensions.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,6 +35,10 @@ #define EXTPROFILEQUERY 1406 #define XMLNS_PROFILE "jabber:iq:profile" +/// Global Lobby Authentication Extension +#define EXTLOBBYAUTH 1407 +#define XMLNS_LOBBYAUTH "jabber:iq:lobbyauth" + class GameReport : public glooxwrapper::StanzaExtension { public: @@ -111,4 +115,21 @@ glooxwrapper::string m_Command; std::vector m_StanzaProfile; }; + +class LobbyAuth : public glooxwrapper::StanzaExtension +{ +public: + LobbyAuth(const glooxwrapper::Tag* tag = 0); + + // Following four methods are all required by gloox + virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const + { + return new LobbyAuth(tag); + } + virtual const glooxwrapper::string& filterString() const; + virtual glooxwrapper::Tag* tag() const; + virtual glooxwrapper::StanzaExtension* clone() const; + + glooxwrapper::string m_Token; +}; #endif // STANZAEXTENSIONS_H Index: source/lobby/StanzaExtensions.cpp =================================================================== --- source/lobby/StanzaExtensions.cpp +++ source/lobby/StanzaExtensions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -123,13 +123,13 @@ * the listing of games from the server, and register/ * unregister/changestate games on the server. */ -GameListQuery::GameListQuery( const glooxwrapper::Tag* tag ) +GameListQuery::GameListQuery(const glooxwrapper::Tag* tag) : StanzaExtension(EXTGAMELISTQUERY) { if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_GAMELIST) return; - const glooxwrapper::Tag* c = tag->findTag_clone( "query/game" ); + const glooxwrapper::Tag* c = tag->findTag_clone("query/game"); if (c) m_Command = c->cdata(); glooxwrapper::Tag::free(c); @@ -238,3 +238,48 @@ glooxwrapper::Tag::free(t); m_StanzaProfile.clear(); } + +/****************************************************** + * LobbyAuth, a custom IQ Stanza, used to send and + * receive a security token for hosting authentication. + */ +LobbyAuth::LobbyAuth(const glooxwrapper::Tag* tag) + : StanzaExtension(EXTLOBBYAUTH) +{ + if (!tag || tag->name() != "auth" || tag->xmlns() != XMLNS_LOBBYAUTH) + return; + + const glooxwrapper::Tag* c = tag->findTag_clone("auth/token"); + if (c) + m_Token = c->cdata(); + + glooxwrapper::Tag::free(c); +} + +/** + * Required by gloox, used to find the LobbyAuth element in a received IQ. + */ +const glooxwrapper::string& LobbyAuth::filterString() const +{ + static const glooxwrapper::string filter = "/iq/auth[@xmlns='" XMLNS_LOBBYAUTH "']"; + return filter; +} + +/** + * Required by gloox, used to serialize the auth object into XML for sending. + */ +glooxwrapper::Tag* LobbyAuth::tag() const +{ + glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("auth"); + t->setXmlns(XMLNS_LOBBYAUTH); + + // Check for the auth token + if (!m_Token.empty()) + t->addChild(glooxwrapper::Tag::allocate("token", m_Token)); + return t; +} + +glooxwrapper::StanzaExtension* LobbyAuth::clone() const +{ + return new LobbyAuth(); +} Index: source/lobby/XmppClient.h =================================================================== --- source/lobby/XmppClient.h +++ source/lobby/XmppClient.h @@ -47,6 +47,8 @@ // Account infos std::string m_username; std::string m_password; + std::string m_server; + std::string m_room; std::string m_nick; std::string m_xpartamuppId; @@ -70,6 +72,7 @@ void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data); void SendIqUnregisterGame(); void SendIqChangeStateGame(const std::string& nbp, const std::string& players); + void SendIqLobbyAuth(const std::string& to, const std::string& token); void SetNick(const std::string& nick); void GetNick(std::string& nick); void kick(const std::string& nick, const std::string& reason); Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -75,24 +75,23 @@ * @param regOpt If we are just registering or not. */ XmppClient::XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt) - : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick), m_initialLoadComplete(false), m_isConnected(false), m_sessionManager() + : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_room(sRoom), m_nick(sNick), m_initialLoadComplete(false), m_isConnected(false), m_sessionManager() { // Read lobby configuration from default.cfg - std::string sServer; std::string sXpartamupp; - CFG_GET_VAL("lobby.server", sServer); + CFG_GET_VAL("lobby.server", m_server); CFG_GET_VAL("lobby.xpartamupp", sXpartamupp); - m_xpartamuppId = sXpartamupp + "@" + sServer + "/CC"; - glooxwrapper::JID clientJid(sUsername + "@" + sServer + "/0ad"); - glooxwrapper::JID roomJid(sRoom + "@conference." + sServer + "/" + sNick); + m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC"; + glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad"); + glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick); // If we are connecting, use the full jid and a password // If we are registering, only use the server name if (!regOpt) m_client = new glooxwrapper::Client(clientJid, sPassword); else - m_client = new glooxwrapper::Client(sServer); + m_client = new glooxwrapper::Client(m_server); // Disable TLS as we haven't set a certificate on the server yet m_client->setTls(gloox::TLSDisabled); @@ -118,6 +117,9 @@ m_client->registerStanzaExtension(new ProfileQuery()); m_client->registerIqHandler(this, EXTPROFILEQUERY); + m_client->registerStanzaExtension(new LobbyAuth()); + m_client->registerIqHandler(this, EXTLOBBYAUTH); + m_client->registerMessageHandler(this); // Uncomment to see the raw stanzas @@ -417,6 +419,25 @@ } /***************************************************** + * iq to clients * + *****************************************************/ + +/** + * Send lobby authentication token. + */ +void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token) +{ + LobbyAuth* auth = new LobbyAuth(); + auth->m_Token = token; + + glooxwrapper::JID clientJid(to + "@" + m_server + "/0ad"); + glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID()); + iq.addExtension(auth); + DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]"); + m_client->send(iq); +} + +/***************************************************** * Account registration * *****************************************************/ @@ -742,6 +763,20 @@ CreateGUIMessage("game", "profile"); } } + else if (iq.subtype() == gloox::IQ::Set) + { + const LobbyAuth* lobbyAuth = iq.findExtension(EXTLOBBYAUTH); + if (lobbyAuth) + { + LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username()); + + glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); + m_client->send(response); + + if (g_NetServer) + g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string()); + } + } else if (iq.subtype() == gloox::IQ::Error) CreateGUIMessage("system", "error", "text", StanzaErrorToString(iq.error_error())); else Index: source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- source/lobby/glooxwrapper/glooxwrapper.h +++ source/lobby/glooxwrapper/glooxwrapper.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -462,6 +462,8 @@ } gloox::IQ::IqType subtype() const; + const string id() const; + const gloox::JID& from() const; gloox::StanzaError error_error() const; // wrapper for ->error()->error() Tag* tag() const; Index: source/lobby/glooxwrapper/glooxwrapper.cpp =================================================================== --- source/lobby/glooxwrapper/glooxwrapper.cpp +++ source/lobby/glooxwrapper/glooxwrapper.cpp @@ -480,6 +480,15 @@ return m_Wrapped->subtype(); } +const glooxwrapper::string glooxwrapper::IQ::id() const +{ + return m_Wrapped->id(); +} + +const gloox::JID& glooxwrapper::IQ::from() const +{ + return m_Wrapped->from(); +} glooxwrapper::JID::JID() { Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -91,6 +91,12 @@ void SetUserName(const CStrW& username); /** + * Set the name of the hosting player. + * This is needed for the secure lobby authentication. + */ + void SetHostingPlayerName(const CStr& hostingPlayerName); + + /** * Returns the GUID of the local client. * Used for distinguishing observers. */ @@ -218,10 +224,14 @@ void SendPausedMessage(bool pause); private: + + void SendAuthenticateMessage(); + // Net message / FSM transition handlers static bool OnConnect(void* context, CFsmEvent* event); static bool OnHandshake(void* context, CFsmEvent* event); static bool OnHandshakeResponse(void* context, CFsmEvent* event); + static bool OnAuthenticateRequest(void* context, CFsmEvent* event); static bool OnAuthenticate(void* context, CFsmEvent* event); static bool OnChat(void* context, CFsmEvent* event); static bool OnReady(void* context, CFsmEvent* event); @@ -251,6 +261,7 @@ CGame *m_Game; CStrW m_UserName; + CStr m_HostingPlayerName; /// Current network session (or NULL if not connected) CNetClientSession* m_Session; Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ #include "lib/byte_order.h" #include "lib/external_libraries/enet.h" #include "lib/sysdep/sysdep.h" +#include "lobby/IXmppClient.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/Compress.h" @@ -88,6 +89,7 @@ AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context); + AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context); AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context); AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context); @@ -158,6 +160,11 @@ m_UserName = username; } +void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName) +{ + m_HostingPlayerName = hostingPlayerName; +} + bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient) { CNetClientSession* session = new CNetClientSession(*this); @@ -476,6 +483,15 @@ SendMessage(&loaded); } +void CNetClient::SendAuthenticateMessage() +{ + CAuthenticateMessage authenticate; + authenticate.m_Name = m_UserName; + authenticate.m_Password = L""; // TODO + authenticate.m_IsLocalClient = m_IsLocalClient; + SendMessage(&authenticate); +} + bool CNetClient::OnConnect(void* context, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE); @@ -516,12 +532,34 @@ CSrvHandshakeResponseMessage* message = (CSrvHandshakeResponseMessage*)event->GetParamRef(); client->m_GUID = message->m_GUID; - CAuthenticateMessage authenticate; - authenticate.m_Name = client->m_UserName; - authenticate.m_Password = L""; // TODO - authenticate.m_IsLocalClient = client->m_IsLocalClient; - client->SendMessage(&authenticate); + if (message->m_Flags & PS_REQUIRE_LOBBYAUTH_FLAG) + { + if (g_XmppClient && !client->m_HostingPlayerName.empty()) + g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID); + else + { + JSContext* cx = client->GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue msg(cx); + client->GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg); + client->GetScriptInterface().SetProperty(msg, "reason", (int)NDR_LOBBY_AUTH_FAILED, false); + client->PushGuiMessage(msg); + LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message"); + } + return true; + } + client->SendAuthenticateMessage(); + return true; +} + +bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event) +{ + ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE); + + CNetClient* client = (CNetClient*)context; + client->SendAuthenticateMessage(); return true; } Index: source/network/NetHost.h =================================================================== --- source/network/NetHost.h +++ source/network/NetHost.h @@ -67,7 +67,8 @@ NDR_KICKED, NDR_BANNED, NDR_PLAYERNAME_IN_USE, - NDR_SERVER_FULL + NDR_SERVER_FULL, + NDR_LOBBY_AUTH_FAILED }; class CNetHost Index: source/network/NetMessages.h =================================================================== --- source/network/NetMessages.h +++ source/network/NetMessages.h @@ -26,10 +26,13 @@ #include "ps/CStr.h" #include "scriptinterface/ScriptVal.h" -#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' -#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' -#define PS_PROTOCOL_VERSION 0x01010015 // Arbitrary protocol -#define PS_DEFAULT_PORT 0x5073 // 'P', 's' +#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' +#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' +#define PS_PROTOCOL_VERSION 0x01010015 // Arbitrary protocol +#define PS_DEFAULT_PORT 0x5073 // 'P', 's' + +// Set when lobby authentication is required. Used in the SrvHandshakeResponseMessage. +#define PS_REQUIRE_LOBBYAUTH_FLAG 0x1 // Defines the list of message types. The order of the list must not change. // The message types having a negative value are used internally and not sent Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -71,6 +71,9 @@ // to agree on the protocol version NSS_HANDSHAKE, + // The client has handshook and we're waiting for its lobby authentication message + NSS_LOBBY_AUTHENTICATE, + // The client has handshook and we're waiting for its authentication message, // to find its name and check its password etc NSS_AUTHENTICATE, @@ -104,7 +107,7 @@ * @param autostartPlayers if positive then StartGame will be called automatically * once this many players are connected (intended for the command-line testing mode). */ - CNetServer(int autostartPlayers = -1); + CNetServer(bool useLobbyAuth = false, int autostartPlayers = -1); ~CNetServer(); @@ -134,6 +137,8 @@ */ void SetTurnLength(u32 msecs); + void OnLobbyAuth(const CStr& name, const CStr& token); + void SendHolePunchingMessage(const CStr& ip, u16 port); private: @@ -178,7 +183,7 @@ friend class CNetServer; friend class CNetFileReceiveTask_ServerRejoin; - CNetServerWorker(int autostartPlayers); + CNetServerWorker(bool useLobbyAuth, int autostartPlayers); ~CNetServerWorker(); /** @@ -229,6 +234,8 @@ */ void SetTurnLength(u32 msecs); + void ProcessLobbyAuth(const CStr& name, const CStr& token); + void AddPlayer(const CStr& guid, const CStrW& name); void RemovePlayer(const CStr& guid); void SendPlayerAssignments(); @@ -291,6 +298,7 @@ JS::PersistentRootedValue m_GameAttributes; int m_AutostartPlayers; + bool m_LobbyAuth; ENetHost* m_Host; std::vector m_Sessions; @@ -351,12 +359,14 @@ pthread_t m_WorkerThread; CMutex m_WorkerMutex; - bool m_Shutdown; // protected by m_WorkerMutex + // protected by m_WorkerMutex + bool m_Shutdown; - // Queues for messages sent by the game thread: - std::vector m_StartGameQueue; // protected by m_WorkerMutex - std::vector m_GameAttributesQueue; // protected by m_WorkerMutex - std::vector m_TurnLengthQueue; // protected by m_WorkerMutex + // Queues for messages sent by the game thread (protected by m_WorkerMutex): + std::vector m_StartGameQueue; + std::vector m_GameAttributesQueue; + std::vector> m_LobbyAuthQueue; + std::vector m_TurnLengthQueue; }; /// Global network server for the standard game Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -127,8 +127,9 @@ * See http://trac.wildfiregames.com/ticket/654 */ -CNetServerWorker::CNetServerWorker(int autostartPlayers) : +CNetServerWorker::CNetServerWorker(bool useLobbyAuth, int autostartPlayers) : m_AutostartPlayers(autostartPlayers), + m_LobbyAuth(useLobbyAuth), m_Shutdown(false), m_ScriptInterface(NULL), m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL), @@ -407,6 +408,7 @@ std::vector newStartGame; std::vector newGameAttributes; + std::vector> newLobbyAuths; std::vector newTurnLength; { @@ -417,6 +419,7 @@ newStartGame.swap(m_StartGameQueue); newGameAttributes.swap(m_GameAttributesQueue); + newLobbyAuths.swap(m_LobbyAuthQueue); newTurnLength.swap(m_TurnLengthQueue); } @@ -434,6 +437,13 @@ if (!newStartGame.empty()) StartGame(); + while (!newLobbyAuths.empty()) + { + const std::pair& auth = newLobbyAuths.back(); + ProcessLobbyAuth(auth.first, auth.second); + newLobbyAuths.pop_back(); + } + // Perform file transfers for (CNetServerSession* session : m_Sessions) session->GetFileTransferer().Poll(); @@ -632,6 +642,9 @@ session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED); session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnClientHandshake, context); + session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED); + session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context); + session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED); session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context); @@ -856,6 +869,23 @@ m_ServerTurnManager->SetTurnLength(msecs); } +void CNetServerWorker::ProcessLobbyAuth(const CStr& name, const CStr& token) +{ + LOGMESSAGE("Net Server: Received lobby auth message from %s with %s", name, token); + // Find the user with that guid + std::vector::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(), + [&](CNetServerSession* session) + { return session->GetGUID() == token; }); + + if (it == m_Sessions.end()) + return; + + (*it)->SetUserName(name.FromUTF8()); + // Send an empty mesage to request authentication from client + CAuthenticateMessage emptyMessage; + (*it)->SendMessage(&emptyMessage); +} + bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE); @@ -892,6 +922,13 @@ handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; handshakeResponse.m_GUID = guid; handshakeResponse.m_Flags = 0; + + if (server.m_LobbyAuth) + { + handshakeResponse.m_Flags = PS_REQUIRE_LOBBYAUTH_FLAG; + session->SetNextState(NSS_LOBBY_AUTHENTICATE); + } + session->SendMessage(&handshakeResponse); return true; @@ -914,20 +951,34 @@ CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef(); CStrW username = SanitisePlayerName(message->m_Name); + CStrW usernameWithoutRating(username.substr(0, username.find(L" ("))); + + if (server.m_LobbyAuth && usernameWithoutRating.LowerCase() != session->GetUserName().LowerCase()) + { + LOGERROR("Net server: lobby auth: %s tried joining as %s", + usernameWithoutRating.ToUTF8(), + session->GetUserName().ToUTF8()); + session->Disconnect(NDR_LOBBY_AUTH_FAILED); + return true; + } // Either deduplicate or prohibit join if name is in use bool duplicatePlayernames = false; CFG_GET_VAL("network.duplicateplayernames", duplicatePlayernames); if (duplicatePlayernames) username = server.DeduplicatePlayerName(username); - else if (std::find_if( + else + { + std::vector::iterator it = std::find_if( server.m_Sessions.begin(), server.m_Sessions.end(), [&username] (const CNetServerSession* session) - { return session->GetUserName() == username; }) - != server.m_Sessions.end()) - { - session->Disconnect(NDR_PLAYERNAME_IN_USE); - return true; + { return session->GetUserName() == username; }); + + if (it != server.m_Sessions.end() && (*it) != session) + { + session->Disconnect(NDR_PLAYERNAME_IN_USE); + return true; + } } // Disconnect banned usernames @@ -984,7 +1035,6 @@ } else if (observerLateJoin == "buddies") { - CStrW usernameWithoutRating(username.substr(0, username.find(L" ("))); CStr buddies; CFG_GET_VAL("lobby.buddies", buddies); std::wstringstream buddiesStream(wstring_from_utf8(buddies)); @@ -1481,8 +1531,8 @@ -CNetServer::CNetServer(int autostartPlayers) : - m_Worker(new CNetServerWorker(autostartPlayers)) +CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) : + m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)) { } @@ -1512,6 +1562,12 @@ m_Worker->m_GameAttributesQueue.push_back(attrsJSON); } +void CNetServer::OnLobbyAuth(const CStr& name, const CStr& token) +{ + CScopeLock lock(m_Worker->m_WorkerMutex); + m_Worker->m_LobbyAuthQueue.push_back(std::make_pair(name, token)); +} + void CNetServer::SetTurnLength(u32 msecs) { CScopeLock lock(m_Worker->m_WorkerMutex); Index: source/network/scripting/JSInterface_Network.h =================================================================== --- source/network/scripting/JSInterface_Network.h +++ source/network/scripting/JSInterface_Network.h @@ -26,7 +26,7 @@ u16 GetDefaultPort(ScriptInterface::CxPrivate* pCxPrivate); void StartNetworkGame(ScriptInterface::CxPrivate* pCxPrivate); void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1); - void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const u16 serverPort); + void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostName, bool useLobbyAuth); void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID); JS::Value FindStunEndpoint(ScriptInterface::CxPrivate* pCxPrivate, int port); void DisconnectNetworkGame(ScriptInterface::CxPrivate* pCxPrivate); Index: source/network/scripting/JSInterface_Network.cpp =================================================================== --- source/network/scripting/JSInterface_Network.cpp +++ source/network/scripting/JSInterface_Network.cpp @@ -39,13 +39,13 @@ return StunClient::FindStunEndpointHost(*(pCxPrivate->pScriptInterface), port); } -void JSI_Network::StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const u16 serverPort) +void JSI_Network::StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostName, bool useLobbyAuth) { ENSURE(!g_NetClient); ENSURE(!g_NetServer); ENSURE(!g_Game); - g_NetServer = new CNetServer(); + g_NetServer = new CNetServer(useLobbyAuth); if (!g_NetServer->SetupConnection(serverPort)) { pCxPrivate->pScriptInterface->ReportError("Failed to start server"); @@ -56,6 +56,7 @@ g_Game = new CGame(); g_NetClient = new CNetClient(g_Game, true); g_NetClient->SetUserName(playerName); + g_NetClient->SetHostingPlayerName(hostName); if (!g_NetClient->SetupConnection("127.0.0.1", serverPort)) { @@ -106,6 +107,7 @@ g_Game = new CGame(); g_NetClient = new CNetClient(g_Game, false); g_NetClient->SetUserName(playerName); + g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); if (g_XmppClient && useSTUN) StunClient::SendHolePunchingMessages(enetClient, serverAddress.c_str(), serverPort); @@ -213,7 +215,7 @@ { scriptInterface.RegisterFunction("GetDefaultPort"); scriptInterface.RegisterFunction("FindStunEndpoint"); - scriptInterface.RegisterFunction("StartNetworkHost"); + scriptInterface.RegisterFunction("StartNetworkHost"); scriptInterface.RegisterFunction("StartNetworkJoin"); scriptInterface.RegisterFunction("DisconnectNetworkGame"); scriptInterface.RegisterFunction("GetPlayerGUID"); Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -1516,7 +1516,7 @@ if (args.Has("autostart-host-players")) maxPlayers = args.Get("autostart-host-players").ToUInt(); - g_NetServer = new CNetServer(maxPlayers); + g_NetServer = new CNetServer(false, maxPlayers); g_NetServer->UpdateGameAttributes(&attrs, scriptInterface);