1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 // Provide BZFS with a list server connection
14 
15 /* class header */
16 #include "ListServerConnection.h"
17 
18 /* system implementation headers */
19 #include <string.h>
20 #include <string>
21 #include <errno.h>
22 #include <set>
23 
24 /* common implementation headers */
25 #include "bzfio.h"
26 #include "version.h"
27 #include "TextUtils.h"
28 #include "Protocol.h"
29 #include "bzfsAPI.h"
30 #include "WorldEventManager.h"
31 
32 /* local implementation headers */
33 #include "bzfs.h"
34 
35 const int ListServerLink::NotConnected = -1;
36 
ListServerLink(std::string listServerURL,std::string publicizedAddress,std::string publicizedTitle,std::string _advertiseGroups)37 ListServerLink::ListServerLink(std::string listServerURL,
38                                std::string publicizedAddress,
39                                std::string publicizedTitle,
40                                std::string _advertiseGroups)
41 {
42 
43     std::string bzfsUserAgent = "bzfs ";
44     bzfsUserAgent     += getAppVersion();
45 
46     setURLwithNonce(listServerURL);
47     setUserAgent(bzfsUserAgent);
48     setTimeout(10);
49 
50     publiclyDisconnected = false;
51 
52     if (clOptions->pingInterface != "")
53         setInterface(clOptions->pingInterface);
54 
55     publicizeAddress     = publicizedAddress;
56     publicizeDescription = publicizedTitle;
57     advertiseGroups      = _advertiseGroups;
58 
59     //if this c'tor is called, it's safe to publicize
60     publicizeServer      = true;
61     queuedRequest = false;
62     // schedule initial ADD message
63     queueMessage(ListServerLink::ADD);
64 }
65 
ListServerLink()66 ListServerLink::ListServerLink(): nextMessageType(), queuedRequest(0)
67 {
68     // does not create a usable link, so checks should be placed
69     // in  all public member functions to ensure that nothing tries
70     // to happen if publicizeServer is false
71     publicizeServer = false;
72 }
73 
~ListServerLink()74 ListServerLink::~ListServerLink()
75 {
76     // now tell the list server that we're going away.  this can
77     // take some time but we don't want to wait too long.  we do
78     // our own multiplexing loop and wait for a maximum of 3 seconds
79     // total.
80 
81     // if we aren't supposed to be publicizing, skip the whole thing
82     // and don't waste 3 seconds.
83     if (!publicizeServer)
84         return;
85 
86     queueMessage(ListServerLink::REMOVE);
87     for (int i = 0; i < 12; i++)
88     {
89         cURLManager::perform();
90         if (!queuedRequest)
91             break;
92         TimeKeeper::sleep(0.25f);
93     }
94 }
95 
finalization(char * data,unsigned int length,bool good)96 void ListServerLink::finalization(char *data, unsigned int length, bool good)
97 {
98     publiclyDisconnected = !good;
99     GameKeeper::Player *playerData = NULL;
100     queuedRequest = false;
101     if (good && (length < 2048))
102     {
103         char buf[2048];
104         memcpy(buf, data, length);
105         int bytes = length;
106         buf[bytes]=0;
107         char* base = buf;
108         const char tokGoodIdentifier[] = "TOKGOOD: ";
109         const char tokBadIdentifier[]  = "TOKBAD: ";
110         const char unknownPlayer[]     = "UNK: ";
111         const char bzIdentifier[]      = "BZID: ";
112         const char ownerIdentifier[]   = "OWNER: ";
113         // walks entire reply including HTTP headers
114         while (*base)
115         {
116             // find next newline
117             char* scan = base;
118             while (*scan && *scan != '\r' && *scan != '\n') scan++;
119             // if no newline then no more complete replies
120             if (*scan != '\r' && *scan != '\n') break;
121             while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
122             logDebugMessage(4,"Got line: \"%s\"\n", base);
123             // TODO don't do this if we don't want central logins
124 
125             // is player globally registered ?
126             bool  registered = false;
127             // is player authenticated ?
128             bool  verified   = false;
129             // this is a reply to an authentication request ?
130             bool  authReply  = false;
131 
132             char *callsign;
133             if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0)
134             {
135                 callsign = base + strlen(tokGoodIdentifier);
136                 registered = true;
137                 verified   = true;
138                 authReply  = true;
139             }
140             else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier)))
141             {
142                 callsign = base + strlen(tokBadIdentifier);
143                 registered = true;
144                 authReply  = true;
145             }
146             else if (!strncmp(base, unknownPlayer, strlen(unknownPlayer)))
147             {
148                 callsign = base + strlen(unknownPlayer);
149                 authReply  = true;
150             }
151             else if (!strncmp(base, ownerIdentifier, strlen(ownerIdentifier)))
152                 setPublicOwner(base + strlen(ownerIdentifier));
153             else if (!strncmp(base, bzIdentifier, strlen(bzIdentifier)))
154             {
155                 std::string line = base;
156                 std::vector<std::string> args = TextUtils::tokenize(line, " \t", 3, true);
157                 if (args.size() < 3)
158                     logDebugMessage(3,"Bad BZID string: %s\n", line.c_str());
159                 else
160                 {
161                     const std::string& bzId = args[1];
162                     const std::string& nick = args[2];
163                     logDebugMessage(4,"Got BZID: \"%s\" || \"%s\"\n", bzId.c_str(), nick.c_str());
164                     for (int i = 0; i < curMaxPlayers; i++)
165                     {
166                         GameKeeper::Player* gkp = GameKeeper::Player::getPlayerByIndex(i);
167                         if ((gkp != NULL) &&
168                                 (strcasecmp(gkp->player.getCallSign(), nick.c_str()) == 0) &&
169                                 (gkp->_LSAState == GameKeeper::Player::verified))
170                         {
171                             gkp->setBzIdentifier(bzId);
172                             logDebugMessage(3,"Set player (%s [%i]) bzId to (%s)\n",
173                                             nick.c_str(), i, bzId.c_str());
174                             break;
175                         }
176                     }
177                 }
178             }
179             /*
180                 char* start = base + strlen(bzIdentifier);
181                 // skip leading white
182                 while ((*start != '\0') && isspace(*start)) start++;
183                 const bool useQuotes = (*start == '"');
184                 if (useQuotes) start++; // ditch the '"'
185                 char* end = start;
186                 // skip until the end of the id
187                 if (useQuotes) {
188                   while ((*end != '\0') && (*end != '"')) end++;
189                 } else {
190                   while ((*end != '\0') && !isspace(*end)) end++;
191                 }
192                 if ((*end != '\0') && (useQuotes && (*end != '"'))) {
193                   if (useQuotes) {
194                     callsign = end + 1;
195                     end--; // ditch the '"'
196                   } else {
197                     callsign = end;
198                   }
199                   // skip leading white
200                   while ((*callsign != '\0') && isspace(*callsign)) callsign++;
201                   if (*callsign != '\0') {
202                     bzId = start;
203                     bzId = bzId.substr(end - start);
204                     if ((bzId.size() > 0) && (strlen(callsign) > 0)) {
205                       bzIdInfo = true;
206                     }
207                   }
208                 }
209                   }
210 
211                   if (bzIdInfo == true) {
212                 logDebugMessage(3,"Got BZID: %s", base);
213                 for (int i = 0; i < curMaxPlayers; i++) {
214                   GameKeeper::Player* gkp = GameKeeper::Player::getPlayerByIndex(i);
215                   if ((gkp != NULL) &&
216                       (strcasecmp(gkp->player.getCallSign(), callsign) == 0)) {
217                     gkp->setBzIdentifier(bzId);
218                     logDebugMessage(3,"Set player (%s [%i]) bzId to (%s)\n", callsign, i, bzId.c_str());
219                     break;
220                   }
221                 }
222                   }
223             */
224             if (authReply)
225             {
226                 logDebugMessage(3,"Got: %s", base);
227                 char *group = (char *)NULL;
228 
229                 // Isolate callsign from groups
230                 if (verified)
231                 {
232                     group = callsign;
233                     if (group)
234                     {
235                         while (*group && (*group != ':')) group++;
236                         while (*group && (*group == ':')) *group++ = 0;
237                     }
238                 }
239                 playerData = NULL;
240                 int playerIndex;
241                 for (playerIndex = 0; playerIndex < curMaxPlayers; playerIndex++)
242                 {
243                     playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
244                     if (!playerData)
245                         continue;
246                     if (playerData->_LSAState != GameKeeper::Player::checking)
247                         continue;
248                     if (!TextUtils::compare_nocase(playerData->player.getCallSign(),
249                                                    callsign))
250                         break;
251                 }
252                 logDebugMessage(3,"[%d]\n", playerIndex);
253 
254                 if (playerIndex < curMaxPlayers)
255                 {
256                     if (registered)
257                     {
258                         if (!playerData->accessInfo.isRegistered())
259                             // Create an entry on the user database even if
260                             // authentication wenk ko. Make the "isRegistered"
261                             // things work
262                             playerData->accessInfo.storeInfo();
263                         if (verified)
264                         {
265                             playerData->_LSAState = GameKeeper::Player::verified;
266                             playerData->accessInfo.setPermissionRights();
267                             while (group && *group)
268                             {
269                                 char *nextgroup = group;
270                                 if (nextgroup)
271                                 {
272                                     while (*nextgroup && (*nextgroup != ':')) nextgroup++;
273                                     while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
274                                 }
275                                 playerData->accessInfo.addGroup(group);
276                                 group = nextgroup;
277                             }
278                             playerData->authentication.global(true);
279                             sendMessage(ServerPlayer, playerIndex,
280                                         "Global login approved!");
281                         }
282                         else
283                         {
284                             playerData->_LSAState = GameKeeper::Player::failed;
285                             sendMessage(ServerPlayer, playerIndex,
286                                         "Global login rejected, bad token.");
287                         }
288                     }
289                     else
290                     {
291                         playerData->_LSAState = GameKeeper::Player::notRequired;
292                         if (!playerData->player.isBot())
293                         {
294                             sendMessage(ServerPlayer, playerIndex,
295                                         "This callsign is not registered.");
296                             sendMessage(ServerPlayer, playerIndex,
297                                         "You can register it at https://forums.bzflag.org/");
298                         }
299                     }
300                     playerData->player.clearToken();
301                 }
302             }
303 
304             // next reply
305             base = scan;
306         }
307     }
308 
309     if (playerData != NULL)
310     {
311         // tell the API that auth is complete
312         bz_AuthenticationCompleteData_V1 eventData;
313         eventData.player = bz_getPlayerByIndex(playerData->getIndex());
314         worldEventManager.callEvents(&eventData);
315     }
316 
317     for (int i = 0; i < curMaxPlayers; i++)
318     {
319         playerData = GameKeeper::Player::getPlayerByIndex(i);
320         if (!playerData)
321             continue;
322         if (playerData->_LSAState != GameKeeper::Player::checking)
323             continue;
324         playerData->_LSAState = GameKeeper::Player::timedOut;
325     }
326     if (nextMessageType != ListServerLink::NONE)
327     {
328         // There was a pending request arrived after we write:
329         // we should redo all the stuff
330         sendQueuedMessages();
331     }
332 }
333 
queueMessage(MessageType type)334 void ListServerLink::queueMessage(MessageType type)
335 {
336     // ignore if the server is not public
337     if (!publicizeServer) return;
338 
339     // record next message to send.
340     nextMessageType = type;
341 
342     if (!queuedRequest)
343         sendQueuedMessages();
344     else
345         logDebugMessage(3,"There is a message already queued to the list server: not sending this one yet.\n");
346 }
347 
sendQueuedMessages()348 void ListServerLink::sendQueuedMessages()
349 {
350     queuedRequest = true;
351     if (nextMessageType == ListServerLink::ADD)
352     {
353         logDebugMessage(3,"Queuing ADD message to list server\n");
354 
355         bz_ListServerUpdateEvent_V1 updateEvent;
356         updateEvent.address = publicizeAddress;
357         updateEvent.description = publicizeDescription;
358         updateEvent.groups = advertiseGroups;
359 
360         worldEventManager.callEvents(bz_eListServerUpdateEvent,&updateEvent);
361 
362         addMe(getTeamCounts(), std::string(updateEvent.address.c_str()), std::string(updateEvent.description.c_str()),
363               std::string(updateEvent.groups.c_str()));
364         lastAddTime = TimeKeeper::getCurrent();
365     }
366     else if (nextMessageType == ListServerLink::REMOVE)
367     {
368         logDebugMessage(3,"Queuing REMOVE message to list server\n");
369         removeMe(publicizeAddress);
370     }
371     nextMessageType = ListServerLink::NONE;
372 }
373 
addMe(PingPacket pingInfo,std::string publicizedAddress,std::string publicizedTitle,std::string _advertiseGroups)374 void ListServerLink::addMe(PingPacket pingInfo,
375                            std::string publicizedAddress,
376                            std::string publicizedTitle,
377                            std::string _advertiseGroups)
378 {
379     std::string msg;
380 
381     // encode ping reply as ascii hex digits plus NULL
382     char gameInfo[PingPacketHexPackedSize + 1];
383     pingInfo.packHex(gameInfo);
384 
385     // send ADD message (must send blank line)
386     msg  = "action=ADD&nameport=";
387     msg += publicizedAddress;
388     msg += "&version=";
389     msg += getServerVersion();
390     msg += "&gameinfo=";
391     msg += gameInfo;
392     msg += "&build=";
393     msg += getAppVersion();
394     msg += "&checktokens=";
395 
396     std::set<std::string> callSigns;
397     // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
398     for (int i = 0; i < curMaxPlayers; i++)
399     {
400         GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
401         if (!playerData)
402             continue;
403         if ((playerData->_LSAState != GameKeeper::Player::required)
404                 && (playerData->_LSAState != GameKeeper::Player::requesting))
405             continue;
406         if (callSigns.count(playerData->player.getCallSign()))
407             continue;
408         callSigns.insert(playerData->player.getCallSign());
409         playerData->_LSAState = GameKeeper::Player::checking;
410         NetHandler *handler = playerData->netHandler;
411         msg += TextUtils::url_encode(playerData->player.getCallSign());
412         Address addr = handler->getIPAddress();
413         if (!addr.isPrivate())
414         {
415             msg += "@";
416             msg += handler->getTargetIP();
417         }
418         msg += "=";
419         msg += playerData->player.getToken();
420         msg += "%0D%0A";
421     }
422 
423     msg += "&groups=";
424     // *groups=GROUP0%0D%0AGROUP1%0D%0A
425     PlayerAccessMap::iterator itr = groupAccess.begin();
426     for ( ; itr != groupAccess.end(); ++itr)
427     {
428         if (itr->first.substr(0, 6) != "LOCAL.")
429         {
430             msg += itr->first.c_str();
431             msg += "%0D%0A";
432         }
433     }
434 
435     if (clOptions && !clOptions->publicizedKey.empty())
436         msg += "&key=" + clOptions->publicizedKey;
437 
438     msg += "&advertgroups=";
439     msg += TextUtils::url_encode(_advertiseGroups);
440     msg += "&title=";
441     msg += TextUtils::url_encode(publicizedTitle);
442 
443     setPostMode(msg);
444     addHandle();
445 }
446 
removeMe(std::string publicizedAddress)447 void ListServerLink::removeMe(std::string publicizedAddress)
448 {
449     std::string msg;
450 
451     msg  = "action=REMOVE&nameport=";
452     msg += publicizedAddress;
453 
454     setPostMode(msg);
455     addHandle();
456 }
457 
458 // Local Variables: ***
459 // mode: C++ ***
460 // tab-width: 4 ***
461 // c-basic-offset: 4 ***
462 // indent-tabs-mode: nil ***
463 // End: ***
464 // ex: shiftwidth=4 tabstop=4
465