Line data Source code
1 : /* Copyright (C) 2021 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 "XmppClient.h"
21 : #include "StanzaExtensions.h"
22 :
23 : #include "i18n/L10n.h"
24 : #include "lib/utf8.h"
25 : #include "network/NetServer.h"
26 : #include "network/NetClient.h"
27 : #include "network/StunClient.h"
28 : #include "ps/CLogger.h"
29 : #include "ps/ConfigDB.h"
30 : #include "ps/GUID.h"
31 : #include "ps/Pyrogenesis.h"
32 : #include "scriptinterface/ScriptInterface.h"
33 : #include "scriptinterface/StructuredClone.h"
34 :
35 : #include <iostream>
36 :
37 : //debug
38 : #if 1
39 : #define DbgXMPP(x)
40 : #else
41 : #define DbgXMPP(x) std::cout << x << std::endl;
42 :
43 : static std::string tag_xml(const glooxwrapper::IQ& iq)
44 : {
45 : std::string ret;
46 : glooxwrapper::Tag* tag = iq.tag();
47 : ret = tag->xml().to_string();
48 : glooxwrapper::Tag::free(tag);
49 : return ret;
50 : }
51 : #endif
52 :
53 0 : static std::string tag_name(const glooxwrapper::IQ& iq)
54 : {
55 0 : std::string ret;
56 0 : glooxwrapper::Tag* tag = iq.tag();
57 0 : ret = tag->name().to_string();
58 0 : glooxwrapper::Tag::free(tag);
59 0 : return ret;
60 : }
61 :
62 0 : IXmppClient* IXmppClient::create(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt)
63 : {
64 0 : return new XmppClient(scriptInterface, sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt);
65 : }
66 :
67 : /**
68 : * Construct the XMPP client.
69 : *
70 : * @param scriptInterface - ScriptInterface to be used for storing GUI messages.
71 : * Can be left blank for non-visual applications.
72 : * @param sUsername Username to login with of register.
73 : * @param sPassword Password to login with or register.
74 : * @param sRoom MUC room to join.
75 : * @param sNick Nick to join with.
76 : * @param historyRequestSize Number of stanzas of room history to request.
77 : * @param regOpt If we are just registering or not.
78 : */
79 0 : XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
80 : : m_ScriptInterface(scriptInterface),
81 : m_client(nullptr),
82 : m_mucRoom(nullptr),
83 : m_registration(nullptr),
84 : m_username(sUsername),
85 : m_password(sPassword),
86 : m_room(sRoom),
87 : m_nick(sNick),
88 : m_initialLoadComplete(false),
89 : m_isConnected(false),
90 : m_sessionManager(nullptr),
91 : m_certStatus(gloox::CertStatus::CertOk),
92 : m_PlayerMapUpdate(false),
93 : m_connectionDataJid(),
94 0 : m_connectionDataIqId()
95 : {
96 0 : if (m_ScriptInterface)
97 0 : JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), XmppClient::Trace, this);
98 :
99 : // Read lobby configuration from default.cfg
100 0 : std::string sXpartamupp;
101 0 : std::string sEchelon;
102 0 : CFG_GET_VAL("lobby.server", m_server);
103 0 : CFG_GET_VAL("lobby.xpartamupp", sXpartamupp);
104 0 : CFG_GET_VAL("lobby.echelon", sEchelon);
105 :
106 0 : m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC";
107 0 : m_echelonId = sEchelon + "@" + m_server + "/CC";
108 : // Generate a unique, unpredictable resource to allow multiple 0 A.D. instances to connect to the lobby.
109 0 : glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad-" + ps_generate_guid());
110 0 : glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick);
111 :
112 : // If we are connecting, use the full jid and a password
113 : // If we are registering, only use the server name
114 0 : if (!regOpt)
115 0 : m_client = new glooxwrapper::Client(clientJid, sPassword);
116 : else
117 0 : m_client = new glooxwrapper::Client(m_server);
118 :
119 : // Optionally join without a TLS certificate, so a local server can be tested quickly.
120 : // Security risks from malicious JS mods can be mitigated if this option and also the hostname and login are shielded from JS access.
121 0 : bool tls = true;
122 0 : CFG_GET_VAL("lobby.tls", tls);
123 0 : m_client->setTls(tls ? gloox::TLSRequired : gloox::TLSDisabled);
124 :
125 : // Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
126 : // if the server doesn't list any supported SASL mechanism or the response
127 : // has been modified to exclude those.
128 0 : const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
129 0 : m_client->setSASLMechanisms(mechs);
130 :
131 0 : m_client->registerConnectionListener(this);
132 0 : m_client->setPresence(gloox::Presence::Available, -1);
133 0 : m_client->disco()->setVersion("Pyrogenesis", engine_version);
134 0 : m_client->disco()->setIdentity("client", "bot");
135 0 : m_client->setCompression(false);
136 :
137 0 : m_client->registerStanzaExtension(new GameListQuery());
138 0 : m_client->registerIqHandler(this, EXTGAMELISTQUERY);
139 :
140 0 : m_client->registerStanzaExtension(new BoardListQuery());
141 0 : m_client->registerIqHandler(this, EXTBOARDLISTQUERY);
142 :
143 0 : m_client->registerStanzaExtension(new ProfileQuery());
144 0 : m_client->registerIqHandler(this, EXTPROFILEQUERY);
145 :
146 0 : m_client->registerStanzaExtension(new LobbyAuth());
147 0 : m_client->registerIqHandler(this, EXTLOBBYAUTH);
148 :
149 0 : m_client->registerStanzaExtension(new ConnectionData());
150 0 : m_client->registerIqHandler(this, EXTCONNECTIONDATA);
151 :
152 0 : m_client->registerMessageHandler(this);
153 :
154 : // Uncomment to see the raw stanzas
155 : //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
156 :
157 0 : if (!regOpt)
158 : {
159 : // Create a Multi User Chat Room
160 0 : m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
161 : // Get room history.
162 0 : m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
163 : }
164 : else
165 : {
166 : // Registration
167 0 : m_registration = new glooxwrapper::Registration(m_client);
168 0 : m_registration->registerRegistrationHandler(this);
169 : }
170 :
171 0 : m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
172 : // Register plugins to allow gloox parse them in incoming sessions
173 0 : m_sessionManager->registerPlugins();
174 0 : }
175 :
176 : /**
177 : * Destroy the xmpp client
178 : */
179 0 : XmppClient::~XmppClient()
180 : {
181 : DbgXMPP("XmppClient destroyed");
182 0 : delete m_registration;
183 0 : delete m_mucRoom;
184 0 : delete m_sessionManager;
185 :
186 : // Workaround for memory leak in gloox 1.0/1.0.1
187 0 : m_client->removePresenceExtension(gloox::ExtCaps);
188 :
189 0 : delete m_client;
190 :
191 0 : for (const glooxwrapper::Tag* const& t : m_GameList)
192 0 : glooxwrapper::Tag::free(t);
193 0 : for (const glooxwrapper::Tag* const& t : m_BoardList)
194 0 : glooxwrapper::Tag::free(t);
195 0 : for (const glooxwrapper::Tag* const& t : m_Profile)
196 0 : glooxwrapper::Tag::free(t);
197 :
198 0 : if (m_ScriptInterface)
199 0 : JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), XmppClient::Trace, this);
200 0 : }
201 :
202 0 : void XmppClient::TraceMember(JSTracer* trc)
203 : {
204 0 : for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
205 0 : JS::TraceEdge(trc, &guiMessage, "m_GuiMessageQueue");
206 :
207 0 : for (JS::Heap<JS::Value>& guiMessage : m_HistoricGuiMessages)
208 0 : JS::TraceEdge(trc, &guiMessage, "m_HistoricGuiMessages");
209 0 : }
210 :
211 : /// Network
212 0 : void XmppClient::connect()
213 : {
214 0 : m_initialLoadComplete = false;
215 0 : m_client->connect(false);
216 0 : }
217 :
218 0 : void XmppClient::disconnect()
219 : {
220 0 : m_client->disconnect();
221 0 : }
222 :
223 0 : bool XmppClient::isConnected()
224 : {
225 0 : return m_isConnected;
226 : }
227 :
228 0 : void XmppClient::recv()
229 : {
230 0 : m_client->recv(1);
231 0 : }
232 :
233 : /**
234 : * Log (debug) Handler
235 : */
236 0 : void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
237 : {
238 0 : std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
239 0 : }
240 :
241 : /*****************************************************
242 : * Connection handlers *
243 : *****************************************************/
244 :
245 : /**
246 : * Handle connection
247 : */
248 0 : void XmppClient::onConnect()
249 : {
250 0 : if (m_mucRoom)
251 : {
252 0 : m_isConnected = true;
253 0 : CreateGUIMessage("system", "connected", std::time(nullptr));
254 0 : m_mucRoom->join();
255 : }
256 :
257 0 : if (m_registration)
258 0 : m_registration->fetchRegistrationFields();
259 0 : }
260 :
261 : /**
262 : * Handle disconnection
263 : */
264 0 : void XmppClient::onDisconnect(gloox::ConnectionError error)
265 : {
266 : // Make sure we properly leave the room so that
267 : // everything works if we decide to come back later
268 0 : if (m_mucRoom)
269 0 : m_mucRoom->leave();
270 :
271 : // Clear game, board and player lists.
272 0 : for (const glooxwrapper::Tag* const& t : m_GameList)
273 0 : glooxwrapper::Tag::free(t);
274 0 : for (const glooxwrapper::Tag* const& t : m_BoardList)
275 0 : glooxwrapper::Tag::free(t);
276 0 : for (const glooxwrapper::Tag* const& t : m_Profile)
277 0 : glooxwrapper::Tag::free(t);
278 :
279 0 : m_BoardList.clear();
280 0 : m_GameList.clear();
281 0 : m_PlayerMap.clear();
282 0 : m_PlayerMapUpdate = true;
283 0 : m_Profile.clear();
284 0 : m_HistoricGuiMessages.clear();
285 0 : m_isConnected = false;
286 0 : m_initialLoadComplete = false;
287 :
288 0 : CreateGUIMessage(
289 : "system",
290 : "disconnected",
291 : std::time(nullptr),
292 : "reason", error,
293 : "certificate_status", m_certStatus);
294 0 : }
295 :
296 : /**
297 : * Handle TLS connection.
298 : */
299 0 : bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
300 : {
301 : DbgXMPP("onTLSConnect");
302 : DbgXMPP(
303 : "status: " << info.status <<
304 : "\nissuer: " << info.issuer <<
305 : "\npeer: " << info.server <<
306 : "\nprotocol: " << info.protocol <<
307 : "\nmac: " << info.mac <<
308 : "\ncipher: " << info.cipher <<
309 : "\ncompression: " << info.compression );
310 :
311 0 : m_certStatus = static_cast<gloox::CertStatus>(info.status);
312 :
313 : // Optionally accept invalid certificates, see require_tls option.
314 0 : bool verify_certificate = true;
315 0 : CFG_GET_VAL("lobby.verify_certificate", verify_certificate);
316 :
317 0 : return info.status == gloox::CertOk || !verify_certificate;
318 : }
319 :
320 : /**
321 : * Handle MUC room errors
322 : */
323 0 : void XmppClient::handleMUCError(glooxwrapper::MUCRoom& UNUSED(room), gloox::StanzaError err)
324 : {
325 : DbgXMPP("MUC Error " << ": " << StanzaErrorToString(err));
326 0 : CreateGUIMessage("system", "error", std::time(nullptr), "text", err);
327 0 : }
328 :
329 : /*****************************************************
330 : * Requests to server *
331 : *****************************************************/
332 :
333 : /**
334 : * Request the leaderboard data from the server.
335 : */
336 0 : void XmppClient::SendIqGetBoardList()
337 : {
338 0 : glooxwrapper::JID echelonJid(m_echelonId);
339 :
340 : // Send IQ
341 0 : BoardListQuery* b = new BoardListQuery();
342 0 : b->m_Command = "getleaderboard";
343 0 : glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
344 0 : iq.addExtension(b);
345 : DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
346 0 : m_client->send(iq);
347 0 : }
348 :
349 : /**
350 : * Request the profile data from the server.
351 : */
352 0 : void XmppClient::SendIqGetProfile(const std::string& player)
353 : {
354 0 : glooxwrapper::JID echelonJid(m_echelonId);
355 :
356 : // Send IQ
357 0 : ProfileQuery* b = new ProfileQuery();
358 0 : b->m_Command = player;
359 0 : glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
360 0 : iq.addExtension(b);
361 : DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
362 0 : m_client->send(iq);
363 0 : }
364 :
365 : /**
366 : * Request the Connection data (ip, port...) from the server.
367 : */
368 0 : void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password, const std::string& clientSalt, bool localIP)
369 : {
370 0 : glooxwrapper::JID targetJID(jid);
371 :
372 0 : ConnectionData* connectionData = new ConnectionData();
373 0 : connectionData->m_Password = password;
374 0 : connectionData->m_ClientSalt = clientSalt;
375 0 : connectionData->m_IsLocalIP = localIP ? "1" : "0";
376 0 : glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID());
377 0 : iq.addExtension(connectionData);
378 0 : m_connectionDataJid = iq.from().full();
379 0 : m_connectionDataIqId = iq.id().to_string();
380 : DbgXMPP("SendIqGetConnectionData [" << tag_xml(iq) << "]");
381 0 : m_client->send(iq);
382 0 : }
383 :
384 : /**
385 : * Send game report containing numerous game properties to the server.
386 : *
387 : * @param data A JS array of game statistics
388 : */
389 0 : void XmppClient::SendIqGameReport(const ScriptRequest& rq, JS::HandleValue data)
390 : {
391 0 : glooxwrapper::JID echelonJid(m_echelonId);
392 :
393 : // Setup some base stanza attributes
394 0 : GameReport* game = new GameReport();
395 0 : glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
396 :
397 : // Iterate through all the properties reported and add them to the stanza.
398 0 : std::vector<std::string> properties;
399 0 : Script::EnumeratePropertyNames(rq, data, true, properties);
400 0 : for (const std::string& p : properties)
401 : {
402 0 : std::wstring value;
403 0 : Script::GetProperty(rq, data, p.c_str(), value);
404 0 : report->addAttribute(p, utf8_from_wstring(value));
405 : }
406 :
407 : // Add stanza to IQ
408 0 : game->m_GameReport.emplace_back(report);
409 :
410 : // Send IQ
411 0 : glooxwrapper::IQ iq(gloox::IQ::Set, echelonJid, m_client->getID());
412 0 : iq.addExtension(game);
413 : DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
414 0 : m_client->send(iq);
415 0 : };
416 :
417 : /**
418 : * Send a request to register a game to the server.
419 : *
420 : * @param data A JS array of game attributes
421 : */
422 0 : void XmppClient::SendIqRegisterGame(const ScriptRequest& rq, JS::HandleValue data)
423 : {
424 0 : glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
425 :
426 : // Setup some base stanza attributes
427 0 : std::unique_ptr<GameListQuery> g = std::make_unique<GameListQuery>();
428 0 : g->m_Command = "register";
429 0 : glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
430 :
431 : // Iterate through all the properties reported and add them to the stanza.
432 0 : std::vector<std::string> properties;
433 0 : Script::EnumeratePropertyNames(rq, data, true, properties);
434 0 : for (const std::string& p : properties)
435 : {
436 0 : std::string value;
437 0 : if (!Script::GetProperty(rq, data, p.c_str(), value))
438 : {
439 0 : LOGERROR("Could not parse attribute '%s' as string.", p);
440 0 : return;
441 : }
442 0 : game->addAttribute(p, value);
443 : }
444 :
445 : // Overwrite some attributes to make it slightly less trivial to do bad things,
446 : // and explicit some invariants.
447 :
448 : // The JID must point to ourself.
449 0 : game->addAttribute("hostJID", GetJID());
450 :
451 : // Push the stanza onto the IQ
452 0 : g->m_GameList.emplace_back(game);
453 :
454 : // Send IQ
455 0 : glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
456 0 : iq.addExtension(g.release());
457 : DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
458 0 : m_client->send(iq);
459 : }
460 :
461 : /**
462 : * Send a request to unregister a game to the server.
463 : */
464 0 : void XmppClient::SendIqUnregisterGame()
465 : {
466 0 : glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
467 :
468 : // Send IQ
469 0 : GameListQuery* g = new GameListQuery();
470 0 : g->m_Command = "unregister";
471 0 : g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game"));
472 :
473 0 : glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
474 0 : iq.addExtension(g);
475 : DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
476 0 : m_client->send(iq);
477 0 : }
478 :
479 : /**
480 : * Send a request to change the state of a registered game on the server.
481 : *
482 : * A game can either be in the 'running' or 'waiting' state - the server
483 : * decides which - but we need to update the current players that are
484 : * in-game so the server can make the calculation.
485 : */
486 0 : void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
487 : {
488 0 : glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
489 :
490 : // Send IQ
491 0 : GameListQuery* g = new GameListQuery();
492 0 : g->m_Command = "changestate";
493 0 : glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
494 0 : game->addAttribute("nbp", nbp);
495 0 : game->addAttribute("players", players);
496 0 : g->m_GameList.emplace_back(game);
497 :
498 0 : glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
499 0 : iq.addExtension(g);
500 : DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
501 0 : m_client->send(iq);
502 0 : }
503 :
504 : /*****************************************************
505 : * iq to clients *
506 : *****************************************************/
507 :
508 : /**
509 : * Send lobby authentication token.
510 : */
511 0 : void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token)
512 : {
513 0 : LobbyAuth* auth = new LobbyAuth();
514 0 : auth->m_Token = token;
515 :
516 0 : glooxwrapper::JID clientJid(to);
517 0 : glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID());
518 0 : iq.addExtension(auth);
519 : DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]");
520 0 : m_client->send(iq);
521 0 : }
522 :
523 : /*****************************************************
524 : * Account registration *
525 : *****************************************************/
526 :
527 0 : void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
528 : {
529 0 : glooxwrapper::RegistrationFields vals;
530 0 : vals.username = m_username;
531 0 : vals.password = m_password;
532 0 : m_registration->createAccount(fields, vals);
533 0 : }
534 :
535 0 : void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
536 : {
537 0 : if (result == gloox::RegistrationSuccess)
538 0 : CreateGUIMessage("system", "registered", std::time(nullptr));
539 : else
540 0 : CreateGUIMessage("system", "error", std::time(nullptr), "text", result);
541 :
542 0 : disconnect();
543 0 : }
544 :
545 0 : void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
546 : {
547 : DbgXMPP("the account already exists");
548 0 : }
549 :
550 0 : void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
551 : {
552 : DbgXMPP("dataForm received");
553 0 : }
554 :
555 0 : void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
556 : {
557 : DbgXMPP("OOB registration requested");
558 0 : }
559 :
560 : /*****************************************************
561 : * Requests from GUI *
562 : *****************************************************/
563 :
564 : /**
565 : * Handle requests from the GUI for the list of players.
566 : *
567 : * @return A JS array containing all known players and their presences
568 : */
569 0 : JS::Value XmppClient::GUIGetPlayerList(const ScriptRequest& rq)
570 : {
571 0 : JS::RootedValue ret(rq.cx);
572 0 : Script::CreateArray(rq, &ret);
573 0 : int j = 0;
574 :
575 0 : for (const std::pair<const glooxwrapper::string, SPlayer>& p : m_PlayerMap)
576 : {
577 0 : JS::RootedValue player(rq.cx);
578 :
579 0 : Script::CreateObject(
580 : rq,
581 : &player,
582 : "name", p.first,
583 : "presence", p.second.m_Presence,
584 : "rating", p.second.m_Rating,
585 : "role", p.second.m_Role);
586 :
587 0 : Script::SetPropertyInt(rq, ret, j++, player);
588 : }
589 0 : return ret;
590 : }
591 :
592 : /**
593 : * Handle requests from the GUI for the list of all active games.
594 : *
595 : * @return A JS array containing all known games
596 : */
597 0 : JS::Value XmppClient::GUIGetGameList(const ScriptRequest& rq)
598 : {
599 0 : JS::RootedValue ret(rq.cx);
600 0 : Script::CreateArray(rq, &ret);
601 0 : int j = 0;
602 :
603 0 : const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword",
604 : "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
605 : "victoryConditions", "startTime", "mods" };
606 :
607 0 : for(const glooxwrapper::Tag* const& t : m_GameList)
608 : {
609 0 : JS::RootedValue game(rq.cx);
610 0 : Script::CreateObject(rq, &game);
611 :
612 0 : for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
613 0 : Script::SetProperty(rq, game, stats[i], t->findAttribute(stats[i]));
614 :
615 0 : Script::SetPropertyInt(rq, ret, j++, game);
616 : }
617 0 : return ret;
618 : }
619 :
620 : /**
621 : * Handle requests from the GUI for leaderboard data.
622 : *
623 : * @return A JS array containing all known leaderboard data
624 : */
625 0 : JS::Value XmppClient::GUIGetBoardList(const ScriptRequest& rq)
626 : {
627 0 : JS::RootedValue ret(rq.cx);
628 0 : Script::CreateArray(rq, &ret);
629 0 : int j = 0;
630 :
631 0 : const char* attributes[] = { "name", "rank", "rating" };
632 :
633 0 : for(const glooxwrapper::Tag* const& t : m_BoardList)
634 : {
635 0 : JS::RootedValue board(rq.cx);
636 0 : Script::CreateObject(rq, &board);
637 :
638 0 : for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
639 0 : Script::SetProperty(rq, board, attributes[i], t->findAttribute(attributes[i]));
640 :
641 0 : Script::SetPropertyInt(rq, ret, j++, board);
642 : }
643 0 : return ret;
644 : }
645 :
646 : /**
647 : * Handle requests from the GUI for profile data.
648 : *
649 : * @return A JS array containing the specific user's profile data
650 : */
651 0 : JS::Value XmppClient::GUIGetProfile(const ScriptRequest& rq)
652 : {
653 0 : JS::RootedValue ret(rq.cx);
654 0 : Script::CreateArray(rq, &ret);
655 0 : int j = 0;
656 :
657 0 : const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
658 :
659 0 : for (const glooxwrapper::Tag* const& t : m_Profile)
660 : {
661 0 : JS::RootedValue profile(rq.cx);
662 0 : Script::CreateObject(rq, &profile);
663 :
664 0 : for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
665 0 : Script::SetProperty(rq, profile, stats[i], t->findAttribute(stats[i]));
666 :
667 0 : Script::SetPropertyInt(rq, ret, j++, profile);
668 : }
669 0 : return ret;
670 : }
671 :
672 : /*****************************************************
673 : * Message interfaces *
674 : *****************************************************/
675 :
676 0 : void SetGUIMessageProperty(const ScriptRequest& UNUSED(rq), JS::HandleObject UNUSED(messageObj))
677 : {
678 0 : }
679 :
680 : template<typename T, typename... Args>
681 0 : void SetGUIMessageProperty(const ScriptRequest& rq, JS::HandleObject messageObj, const std::string& propertyName, const T& propertyValue, Args const&... args)
682 : {
683 0 : JS::RootedValue scriptPropertyValue(rq.cx);
684 0 : Script::ToJSVal(rq, &scriptPropertyValue, propertyValue);
685 0 : JS_DefineProperty(rq.cx, messageObj, propertyName.c_str(), scriptPropertyValue, JSPROP_ENUMERATE);
686 0 : SetGUIMessageProperty(rq, messageObj, args...);
687 0 : }
688 :
689 : template<typename... Args>
690 0 : void XmppClient::CreateGUIMessage(
691 : const std::string& type,
692 : const std::string& level,
693 : const std::time_t time,
694 : Args const&... args)
695 : {
696 0 : if (!m_ScriptInterface)
697 0 : return;
698 0 : ScriptRequest rq(m_ScriptInterface);
699 0 : JS::RootedValue message(rq.cx);
700 0 : Script::CreateObject(
701 : rq,
702 : &message,
703 : "type", type,
704 : "level", level,
705 : "historic", false,
706 : "time", static_cast<double>(time));
707 :
708 0 : JS::RootedObject messageObj(rq.cx, message.toObjectOrNull());
709 0 : SetGUIMessageProperty(rq, messageObj, args...);
710 0 : Script::FreezeObject(rq, message, true);
711 0 : m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
712 : }
713 :
714 0 : bool XmppClient::GuiPollHasPlayerListUpdate()
715 : {
716 : // The initial playerlist will be received in multiple messages
717 : // Only inform the GUI after all of these playerlist fragments were received.
718 0 : if (!m_initialLoadComplete)
719 0 : return false;
720 :
721 0 : bool hasUpdate = m_PlayerMapUpdate;
722 0 : m_PlayerMapUpdate = false;
723 0 : return hasUpdate;
724 : }
725 :
726 0 : JS::Value XmppClient::GuiPollNewMessages(const ScriptInterface& guiInterface)
727 : {
728 0 : if ((m_isConnected && !m_initialLoadComplete) || m_GuiMessageQueue.empty())
729 0 : return JS::UndefinedValue();
730 :
731 0 : ScriptRequest rq(m_ScriptInterface);
732 :
733 : // Optimize for batch message processing that is more
734 : // performance demanding than processing a lone message.
735 0 : JS::RootedValue messages(rq.cx);
736 0 : Script::CreateArray(rq, &messages);
737 :
738 0 : int j = 0;
739 :
740 0 : for (const JS::Heap<JS::Value>& message : m_GuiMessageQueue)
741 : {
742 0 : Script::SetPropertyInt(rq, messages, j++, message);
743 :
744 : // Store historic chat messages.
745 : // Only store relevant messages to minimize memory footprint.
746 0 : JS::RootedValue rootedMessage(rq.cx, message);
747 0 : std::string type;
748 0 : Script::GetProperty(rq, rootedMessage, "type", type);
749 0 : if (type != "chat")
750 0 : continue;
751 :
752 0 : std::string level;
753 0 : Script::GetProperty(rq, rootedMessage, "level", level);
754 0 : if (level != "room-message" && level != "private-message")
755 0 : continue;
756 :
757 0 : JS::RootedValue historicMessage(rq.cx, Script::DeepCopy(rq, rootedMessage));
758 : if (true)
759 : {
760 0 : Script::SetProperty(rq, historicMessage, "historic", true);
761 0 : Script::FreezeObject(rq, historicMessage, true);
762 0 : m_HistoricGuiMessages.push_back(JS::Heap<JS::Value>(historicMessage));
763 : }
764 : else
765 : LOGERROR("Could not clone historic lobby GUI message!");
766 : }
767 0 : m_GuiMessageQueue.clear();
768 :
769 : // Copy the messages over to the caller script interface.
770 0 : return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
771 : }
772 :
773 0 : JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& guiInterface)
774 : {
775 0 : if (m_HistoricGuiMessages.empty())
776 0 : return JS::UndefinedValue();
777 :
778 0 : ScriptRequest rq(m_ScriptInterface);
779 :
780 0 : JS::RootedValue messages(rq.cx);
781 0 : Script::CreateArray(rq, &messages);
782 :
783 0 : int j = 0;
784 0 : for (const JS::Heap<JS::Value>& message : m_HistoricGuiMessages)
785 0 : Script::SetPropertyInt(rq, messages, j++, message);
786 :
787 : // Copy the messages over to the caller script interface.
788 0 : return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
789 : }
790 :
791 : /**
792 : * Send a standard MUC textual message.
793 : */
794 0 : void XmppClient::SendMUCMessage(const std::string& message)
795 : {
796 0 : m_mucRoom->send(message);
797 0 : }
798 :
799 : /**
800 : * Handle a room message.
801 : */
802 0 : void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::Message& msg, bool priv)
803 : {
804 : DbgXMPP(msg.from().resource() << " said " << msg.body());
805 :
806 0 : CreateGUIMessage(
807 : "chat",
808 : priv ? "private-message" : "room-message",
809 : ComputeTimestamp(msg),
810 0 : "from", msg.from().resource(),
811 0 : "text", msg.body());
812 0 : }
813 :
814 : /**
815 : * Handle a private message.
816 : */
817 0 : void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession*)
818 : {
819 : DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
820 : << ", message " << msg.body() << ", thread id " << msg.thread());
821 :
822 0 : CreateGUIMessage(
823 : "chat",
824 : "private-message",
825 : ComputeTimestamp(msg),
826 0 : "from", msg.from().resource(),
827 0 : "text", msg.body());
828 0 : }
829 :
830 : /**
831 : * Handle portions of messages containing custom stanza extensions.
832 : */
833 0 : bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
834 : {
835 : DbgXMPP("handleIq [" << tag_xml(iq) << "]");
836 :
837 0 : if (iq.subtype() == gloox::IQ::Result)
838 : {
839 0 : const GameListQuery* gq = iq.findExtension<GameListQuery>(EXTGAMELISTQUERY);
840 0 : const BoardListQuery* bq = iq.findExtension<BoardListQuery>(EXTBOARDLISTQUERY);
841 0 : const ProfileQuery* pq = iq.findExtension<ProfileQuery>(EXTPROFILEQUERY);
842 0 : const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
843 0 : if (cd)
844 : {
845 0 : if (g_NetServer || !g_NetClient)
846 0 : return true;
847 :
848 0 : if (!m_connectionDataJid.empty() && m_connectionDataJid.compare(iq.from().full()) != 0) {
849 0 : LOGMESSAGE("XmppClient: Received connection data from invalid host: %s", iq.from().username());
850 0 : return true;
851 : }
852 :
853 0 : if (!m_connectionDataIqId.empty() && m_connectionDataIqId.compare(iq.id().to_string()) != 0) {
854 0 : LOGMESSAGE("XmppClient: Received connection data with invalid id");
855 0 : return true;
856 : }
857 :
858 0 : if (!cd->m_Error.empty())
859 : {
860 0 : g_NetClient->HandleGetServerDataFailed(cd->m_Error.c_str());
861 0 : return true;
862 : }
863 :
864 0 : g_NetClient->SetupServerData(cd->m_Ip.to_string(), stoi(cd->m_Port.to_string()), !cd->m_UseSTUN.empty());
865 0 : g_NetClient->TryToConnect(iq.from().full(), !cd->m_IsLocalIP.empty());
866 : }
867 0 : if (gq)
868 : {
869 0 : if (iq.from().full() == m_xpartamuppId && gq->m_Command == "register" && g_NetServer && !g_NetServer->GetUseSTUN())
870 : {
871 0 : if (gq->m_GameList.empty())
872 : {
873 0 : LOGWARNING("XmppClient: Received empty game list in response to Game Register");
874 0 : return true;
875 : }
876 0 : std::string publicIP = gq->m_GameList.front()->findAttribute("ip").to_string();
877 0 : if (publicIP.empty())
878 : {
879 0 : LOGWARNING("XmppClient: Received game with no IP in response to Game Register");
880 0 : return true;
881 : }
882 0 : g_NetServer->SetConnectionData(publicIP, g_NetServer->GetPublicPort());
883 0 : return true;
884 : }
885 :
886 0 : for (const glooxwrapper::Tag* const& t : m_GameList)
887 0 : glooxwrapper::Tag::free(t);
888 0 : m_GameList.clear();
889 :
890 0 : for (const glooxwrapper::Tag* const& t : gq->m_GameList)
891 0 : m_GameList.emplace_back(t->clone());
892 :
893 0 : CreateGUIMessage("game", "gamelist", std::time(nullptr));
894 : }
895 0 : if (bq)
896 : {
897 0 : if (bq->m_Command == "boardlist")
898 : {
899 0 : for (const glooxwrapper::Tag* const& t : m_BoardList)
900 0 : glooxwrapper::Tag::free(t);
901 0 : m_BoardList.clear();
902 :
903 0 : for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
904 0 : m_BoardList.emplace_back(t->clone());
905 :
906 0 : CreateGUIMessage("game", "leaderboard", std::time(nullptr));
907 : }
908 0 : else if (bq->m_Command == "ratinglist")
909 : {
910 0 : for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
911 : {
912 0 : const PlayerMap::iterator it = m_PlayerMap.find(t->findAttribute("name"));
913 0 : if (it != m_PlayerMap.end())
914 : {
915 0 : it->second.m_Rating = t->findAttribute("rating");
916 0 : m_PlayerMapUpdate = true;
917 : }
918 : }
919 0 : CreateGUIMessage("game", "ratinglist", std::time(nullptr));
920 : }
921 : }
922 0 : if (pq)
923 : {
924 0 : for (const glooxwrapper::Tag* const& t : m_Profile)
925 0 : glooxwrapper::Tag::free(t);
926 0 : m_Profile.clear();
927 :
928 0 : for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile)
929 0 : m_Profile.emplace_back(t->clone());
930 :
931 0 : CreateGUIMessage("game", "profile", std::time(nullptr));
932 : }
933 : }
934 0 : else if (iq.subtype() == gloox::IQ::Set)
935 : {
936 0 : const LobbyAuth* lobbyAuth = iq.findExtension<LobbyAuth>(EXTLOBBYAUTH);
937 0 : if (lobbyAuth)
938 : {
939 0 : LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username());
940 :
941 0 : glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
942 0 : m_client->send(response);
943 :
944 0 : if (g_NetServer)
945 0 : g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string());
946 : else
947 0 : LOGMESSAGE("Received lobby authentication request, but not hosting currently!");
948 : }
949 : }
950 0 : else if (iq.subtype() == gloox::IQ::Get)
951 : {
952 0 : const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
953 0 : if (cd)
954 : {
955 0 : LOGMESSAGE("XmppClient: Received request for connection data from %s", iq.from().username());
956 0 : if (!g_NetServer)
957 : {
958 0 : glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
959 0 : ConnectionData* connectionData = new ConnectionData();
960 0 : connectionData->m_Error = "not_server";
961 :
962 0 : response.addExtension(connectionData);
963 :
964 0 : m_client->send(response);
965 0 : return true;
966 : }
967 0 : if (g_NetServer->IsBanned(iq.from().username()))
968 : {
969 0 : glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
970 0 : ConnectionData* connectionData = new ConnectionData();
971 0 : connectionData->m_Error = "banned";
972 :
973 0 : response.addExtension(connectionData);
974 :
975 0 : m_client->send(response);
976 0 : return true;
977 : }
978 0 : if (!g_NetServer->CheckPasswordAndIncrement(iq.from().username(), cd->m_Password.to_string(), cd->m_ClientSalt.to_string()))
979 : {
980 0 : glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
981 0 : ConnectionData* connectionData = new ConnectionData();
982 0 : connectionData->m_Error = "invalid_password";
983 :
984 0 : response.addExtension(connectionData);
985 :
986 0 : m_client->send(response);
987 0 : return true;
988 : }
989 :
990 0 : glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
991 0 : ConnectionData* connectionData = new ConnectionData();
992 :
993 0 : if (cd->m_IsLocalIP.to_string() == "0")
994 : {
995 0 : connectionData->m_Ip = g_NetServer->GetPublicIp();
996 0 : connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort());
997 0 : connectionData->m_UseSTUN = g_NetServer->GetUseSTUN() ? "true" : "";
998 0 : connectionData->m_IsLocalIP = "";
999 : }
1000 : else
1001 : {
1002 0 : CStr ip;
1003 0 : if (StunClient::FindLocalIP(ip))
1004 : {
1005 0 : connectionData->m_Ip = ip;
1006 0 : connectionData->m_Port = std::to_string(g_NetServer->GetLocalPort());
1007 0 : connectionData->m_UseSTUN = "";
1008 0 : connectionData->m_IsLocalIP = "true";
1009 : }
1010 : else
1011 0 : connectionData->m_Error = "local_ip_failed";
1012 : }
1013 :
1014 0 : response.addExtension(connectionData);
1015 :
1016 0 : m_client->send(response);
1017 : }
1018 :
1019 : }
1020 0 : else if (iq.subtype() == gloox::IQ::Error)
1021 0 : CreateGUIMessage("system", "error", std::time(nullptr), "text", iq.error_error());
1022 : else
1023 : {
1024 0 : CreateGUIMessage("system", "error", std::time(nullptr), "text", wstring_from_utf8(g_L10n.Translate("unknown subtype (see logs)")));
1025 0 : LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str());
1026 : }
1027 :
1028 0 : return true;
1029 : }
1030 :
1031 : /**
1032 : * Update local data when a user changes presence.
1033 : */
1034 0 : void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
1035 : {
1036 0 : const glooxwrapper::string& nick = participant.nick->resource();
1037 :
1038 0 : if (presence.presence() == gloox::Presence::Unavailable)
1039 : {
1040 0 : if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
1041 : {
1042 : // we have a nick change
1043 0 : if (m_PlayerMap.find(participant.newNick) == m_PlayerMap.end())
1044 0 : m_PlayerMap.emplace(
1045 : std::piecewise_construct,
1046 0 : std::forward_as_tuple(participant.newNick),
1047 0 : std::forward_as_tuple(presence.presence(), participant.role, std::move(m_PlayerMap.at(nick).m_Rating)));
1048 : else
1049 0 : LOGERROR("Nickname changed to an existing nick!");
1050 :
1051 : DbgXMPP(nick << " is now known as " << participant.newNick);
1052 0 : CreateGUIMessage(
1053 : "chat",
1054 : "nick",
1055 : std::time(nullptr),
1056 : "oldnick", nick,
1057 : "newnick", participant.newNick);
1058 : }
1059 0 : else if (participant.flags & gloox::UserKicked)
1060 : {
1061 : DbgXMPP(nick << " was kicked. Reason: " << participant.reason);
1062 0 : CreateGUIMessage(
1063 : "chat",
1064 : "kicked",
1065 : std::time(nullptr),
1066 : "nick", nick,
1067 : "reason", participant.reason);
1068 : }
1069 0 : else if (participant.flags & gloox::UserBanned)
1070 : {
1071 : DbgXMPP(nick << " was banned. Reason: " << participant.reason);
1072 0 : CreateGUIMessage(
1073 : "chat",
1074 : "banned",
1075 : std::time(nullptr),
1076 : "nick", nick,
1077 : "reason", participant.reason);
1078 : }
1079 : else
1080 : {
1081 : DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
1082 0 : CreateGUIMessage(
1083 : "chat",
1084 : "leave",
1085 : std::time(nullptr),
1086 : "nick", nick);
1087 : }
1088 0 : m_PlayerMap.erase(nick);
1089 : }
1090 : else
1091 : {
1092 0 : const PlayerMap::iterator it = m_PlayerMap.find(nick);
1093 :
1094 : /* During the initialization process, we receive join messages for everyone
1095 : * currently in the room. We don't want to display these, so we filter them
1096 : * out. We will always be the last to join during initialization.
1097 : */
1098 0 : if (!m_initialLoadComplete)
1099 : {
1100 0 : if (m_mucRoom->nick() == nick)
1101 0 : m_initialLoadComplete = true;
1102 : }
1103 0 : else if (it == m_PlayerMap.end())
1104 : {
1105 0 : CreateGUIMessage(
1106 : "chat",
1107 : "join",
1108 : std::time(nullptr),
1109 : "nick", nick);
1110 : }
1111 0 : else if (it->second.m_Role != participant.role)
1112 : {
1113 0 : CreateGUIMessage(
1114 : "chat",
1115 : "role",
1116 : std::time(nullptr),
1117 : "nick", nick,
1118 0 : "oldrole", it->second.m_Role,
1119 : "newrole", participant.role);
1120 : }
1121 : else
1122 : {
1123 : // Don't create a GUI message for regular presence changes, because
1124 : // several hundreds of them accumulate during a match, impacting performance terribly and
1125 : // the only way they are used is to determine whether to update the playerlist.
1126 : }
1127 :
1128 : DbgXMPP(
1129 : nick << " is in the room, "
1130 : "presence: " << GetPresenceString(presence.presence()) << ", "
1131 : "role: "<< GetRoleString(participant.role));
1132 :
1133 0 : if (it == m_PlayerMap.end())
1134 : {
1135 0 : m_PlayerMap.emplace(
1136 : std::piecewise_construct,
1137 0 : std::forward_as_tuple(nick),
1138 0 : std::forward_as_tuple(presence.presence(), participant.role, std::string()));
1139 : }
1140 : else
1141 : {
1142 0 : it->second.m_Presence = presence.presence();
1143 0 : it->second.m_Role = participant.role;
1144 : }
1145 : }
1146 :
1147 0 : m_PlayerMapUpdate = true;
1148 0 : }
1149 :
1150 : /**
1151 : * Update local cache when subject changes.
1152 : */
1153 0 : void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::string& nick, const glooxwrapper::string& subject)
1154 : {
1155 0 : m_Subject = wstring_from_utf8(subject.to_string());
1156 :
1157 0 : CreateGUIMessage(
1158 : "chat",
1159 : "subject",
1160 : std::time(nullptr),
1161 : "nick", nick,
1162 : "subject", m_Subject);
1163 0 : }
1164 :
1165 : /**
1166 : * Get current subject.
1167 : */
1168 0 : const std::wstring& XmppClient::GetSubject()
1169 : {
1170 0 : return m_Subject;
1171 : }
1172 :
1173 : /**
1174 : * Request nick change, real change via mucRoomHandler.
1175 : *
1176 : * @param nick Desired nickname
1177 : */
1178 0 : void XmppClient::SetNick(const std::string& nick)
1179 : {
1180 0 : m_mucRoom->setNick(nick);
1181 0 : }
1182 :
1183 : /**
1184 : * Get current nickname.
1185 : */
1186 0 : std::string XmppClient::GetNick() const
1187 : {
1188 0 : return m_mucRoom->nick().to_string();
1189 : }
1190 :
1191 0 : std::string XmppClient::GetJID() const
1192 : {
1193 0 : return m_client->getJID().to_string();
1194 : }
1195 :
1196 : /**
1197 : * Kick a player from the current room.
1198 : *
1199 : * @param nick Nickname to be kicked
1200 : * @param reason Reason the player was kicked
1201 : */
1202 0 : void XmppClient::kick(const std::string& nick, const std::string& reason)
1203 : {
1204 0 : m_mucRoom->kick(nick, reason);
1205 0 : }
1206 :
1207 : /**
1208 : * Ban a player from the current room.
1209 : *
1210 : * @param nick Nickname to be banned
1211 : * @param reason Reason the player was banned
1212 : */
1213 0 : void XmppClient::ban(const std::string& nick, const std::string& reason)
1214 : {
1215 0 : m_mucRoom->ban(nick, reason);
1216 0 : }
1217 :
1218 : /**
1219 : * Change the xmpp presence of the client.
1220 : *
1221 : * @param presence A string containing the desired presence
1222 : */
1223 0 : void XmppClient::SetPresence(const std::string& presence)
1224 : {
1225 : #define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
1226 0 : IF("available", Available);
1227 0 : else IF("chat", Chat);
1228 0 : else IF("away", Away);
1229 0 : else IF("playing", DND);
1230 0 : else IF("offline", Unavailable);
1231 : // The others are not to be set
1232 : #undef IF
1233 0 : else LOGERROR("Unknown presence '%s'", presence.c_str());
1234 0 : }
1235 :
1236 : /**
1237 : * Get the current xmpp presence of the given nick.
1238 : */
1239 0 : const char* XmppClient::GetPresence(const std::string& nick)
1240 : {
1241 0 : const PlayerMap::iterator it = m_PlayerMap.find(nick);
1242 :
1243 0 : if (it == m_PlayerMap.end())
1244 0 : return "offline";
1245 :
1246 0 : return GetPresenceString(it->second.m_Presence);
1247 : }
1248 :
1249 : /**
1250 : * Get the current xmpp role of the given nick.
1251 : */
1252 0 : const char* XmppClient::GetRole(const std::string& nick)
1253 : {
1254 0 : const PlayerMap::iterator it = m_PlayerMap.find(nick);
1255 :
1256 0 : if (it == m_PlayerMap.end())
1257 0 : return "";
1258 :
1259 0 : return GetRoleString(it->second.m_Role);
1260 : }
1261 :
1262 : /**
1263 : * Get the most recent received rating of the given nick.
1264 : * Notice that this doesn't request a rating profile if it hasn't been received yet.
1265 : */
1266 0 : std::wstring XmppClient::GetRating(const std::string& nick)
1267 : {
1268 0 : const PlayerMap::iterator it = m_PlayerMap.find(nick);
1269 :
1270 0 : if (it == m_PlayerMap.end())
1271 0 : return std::wstring();
1272 :
1273 0 : return wstring_from_utf8(it->second.m_Rating.to_string());
1274 : }
1275 :
1276 : /*****************************************************
1277 : * Utilities *
1278 : *****************************************************/
1279 :
1280 : /**
1281 : * Parse and return the timestamp of a historic chat message and return the current time for new chat messages.
1282 : * Historic chat messages are implement as DelayedDelivers as specified in XEP-0203.
1283 : * Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
1284 : *
1285 : * @returns Seconds since the epoch.
1286 : */
1287 0 : std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg)
1288 : {
1289 : // Only historic messages contain a timestamp!
1290 0 : if (!msg.when())
1291 0 : return std::time(nullptr);
1292 :
1293 : // The locale is irrelevant, because the XMPP date format doesn't contain written month names
1294 0 : for (const std::string& format : std::vector<std::string>{ "Y-M-d'T'H:m:sZ", "Y-M-d'T'H:m:s.SZ" })
1295 : {
1296 0 : UDate dateTime = g_L10n.ParseDateTime(msg.when()->stamp().to_string(), format, icu::Locale::getUS());
1297 0 : if (dateTime)
1298 0 : return dateTime / 1000.0;
1299 : }
1300 :
1301 0 : return std::time(nullptr);
1302 : }
1303 :
1304 : /**
1305 : * Convert a gloox presence type to an untranslated string literal to be used as an identifier by the scripts.
1306 : */
1307 0 : const char* XmppClient::GetPresenceString(const gloox::Presence::PresenceType presenceType)
1308 : {
1309 0 : switch (presenceType)
1310 : {
1311 : #define CASE(X,Y) case gloox::Presence::X: return Y
1312 0 : CASE(Available, "available");
1313 0 : CASE(Chat, "chat");
1314 0 : CASE(Away, "away");
1315 0 : CASE(DND, "playing");
1316 0 : CASE(XA, "away");
1317 0 : CASE(Unavailable, "offline");
1318 0 : CASE(Probe, "probe");
1319 0 : CASE(Error, "error");
1320 0 : CASE(Invalid, "invalid");
1321 0 : default:
1322 0 : LOGERROR("Unknown presence type '%d'", static_cast<int>(presenceType));
1323 0 : return "";
1324 : #undef CASE
1325 : }
1326 : }
1327 :
1328 : /**
1329 : * Convert a gloox role type to an untranslated string literal to be used as an identifier by the scripts.
1330 : */
1331 0 : const char* XmppClient::GetRoleString(const gloox::MUCRoomRole role)
1332 : {
1333 0 : switch (role)
1334 : {
1335 : #define CASE(X, Y) case gloox::X: return Y
1336 0 : CASE(RoleNone, "none");
1337 0 : CASE(RoleVisitor, "visitor");
1338 0 : CASE(RoleParticipant, "participant");
1339 0 : CASE(RoleModerator, "moderator");
1340 0 : CASE(RoleInvalid, "invalid");
1341 0 : default:
1342 0 : LOGERROR("Unknown role type '%d'", static_cast<int>(role));
1343 0 : return "";
1344 : #undef CASE
1345 : }
1346 : }
1347 :
1348 : /**
1349 : * Translates a gloox certificate error codes, i.e. gloox certificate statuses except CertOk.
1350 : * Keep in sync with specifications.
1351 : */
1352 0 : std::string XmppClient::CertificateErrorToString(gloox::CertStatus status)
1353 : {
1354 : std::map<gloox::CertStatus, std::string> certificateErrorStrings = {
1355 0 : { gloox::CertInvalid, g_L10n.Translate("The certificate is not trusted.") },
1356 0 : { gloox::CertSignerUnknown, g_L10n.Translate("The certificate hasn't got a known issuer.") },
1357 0 : { gloox::CertRevoked, g_L10n.Translate("The certificate has been revoked.") },
1358 0 : { gloox::CertExpired, g_L10n.Translate("The certificate has expired.") },
1359 0 : { gloox::CertNotActive, g_L10n.Translate("The certificate is not yet active.") },
1360 0 : { gloox::CertWrongPeer, g_L10n.Translate("The certificate has not been issued for the peer connected to.") },
1361 0 : { gloox::CertSignerNotCa, g_L10n.Translate("The certificate signer is not a certificate authority.") }
1362 0 : };
1363 :
1364 0 : std::string result;
1365 :
1366 0 : for (std::map<gloox::CertStatus, std::string>::iterator it = certificateErrorStrings.begin(); it != certificateErrorStrings.end(); ++it)
1367 0 : if (status & it->first)
1368 0 : result += "\n" + it->second;
1369 :
1370 0 : return result;
1371 : }
1372 :
1373 : /**
1374 : * Convert a gloox stanza error type to string.
1375 : * Keep in sync with Gloox documentation
1376 : *
1377 : * @param err Error to be converted
1378 : * @return Converted error string
1379 : */
1380 0 : std::string XmppClient::StanzaErrorToString(gloox::StanzaError err)
1381 : {
1382 : #define CASE(X, Y) case gloox::X: return Y
1383 : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1384 0 : switch (err)
1385 : {
1386 0 : CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
1387 0 : DEBUG_CASE(StanzaErrorBadRequest, "Server received malformed XML");
1388 0 : CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in"));
1389 0 : DEBUG_CASE(StanzaErrorFeatureNotImplemented, "Server does not implement requested feature");
1390 0 : CASE(StanzaErrorForbidden, g_L10n.Translate("Forbidden"));
1391 0 : DEBUG_CASE(StanzaErrorGone, "Unable to find message receipiant");
1392 0 : CASE(StanzaErrorInternalServerError, g_L10n.Translate("Internal server error"));
1393 0 : DEBUG_CASE(StanzaErrorItemNotFound, "Message receipiant does not exist");
1394 0 : DEBUG_CASE(StanzaErrorJidMalformed, "JID (XMPP address) malformed");
1395 0 : DEBUG_CASE(StanzaErrorNotAcceptable, "Receipiant refused message. Possible policy issue");
1396 0 : CASE(StanzaErrorNotAllowed, g_L10n.Translate("Not allowed"));
1397 0 : CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized"));
1398 0 : DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request");
1399 0 : DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment");
1400 0 : CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable"));
1401 0 : DEBUG_CASE(StanzaErrorRedirect, "Request redirected");
1402 0 : CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required"));
1403 0 : DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
1404 0 : DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out");
1405 0 : DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints");
1406 0 : CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable"));
1407 0 : DEBUG_CASE(StanzaErrorSubscribtionRequired, "Service requires subscription");
1408 0 : DEBUG_CASE(StanzaErrorUnexpectedRequest, "Attempt to send from invalid stanza address");
1409 0 : DEBUG_CASE(StanzaErrorUnknownSender, "Invalid 'from' address");
1410 0 : default:
1411 0 : return g_L10n.Translate("Unknown error");
1412 : }
1413 : #undef DEBUG_CASE
1414 : #undef CASE
1415 : }
1416 :
1417 : /**
1418 : * Convert a gloox connection error enum to string
1419 : * Keep in sync with Gloox documentation
1420 : *
1421 : * @param err Error to be converted
1422 : * @return Converted error string
1423 : */
1424 0 : std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err)
1425 : {
1426 : #define CASE(X, Y) case gloox::X: return Y
1427 : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1428 0 : switch (err)
1429 : {
1430 0 : CASE(ConnNoError, g_L10n.Translate("No error"));
1431 0 : CASE(ConnStreamError, g_L10n.Translate("Stream error"));
1432 0 : CASE(ConnStreamVersionError, g_L10n.Translate("The incoming stream version is unsupported"));
1433 0 : CASE(ConnStreamClosed, g_L10n.Translate("The stream has been closed by the server"));
1434 0 : DEBUG_CASE(ConnProxyAuthRequired, "The HTTP/SOCKS5 proxy requires authentication");
1435 0 : DEBUG_CASE(ConnProxyAuthFailed, "HTTP/SOCKS5 proxy authentication failed");
1436 0 : DEBUG_CASE(ConnProxyNoSupportedAuth, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism");
1437 0 : CASE(ConnIoError, g_L10n.Translate("An I/O error occurred"));
1438 0 : DEBUG_CASE(ConnParseError, "An XML parse error occurred");
1439 0 : CASE(ConnConnectionRefused, g_L10n.Translate("The connection was refused by the server"));
1440 0 : CASE(ConnDnsError, g_L10n.Translate("Resolving the server's hostname failed"));
1441 0 : CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory"));
1442 0 : DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
1443 0 : CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
1444 0 : CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption"));
1445 0 : DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed");
1446 0 : CASE(ConnAuthenticationFailed, g_L10n.Translate("Authentication failed. Incorrect password or account does not exist"));
1447 0 : CASE(ConnUserDisconnected, g_L10n.Translate("The user or system requested a disconnect"));
1448 0 : CASE(ConnNotConnected, g_L10n.Translate("There is no active connection"));
1449 0 : default:
1450 0 : return g_L10n.Translate("Unknown error");
1451 : }
1452 : #undef DEBUG_CASE
1453 : #undef CASE
1454 : }
1455 :
1456 : /**
1457 : * Convert a gloox registration result enum to string
1458 : * Keep in sync with Gloox documentation
1459 : *
1460 : * @param err Enum to be converted
1461 : * @return Converted string
1462 : */
1463 0 : std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res)
1464 : {
1465 : #define CASE(X, Y) case gloox::X: return Y
1466 : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1467 0 : switch (res)
1468 : {
1469 0 : CASE(RegistrationSuccess, g_L10n.Translate("Your account has been successfully registered"));
1470 0 : CASE(RegistrationNotAcceptable, g_L10n.Translate("Not all necessary information provided"));
1471 0 : CASE(RegistrationConflict, g_L10n.Translate("Username already exists"));
1472 0 : DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change");
1473 0 : DEBUG_CASE(RegistrationBadRequest, "Server received an incomplete request");
1474 0 : DEBUG_CASE(RegistrationForbidden, "Registration forbidden");
1475 0 : DEBUG_CASE(RegistrationRequired, "Account cannot be removed as it does not exist");
1476 0 : DEBUG_CASE(RegistrationUnexpectedRequest, "This client is unregistered with the server");
1477 0 : DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes");
1478 0 : default:
1479 0 : return "";
1480 : }
1481 : #undef DEBUG_CASE
1482 : #undef CASE
1483 : }
1484 :
1485 0 : void XmppClient::SendStunEndpointToHost(const std::string& ip, u16 port, const std::string& hostJIDStr)
1486 : {
1487 : DbgXMPP("SendStunEndpointToHost " << hostJIDStr);
1488 :
1489 0 : glooxwrapper::JID hostJID(hostJIDStr);
1490 0 : glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
1491 0 : session.sessionInitiate(ip.c_str(), port);
1492 0 : }
1493 :
1494 0 : void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle)
1495 : {
1496 0 : if (action == gloox::Jingle::SessionInitiate)
1497 0 : handleSessionInitiation(session, jingle);
1498 0 : }
1499 :
1500 0 : void XmppClient::handleSessionInitiation(glooxwrapper::Jingle::Session& UNUSED(session), const glooxwrapper::Jingle::Session::Jingle& jingle)
1501 : {
1502 0 : glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle.getCandidate();
1503 :
1504 0 : if (candidate.ip.empty())
1505 : {
1506 0 : LOGERROR("Failed to retrieve Jingle candidate");
1507 0 : return;
1508 : }
1509 :
1510 0 : if (!g_NetServer)
1511 : {
1512 0 : LOGERROR("Received STUN connection request, but not hosting currently!");
1513 0 : return;
1514 : }
1515 :
1516 0 : g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
1517 3 : }
|