Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -381,6 +381,7 @@ server = "lobby.wildfiregames.com" ; Address of lobby server 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 +secureauth = true ; Secure Lobby Authentication: Prevent players joining as different players [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,7 +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("Player identifier in use. If you were disconnected, retry in few seconds"); + 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"); @@ -197,8 +199,8 @@ Engine.SwitchGuiPage("page_loading.xml", { "attribs": g_GameAttributes, - "isNetworked" : true, - "isRejoining" : g_IsRejoining, + "isNetworked": true, + "isRejoining": g_IsRejoining, "playerAssignments": g_PlayerAssignments }); break; @@ -233,16 +235,13 @@ g_IsRejoining = true; return; // we'll process the game setup messages in the next tick } - else - { - Engine.SwitchGuiPage("page_gamesetup.xml", { - "type": g_GameType, - "serverName": g_ServerName, - "serverPort": g_ServerPort, - "stunEndpoint": g_StunEndpoint - }); - return; // don't process any more messages - leave them for the game GUI loop - } + Engine.SwitchGuiPage("page_gamesetup.xml", { + "type": g_GameType, + "serverName": g_ServerName, + "serverPort": g_ServerPort, + "stunEndpoint": g_StunEndpoint + }); + return; // don't process any more messages - leave them for the game GUI loop case "disconnected": cancelSetup(); @@ -268,10 +267,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 hHeight = newPage == "pageJoin" ? 130 : Engine.HasXmppClient() ? 145 : 110; + pageSize.top = -hHeight; + pageSize.bottom = hHeight; + multiplayerPages.size = pageSize; + } + Engine.GetGUIObjectByName(newPage).hidden = false; Engine.GetGUIObjectByName("hostPlayerNameWrapper").hidden = Engine.HasXmppClient(); @@ -280,10 +289,10 @@ Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting"; } -function saveSTUNSetting(enabled) +function saveHostingSetting(setting, enabled) { - Engine.ConfigDB_CreateValue("user", "lobby.stun.enabled", enabled); - Engine.ConfigDB_WriteValueToFile("user", "lobby.stun.enabled", enabled, "config/user.cfg"); + Engine.ConfigDB_CreateValue("user", "lobby." + setting, enabled); + Engine.ConfigDB_WriteValueToFile("user", "lobby." + setting, enabled, "config/user.cfg"); } function startHost(playername, servername, port) @@ -320,12 +329,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 @@ -109,17 +109,27 @@ - - - saveSTUNSetting(String(this.checked)); + + + saveHostingSetting("stun.enabled", String(this.checked)); - + Use STUN to work around firewalls + + + + saveHostingSetting("secureauth", String(this.checked)); + + + Require Lobby authentication + Prevent anyone joining as different player + + - + Continue Index: source/lobby/IXmppClient.h =================================================================== --- source/lobby/IXmppClient.h +++ source/lobby/IXmppClient.h @@ -40,6 +40,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 @@ -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,20 @@ 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 @@ -238,3 +238,49 @@ 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 GameList 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 game object into XML for sending. + */ +glooxwrapper::Tag* LobbyAuth::tag() const +{ + glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("auth"); + t->setXmlns(XMLNS_LOBBYAUTH); + + // Check for register / unregister command + if (!m_Token.empty()) + t->addChild(glooxwrapper::Tag::allocate("token", m_Token)); + return t; +} + +glooxwrapper::StanzaExtension* LobbyAuth::clone() const +{ + LobbyAuth* q = new LobbyAuth(); + return q; +} 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; @@ -68,6 +70,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 @@ -35,7 +35,7 @@ #include "scriptinterface/ScriptInterface.h" //debug -#if 1 +#if 0 #define DbgXMPP(x) #else #define DbgXMPP(x) std::cout << x << std::endl; @@ -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_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_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 @@ -409,6 +411,26 @@ } /***************************************************** + * 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::Result, clientJid); + iq.addExtension(auth); + DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]"); + m_client->send(iq); +} + + +/***************************************************** * Account registration * *****************************************************/ @@ -684,6 +706,7 @@ const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY); const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY); const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY); + const LobbyAuth* la = iq.findExtension(EXTLOBBYAUTH); if (gq) { for (const glooxwrapper::Tag* const& t : m_GameList) @@ -720,6 +743,13 @@ CreateGUIMessage("game", "ratinglist"); } } + if (la) + { + LOGMESSAGE("XmppClient: Received lobbyauth: %s from %s", la->m_Token.to_string(), iq.fromName().to_string()); + + if (g_NetServer) + g_NetServer->OnLobbyAuth(iq.fromName().to_string(), la->m_Token.to_string()); + } if (pq) { for (const glooxwrapper::Tag* const& t : m_Profile) Index: source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- source/lobby/glooxwrapper/glooxwrapper.h +++ source/lobby/glooxwrapper/glooxwrapper.h @@ -461,6 +461,7 @@ } gloox::IQ::IqType subtype() const; + string fromName() 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 @@ -476,6 +476,11 @@ return m_Wrapped->subtype(); } +glooxwrapper::string glooxwrapper::IQ::fromName() const +{ + return m_Wrapped->from().username(); +} + glooxwrapper::JID::JID() { Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -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 SetHostName(const CStr& hostName); + + /** * 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_HostName; /// Current network session (or NULL if not connected) CNetClientSession* m_Session; Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -26,12 +26,12 @@ #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" #include "ps/CStr.h" #include "ps/Game.h" -#include "ps/GUID.h" #include "ps/Loader.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" @@ -70,7 +70,7 @@ CNetClient::CNetClient(CGame* game, bool isLocalClient) : m_Session(NULL), m_UserName(L"anonymous"), - m_GUID(ps_generate_guid()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game), + m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game), m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()), m_IsLocalClient(isLocalClient), m_LastConnectionCheck(0), @@ -89,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); @@ -159,6 +160,11 @@ m_UserName = username; } +void CNetClient::SetHostName(const CStr& hostName) +{ + m_HostName = hostName; +} + bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient) { CNetClientSession* session = new CNetClientSession(*this); @@ -477,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); @@ -514,13 +529,38 @@ CNetClient* client = (CNetClient*)context; - CAuthenticateMessage authenticate; - authenticate.m_GUID = client->m_GUID; - authenticate.m_Name = client->m_UserName; - authenticate.m_Password = L""; // TODO - authenticate.m_IsLocalClient = client->m_IsLocalClient; - client->SendMessage(&authenticate); + CSrvHandshakeResponseMessage* message = (CSrvHandshakeResponseMessage*)event->GetParamRef(); + client->m_GUID = message->m_GUID; + + if (message->m_Flags & SRVHSRES_LOBBYAUTH_FLAG) + { + if (g_XmppClient && !client->m_HostName.empty()) + g_XmppClient->SendIqLobbyAuth(client->m_HostName, client->m_GUID); + else + { + LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message"); + + 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); + } + 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 @@ -68,7 +68,7 @@ NDR_BANNED, NDR_PLAYERNAME_IN_USE, NDR_SERVER_FULL, - NDR_PLAYERGUID_IN_USE + NDR_LOBBY_AUTH_FAILED }; class CNetHost Index: source/network/NetMessages.h =================================================================== --- source/network/NetMessages.h +++ source/network/NetMessages.h @@ -31,6 +31,7 @@ #define PS_PROTOCOL_VERSION 0x01010015 // Arbitrary protocol #define PS_DEFAULT_PORT 0x5073 // 'P', 's' +#define SRVHSRES_LOBBYAUTH_FLAG 0x1 // Set when lobby authentication is required // 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 // over the network. The message types used for network communication have @@ -113,11 +114,10 @@ START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE) NMT_FIELD_INT(m_UseProtocolVersion, u32, 4) NMT_FIELD_INT(m_Flags, u32, 4) - NMT_FIELD(CStrW, m_Message) + NMT_FIELD(CStr, m_GUID) END_NMT_CLASS() START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE) - NMT_FIELD(CStr, m_GUID) NMT_FIELD(CStrW, m_Name) NMT_FIELD(CStrW, m_Password) NMT_FIELD_INT(m_IsLocalClient, u8, 1) 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(CStr name, 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(); /** @@ -291,6 +296,7 @@ JS::PersistentRootedValue m_GameAttributes; int m_AutostartPlayers; + bool m_LobbyAuth; ENetHost* m_Host; std::vector m_Sessions; @@ -357,6 +363,7 @@ // 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_LobbyAuthQueue; // protected by m_WorkerMutex std::vector m_TurnLengthQueue; // protected by m_WorkerMutex }; Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -29,6 +29,7 @@ #include "network/StunClient.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" +#include "ps/GUID.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRuntime.h" @@ -127,8 +128,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), @@ -408,6 +410,7 @@ std::vector newStartGame; std::vector newGameAttributes; + std::vector newLobbyAuths; std::vector newTurnLength; { @@ -418,6 +421,7 @@ newStartGame.swap(m_StartGameQueue); newGameAttributes.swap(m_GameAttributesQueue); + newLobbyAuths.swap(m_LobbyAuthQueue); newTurnLength.swap(m_TurnLengthQueue); } @@ -435,6 +439,28 @@ if (!newStartGame.empty()) StartGame(); + while (!newLobbyAuths.empty()) + { + CStr token = newLobbyAuths.back(); + newLobbyAuths.pop_back(); + CStr name = newLobbyAuths.back(); + newLobbyAuths.pop_back(); + 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()) + continue; + + (*it)->SetUserName(name.FromUTF8()); + // Send an empty mesage to request authentication from client + CAuthenticateMessage emptyMessage; + (*it)->SendMessage(&emptyMessage); + } + // Perform file transfers for (CNetServerSession* session : m_Sessions) session->GetFileTransferer().Poll(); @@ -633,6 +659,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); @@ -871,10 +900,35 @@ return false; } + // Ensure unique GUID + CStr guid = ps_generate_guid(); + int count = 0; + while(std::find_if( + server.m_Sessions.begin(), server.m_Sessions.end(), + [&guid] (const CNetServerSession* session) + { return session->GetGUID() == guid; }) != server.m_Sessions.end()) + { + if (++count > 100) + { + session->Disconnect(NDR_UNKNOWN); + return true; + } + guid = ps_generate_guid(); + } + + session->SetGUID(guid); + CSrvHandshakeResponseMessage handshakeResponse; handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; - handshakeResponse.m_Message = server.m_WelcomeMessage; + handshakeResponse.m_GUID = guid; handshakeResponse.m_Flags = 0; + + if (server.m_LobbyAuth) + { + handshakeResponse.m_Flags = SRVHSRES_LOBBYAUTH_FLAG; + session->SetNextState(NSS_LOBBY_AUTHENTICATE); + } + session->SendMessage(&handshakeResponse); return true; @@ -897,33 +951,38 @@ CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef(); CStrW username = SanitisePlayerName(message->m_Name); - CStr guid = message->m_GUID; + CStrW usernameWithoutRating(username.substr(0, username.find(L" ("))); + + if (server.m_LobbyAuth && usernameWithoutRating.LowerCase() != session->GetUserName().LowerCase()) + { + LOGWARNING("Net server: lobby auth: %s tried joining as %s", + utf8_from_wstring(usernameWithoutRating), + utf8_from_wstring(session->GetUserName())); + 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( - server.m_Sessions.begin(), server.m_Sessions.end(), - [&username] (const CNetServerSession* session) - { return session->GetUserName() == username; }) - != server.m_Sessions.end()) + else { - session->Disconnect(NDR_PLAYERNAME_IN_USE); - return true; - } + std::vector::iterator it = std::find_if( + server.m_Sessions.begin(), server.m_Sessions.end(), + [&username] (const CNetServerSession* session) + { return session->GetUserName() == username; }); - // Disconnect user if the provided GUID is already in use - if (std::find_if( - server.m_Sessions.begin(), server.m_Sessions.end(), - [&guid] (const CNetServerSession* session) - { return session->GetGUID() == guid; }) != server.m_Sessions.end()) - { - session->Disconnect(NDR_PLAYERGUID_IN_USE); - return true; + if (it != server.m_Sessions.end() && (*it) != session) + { + session->Disconnect(NDR_PLAYERNAME_IN_USE); + return true; + } } + // Disconnect banned usernames if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), username) != server.m_BannedPlayers.end()) { @@ -978,7 +1037,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)); @@ -1018,7 +1076,6 @@ u32 newHostID = server.m_NextHostID++; session->SetUserName(username); - session->SetGUID(message->m_GUID); session->SetHostID(newHostID); session->SetLocalClient(message->m_IsLocalClient); @@ -1476,8 +1533,8 @@ -CNetServer::CNetServer(int autostartPlayers) : - m_Worker(new CNetServerWorker(autostartPlayers)) +CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) : + m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)) { } @@ -1507,6 +1564,13 @@ m_Worker->m_GameAttributesQueue.push_back(attrsJSON); } +void CNetServer::OnLobbyAuth(CStr name, CStr token) +{ + CScopeLock lock(m_Worker->m_WorkerMutex); + m_Worker->m_LobbyAuthQueue.push_back(name); + m_Worker->m_LobbyAuthQueue.push_back(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->SetHostName(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->SetHostName(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 @@ -1548,7 +1548,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);