forked from 0ad/0ad
Secure lobby authentication - prevent joins as a different player
Reviewed by: Dunedan, elexis, Itms Fixes #3549 Differential Revision: https://code.wildfiregames.com/D897 This was SVN commit r21520.
This commit is contained in:
parent
520f70ab2c
commit
0fd8aa2a77
@ -409,6 +409,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
|
||||
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
|
||||
|
@ -73,6 +73,7 @@ function getDisconnectReason(id, wasConnected)
|
||||
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 });
|
||||
|
@ -48,6 +48,7 @@ function init(attribs)
|
||||
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 @@ function init(attribs)
|
||||
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 pollAndHandleNetworkClient()
|
||||
|
||||
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 @@ function startHost(playername, servername, port)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -106,17 +106,27 @@
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object name="hostSTUNWrapper">
|
||||
<object name="useSTUN" size="120 116 152 146" type="checkbox" style="ModernTickBox">
|
||||
<object name="hostSTUNWrapper" size="120 106 100% 146">
|
||||
<object name="useSTUN" size="0 10 32 100%" type="checkbox" style="ModernTickBox">
|
||||
<action on="Press">saveSettingAndWriteToUserConfig("lobby.stun.enabled", String(this.checked));</action>
|
||||
</object>
|
||||
<object type="text" size="146 106 100% 146" style="ModernLeftLabelText">
|
||||
<object type="text" size="26 0 100% 100%" style="ModernLeftLabelText">
|
||||
<translatableAttribute id="caption">Use STUN to work around firewalls</translatableAttribute>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object name="hostLobbyAuthWrapper" size="120 141 100% 181">
|
||||
<object name="useLobbyAuth" size="0 10 32 100%" type="checkbox" style="ModernTickBox">
|
||||
<action on="Press">saveSettingAndWriteToUserConfig("lobby.secureauth", String(this.checked));</action>
|
||||
</object>
|
||||
<object type="text" size="26 0 100% 100%" style="ModernLeftLabelText">
|
||||
<translatableAttribute id="caption">Require Lobby Authentication</translatableAttribute>
|
||||
<translatableAttribute id="tooltip">This prevents the impersonation of other players. The lobby server confirms the identity of the player before they join.</translatableAttribute>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object name="hostFeedback" type="text" style="ModernLabelText" size="50 100%-80 100%-50 100%-50" textcolor="red"/>
|
||||
<object name="hostFeedback" type="text" style="ModernLabelText" size="50 100%-80 100%-50 100%-45" textcolor="red"/>
|
||||
|
||||
<object name="continueButton" hotkey="confirm" type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
|
||||
<translatableAttribute id="caption">Continue</translatableAttribute>
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
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;
|
||||
|
@ -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 @@ BoardListQuery::~BoardListQuery()
|
||||
* 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 @@ ProfileQuery::~ProfileQuery()
|
||||
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();
|
||||
}
|
||||
|
@ -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 @@ public:
|
||||
glooxwrapper::string m_Command;
|
||||
std::vector<const glooxwrapper::Tag*> 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
|
||||
|
@ -75,24 +75,23 @@ IXmppClient* IXmppClient::create(const std::string& sUsername, const std::string
|
||||
* @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 @@ XmppClient::XmppClient(const std::string& sUsername, const std::string& sPasswor
|
||||
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
|
||||
@ -416,6 +418,25 @@ void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string
|
||||
m_client->send(iq);
|
||||
}
|
||||
|
||||
/*****************************************************
|
||||
* 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 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
|
||||
CreateGUIMessage("game", "profile");
|
||||
}
|
||||
}
|
||||
else if (iq.subtype() == gloox::IQ::Set)
|
||||
{
|
||||
const LobbyAuth* lobbyAuth = iq.findExtension<LobbyAuth>(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
|
||||
|
@ -47,6 +47,8 @@ private:
|
||||
// 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 @@ public:
|
||||
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);
|
||||
|
@ -480,6 +480,15 @@ gloox::IQ::IqType glooxwrapper::IQ::subtype() const
|
||||
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()
|
||||
{
|
||||
|
@ -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 @@ namespace glooxwrapper
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -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 @@ CNetClient::CNetClient(CGame* game, bool isLocalClient) :
|
||||
|
||||
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 @@ void CNetClient::SetUserName(const CStrW& username)
|
||||
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 @@ void CNetClient::LoadFinished()
|
||||
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 @@ bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
|
||||
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_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -90,6 +90,12 @@ public:
|
||||
*/
|
||||
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 @@ public:
|
||||
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 @@ private:
|
||||
|
||||
CGame *m_Game;
|
||||
CStrW m_UserName;
|
||||
CStr m_HostingPlayerName;
|
||||
|
||||
/// Current network session (or NULL if not connected)
|
||||
CNetClientSession* m_Session;
|
||||
|
@ -67,7 +67,8 @@ enum NetDisconnectReason
|
||||
NDR_KICKED,
|
||||
NDR_BANNED,
|
||||
NDR_PLAYERNAME_IN_USE,
|
||||
NDR_SERVER_FULL
|
||||
NDR_SERVER_FULL,
|
||||
NDR_LOBBY_AUTH_FAILED
|
||||
};
|
||||
|
||||
class CNetHost
|
||||
|
@ -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_NETWORK_FLAG_REQUIRE_LOBBYAUTH 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
|
||||
|
@ -127,8 +127,9 @@ private:
|
||||
* 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 @@ bool CNetServerWorker::RunStep()
|
||||
|
||||
std::vector<bool> newStartGame;
|
||||
std::vector<std::string> newGameAttributes;
|
||||
std::vector<std::pair<CStr, CStr>> newLobbyAuths;
|
||||
std::vector<u32> newTurnLength;
|
||||
|
||||
{
|
||||
@ -417,6 +419,7 @@ bool CNetServerWorker::RunStep()
|
||||
|
||||
newStartGame.swap(m_StartGameQueue);
|
||||
newGameAttributes.swap(m_GameAttributesQueue);
|
||||
newLobbyAuths.swap(m_LobbyAuthQueue);
|
||||
newTurnLength.swap(m_TurnLengthQueue);
|
||||
}
|
||||
|
||||
@ -434,6 +437,13 @@ bool CNetServerWorker::RunStep()
|
||||
if (!newStartGame.empty())
|
||||
StartGame();
|
||||
|
||||
while (!newLobbyAuths.empty())
|
||||
{
|
||||
const std::pair<CStr, CStr>& 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 @@ void CNetServerWorker::SetupSession(CNetServerSession* session)
|
||||
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,24 @@ void CNetServerWorker::SetTurnLength(u32 msecs)
|
||||
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<CNetServerSession*>::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 message to request the authentication message from the client
|
||||
// after its identity has been confirmed via the lobby
|
||||
CAuthenticateMessage emptyMessage;
|
||||
(*it)->SendMessage(&emptyMessage);
|
||||
}
|
||||
|
||||
bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
|
||||
{
|
||||
ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
|
||||
@ -892,6 +923,13 @@ bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
|
||||
handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
|
||||
handshakeResponse.m_GUID = guid;
|
||||
handshakeResponse.m_Flags = 0;
|
||||
|
||||
if (server.m_LobbyAuth)
|
||||
{
|
||||
handshakeResponse.m_Flags |= PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH;
|
||||
session->SetNextState(NSS_LOBBY_AUTHENTICATE);
|
||||
}
|
||||
|
||||
session->SendMessage(&handshakeResponse);
|
||||
|
||||
return true;
|
||||
@ -914,20 +952,36 @@ bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
|
||||
|
||||
CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
|
||||
CStrW username = SanitisePlayerName(message->m_Name);
|
||||
CStrW usernameWithoutRating(username.substr(0, username.find(L" (")));
|
||||
|
||||
// Compare the lowercase names as specified by https://xmpp.org/extensions/xep-0029.html#sect-idm139493404168176
|
||||
// "[...] comparisons will be made in case-normalized canonical form."
|
||||
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<CNetServerSession*>::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 +1038,6 @@ bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
|
||||
}
|
||||
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 +1534,8 @@ void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
|
||||
|
||||
|
||||
|
||||
CNetServer::CNetServer(int autostartPlayers) :
|
||||
m_Worker(new CNetServerWorker(autostartPlayers))
|
||||
CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) :
|
||||
m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers))
|
||||
{
|
||||
}
|
||||
|
||||
@ -1512,6 +1565,12 @@ void CNetServer::UpdateGameAttributes(JS::MutableHandleValue attrs, const Script
|
||||
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);
|
||||
|
@ -71,6 +71,9 @@ enum NetServerSessionState
|
||||
// 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 @@ public:
|
||||
* @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 @@ public:
|
||||
*/
|
||||
void SetTurnLength(u32 msecs);
|
||||
|
||||
void OnLobbyAuth(const CStr& name, const CStr& token);
|
||||
|
||||
void SendHolePunchingMessage(const CStr& ip, u16 port);
|
||||
|
||||
private:
|
||||
@ -178,7 +183,7 @@ private:
|
||||
friend class CNetServer;
|
||||
friend class CNetFileReceiveTask_ServerRejoin;
|
||||
|
||||
CNetServerWorker(int autostartPlayers);
|
||||
CNetServerWorker(bool useLobbyAuth, int autostartPlayers);
|
||||
~CNetServerWorker();
|
||||
|
||||
/**
|
||||
@ -229,6 +234,8 @@ private:
|
||||
*/
|
||||
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 @@ private:
|
||||
JS::PersistentRootedValue m_GameAttributes;
|
||||
|
||||
int m_AutostartPlayers;
|
||||
bool m_LobbyAuth;
|
||||
|
||||
ENetHost* m_Host;
|
||||
std::vector<CNetServerSession*> m_Sessions;
|
||||
@ -351,12 +359,14 @@ private:
|
||||
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<bool> m_StartGameQueue; // protected by m_WorkerMutex
|
||||
std::vector<std::string> m_GameAttributesQueue; // protected by m_WorkerMutex
|
||||
std::vector<u32> m_TurnLengthQueue; // protected by m_WorkerMutex
|
||||
// Queues for messages sent by the game thread (protected by m_WorkerMutex):
|
||||
std::vector<bool> m_StartGameQueue;
|
||||
std::vector<std::string> m_GameAttributesQueue;
|
||||
std::vector<std::pair<CStr, CStr>> m_LobbyAuthQueue;
|
||||
std::vector<u32> m_TurnLengthQueue;
|
||||
};
|
||||
|
||||
/// Global network server for the standard game
|
||||
|
@ -39,13 +39,13 @@ JS::Value JSI_Network::FindStunEndpoint(ScriptInterface::CxPrivate* pCxPrivate,
|
||||
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& hostLobbyName, 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 @@ void JSI_Network::StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const
|
||||
g_Game = new CGame();
|
||||
g_NetClient = new CNetClient(g_Game, true);
|
||||
g_NetClient->SetUserName(playerName);
|
||||
g_NetClient->SetHostingPlayerName(hostLobbyName);
|
||||
|
||||
if (!g_NetClient->SetupConnection("127.0.0.1", serverPort))
|
||||
{
|
||||
@ -106,6 +107,7 @@ void JSI_Network::StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const
|
||||
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 @@ void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface
|
||||
{
|
||||
scriptInterface.RegisterFunction<u16, &GetDefaultPort>("GetDefaultPort");
|
||||
scriptInterface.RegisterFunction<JS::Value, int, &FindStunEndpoint>("FindStunEndpoint");
|
||||
scriptInterface.RegisterFunction<void, CStrW, u16, &StartNetworkHost>("StartNetworkHost");
|
||||
scriptInterface.RegisterFunction<void, CStrW, u16, CStr, bool, &StartNetworkHost>("StartNetworkHost");
|
||||
scriptInterface.RegisterFunction<void, CStrW, CStr, u16, bool, CStr, &StartNetworkJoin>("StartNetworkJoin");
|
||||
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
|
||||
scriptInterface.RegisterFunction<CStr, &GetPlayerGUID>("GetPlayerGUID");
|
||||
|
@ -26,7 +26,7 @@ namespace JSI_Network
|
||||
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& hostLobbyName, 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);
|
||||
|
@ -1516,7 +1516,7 @@ bool Autostart(const CmdLineArgs& args)
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user