From 30ce141de2bb33ce1af887973c583331a2edd289 Mon Sep 17 00:00:00 2001 From: blast007 Date: Tue, 26 Nov 2024 04:05:14 -0800 Subject: [PATCH] Request an authentication token only at join, and using the GETTOKEN action instead of the LIST action. (#351) Requesting an authentication token no longer fetches the entire server list. The GETTOKEN action is used to fetch a token when connecting to a server, and the server host/port are passed along. This also instructs the list server to not generate a token for the LIST action, since we're now trying to restrict tokens to a specific server instead of the player IP in order to handle CGNAT. --- MSVC/build/game.vcxproj | 2 + MSVC/build/game.vcxproj.filters | 6 ++ Xcode/BZFlag.xcodeproj/project.pbxproj | 6 ++ include/Makefile.am | 1 + include/ServerAuth.h | 38 +++++++++ include/cURLManager.h | 1 - src/bzadmin/BZAdminClient.cxx | 22 ++++- src/bzflag/playing.cxx | 26 +++--- src/bzfs/ListServerConnection.cxx | 2 +- src/common/cURLManager.cxx | 8 -- src/game/Makefile.am | 1 + src/game/ServerAuth.cxx | 111 +++++++++++++++++++++++++ src/game/ServerList.cxx | 56 +++---------- 13 files changed, 210 insertions(+), 70 deletions(-) create mode 100644 include/ServerAuth.h create mode 100644 src/game/ServerAuth.cxx diff --git a/MSVC/build/game.vcxproj b/MSVC/build/game.vcxproj index 0f0319a9c8..7902dfa359 100644 --- a/MSVC/build/game.vcxproj +++ b/MSVC/build/game.vcxproj @@ -164,6 +164,7 @@ + @@ -184,6 +185,7 @@ + diff --git a/MSVC/build/game.vcxproj.filters b/MSVC/build/game.vcxproj.filters index 0b0ce9f449..ca2e99da21 100644 --- a/MSVC/build/game.vcxproj.filters +++ b/MSVC/build/game.vcxproj.filters @@ -61,6 +61,9 @@ Source Files + + Source Files + @@ -114,6 +117,9 @@ Header Files + + Header Files + diff --git a/Xcode/BZFlag.xcodeproj/project.pbxproj b/Xcode/BZFlag.xcodeproj/project.pbxproj index 62493fc74d..7331f32c58 100644 --- a/Xcode/BZFlag.xcodeproj/project.pbxproj +++ b/Xcode/BZFlag.xcodeproj/project.pbxproj @@ -529,6 +529,7 @@ C38E766527F9062B00EC312C /* CustomZoneSample.bzw in Copy Files (1 item) */ = {isa = PBXBuildFile; fileRef = C353B1E81AE2489900C5AED5 /* CustomZoneSample.bzw */; }; C39F69371E3D3E8100132C55 /* customPollTypeSample.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = C333EFC91E2B9F0800B4B182 /* customPollTypeSample.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C3DE232E21E2FBDD0081B64E /* libplugin_utils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0394E895167B2513007F4035 /* libplugin_utils.a */; }; + D21A35892BFAA6BD001A28EB /* ServerAuth.cxx in Sources */ = {isa = PBXBuildFile; fileRef = D21A35882BFAA6BD001A28EB /* ServerAuth.cxx */; }; D2B721C92BFAA226007126EE /* mathRoutine.cxx in Sources */ = {isa = PBXBuildFile; fileRef = D2B721C82BFAA225007126EE /* mathRoutine.cxx */; }; DF28982B16B4F5AE006C78AC /* ShotManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DF28982916B4F5AE006C78AC /* ShotManager.cxx */; }; /* End PBXBuildFile section */ @@ -1814,6 +1815,8 @@ C353B1E01AE23C8100C5AED5 /* CustomZoneSample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CustomZoneSample.cpp; sourceTree = ""; }; C353B1E81AE2489900C5AED5 /* CustomZoneSample.bzw */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CustomZoneSample.bzw; sourceTree = ""; }; C353B1E91AE2490D00C5AED5 /* flagStay.bzw */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = flagStay.bzw; sourceTree = ""; }; + D21A35872BFAA6AE001A28EB /* ServerAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ServerAuth.h; sourceTree = ""; }; + D21A35882BFAA6BD001A28EB /* ServerAuth.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ServerAuth.cxx; sourceTree = ""; }; D2B721C72BFAA1E1007126EE /* mathRoutine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mathRoutine.h; sourceTree = ""; }; D2B721C82BFAA225007126EE /* mathRoutine.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mathRoutine.cxx; sourceTree = ""; }; DF28982916B4F5AE006C78AC /* ShotManager.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShotManager.cxx; sourceTree = ""; }; @@ -2347,6 +2350,7 @@ 0305D5D9166C9DAE00557FC4 /* SceneDatabase.h */, 0305D5DA166C9DAE00557FC4 /* SceneNode.h */, 0305D5DB166C9DAE00557FC4 /* SceneRenderer.h */, + D21A35872BFAA6AE001A28EB /* ServerAuth.h */, 0305D5DC166C9DAE00557FC4 /* ServerItem.h */, 0305D5DD166C9DAE00557FC4 /* ServerList.h */, 0305D5DE166C9DAE00557FC4 /* ServerListCache.h */, @@ -2858,6 +2862,7 @@ 0355447B166C846F008806E9 /* PlayerInfo.cxx */, 0355447C166C846F008806E9 /* Ray.cxx */, 0355447D166C846F008806E9 /* README */, + D21A35882BFAA6BD001A28EB /* ServerAuth.cxx */, 0355447E166C846F008806E9 /* ServerItem.cxx */, 0355447F166C846F008806E9 /* ServerList.cxx */, 03554480166C846F008806E9 /* ServerListCache.cxx */, @@ -5070,6 +5075,7 @@ 0357A80E1670B2090056C938 /* PhysicsDriver.cxx in Sources */, 0357A80F1670B2090056C938 /* PlayerInfo.cxx in Sources */, 0357A8101670B2090056C938 /* Ray.cxx in Sources */, + D21A35892BFAA6BD001A28EB /* ServerAuth.cxx in Sources */, 0357A8111670B2090056C938 /* ServerItem.cxx in Sources */, 0357A8121670B2090056C938 /* ServerList.cxx in Sources */, 0357A8131670B2090056C938 /* ServerListCache.cxx in Sources */, diff --git a/include/Makefile.am b/include/Makefile.am index 0730ef41e7..3ad03bd874 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -102,6 +102,7 @@ noinst_HEADERS = \ SceneDatabase.h \ SceneNode.h \ SceneRenderer.h \ + ServerAuth.h \ ServerItem.h \ ServerList.h \ ServerListCache.h \ diff --git a/include/ServerAuth.h b/include/ServerAuth.h new file mode 100644 index 0000000000..3309657f59 --- /dev/null +++ b/include/ServerAuth.h @@ -0,0 +1,38 @@ +/* bzflag + * Copyright (c) 1993-2024 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef BZF_SERVERAUTH_H +#define BZF_SERVERAUTH_H + +#include "common.h" + +/* system interface headers */ +#include + +/* common interface headers */ +#include "StartupInfo.h" +#include "cURLManager.h" + +class ServerAuth : cURLManager +{ +public: + ServerAuth(); + virtual ~ServerAuth(); + + void requestToken(StartupInfo *info); + void finalization(char *data, unsigned int length, bool good); + +private: + StartupInfo *startupInfo; +}; + +#endif // BZF_SERVERAUTH_H diff --git a/include/cURLManager.h b/include/cURLManager.h index 34da946d58..059d282645 100644 --- a/include/cURLManager.h +++ b/include/cURLManager.h @@ -46,7 +46,6 @@ class cURLManager void setPostMode(std::string postData); void setRequestFileTime(bool request); void setURL(const std::string &url); - void setURLwithNonce(const std::string &url); void setProgressFunction(curl_xferinfo_callback func, const void* data); void setTimeCondition(timeCondition condition, time_t &t); void setInterface(const std::string &interfaceIP); diff --git a/src/bzadmin/BZAdminClient.cxx b/src/bzadmin/BZAdminClient.cxx index fd1c4198c9..aee74f8c8b 100644 --- a/src/bzadmin/BZAdminClient.cxx +++ b/src/bzadmin/BZAdminClient.cxx @@ -30,6 +30,7 @@ #include "TextUtils.h" #include "version.h" #include "Team.h" +#include "ServerAuth.h" #include "ServerList.h" #include "ErrorHandler.h" #include "cURLManager.h" @@ -73,10 +74,25 @@ BZAdminClient::BZAdminClient(BZAdminUI* bzInterface) std::cout << std::endl; return; } - if ((startupInfo.token[0] == '\0') && (startupInfo.password[0] != '\0')) + + // If a password was provided, get a token + if (startupInfo.password[0] != '\0') { - // won't really output anything, just gets token - outputServerList(); + ServerAuth* serverAuth = new ServerAuth; + serverAuth->requestToken(&startupInfo); + // wait no more than 10 seconds for a token + for (int j = 0; j < 40; j++) + { + cURLManager::perform(); + if (startupInfo.token[0] != '\0') break; + TimeKeeper::sleep(0.25f); + } + delete serverAuth; + + // don't let the bad token specifier slip through to the server, + // just erase it + if (strcmp(startupInfo.token, "badtoken") == 0) + startupInfo.token[0] = '\0'; } sLink.sendEnter(TankPlayer, myTeam, startupInfo.callsign, "bzadmin", startupInfo.token); if (sLink.getState() != ServerLink::Okay) diff --git a/src/bzflag/playing.cxx b/src/bzflag/playing.cxx index cb1c8bf5a6..a7b0291a5d 100644 --- a/src/bzflag/playing.cxx +++ b/src/bzflag/playing.cxx @@ -52,7 +52,7 @@ #include "PhysicsDriver.h" #include "PlatformFactory.h" #include "QuadWallSceneNode.h" -#include "ServerList.h" +#include "ServerAuth.h" #include "SphereSceneNode.h" #include "TankGeometryMgr.h" #include "Team.h" @@ -7010,26 +7010,28 @@ static void playingLoop() // if already connected to a game then first sign off if (myTank) leaveGame(); - // get token if we need to (have a password but no token) - if ((startupInfo.token[0] == '\0') - && (startupInfo.password[0] != '\0')) + // Erase any existing token + startupInfo.token[0] = '\0'; + + // get token if we have a password + if (startupInfo.password[0] != '\0') { - ServerList* serverList = new ServerList; - serverList->startServerPings(&startupInfo); + ServerAuth* serverAuth = new ServerAuth; + serverAuth->requestToken(&startupInfo); // wait no more than 10 seconds for a token for (int j = 0; j < 40; j++) { - serverList->checkEchos(getStartupInfo()); cURLManager::perform(); if (startupInfo.token[0] != '\0') break; TimeKeeper::sleep(0.25f); } - delete serverList; + delete serverAuth; + + // don't let the bad token specifier slip through to the server, + // just erase it + if (strcmp(startupInfo.token, "badtoken") == 0) + startupInfo.token[0] = '\0'; } - // don't let the bad token specifier slip through to the server, - // just erase it - if (strcmp(startupInfo.token, "badtoken") == 0) - startupInfo.token[0] = '\0'; ares->queryHost(startupInfo.serverName); waitingDNS = true; diff --git a/src/bzfs/ListServerConnection.cxx b/src/bzfs/ListServerConnection.cxx index 4f9b5a13f0..d15fa7889b 100644 --- a/src/bzfs/ListServerConnection.cxx +++ b/src/bzfs/ListServerConnection.cxx @@ -43,7 +43,7 @@ ListServerLink::ListServerLink(std::string listServerURL, std::string bzfsUserAgent = "bzfs "; bzfsUserAgent += getAppVersion(); - setURLwithNonce(listServerURL); + setURL(listServerURL); setUserAgent(bzfsUserAgent); setTimeout(10); diff --git a/src/common/cURLManager.cxx b/src/common/cURLManager.cxx index 3b0818c503..5155f8070c 100644 --- a/src/common/cURLManager.cxx +++ b/src/common/cURLManager.cxx @@ -186,14 +186,6 @@ void cURLManager::setURL(const std::string &url) logDebugMessage(1,"CURLOPT_URL error %d : %s\n", result, errorBuffer); } -void cURLManager::setURLwithNonce(const std::string &url) -{ - // only the default list server is known to support the nonce parameter - const std::string nonce = (strcasecmp(url.c_str(), DefaultListServerURL) == 0) ? TextUtils::format("?nocache=%lu", - time(0)) : ""; - setURL(url + nonce); -} - void cURLManager::setProgressFunction(curl_xferinfo_callback func, const void* data) { CURLcode result; diff --git a/src/game/Makefile.am b/src/game/Makefile.am index 7ddefe71e1..1d10ac8e74 100644 --- a/src/game/Makefile.am +++ b/src/game/Makefile.am @@ -26,6 +26,7 @@ libGame_la_SOURCES = \ PlayerInfo.cxx \ Ray.cxx \ ServerItem.cxx \ + ServerAuth.cxx \ ServerList.cxx \ ServerListCache.cxx \ StartupInfo.cxx \ diff --git a/src/game/ServerAuth.cxx b/src/game/ServerAuth.cxx new file mode 100644 index 0000000000..fc4d393505 --- /dev/null +++ b/src/game/ServerAuth.cxx @@ -0,0 +1,111 @@ +/* bzflag + * Copyright (c) 1993-2024 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* interface header */ +#include "ServerAuth.h" + +/* system headers */ +#include + +/* common implementation headers */ +#include "TextUtils.h" +#include "ErrorHandler.h" + +ServerAuth::ServerAuth() +{ +} + +ServerAuth::~ServerAuth() +{ +} + +void ServerAuth::requestToken(StartupInfo *info) +{ + startupInfo = info; + + std::string url = info->listServerURL; + + std::string msg = "action=GETTOKEN&callsign="; + msg += TextUtils::url_encode(info->callsign); + msg += "&password="; + msg += TextUtils::url_encode(info->password); + if (info->serverName[0] != '\0') + { + msg += "&nameport="; + msg += info->serverName; + msg += ':'; + msg += std::to_string(info->serverPort); + } + setPostMode(msg); + setURL(url); + addHandle(); +} + +void ServerAuth::finalization(char *_data, unsigned int length, bool good) +{ + if (good) + { + char *base = (char *)_data; + char *endS = base + length; + const char tokenIdentifier[] = "TOKEN: "; + const char noTokenIdentifier[] = "NOTOK: "; + const char errorIdentifier[] = "ERROR: "; + const char noticeIdentifier[] = "NOTICE: "; + + while (base < endS) + { + // find next newline + char* scan = base; + while (scan < endS && *scan != '\n') + scan++; + + // if no newline then no more complete replies + if (scan >= endS) + break; + *scan++ = '\0'; + + // look for TOKEN: and save token if found also look for NOTOK: + // and record "badtoken" into the token string and print an + // error + if (strncmp(base, tokenIdentifier, strlen(tokenIdentifier)) == 0) + { + strncpy(startupInfo->token, (char *)(base + strlen(tokenIdentifier)), + TokenLen - 1); + startupInfo->token[TokenLen - 1] = '\0'; +#ifdef DEBUG + std::vector args; + args.push_back(startupInfo->token); + printError("got token: {1}", &args); +#endif + } + else if (!strncmp(base, noTokenIdentifier, + strlen(noTokenIdentifier))) + { + printError("ERROR: did not get token:"); + printError(base); + strcpy(startupInfo->token, "badtoken\0"); + } + else if (!strncmp(base, errorIdentifier, strlen(errorIdentifier))) + { + printError(base); + strcpy(startupInfo->token, "badtoken\0"); + } + else if (!strncmp(base, noticeIdentifier, strlen(noticeIdentifier))) + printError(base); + + // next reply + base = scan; + } + } + else + strcpy(startupInfo->token, "badtoken\0"); +} diff --git a/src/game/ServerList.cxx b/src/game/ServerList.cxx index 0abb76bc51..15e63ae2ea 100644 --- a/src/game/ServerList.cxx +++ b/src/game/ServerList.cxx @@ -64,9 +64,6 @@ void ServerList::readServerList() { char *base = (char *)theData; char *endS = base + theLen; - const char tokenIdentifier[] = "TOKEN: "; - const char noTokenIdentifier[] = "NOTOK: "; - const char errorIdentifier[] = "ERROR: "; const char noticeIdentifier[] = "NOTICE: "; // walks entire reply including HTTP headers while (base < endS) @@ -81,38 +78,7 @@ void ServerList::readServerList() break; *scan++ = '\0'; - // look for TOKEN: and save token if found also look for NOTOK: - // and record "badtoken" into the token string and print an - // error - if (strncmp(base, tokenIdentifier, strlen(tokenIdentifier)) == 0) - { - strncpy(startupInfo->token, (char *)(base + strlen(tokenIdentifier)), - TokenLen - 1); - startupInfo->token[TokenLen - 1] = '\0'; -#ifdef DEBUG - printError("got token:"); - printError(startupInfo->token); -#endif - base = scan; - continue; - } - else if (!strncmp(base, noTokenIdentifier, - strlen(noTokenIdentifier))) - { - printError("ERROR: did not get token:"); - printError(base); - strcpy(startupInfo->token, "badtoken\0"); - base = scan; - continue; - } - else if (!strncmp(base, errorIdentifier, strlen(errorIdentifier))) - { - printError(base); - strcpy(startupInfo->token, "badtoken\0"); - base = scan; - continue; - } - else if (!strncmp(base, noticeIdentifier, strlen(noticeIdentifier))) + if (!strncmp(base, noticeIdentifier, strlen(noticeIdentifier))) { printError(base); base = scan; @@ -306,19 +272,19 @@ void ServerList::checkEchos(StartupInfo *info) std::string msg = "action=LIST&version="; msg += getServerVersion(); - msg += "&callsign="; - msg += TextUtils::url_encode(info->callsign); - msg += "&password="; - msg += TextUtils::url_encode(info->password); - if (info->serverName[0] != '\0') + // Send callsign/password only if the password is set + if (info->password[0] != '\0') { - msg += "&nameport="; - msg += info->serverName; - msg += ':'; - msg += std::to_string(info->serverPort); + msg += "&callsign="; + msg += TextUtils::url_encode(info->callsign); + msg += "&password="; + msg += TextUtils::url_encode(info->password); + // Since tokens are now tied to a game server host/port instead of the player IP, we will skip generating + // a token during the LIST operation. + msg += "&skiptoken=1"; } setPostMode(msg); - setURLwithNonce(url); + setURL(url); addHandle(); // do phase 1 only if we found a valid list server url