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 : }
|