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