LCOV - code coverage report
Current view: top level - source/network/scripting - JSInterface_Network.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 21 142 14.8 %
Date: 2023-01-19 00:18:29 Functions: 3 21 14.3 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "JSInterface_Network.h"
      21             : 
      22             : #include "lib/external_libraries/enet.h"
      23             : #include "lib/external_libraries/libsdl.h"
      24             : #include "lib/types.h"
      25             : #include "lobby/IXmppClient.h"
      26             : #include "network/NetClient.h"
      27             : #include "network/NetMessage.h"
      28             : #include "network/NetServer.h"
      29             : #include "network/StunClient.h"
      30             : #include "ps/CLogger.h"
      31             : #include "ps/CStr.h"
      32             : #include "ps/Game.h"
      33             : #include "ps/GUID.h"
      34             : #include "ps/Hashing.h"
      35             : #include "ps/Pyrogenesis.h"
      36             : #include "ps/Util.h"
      37             : #include "scriptinterface/FunctionWrapper.h"
      38             : #include "scriptinterface/StructuredClone.h"
      39             : #include "scriptinterface/JSON.h"
      40             : 
      41             : #include "third_party/encryption/pkcs5_pbkdf2.h"
      42             : 
      43             : namespace JSI_Network
      44             : {
      45           0 : u16 GetDefaultPort()
      46             : {
      47           0 :     return PS_DEFAULT_PORT;
      48             : }
      49             : 
      50           0 : bool IsNetController()
      51             : {
      52           0 :     return !!g_NetClient && g_NetClient->IsController();
      53             : }
      54             : 
      55           0 : bool HasNetServer()
      56             : {
      57           0 :     return !!g_NetServer;
      58             : }
      59             : 
      60           0 : bool HasNetClient()
      61             : {
      62           0 :     return !!g_NetClient;
      63             : }
      64             : 
      65           0 : void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u16 serverPort, bool useSTUN, const CStr& password, bool storeReplay)
      66             : {
      67           0 :     ENSURE(!g_NetClient);
      68           0 :     ENSURE(!g_NetServer);
      69           0 :     ENSURE(!g_Game);
      70             : 
      71             :     // Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames.
      72           0 :     bool hasLobby = !!g_XmppClient;
      73           0 :     g_NetServer = new CNetServer(hasLobby);
      74             : 
      75           0 :     if (!g_NetServer->SetupConnection(serverPort))
      76             :     {
      77           0 :         ScriptException::Raise(rq, "Failed to start server");
      78           0 :         SAFE_DELETE(g_NetServer);
      79           0 :         return;
      80             :     }
      81             : 
      82             :     // In lobby, we send our public ip and port on request to the players who want to connect.
      83             :     // Thus we need to know our public IP. Use STUN if that's available,
      84             :     // otherwise, the lobby's reponse to the game registration stanza will tell us our public IP.
      85           0 :     if (hasLobby)
      86             :     {
      87           0 :         if (!useSTUN)
      88             :             // Don't store IP - the lobby bot will send it later.
      89             :             // (if a client tries to connect before it's setup, they'll be disconnected)
      90           0 :             g_NetServer->SetConnectionData("", serverPort);
      91           0 :         else if (!g_NetServer->SetConnectionDataViaSTUN())
      92             :         {
      93           0 :             ScriptException::Raise(rq, "Failed to host via STUN.");
      94           0 :             SAFE_DELETE(g_NetServer);
      95           0 :             return;
      96             :         }
      97             :     }
      98             : 
      99             :     // Generate a secret to identify the host client.
     100           0 :     std::string secret = ps_generate_guid();
     101           0 :     g_NetServer->SetControllerSecret(secret);
     102             : 
     103           0 :     g_Game = new CGame(storeReplay);
     104           0 :     g_NetClient = new CNetClient(g_Game);
     105           0 :     g_NetClient->SetUserName(playerName);
     106             : 
     107           0 :     if (hasLobby)
     108             :     {
     109           0 :         CStr hostJID = g_XmppClient->GetJID();
     110             : 
     111             :         /**
     112             :          * Password security - we want 0 A.D. to protect players from malicious hosts. We assume that clients
     113             :          * might mistakenly send a personal password instead of the game password (e.g. enter their mail account's password on autopilot).
     114             :          * Malicious dedicated servers might be set up to farm these failed logins and possibly obtain user credentials.
     115             :          * Therefore, we hash the passwords on the client side before sending them to the server.
     116             :          * This still makes the passwords potentially recoverable, but makes it much harder at scale.
     117             :          * To prevent the creation of rainbow tables, hash with:
     118             :          * - the host name
     119             :          * - the client name (this makes rainbow tables completely unworkable unless a specific user is targeted,
     120             :          *   but that would require both computing the matching rainbow table _and_ for that specific user to mistype a personal password,
     121             :          *   at which point we assume the attacker would/could probably just rather use another means of obtaining the password).
     122             :          * - the password itself
     123             :          * - the engine version (so that the hashes change periodically)
     124             :          * TODO: it should be possible to implement SRP or something along those lines to completely protect from this,
     125             :          * but the cost/benefit ratio is probably not worth it.
     126             :          */
     127           0 :         CStr hashedPass = HashCryptographically(password, hostJID + password + engine_version);
     128           0 :         g_NetServer->SetPassword(hashedPass);
     129           0 :         g_NetClient->SetHostJID(hostJID);
     130           0 :         g_NetClient->SetGamePassword(hashedPass);
     131             :     }
     132             : 
     133           0 :     g_NetClient->SetupServerData("127.0.0.1", serverPort, false);
     134           0 :     g_NetClient->SetControllerSecret(secret);
     135             : 
     136           0 :     if (!g_NetClient->SetupConnection(nullptr))
     137             :     {
     138           0 :         ScriptException::Raise(rq, "Failed to connect to server");
     139           0 :         SAFE_DELETE(g_NetClient);
     140           0 :         SAFE_DELETE(g_Game);
     141             :     }
     142             : }
     143             : 
     144           0 : void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool storeReplay)
     145             : {
     146           0 :     ENSURE(!g_NetClient);
     147           0 :     ENSURE(!g_NetServer);
     148           0 :     ENSURE(!g_Game);
     149             : 
     150           0 :     g_Game = new CGame(storeReplay);
     151           0 :     g_NetClient = new CNetClient(g_Game);
     152           0 :     g_NetClient->SetUserName(playerName);
     153           0 :     g_NetClient->SetupServerData(serverAddress, serverPort, false);
     154             : 
     155           0 :     if (!g_NetClient->SetupConnection(nullptr))
     156             :     {
     157           0 :         ScriptException::Raise(rq, "Failed to connect to server");
     158           0 :         SAFE_DELETE(g_NetClient);
     159           0 :         SAFE_DELETE(g_Game);
     160             :     }
     161           0 : }
     162             : 
     163             : /**
     164             :  * Requires XmppClient to send iq request to the server to get server's ip and port based on passed password.
     165             :  * This is needed to not force server to share it's public ip with all potential clients in the lobby.
     166             :  * XmppClient will also handle logic after receiving the answer.
     167             :  */
     168           0 : void StartNetworkJoinLobby(const CStrW& playerName, const CStr& hostJID, const CStr& password)
     169             : {
     170           0 :     ENSURE(!!g_XmppClient);
     171           0 :     ENSURE(!g_NetClient);
     172           0 :     ENSURE(!g_NetServer);
     173           0 :     ENSURE(!g_Game);
     174             : 
     175           0 :     CStr hashedPass = HashCryptographically(password, hostJID + password + engine_version);
     176           0 :     g_Game = new CGame(true);
     177           0 :     g_NetClient = new CNetClient(g_Game);
     178           0 :     g_NetClient->SetUserName(playerName);
     179           0 :     g_NetClient->SetHostJID(hostJID);
     180           0 :     g_NetClient->SetGamePassword(hashedPass);
     181           0 :     g_NetClient->SetupConnectionViaLobby();
     182           0 : }
     183             : 
     184           0 : void DisconnectNetworkGame()
     185             : {
     186             :     // TODO: we ought to do async reliable disconnections
     187             : 
     188           0 :     SAFE_DELETE(g_NetServer);
     189           0 :     SAFE_DELETE(g_NetClient);
     190           0 :     SAFE_DELETE(g_Game);
     191           0 : }
     192             : 
     193           0 : CStr GetPlayerGUID()
     194             : {
     195           0 :     if (!g_NetClient)
     196           0 :         return "local";
     197             : 
     198           0 :     return g_NetClient->GetGUID();
     199             : }
     200             : 
     201           0 : JS::Value PollNetworkClient(const ScriptInterface& guiInterface)
     202             : {
     203           0 :     if (!g_NetClient)
     204           0 :         return JS::UndefinedValue();
     205             : 
     206             :     // Convert from net client context to GUI script context
     207           0 :     ScriptRequest rqNet(g_NetClient->GetScriptInterface());
     208           0 :     JS::RootedValue pollNet(rqNet.cx);
     209           0 :     g_NetClient->GuiPoll(&pollNet);
     210           0 :     return Script::CloneValueFromOtherCompartment(guiInterface, g_NetClient->GetScriptInterface(), pollNet);
     211             : }
     212             : 
     213           0 : void SendGameSetupMessage(const ScriptInterface& scriptInterface, JS::HandleValue attribs1)
     214             : {
     215           0 :     ENSURE(g_NetClient);
     216             : 
     217             :     // TODO: This is a workaround because we need to pass a MutableHandle to a JSAPI functions somewhere (with no obvious reason).
     218           0 :     ScriptRequest rq(scriptInterface);
     219           0 :     JS::RootedValue attribs(rq.cx, attribs1);
     220             : 
     221           0 :     g_NetClient->SendGameSetupMessage(&attribs, scriptInterface);
     222           0 : }
     223             : 
     224           0 : void AssignNetworkPlayer(int playerID, const CStr& guid)
     225             : {
     226           0 :     ENSURE(g_NetClient);
     227             : 
     228           0 :     g_NetClient->SendAssignPlayerMessage(playerID, guid);
     229           0 : }
     230             : 
     231           0 : void KickPlayer(const CStrW& playerName, bool ban)
     232             : {
     233           0 :     ENSURE(g_NetClient);
     234             : 
     235           0 :     g_NetClient->SendKickPlayerMessage(playerName, ban);
     236           0 : }
     237             : 
     238           0 : void SendNetworkChat(const CStrW& message)
     239             : {
     240           0 :     ENSURE(g_NetClient);
     241             : 
     242           0 :     g_NetClient->SendChatMessage(message);
     243           0 : }
     244             : 
     245           0 : void SendNetworkReady(int message)
     246             : {
     247           0 :     ENSURE(g_NetClient);
     248             : 
     249           0 :     g_NetClient->SendReadyMessage(message);
     250           0 : }
     251             : 
     252           0 : void ClearAllPlayerReady ()
     253             : {
     254           0 :     ENSURE(g_NetClient);
     255             : 
     256           0 :     g_NetClient->SendClearAllReadyMessage();
     257           0 : }
     258             : 
     259           0 : void StartNetworkGame(const ScriptInterface& scriptInterface, JS::HandleValue attribs1)
     260             : {
     261           0 :     ENSURE(g_NetClient);
     262             : 
     263             :     // TODO: This is a workaround because we need to pass a MutableHandle to a JSAPI functions somewhere (with no obvious reason).
     264           0 :     ScriptRequest rq(scriptInterface);
     265           0 :     JS::RootedValue attribs(rq.cx, attribs1);
     266           0 :     g_NetClient->SendStartGameMessage(Script::StringifyJSON(rq, &attribs));
     267           0 : }
     268             : 
     269           0 : void SetTurnLength(int length)
     270             : {
     271           0 :     if (g_NetServer)
     272           0 :         g_NetServer->SetTurnLength(length);
     273             :     else
     274           0 :         LOGERROR("Only network host can change turn length");
     275           0 : }
     276             : 
     277          12 : void RegisterScriptFunctions(const ScriptRequest& rq)
     278             : {
     279          12 :     ScriptFunction::Register<&GetDefaultPort>(rq, "GetDefaultPort");
     280          12 :     ScriptFunction::Register<&IsNetController>(rq, "IsNetController");
     281          12 :     ScriptFunction::Register<&HasNetServer>(rq, "HasNetServer");
     282          12 :     ScriptFunction::Register<&HasNetClient>(rq, "HasNetClient");
     283          12 :     ScriptFunction::Register<&StartNetworkHost>(rq, "StartNetworkHost");
     284          12 :     ScriptFunction::Register<&StartNetworkJoin>(rq, "StartNetworkJoin");
     285          12 :     ScriptFunction::Register<&StartNetworkJoinLobby>(rq, "StartNetworkJoinLobby");
     286          12 :     ScriptFunction::Register<&DisconnectNetworkGame>(rq, "DisconnectNetworkGame");
     287          12 :     ScriptFunction::Register<&GetPlayerGUID>(rq, "GetPlayerGUID");
     288          12 :     ScriptFunction::Register<&PollNetworkClient>(rq, "PollNetworkClient");
     289          12 :     ScriptFunction::Register<&SendGameSetupMessage>(rq, "SendGameSetupMessage");
     290          12 :     ScriptFunction::Register<&AssignNetworkPlayer>(rq, "AssignNetworkPlayer");
     291          12 :     ScriptFunction::Register<&KickPlayer>(rq, "KickPlayer");
     292          12 :     ScriptFunction::Register<&SendNetworkChat>(rq, "SendNetworkChat");
     293          12 :     ScriptFunction::Register<&SendNetworkReady>(rq, "SendNetworkReady");
     294          12 :     ScriptFunction::Register<&ClearAllPlayerReady>(rq, "ClearAllPlayerReady");
     295          12 :     ScriptFunction::Register<&StartNetworkGame>(rq, "StartNetworkGame");
     296          12 :     ScriptFunction::Register<&SetTurnLength>(rq, "SetTurnLength");
     297          12 : }
     298           3 : }

Generated by: LCOV version 1.13