From f5f9656917c0cb0cc68aee50178aafd3f24c417f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 28 Jul 2014 12:37:48 +0200 Subject: [PATCH 01/19] cAuthenticator: Added GetUUIDsFromPlayerNames(). --- src/Protocol/Authenticator.cpp | 99 ++++++++++++++++++++++++++++++++-- src/Protocol/Authenticator.h | 20 +++++++ 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/Protocol/Authenticator.cpp b/src/Protocol/Authenticator.cpp index 2a7cbc7bc..75721589f 100644 --- a/src/Protocol/Authenticator.cpp +++ b/src/Protocol/Authenticator.cpp @@ -17,6 +17,8 @@ #define DEFAULT_AUTH_SERVER "sessionserver.mojang.com" #define DEFAULT_AUTH_ADDRESS "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%" +#define DEFAULT_NAME_TO_UUID_SERVER "api.mojang.com" +#define DEFAULT_NAME_TO_UUID_ADDRESS "/profiles/minecraft" /** This is the data of the root certs for Starfield Technologies, the CA that signed sessionserver.mojang.com's cert: Downloaded from http://certs.starfieldtech.com/repository/ */ @@ -83,6 +85,8 @@ cAuthenticator::cAuthenticator(void) : super("cAuthenticator"), m_Server(DEFAULT_AUTH_SERVER), m_Address(DEFAULT_AUTH_ADDRESS), + m_NameToUUIDServer(DEFAULT_NAME_TO_UUID_SERVER), + m_NameToUUIDAddress(DEFAULT_NAME_TO_UUID_ADDRESS), m_ShouldAuthenticate(true) { } @@ -102,9 +106,11 @@ cAuthenticator::~cAuthenticator() void cAuthenticator::ReadINI(cIniFile & IniFile) { - m_Server = IniFile.GetValueSet("Authentication", "Server", DEFAULT_AUTH_SERVER); - m_Address = IniFile.GetValueSet("Authentication", "Address", DEFAULT_AUTH_ADDRESS); + m_Server = IniFile.GetValueSet ("Authentication", "Server", DEFAULT_AUTH_SERVER); + m_Address = IniFile.GetValueSet ("Authentication", "Address", DEFAULT_AUTH_ADDRESS); m_ShouldAuthenticate = IniFile.GetValueSetB("Authentication", "Authenticate", true); + m_NameToUUIDServer = IniFile.GetValueSet ("Authentication", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER); + m_NameToUUIDAddress = IniFile.GetValueSet ("Authentication", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS); } @@ -151,6 +157,93 @@ void cAuthenticator::Stop(void) +AStringVector cAuthenticator::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames) +{ + AStringVector res; + + // Create the request body - a JSON containing all the playernames: + Json::Value root; + for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr) + { + Json::Value req(*itr); + root.append(req); + } // for itr - a_PlayerNames[] + Json::FastWriter Writer; + AString RequestBody = Writer.write(root); + + // Create the HTTP request: + AString Request; + Request += "POST " + m_NameToUUIDAddress + " HTTP/1.1\r\n"; + Request += "Host: " + m_NameToUUIDServer + "\r\n"; + Request += "User-Agent: MCServer\r\n"; + Request += "Connection: close\r\n"; + Request += "Content-Type: application/json\r\n"; + Request += Printf("Content-Length: %u\r\n", (unsigned)RequestBody.length()); + Request += "\r\n"; + Request += RequestBody; + + // Get the response from the server: + AString Response; + if (!SecureGetFromAddress(StarfieldCACert(), m_NameToUUIDServer, Request, Response)) + { + return res; + } + + // Check the HTTP status line: + const AString Prefix("HTTP/1.1 200 OK"); + AString HexDump; + if (Response.compare(0, Prefix.size(), Prefix)) + { + LOGINFO("%s failed: bad HTTP status line received", __FUNCTION__); + LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + return res; + } + + // Erase the HTTP headers from the response: + size_t idxHeadersEnd = Response.find("\r\n\r\n"); + if (idxHeadersEnd == AString::npos) + { + LOGINFO("%s failed: bad HTTP response header received", __FUNCTION__); + LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + return res; + } + Response.erase(0, idxHeadersEnd + 4); + + // Parse the returned string into Json: + Json::Reader reader; + if (!reader.parse(Response, root, false) || !root.isArray()) + { + LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON!", __FUNCTION__); + LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + return res; + } + + // Fill in the resulting array; do not expect to get the UUIDs in the same order as the inputs: + size_t len = a_PlayerNames.size(); + size_t JsonCount = root.size(); + res.resize(len); + for (size_t idx = 0; idx < len; idx++) // For each input username... + { + const AString & InputName = a_PlayerNames[idx]; + for (size_t IdxJson = 0; IdxJson < JsonCount; ++IdxJson) + { + Json::Value & Val = root[IdxJson]; + AString JsonName = Val.get("name", "").asString(); + if (NoCaseCompare(JsonName, InputName) == 0) + { + res[idx] = Val.get("id", "").asString(); + break; + } + } // for IdxJson - root[] + } // for idx - a_PlayerNames[] / res[] + + return res; +} + + + + + void cAuthenticator::Execute(void) { for (;;) @@ -307,7 +400,7 @@ bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_S a_UUID = root.get("id", "").asString(); a_Properties = root["properties"]; - // If the UUID doesn't contain the hashes, insert them at the proper places: + // If the UUID doesn't contain the dashes, insert them at the proper places: if (a_UUID.size() == 32) { a_UUID.insert(8, "-"); diff --git a/src/Protocol/Authenticator.h b/src/Protocol/Authenticator.h index 244d94c0b..82ecb1f7a 100644 --- a/src/Protocol/Authenticator.h +++ b/src/Protocol/Authenticator.h @@ -52,6 +52,12 @@ public: /** Stops the authenticator thread. The thread may be started and stopped repeatedly */ void Stop(void); + + /** Converts the player names into UUIDs. + a_PlayerName[idx] will be converted to UUID and returned as idx-th value + The UUID will be empty on error. + Blocking operation, do not use in world-tick thread! */ + AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName); private: @@ -76,8 +82,22 @@ private: cUserList m_Queue; cEvent m_QueueNonempty; + /** The server that is to be contacted for auth / UUID conversions */ AString m_Server; + + /** The URL to use for auth, without server part. + %USERNAME% will be replaced with actual user name. + %SERVERID% will be replaced with server's ID. + For example "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%". */ AString m_Address; + + /** The server to connect to when converting player names to UUIDs. For example "api.mojang.com". */ + AString m_NameToUUIDServer; + + /** The URL to use for converting player names to UUIDs, without server part. + For example "/profiles/page/1". */ + AString m_NameToUUIDAddress; + AString m_PropertiesAddress; bool m_ShouldAuthenticate; From 1acd03f96f9b1133e1f76d76004f5fcddd0c4788 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 28 Jul 2014 17:09:39 +0200 Subject: [PATCH 02/19] Added cClientHandle:GetUUIDsFromPlayerNames() to Lua API. --- MCServer/Plugins/APIDump/APIDesc.lua | 1 + src/Bindings/ManualBindings.cpp | 65 ++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index e65da1d16..f29c47338 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -529,6 +529,7 @@ end GetPlayer = { Params = "", Return = "{{cPlayer|cPlayer}}", Notes = "Returns the player object connected to this client. Note that this may be nil, for example if the player object is not yet spawned." }, GetUniqueID = { Params = "", Return = "number", Notes = "Returns the UniqueID of the client used to identify the client in the server" }, GetUUID = { Params = "", Return = "string", Notes = "Returns the authentication-based UUID of the client. This UUID should be used to identify the player when persisting any player-related data." }, + GetUUIDsFromPlayerNames = { Params = "PlayerNames", Return = "table", Notes = "(STATIC) Returns a table that contains the map, 'PlayerName' -> 'UUID', for all valid playernames in the input array-table. PlayerNames not recognized will not be set in the returned map. Queries the Mojang servers for the results. WARNING: Do NOT use this function while the server is running. Only use it when the server is starting up (inside the Initialize() method), otherwise you will lag the server severely. NOTE: Mojang API has a limit of 100 names per query and 600 queries per 10 minutes (may change)" }, GetUsername = { Params = "", Return = "string", Notes = "Returns the username that the client has provided" }, GetViewDistance = { Params = "", Return = "number", Notes = "Returns the viewdistance (number of chunks loaded for the player in each direction)" }, HasPluginChannel = { Params = "ChannelName", Return = "bool", Notes = "Returns true if the client has registered to receive messages on the specified plugin channel." }, diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index df9687fc0..28ee00b36 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -26,6 +26,7 @@ #include "../BlockEntities/MobHeadEntity.h" #include "../BlockEntities/FlowerPotEntity.h" #include "../LineBlockTracer.h" +#include "../Protocol/Authenticator.h" #include "../WorldStorage/SchematicFileSerializer.h" #include "../CompositeChat.h" @@ -2156,6 +2157,63 @@ static int tolua_cClientHandle_SendPluginMessage(lua_State * L) +static int tolua_cClientHandle_GetUUIDsFromPlayerNames(lua_State * L) +{ + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cClientHandle") || + !S.CheckParamTable(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Convert the input table into AStringVector: + AStringVector PlayerNames; + int NumNames = luaL_getn(L, 2); + PlayerNames.reserve(NumNames); + for (int i = 1; i <= NumNames; i++) + { + lua_rawgeti(L, 2, i); + AString Name; + S.GetStackValue(3, Name); + if (!Name.empty()) + { + PlayerNames.push_back(Name); + } + lua_pop(L, 1); + } + + // Push the output table onto the stack: + lua_newtable(L); // stack index 3 + + // Get the UUIDs: + AStringVector UUIDs = cRoot::Get()->GetAuthenticator().GetUUIDsFromPlayerNames(PlayerNames); + if (UUIDs.size() != PlayerNames.size()) + { + // A hard error has occured while processing the request, no UUIDs were returned. Return an empty table: + return 1; + } + + // Convert to output table, PlayerName -> UUID: + for (int i = 0; i < NumNames; i++) + { + if (UUIDs[i].empty()) + { + // No UUID was provided for PlayerName[i], skip it in the resulting table + continue; + } + lua_pushlstring(L, UUIDs[i].c_str(), UUIDs[i].length()); + lua_setfield(L, 3, PlayerNames[i].c_str()); + } + return 1; +} + + + + + static int Lua_ItemGrid_GetSlotCoords(lua_State * L) { tolua_Error tolua_err; @@ -3083,9 +3141,10 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cClientHandle"); - tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); - tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); - tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); + tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); + tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); + tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); + tolua_function(tolua_S, "GetUUIDsFromPlayerNames", tolua_cClientHandle_GetUUIDsFromPlayerNames); tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cItemGrid"); From b6677efecdd026a7eb8d652a067e4770182cd7c1 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 28 Jul 2014 17:14:23 +0200 Subject: [PATCH 03/19] Debuggers: Added an example for cClientHandle:GetUUIDsFromPlayerNames(). --- MCServer/Plugins/Debuggers/Debuggers.lua | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index b402c1867..80416acc7 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -80,6 +80,7 @@ function Initialize(Plugin) TestBlockAreasString() TestStringBase64() + TestUUIDFromName() --[[ -- Test cCompositeChat usage in console-logging: @@ -275,6 +276,36 @@ end +function TestUUIDFromName() + LOG("Testing UUID-from-Name resolution...") + + -- Test by querying a few existing names, along with a non-existent one: + local PlayerNames = + { + "xoft", + "aloe_vera", + "nonexistent_player", + } + -- WARNING: Blocking operation! DO NOT USE IN TICK THREAD! + local UUIDs = cClientHandle:GetUUIDsFromPlayerNames(PlayerNames) + + -- Log the results: + for _, name in ipairs(PlayerNames) do + local UUID = UUIDs[name] + if (UUID == nil) then + LOG(" UUID(" .. name .. ") not found.") + else + LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"") + end + end + + LOG("UUID-from-Name resolution test finished.") +end + + + + + function TestSQLiteBindings() LOG("Testing SQLite bindings..."); From 036a8ff98e890071089e9938cb87c5c4323152f0 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 28 Jul 2014 19:59:53 +0200 Subject: [PATCH 04/19] Added SQLiteCpp library. This provides C++ wrappers for SQLite, making it safer to use in the C++ environment. --- .gitmodules | 3 +++ CMakeLists.txt | 9 +++++++++ Install/SQLiteCpp-LICENSE.txt | 20 ++++++++++++++++++++ Install/Zip2008.list | 1 + lib/SQLiteCpp | 1 + 5 files changed, 34 insertions(+) create mode 100644 Install/SQLiteCpp-LICENSE.txt create mode 160000 lib/SQLiteCpp diff --git a/.gitmodules b/.gitmodules index 2aaee3624..8e63ee0a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/polarssl"] path = lib/polarssl url = https://github.com/mc-server/polarssl +[submodule "lib/SQLiteCpp"] + path = lib/SQLiteCpp + url = https://github.com/mc-server/SQLiteCpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a6400c1b4..478b79047 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,14 @@ endif() project (MCServer) +# Set options for SQLiteCpp, disable all their tests and lints: +set(SQLITECPP_RUN_CPPLINT OFF CACHE BOOL "Run cpplint.py tool for Google C++ StyleGuide." FORCE) +set(SQLITECPP_RUN_CPPCHECK OFF CACHE BOOL "Run cppcheck C++ static analysis tool." FORCE) +set(SQLITECPP_RUN_DOXYGEN OFF CACHE BOOL "Run Doxygen C++ documentation tool." FORCE) +set(SQLITECPP_BUILD_EXAMPLES OFF CACHE BOOL "Build examples." FORCE) +set(SQLITECPP_BUILD_TESTS OFF CACHE BOOL "Build and run tests." FORCE) +set(SQLITECPP_INTERNAL_SQLITE OFF CACHE BOOL "Add the internal SQLite3 source to the project." FORCE) + # Include all the libraries: add_subdirectory(lib/inifile/) add_subdirectory(lib/jsoncpp/) @@ -60,6 +68,7 @@ add_subdirectory(lib/zlib/) add_subdirectory(lib/lua/) add_subdirectory(lib/tolua++/) add_subdirectory(lib/sqlite/) +add_subdirectory(lib/SQLiteCpp/) add_subdirectory(lib/expat/) add_subdirectory(lib/luaexpat/) diff --git a/Install/SQLiteCpp-LICENSE.txt b/Install/SQLiteCpp-LICENSE.txt new file mode 100644 index 000000000..ec952abba --- /dev/null +++ b/Install/SQLiteCpp-LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Install/Zip2008.list b/Install/Zip2008.list index b118ccbf9..5736867d8 100644 --- a/Install/Zip2008.list +++ b/Install/Zip2008.list @@ -11,4 +11,5 @@ MCServer*debug.cmd Lua-LICENSE.txt LuaExpat-license.html LuaSQLite3-LICENSE.txt +SQLiteCpp-LICENSE.txt MersenneTwister-LICENSE.txt diff --git a/lib/SQLiteCpp b/lib/SQLiteCpp new file mode 160000 index 000000000..27b9d1118 --- /dev/null +++ b/lib/SQLiteCpp @@ -0,0 +1 @@ +Subproject commit 27b9d111818af3b05bcf4153bb6e380fe1dd6816 From 5fb5f6671f2b138dc56187183f0650b70a4d2eb4 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 28 Jul 2014 20:16:24 +0200 Subject: [PATCH 05/19] Fixed include directories for SQLiteCpp. --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 478b79047..2b30e1e0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,12 @@ add_subdirectory(lib/SQLiteCpp/) add_subdirectory(lib/expat/) add_subdirectory(lib/luaexpat/) +# Add proper include directories so that SQLiteCpp can find SQLite3: +get_property(SQLITECPP_INCLUDES DIRECTORY "lib/SQLiteCpp/" PROPERTY INCLUDE_DIRECTORIES) +set(SQLITECPP_INCLUDES "${SQLITECPP_INCLUDES}" "${CMAKE_CURRENT_SOURCE_DIR}/lib/sqlite/") +message("SQLiteCpp includes: " "${SQLITECPP_INCLUDES}") +set_property(DIRECTORY lib/SQLiteCpp/ PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}") + if (WIN32) add_subdirectory(lib/luaproxy/) endif() From 7f7604a186885107d0bd9625d969aef45a60dcce Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 28 Jul 2014 22:06:47 +0200 Subject: [PATCH 06/19] Fixed SQLiteCpp include paths for MSVC2010+. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b30e1e0b..dd9b1e67c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,8 +75,8 @@ add_subdirectory(lib/luaexpat/) # Add proper include directories so that SQLiteCpp can find SQLite3: get_property(SQLITECPP_INCLUDES DIRECTORY "lib/SQLiteCpp/" PROPERTY INCLUDE_DIRECTORIES) set(SQLITECPP_INCLUDES "${SQLITECPP_INCLUDES}" "${CMAKE_CURRENT_SOURCE_DIR}/lib/sqlite/") -message("SQLiteCpp includes: " "${SQLITECPP_INCLUDES}") set_property(DIRECTORY lib/SQLiteCpp/ PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}") +set_property(TARGET SQLiteCpp PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}") if (WIN32) add_subdirectory(lib/luaproxy/) From 4dd858f8997488e2252f5a04df9df1654a70d67f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 29 Jul 2014 17:45:55 +0200 Subject: [PATCH 07/19] Added a cMojangAPI class for PlayerName -> UUID lookups, with cache. The cache is persisted into a SQLite DB file on server shutdown. --- src/Bindings/ManualBindings.cpp | 2 +- src/CMakeLists.txt | 4 +- src/Protocol/Authenticator.cpp | 216 +--------------- src/Protocol/Authenticator.h | 27 +- src/Protocol/CMakeLists.txt | 2 + src/Protocol/MojangAPI.cpp | 446 ++++++++++++++++++++++++++++++++ src/Protocol/MojangAPI.h | 102 ++++++++ src/Root.cpp | 1 + src/Root.h | 3 + 9 files changed, 564 insertions(+), 239 deletions(-) create mode 100644 src/Protocol/MojangAPI.cpp create mode 100644 src/Protocol/MojangAPI.h diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 28ee00b36..6d69e2595 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -2189,7 +2189,7 @@ static int tolua_cClientHandle_GetUUIDsFromPlayerNames(lua_State * L) lua_newtable(L); // stack index 3 // Get the UUIDs: - AStringVector UUIDs = cRoot::Get()->GetAuthenticator().GetUUIDsFromPlayerNames(PlayerNames); + AStringVector UUIDs = cRoot::Get()->GetMojangAPI().GetUUIDsFromPlayerNames(PlayerNames); if (UUIDs.size() != PlayerNames.size()) { // A hard error has occured while processing the request, no UUIDs were returned. Return an empty table: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29337cb2e..1d1c33088 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -138,6 +138,8 @@ SET (HDRS XMLParser.h) include_directories(".") +include_directories ("${CMAKE_CURRENT_SOURCE_DIR}/../lib/sqlite") +include_directories ("${CMAKE_CURRENT_SOURCE_DIR}/../lib/SQLiteCpp/include") if (NOT MSVC) # Bindings need to reference other folders, so they are done here instead @@ -311,4 +313,4 @@ endif () if (WIN32) target_link_libraries(${EXECUTABLE} expat tolualib ws2_32.lib Psapi.lib) endif() -target_link_libraries(${EXECUTABLE} luaexpat iniFile jsoncpp polarssl zlib sqlite lua) +target_link_libraries(${EXECUTABLE} luaexpat iniFile jsoncpp polarssl zlib sqlite lua SQLiteCpp) diff --git a/src/Protocol/Authenticator.cpp b/src/Protocol/Authenticator.cpp index 75721589f..e5d16cf10 100644 --- a/src/Protocol/Authenticator.cpp +++ b/src/Protocol/Authenticator.cpp @@ -2,6 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Authenticator.h" +#include "MojangAPI.h" #include "../Root.h" #include "../Server.h" #include "../ClientHandle.h" @@ -17,76 +18,11 @@ #define DEFAULT_AUTH_SERVER "sessionserver.mojang.com" #define DEFAULT_AUTH_ADDRESS "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%" -#define DEFAULT_NAME_TO_UUID_SERVER "api.mojang.com" -#define DEFAULT_NAME_TO_UUID_ADDRESS "/profiles/minecraft" - -/** This is the data of the root certs for Starfield Technologies, the CA that signed sessionserver.mojang.com's cert: -Downloaded from http://certs.starfieldtech.com/repository/ */ -static const AString StarfieldCACert() -{ - return AString( - // G2 cert - "-----BEGIN CERTIFICATE-----\n" - "MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx\n" - "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\n" - "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs\n" - "ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw\n" - "MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6\n" - "b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj\n" - "aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp\n" - "Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" - "ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\n" - "nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1\n" - "HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N\n" - "Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN\n" - "dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0\n" - "HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\n" - "BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G\n" - "CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU\n" - "sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3\n" - "4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n" - "8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K\n" - "pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1\n" - "mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0\n" - "-----END CERTIFICATE-----\n\n" - // Original (G1) cert: - "-----BEGIN CERTIFICATE-----\n" - "MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\n" - "MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\n" - "U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\n" - "NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n" - "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\n" - "ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\n" - "DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\n" - "8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\n" - "+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n" - "X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\n" - "K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\n" - "1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\n" - "A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\n" - "zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\n" - "YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\n" - "bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\n" - "DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\n" - "L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n" - "eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\n" - "xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\n" - "VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\n" - "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" - "-----END CERTIFICATE-----\n" - ); -} - - - - cAuthenticator::cAuthenticator(void) : super("cAuthenticator"), m_Server(DEFAULT_AUTH_SERVER), m_Address(DEFAULT_AUTH_ADDRESS), - m_NameToUUIDServer(DEFAULT_NAME_TO_UUID_SERVER), - m_NameToUUIDAddress(DEFAULT_NAME_TO_UUID_ADDRESS), m_ShouldAuthenticate(true) { } @@ -109,8 +45,6 @@ void cAuthenticator::ReadINI(cIniFile & IniFile) m_Server = IniFile.GetValueSet ("Authentication", "Server", DEFAULT_AUTH_SERVER); m_Address = IniFile.GetValueSet ("Authentication", "Address", DEFAULT_AUTH_ADDRESS); m_ShouldAuthenticate = IniFile.GetValueSetB("Authentication", "Authenticate", true); - m_NameToUUIDServer = IniFile.GetValueSet ("Authentication", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER); - m_NameToUUIDAddress = IniFile.GetValueSet ("Authentication", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS); } @@ -157,93 +91,6 @@ void cAuthenticator::Stop(void) -AStringVector cAuthenticator::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames) -{ - AStringVector res; - - // Create the request body - a JSON containing all the playernames: - Json::Value root; - for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr) - { - Json::Value req(*itr); - root.append(req); - } // for itr - a_PlayerNames[] - Json::FastWriter Writer; - AString RequestBody = Writer.write(root); - - // Create the HTTP request: - AString Request; - Request += "POST " + m_NameToUUIDAddress + " HTTP/1.1\r\n"; - Request += "Host: " + m_NameToUUIDServer + "\r\n"; - Request += "User-Agent: MCServer\r\n"; - Request += "Connection: close\r\n"; - Request += "Content-Type: application/json\r\n"; - Request += Printf("Content-Length: %u\r\n", (unsigned)RequestBody.length()); - Request += "\r\n"; - Request += RequestBody; - - // Get the response from the server: - AString Response; - if (!SecureGetFromAddress(StarfieldCACert(), m_NameToUUIDServer, Request, Response)) - { - return res; - } - - // Check the HTTP status line: - const AString Prefix("HTTP/1.1 200 OK"); - AString HexDump; - if (Response.compare(0, Prefix.size(), Prefix)) - { - LOGINFO("%s failed: bad HTTP status line received", __FUNCTION__); - LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); - return res; - } - - // Erase the HTTP headers from the response: - size_t idxHeadersEnd = Response.find("\r\n\r\n"); - if (idxHeadersEnd == AString::npos) - { - LOGINFO("%s failed: bad HTTP response header received", __FUNCTION__); - LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); - return res; - } - Response.erase(0, idxHeadersEnd + 4); - - // Parse the returned string into Json: - Json::Reader reader; - if (!reader.parse(Response, root, false) || !root.isArray()) - { - LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON!", __FUNCTION__); - LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); - return res; - } - - // Fill in the resulting array; do not expect to get the UUIDs in the same order as the inputs: - size_t len = a_PlayerNames.size(); - size_t JsonCount = root.size(); - res.resize(len); - for (size_t idx = 0; idx < len; idx++) // For each input username... - { - const AString & InputName = a_PlayerNames[idx]; - for (size_t IdxJson = 0; IdxJson < JsonCount; ++IdxJson) - { - Json::Value & Val = root[IdxJson]; - AString JsonName = Val.get("name", "").asString(); - if (NoCaseCompare(JsonName, InputName) == 0) - { - res[idx] = Val.get("id", "").asString(); - break; - } - } // for IdxJson - root[] - } // for idx - a_PlayerNames[] / res[] - - return res; -} - - - - - void cAuthenticator::Execute(void) { for (;;) @@ -286,62 +133,6 @@ void cAuthenticator::Execute(void) -bool cAuthenticator::SecureGetFromAddress(const AString & a_CACerts, const AString & a_ExpectedPeerName, const AString & a_Data, AString & a_Response) -{ - // Connect the socket: - cBlockingSslClientSocket Socket; - Socket.SetTrustedRootCertsFromString(a_CACerts, a_ExpectedPeerName); - if (!Socket.Connect(a_ExpectedPeerName, 443)) - { - LOGWARNING("cAuthenticator: Can't connect to %s: %s", a_ExpectedPeerName.c_str(), Socket.GetLastErrorText().c_str()); - return false; - } - - if (!Socket.Send(a_Data.c_str(), a_Data.size())) - { - LOGWARNING("cAuthenticator: Writing SSL data failed: %s", Socket.GetLastErrorText().c_str()); - return false; - } - - // Read the HTTP response: - int ret; - unsigned char buf[1024]; - - for (;;) - { - ret = Socket.Receive(buf, sizeof(buf)); - - if ((ret == POLARSSL_ERR_NET_WANT_READ) || (ret == POLARSSL_ERR_NET_WANT_WRITE)) - { - // This value should never be returned, it is handled internally by cBlockingSslClientSocket - LOGWARNING("cAuthenticator: SSL reading failed internally"); - return false; - } - if (ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) - { - break; - } - if (ret < 0) - { - LOGWARNING("cAuthenticator: SSL reading failed: -0x%x", -ret); - return false; - } - if (ret == 0) - { - break; - } - - a_Response.append((const char *)buf, (size_t)ret); - } - - Socket.Disconnect(); - return true; -} - - - - - bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_ServerId, AString & a_UUID, Json::Value & a_Properties) { LOGD("Trying to authenticate user %s", a_UserName.c_str()); @@ -359,7 +150,7 @@ bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_S Request += "\r\n"; AString Response; - if (!SecureGetFromAddress(StarfieldCACert(), m_Server, Request, Response)) + if (!cMojangAPI::SecureRequest(m_Server, Request, Response)) { return false; } @@ -399,6 +190,9 @@ bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_S a_UserName = root.get("name", "Unknown").asString(); a_UUID = root.get("id", "").asString(); a_Properties = root["properties"]; + + // Store the player's UUID in the NameToUUID map in MojangAPI: + cRoot::Get()->GetMojangAPI().AddPlayerNameToUUIDMapping(a_UserName, a_UUID); // If the UUID doesn't contain the dashes, insert them at the proper places: if (a_UUID.size() == 32) diff --git a/src/Protocol/Authenticator.h b/src/Protocol/Authenticator.h index 82ecb1f7a..853eff535 100644 --- a/src/Protocol/Authenticator.h +++ b/src/Protocol/Authenticator.h @@ -11,8 +11,6 @@ #pragma once -#ifndef CAUTHENTICATOR_H_INCLUDED -#define CAUTHENTICATOR_H_INCLUDED #include "../OSSupport/IsThread.h" @@ -53,12 +51,6 @@ public: /** Stops the authenticator thread. The thread may be started and stopped repeatedly */ void Stop(void); - /** Converts the player names into UUIDs. - a_PlayerName[idx] will be converted to UUID and returned as idx-th value - The UUID will be empty on error. - Blocking operation, do not use in world-tick thread! */ - AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName); - private: class cUser @@ -91,34 +83,17 @@ private: For example "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%". */ AString m_Address; - /** The server to connect to when converting player names to UUIDs. For example "api.mojang.com". */ - AString m_NameToUUIDServer; - - /** The URL to use for converting player names to UUIDs, without server part. - For example "/profiles/page/1". */ - AString m_NameToUUIDAddress; - AString m_PropertiesAddress; bool m_ShouldAuthenticate; /** cIsThread override: */ virtual void Execute(void) override; - /** Connects to a hostname using SSL, sends given data, and sets the response, returning whether all was successful or not */ - bool SecureGetFromAddress(const AString & a_CACerts, const AString & a_ExpectedPeerName, const AString & a_Request, AString & a_Response); - /** Returns true if the user authenticated okay, false on error - Sets the username, UUID, and properties (i.e. skin) fields - */ + Returns the case-corrected username, UUID, and properties (eg. skin). */ bool AuthWithYggdrasil(AString & a_UserName, const AString & a_ServerId, AString & a_UUID, Json::Value & a_Properties); }; - -#endif // CAUTHENTICATOR_H_INCLUDED - - - - diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index ae447ce54..1ba66ff1f 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -7,6 +7,7 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS Authenticator.cpp ChunkDataSerializer.cpp + MojangAPI.cpp Protocol125.cpp Protocol132.cpp Protocol14x.cpp @@ -18,6 +19,7 @@ SET (SRCS SET (HDRS Authenticator.h ChunkDataSerializer.h + MojangAPI.h Protocol.h Protocol125.h Protocol132.h diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp new file mode 100644 index 000000000..05f9c09a7 --- /dev/null +++ b/src/Protocol/MojangAPI.cpp @@ -0,0 +1,446 @@ + +// MojangAPI.cpp + +// Implements the cMojangAPI class representing the various API points provided by Mojang's webservices, and a cache for their results + +#include "Globals.h" +#include "MojangAPI.h" +#include "SQLiteCpp/Database.h" +#include "SQLiteCpp/Statement.h" +#include "inifile/iniFile.h" +#include "json/json.h" +#include "PolarSSL++/BlockingSslClientSocket.h" + + + + + +/** The maximum age for items to be kept in the cache. Any item older than this will be removed. */ +const Int64 MAX_AGE = 7 * 24 * 60 * 60; // 7 days ago + + + + + +#define DEFAULT_NAME_TO_UUID_SERVER "api.mojang.com" +#define DEFAULT_NAME_TO_UUID_ADDRESS "/profiles/minecraft" + + + + + +/** This is the data of the root certs for Starfield Technologies, the CA that signed sessionserver.mojang.com's cert: +Downloaded from http://certs.starfieldtech.com/repository/ */ +static const AString & StarfieldCACert(void) +{ + static const AString Cert( + // G2 cert + "-----BEGIN CERTIFICATE-----\n" + "MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx\n" + "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\n" + "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs\n" + "ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw\n" + "MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6\n" + "b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj\n" + "aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp\n" + "Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" + "ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\n" + "nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1\n" + "HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N\n" + "Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN\n" + "dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0\n" + "HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\n" + "BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G\n" + "CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU\n" + "sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3\n" + "4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n" + "8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K\n" + "pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1\n" + "mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0\n" + "-----END CERTIFICATE-----\n\n" + // Original (G1) cert: + "-----BEGIN CERTIFICATE-----\n" + "MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\n" + "MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\n" + "U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\n" + "NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n" + "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\n" + "ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\n" + "DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\n" + "8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\n" + "+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n" + "X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\n" + "K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\n" + "1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\n" + "A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\n" + "zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\n" + "YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\n" + "bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\n" + "DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\n" + "L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n" + "eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\n" + "xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\n" + "VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\n" + "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" + "-----END CERTIFICATE-----\n" + ); + + return Cert; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cMojangAPI: + +cMojangAPI::cMojangAPI(void) : + m_NameToUUIDServer(DEFAULT_NAME_TO_UUID_SERVER), + m_NameToUUIDAddress(DEFAULT_NAME_TO_UUID_ADDRESS) +{ +} + + + + + +cMojangAPI::~cMojangAPI() +{ + SaveCachesToDisk(); +} + + + + + +void cMojangAPI::Start(cIniFile & a_SettingsIni) +{ + m_NameToUUIDServer = a_SettingsIni.GetValueSet("Authentication", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER); + m_NameToUUIDAddress = a_SettingsIni.GetValueSet("Authentication", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS); + LoadCachesFromDisk(); +} + + + + + +AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames) +{ + // Convert all playernames to lowercase: + AStringVector PlayerNames; + for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr) + { + AString Lower(*itr); + PlayerNames.push_back(StrToLower(Lower)); + } // for itr - a_PlayerNames[] + + // Request the cache to populate any names not yet contained: + CacheNamesToUUIDs(PlayerNames); + + // Retrieve from cache: + size_t idx = 0; + AStringVector res; + res.resize(PlayerNames.size()); + cCSLock Lock(m_CSNameToUUID); + for (AStringVector::const_iterator itr = PlayerNames.begin(), end = PlayerNames.end(); itr != end; ++itr, ++idx) + { + cNameToUUIDMap::const_iterator itrN = m_NameToUUID.find(*itr); + if (itrN != m_NameToUUID.end()) + { + res[idx] = itrN->second.m_UUID; + } + } // for itr - PlayerNames[] + return res; +} + + + + + +void cMojangAPI::AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID) +{ + AString lcName(a_PlayerName); + Int64 Now = time(NULL); + cCSLock Lock(m_CSNameToUUID); + m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(a_PlayerName, a_UUID, Now); +} + + + + + +bool cMojangAPI::SecureRequest(const AString & a_ServerName, const AString & a_Request, AString & a_Response) +{ + // Connect the socket: + cBlockingSslClientSocket Socket; + Socket.SetTrustedRootCertsFromString(StarfieldCACert(), a_ServerName); + if (!Socket.Connect(a_ServerName, 443)) + { + LOGWARNING("%s: Can't connect to %s: %s", __FUNCTION__, a_ServerName.c_str(), Socket.GetLastErrorText().c_str()); + return false; + } + + if (!Socket.Send(a_Request.c_str(), a_Request.size())) + { + LOGWARNING("%s: Writing SSL data failed: %s", __FUNCTION__, Socket.GetLastErrorText().c_str()); + return false; + } + + // Read the HTTP response: + int ret; + unsigned char buf[1024]; + + for (;;) + { + ret = Socket.Receive(buf, sizeof(buf)); + + if ((ret == POLARSSL_ERR_NET_WANT_READ) || (ret == POLARSSL_ERR_NET_WANT_WRITE)) + { + // This value should never be returned, it is handled internally by cBlockingSslClientSocket + LOGWARNING("%s: SSL reading failed internally", __FUNCTION__); + return false; + } + if (ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) + { + break; + } + if (ret < 0) + { + LOGWARNING("%s: SSL reading failed: -0x%x", __FUNCTION__, -ret); + return false; + } + if (ret == 0) + { + break; + } + + a_Response.append((const char *)buf, (size_t)ret); + } + + Socket.Disconnect(); + return true; +} + + + + + +AString cMojangAPI::MakeUUIDShort(const AString & a_UUID) +{ + switch (a_UUID.size()) + { + case 32: return a_UUID; + + case 36: + { + AString res; + // TODO + return res; + } + } + LOGWARNING("%s: Not an UUID: \"%s\".", __FUNCTION__, a_UUID.c_str()); + return ""; +} + + + + + +AString cMojangAPI::MakeUUIDDashed(const AString & a_UUID) +{ + switch (a_UUID.size()) + { + case 36: return a_UUID; + + case 32: + { + AString res; + // TODO + return res; + } + } + LOGWARNING("%s: Not an UUID: \"%s\".", __FUNCTION__, a_UUID.c_str()); + return ""; +} + + + + + +void cMojangAPI::LoadCachesFromDisk(void) +{ + try + { + // Open up the SQLite DB: + SQLite::Database db("NameToUUID.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)"); + + // Clean up old entries: + { + SQLite::Statement stmt(db, "DELETE FROM PlayerNameToUUID WHERE DateTime < ?"); + Int64 LimitDateTime = time(NULL) - MAX_AGE; + stmt.bind(1, LimitDateTime); + stmt.exec(); + } + + // Retrieve all remaining entries:: + SQLite::Statement stmt(db, "SELECT PlayerName, UUID, DateTime FROM PlayerNameToUUID"); + while (stmt.executeStep()) + { + AString PlayerName = stmt.getColumn(0); + AString UUID = stmt.getColumn(1); + Int64 DateTime = stmt.getColumn(2); + AString lcPlayerName = PlayerName; + m_NameToUUID[StrToLower(lcPlayerName)] = sUUIDRecord(PlayerName, UUID, DateTime); + } + } + catch (const SQLite::Exception & ex) + { + LOGINFO("Loading MojangAPI cache failed: %s", ex.what()); + } +} + + + + + +void cMojangAPI::SaveCachesToDisk(void) +{ + try + { + // Open up the SQLite DB: + SQLite::Database db("NameToUUID.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)"); + + // Remove all entries: + db.exec("DELETE FROM PlayerNameToUUID"); + + // Save all cache entries: + SQLite::Statement stmt(db, "INSERT INTO PlayerNameToUUID(PlayerName, UUID, DateTime) VALUES (?, ?, ?)"); + Int64 LimitDateTime = time(NULL) - MAX_AGE; + cCSLock Lock(m_CSNameToUUID); + for (cNameToUUIDMap::const_iterator itr = m_NameToUUID.begin(), end = m_NameToUUID.end(); itr != end; ++itr) + { + if (itr->second.m_DateTime < LimitDateTime) + { + // This item is too old, do not save + continue; + } + stmt.bind(1, itr->second.m_PlayerName); + stmt.bind(2, itr->second.m_UUID); + stmt.bind(3, itr->second.m_DateTime); + stmt.exec(); + stmt.reset(); + } + } + catch (const SQLite::Exception & ex) + { + LOGINFO("Saving MojangAPI cache failed: %s", ex.what()); + } +} + + + + + +void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames) +{ + // Create a list of names to query, by removing those that are already cached: + AStringVector NamesToQuery; + NamesToQuery.reserve(a_PlayerNames.size()); + { + cCSLock Lock(m_CSNameToUUID); + for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr) + { + if (m_NameToUUID.find(*itr) == m_NameToUUID.end()) + { + NamesToQuery.push_back(*itr); + } + } // for itr - a_PlayerNames[] + } // Lock(m_CSNameToUUID) + + while (!NamesToQuery.empty()) + { + // Create the request body - a JSON containing up to 100 playernames: + Json::Value root; + int Count = 0; + AStringVector::iterator itr = NamesToQuery.begin(), end = NamesToQuery.end(); + for (; (itr != end) && (Count < 100); ++itr, ++Count) + { + Json::Value req(*itr); + root.append(req); + } // for itr - a_PlayerNames[] + NamesToQuery.erase(NamesToQuery.begin(), itr); + Json::FastWriter Writer; + AString RequestBody = Writer.write(root); + + // Create the HTTP request: + AString Request; + Request += "POST " + m_NameToUUIDAddress + " HTTP/1.0\r\n"; + Request += "Host: " + m_NameToUUIDServer + "\r\n"; + Request += "User-Agent: MCServer\r\n"; + Request += "Connection: close\r\n"; + Request += "Content-Type: application/json\r\n"; + Request += Printf("Content-Length: %u\r\n", (unsigned)RequestBody.length()); + Request += "\r\n"; + Request += RequestBody; + + // Get the response from the server: + AString Response; + if (!SecureRequest(m_NameToUUIDServer, Request, Response)) + { + continue; + } + + // Check the HTTP status line: + const AString Prefix("HTTP/1.1 200 OK"); + AString HexDump; + if (Response.compare(0, Prefix.size(), Prefix)) + { + LOGINFO("%s failed: bad HTTP status line received", __FUNCTION__); + LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + continue; + } + + // Erase the HTTP headers from the response: + size_t idxHeadersEnd = Response.find("\r\n\r\n"); + if (idxHeadersEnd == AString::npos) + { + LOGINFO("%s failed: bad HTTP response header received", __FUNCTION__); + LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + continue; + } + Response.erase(0, idxHeadersEnd + 4); + + // Parse the returned string into Json: + Json::Reader reader; + if (!reader.parse(Response, root, false) || !root.isArray()) + { + LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON!", __FUNCTION__); + LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str()); + continue; + } + + // Store the returned results into cache: + size_t JsonCount = root.size(); + Int64 Now = time(NULL); + cCSLock Lock(m_CSNameToUUID); + for (size_t idx = 0; idx < JsonCount; ++idx) + { + Json::Value & Val = root[idx]; + AString JsonName = Val.get("name", "").asString(); + AString JsonUUID = Val.get("id", "").asString(); + if (JsonUUID.empty()) + { + continue; + } + AString lcName = JsonName; + m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(JsonName, JsonUUID, Now); + } // for idx - root[] + } // while (!NamesToQuery.empty()) +} + + + + diff --git a/src/Protocol/MojangAPI.h b/src/Protocol/MojangAPI.h new file mode 100644 index 000000000..789fdf818 --- /dev/null +++ b/src/Protocol/MojangAPI.h @@ -0,0 +1,102 @@ + +// MojangAPI.h + +// Declares the cMojangAPI class representing the various API points provided by Mojang's webservices, and a cache for their results + + + + + +#pragma once + +#include + + + + + +class cMojangAPI +{ +public: + cMojangAPI(void); + ~cMojangAPI(); + + /** Initializes the API; reads the settings from the specified ini file. + Loads cached results from disk. */ + void Start(cIniFile & a_SettingsIni); + + /** Connects to the specified server using SSL, sends the given request and receives the response. + Checks Mojang certificates using the hard-coded Starfield root CA certificate. + Returns true if all was successful, false on failure. */ + static bool SecureRequest(const AString & a_ServerName, const AString & a_Request, AString & a_Response); + + /** Converts the given UUID to its short form (32 bytes, no dashes). + Logs a warning and returns empty string if not a UUID. */ + static AString MakeUUIDShort(const AString & a_UUID); + + /** Converts the given UUID to its dashed form (36 bytes, 4 dashes). + Logs a warning and returns empty string if not a UUID. */ + static AString MakeUUIDDashed(const AString & a_UUID); + + /** Converts the player names into UUIDs. + a_PlayerName[idx] will be converted to UUID and returned as idx-th value + The UUID will be empty on error. + Blocking operation, do not use in world-tick thread! */ + AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName); + + /** Called by the Authenticator to add a PlayerName -> UUID mapping that it has received from + authenticating a user. This adds the cache item and "refreshes" it if existing, adjusting its datetime + stamp to now. */ + void AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID); + +protected: + struct sUUIDRecord + { + AString m_PlayerName; // Case-correct playername + AString m_UUID; + Int64 m_DateTime; // UNIXtime of the UUID lookup + + sUUIDRecord(void) : + m_UUID(), + m_DateTime(time(NULL)) + { + } + + sUUIDRecord(const AString & a_PlayerName, const AString & a_UUID, Int64 a_DateTime) : + m_PlayerName(a_PlayerName), + m_UUID(a_UUID), + m_DateTime(a_DateTime) + { + } + }; + typedef std::map cNameToUUIDMap; // maps Lowercased PlayerName to sUUIDRecord + + /** The server to connect to when converting player names to UUIDs. For example "api.mojang.com". */ + AString m_NameToUUIDServer; + + /** The URL to use for converting player names to UUIDs, without server part. + For example "/profiles/page/1". */ + AString m_NameToUUIDAddress; + + /** Cache for the Name-to-UUID lookups. The map key is expected lowercased. Protected by m_CSNameToUUID. */ + cNameToUUIDMap m_NameToUUID; + + /** Protects m_NameToUUID against simultaneous multi-threaded access. */ + cCriticalSection m_CSNameToUUID; + + + /** Loads the caches from a disk storage. */ + void LoadCachesFromDisk(void); + + /** Saves the caches to a disk storage. */ + void SaveCachesToDisk(void); + + /** Makes sure all specified names are in the cache. Downloads any missing ones from Mojang API servers. + Names that are not valid are not added into the cache. + ASSUMEs that a_PlayerNames contains lowercased player names. */ + void CacheNamesToUUIDs(const AStringVector & a_PlayerNames); +} ; + + + + diff --git a/src/Root.cpp b/src/Root.cpp index b03a13382..7d4fb80fd 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -145,6 +145,7 @@ void cRoot::Start(void) } LOG("Starting server..."); + m_MojangAPI.Start(IniFile); // Mojang API needs to be started before plugins, so that plugins may use it for DB upgrades on server init if (!m_Server->InitServer(IniFile)) { LOGERROR("Failure starting server, aborting..."); diff --git a/src/Root.h b/src/Root.h index 08aafe3c9..92a60a10d 100644 --- a/src/Root.h +++ b/src/Root.h @@ -2,6 +2,7 @@ #pragma once #include "Protocol/Authenticator.h" +#include "Protocol/MojangAPI.h" #include "HTTPServer/HTTPServer.h" #include "Defines.h" @@ -78,6 +79,7 @@ public: cWebAdmin * GetWebAdmin (void) { return m_WebAdmin; } // tolua_export cPluginManager * GetPluginManager (void) { return m_PluginManager; } // tolua_export cAuthenticator & GetAuthenticator (void) { return m_Authenticator; } + cMojangAPI & GetMojangAPI (void) { return m_MojangAPI; } /** Queues a console command for execution through the cServer class. The command will be executed in the tick thread @@ -182,6 +184,7 @@ private: cWebAdmin * m_WebAdmin; cPluginManager * m_PluginManager; cAuthenticator m_Authenticator; + cMojangAPI m_MojangAPI; cHTTPServer m_HTTPServer; cMCLogger * m_Log; From 6476bd0e2ee7e128e3eaa56159f169f0a53736ff Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 13:44:03 +0200 Subject: [PATCH 08/19] Exported cMojangAPI to Lua. --- MCServer/Plugins/APIDump/APIDesc.lua | 27 ++++++++++++++++++++++++++- src/Bindings/AllToLua.pkg | 1 + src/Bindings/ManualBindings.cpp | 9 ++++++--- src/Protocol/MojangAPI.h | 13 ++++++++++++- src/Root.h | 2 +- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index f29c47338..e5114784e 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -529,7 +529,6 @@ end GetPlayer = { Params = "", Return = "{{cPlayer|cPlayer}}", Notes = "Returns the player object connected to this client. Note that this may be nil, for example if the player object is not yet spawned." }, GetUniqueID = { Params = "", Return = "number", Notes = "Returns the UniqueID of the client used to identify the client in the server" }, GetUUID = { Params = "", Return = "string", Notes = "Returns the authentication-based UUID of the client. This UUID should be used to identify the player when persisting any player-related data." }, - GetUUIDsFromPlayerNames = { Params = "PlayerNames", Return = "table", Notes = "(STATIC) Returns a table that contains the map, 'PlayerName' -> 'UUID', for all valid playernames in the input array-table. PlayerNames not recognized will not be set in the returned map. Queries the Mojang servers for the results. WARNING: Do NOT use this function while the server is running. Only use it when the server is starting up (inside the Initialize() method), otherwise you will lag the server severely. NOTE: Mojang API has a limit of 100 names per query and 600 queries per 10 minutes (may change)" }, GetUsername = { Params = "", Return = "string", Notes = "Returns the username that the client has provided" }, GetViewDistance = { Params = "", Return = "number", Notes = "Returns the viewdistance (number of chunks loaded for the player in each direction)" }, HasPluginChannel = { Params = "ChannelName", Return = "bool", Notes = "Returns true if the client has registered to receive messages on the specified plugin channel." }, @@ -1607,6 +1606,31 @@ a_Player:OpenWindow(Window); }, -- cMapManager + cMojangAPI = + { + Desc = [[ + Provides interface to various API functions that Mojang provides through their servers. Note that + some of these calls will wait for a response from the network, and so shouldn't be used while the + server is fully running (or at least when there are players connected) to avoid percepted lag.

+

+ Some functions are static and do not require an instance to be called. For others, you need to get + the singleton instance of this class using {{cRoot}}'s GetMojangAPI() function.

+

+ Mojang uses two formats for UUIDs, short and dashed. MCServer works with short UUIDs internally, but + will convert to dashed UUIDs where needed - in the protocol login for example. The MakeUUIDShort() + and MakeUUIDDashed() functions are provided for plugins to use for conversion between the two + formats. + ]], + Functions = + { + AddPlayerNameToUUIDMapping = { Params = "PlayerName, UUID", Return = "", Notes = "Adds the specified PlayerName-to-UUID mapping into the cache, with current timestamp." }, + GetUUIDsFromPlayerNames = { Params = "PlayerNames", Return = "table", Notes = "Returns a table that contains the map, 'PlayerName' -> 'UUID', for all valid playernames in the input array-table. PlayerNames not recognized will not be set in the returned map. Queries the Mojang servers for the results. WARNING: Do NOT use this function while the server is running. Only use it when the server is starting up (inside the Initialize() method), otherwise you will lag the server severely." }, + MakeUUIDDashed = { Params = "UUID", Return = "DashedUUID", Notes = "(STATIC) Converts the UUID to a dashed format (\"01234567-8901-2345-6789-012345678901\"). Accepts both dashed and short UUIDs. Logs a warning and returns an empty string if UUID format not recognized." }, + MakeUUIDShort = { Params = "UUID", Return = "ShortUUID", Notes = "(STATIC) Converts the UUID to a short format (without dashes, \"01234567890123456789012345678901\"). Accepts both dashed and short UUIDs. Logs a warning and returns an empty string if UUID format not recognized." }, + }, + + }, + cMonster = { Desc = [[ @@ -2002,6 +2026,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage); GetFurnaceFuelBurnTime = { Params = "{{cItem|Fuel}}", Return = "number", Notes = "(STATIC) Returns the number of ticks for how long the item would fuel a furnace. Returns zero if not a fuel." }, GetFurnaceRecipe = { Params = "{{cItem|InItem}}", Return = "{{cItem|OutItem}}, NumTicks, {{cItem|InItem}}", Notes = "(STATIC) Returns the furnace recipe for smelting the specified input. If a recipe is found, returns the smelted result, the number of ticks required for the smelting operation, and the input consumed (note that MCServer supports smelting M items into N items and different smelting rates). If no recipe is found, returns no value." }, GetGroupManager = { Params = "", Return = "{{cGroupManager|cGroupManager}}", Notes = "Returns the cGroupManager object." }, + GetMojangAPI = { Params = "", Return = "{{cMojangAPI}}", Notes = "Returns the {{cMojangAPI}} object." }, GetPhysicalRAMUsage = { Params = "", Return = "number", Notes = "Returns the amount of physical RAM that the entire MCServer process is using, in KiB. Negative if the OS doesn't support this query." }, GetPluginManager = { Params = "", Return = "{{cPluginManager|cPluginManager}}", Notes = "Returns the cPluginManager object." }, GetPrimaryServerVersion = { Params = "", Return = "number", Notes = "Returns the servers primary server version." }, diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg index 1e5dfd2fe..d3e3f5b45 100644 --- a/src/Bindings/AllToLua.pkg +++ b/src/Bindings/AllToLua.pkg @@ -77,6 +77,7 @@ $cfile "../Map.h" $cfile "../MapManager.h" $cfile "../Scoreboard.h" $cfile "../Statistics.h" +$cfile "../Protocol/MojangAPI.h" diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 6d69e2595..026a6bad5 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -2157,11 +2157,11 @@ static int tolua_cClientHandle_SendPluginMessage(lua_State * L) -static int tolua_cClientHandle_GetUUIDsFromPlayerNames(lua_State * L) +static int tolua_cMojangAPI_GetUUIDsFromPlayerNames(lua_State * L) { cLuaState S(L); if ( - !S.CheckParamUserTable(1, "cClientHandle") || + !S.CheckParamUserTable(1, "cMojangAPI") || !S.CheckParamTable(2) || !S.CheckParamEnd(3) ) @@ -3144,9 +3144,12 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); - tolua_function(tolua_S, "GetUUIDsFromPlayerNames", tolua_cClientHandle_GetUUIDsFromPlayerNames); tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cMojangAPI"); + tolua_function(tolua_S, "GetUUIDsFromPlayerNames", tolua_cMojangAPI_GetUUIDsFromPlayerNames); + tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cItemGrid"); tolua_function(tolua_S, "GetSlotCoords", Lua_ItemGrid_GetSlotCoords); tolua_endmodule(tolua_S); diff --git a/src/Protocol/MojangAPI.h b/src/Protocol/MojangAPI.h index 789fdf818..cc2902a19 100644 --- a/src/Protocol/MojangAPI.h +++ b/src/Protocol/MojangAPI.h @@ -15,9 +15,12 @@ +// tolua_begin class cMojangAPI { public: + // tolua_end + cMojangAPI(void); ~cMojangAPI(); @@ -30,6 +33,8 @@ public: Returns true if all was successful, false on failure. */ static bool SecureRequest(const AString & a_ServerName, const AString & a_Request, AString & a_Response); + // tolua_begin + /** Converts the given UUID to its short form (32 bytes, no dashes). Logs a warning and returns empty string if not a UUID. */ static AString MakeUUIDShort(const AString & a_UUID); @@ -38,16 +43,22 @@ public: Logs a warning and returns empty string if not a UUID. */ static AString MakeUUIDDashed(const AString & a_UUID); + // tolua_end + /** Converts the player names into UUIDs. a_PlayerName[idx] will be converted to UUID and returned as idx-th value The UUID will be empty on error. Blocking operation, do not use in world-tick thread! */ AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName); + // tolua_begin + /** Called by the Authenticator to add a PlayerName -> UUID mapping that it has received from authenticating a user. This adds the cache item and "refreshes" it if existing, adjusting its datetime stamp to now. */ void AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID); + + // tolua_end protected: struct sUUIDRecord @@ -95,7 +106,7 @@ protected: Names that are not valid are not added into the cache. ASSUMEs that a_PlayerNames contains lowercased player names. */ void CacheNamesToUUIDs(const AStringVector & a_PlayerNames); -} ; +} ; // tolua_export diff --git a/src/Root.h b/src/Root.h index 92a60a10d..210c36ea9 100644 --- a/src/Root.h +++ b/src/Root.h @@ -79,7 +79,7 @@ public: cWebAdmin * GetWebAdmin (void) { return m_WebAdmin; } // tolua_export cPluginManager * GetPluginManager (void) { return m_PluginManager; } // tolua_export cAuthenticator & GetAuthenticator (void) { return m_Authenticator; } - cMojangAPI & GetMojangAPI (void) { return m_MojangAPI; } + cMojangAPI & GetMojangAPI (void) { return m_MojangAPI; } // tolua_export /** Queues a console command for execution through the cServer class. The command will be executed in the tick thread From 17a94b16ea6207fd5f38fbd309b4db0d92de0d31 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 13:52:51 +0200 Subject: [PATCH 09/19] MojangAPI: Implemented UUID shortening and dashing. --- src/ClientHandle.cpp | 3 ++- src/ClientHandle.h | 8 +++++++- src/Protocol/Authenticator.cpp | 11 +---------- src/Protocol/MojangAPI.cpp | 34 +++++++++++++++++++++++++++------- src/Protocol/Protocol17x.cpp | 6 +++--- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index e4ad218a2..3f81f0a29 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -233,13 +233,14 @@ AString cClientHandle::GenerateOfflineUUID(const AString & a_Username) // This guarantees that they will never collide with an online UUID and can be distinguished. // Proper format for a version 3 UUID is: // xxxxxxxx-xxxx-3xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal digit and y is one of 8, 9, A, or B + // Note that we generate a short UUID (without the dashes) // Generate an md5 checksum, and use it as base for the ID: unsigned char MD5[16]; md5((const unsigned char *)a_Username.c_str(), a_Username.length(), MD5); MD5[6] &= 0x0f; // Need to trim to 4 bits only... MD5[8] &= 0x0f; // ... otherwise %01x overflows into two chars - return Printf("%02x%02x%02x%02x-%02x%02x-3%01x%02x-8%01x%02x-%02x%02x%02x%02x%02x%02x", + return Printf("%02x%02x%02x%02x%02x%02x3%01x%02x8%01x%02x%02x%02x%02x%02x%02x%02x", MD5[0], MD5[1], MD5[2], MD5[3], MD5[4], MD5[5], MD5[6], MD5[7], MD5[8], MD5[9], MD5[10], MD5[11], diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 50ed596d5..f2183a5ca 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -66,7 +66,9 @@ public: cPlayer * GetPlayer(void) { return m_Player; } // tolua_export + /** Returns the player's UUID, as used by the protocol, in the short form (no dashes) */ const AString & GetUUID(void) const { return m_UUID; } // tolua_export + void SetUUID(const AString & a_UUID) { m_UUID = a_UUID; } const Json::Value & GetProperties(void) const { return m_Properties; } @@ -80,7 +82,7 @@ public: /** Generates an UUID based on the player name provided. This is used for the offline (non-auth) mode, when there's no UUID source. Each username generates a unique and constant UUID, so that when the player reconnects with the same name, their UUID is the same. - Returns a 36-char UUID (with dashes). */ + Returns a 32-char UUID (no dashes). */ static AString GenerateOfflineUUID(const AString & a_Username); // tolua_export /** Returns true if the UUID is generated by online auth, false if it is an offline-generated UUID. @@ -360,7 +362,11 @@ private: int m_NumBlockChangeInteractionsThisTick; static int s_ClientCount; + + /** ID used for identification during authenticating. Assigned sequentially for each new instance. */ int m_UniqueID; + + /** Contains the UUID used by Mojang to identify the player's account. Short UUID stored here (without dashes) */ AString m_UUID; /** Set to true when the chunk where the player is is sent to the client. Used for spawning the player */ diff --git a/src/Protocol/Authenticator.cpp b/src/Protocol/Authenticator.cpp index e5d16cf10..160564d51 100644 --- a/src/Protocol/Authenticator.cpp +++ b/src/Protocol/Authenticator.cpp @@ -188,21 +188,12 @@ bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_S return false; } a_UserName = root.get("name", "Unknown").asString(); - a_UUID = root.get("id", "").asString(); + a_UUID = cMojangAPI::MakeUUIDShort(root.get("id", "").asString()); a_Properties = root["properties"]; // Store the player's UUID in the NameToUUID map in MojangAPI: cRoot::Get()->GetMojangAPI().AddPlayerNameToUUIDMapping(a_UserName, a_UUID); - // If the UUID doesn't contain the dashes, insert them at the proper places: - if (a_UUID.size() == 32) - { - a_UUID.insert(8, "-"); - a_UUID.insert(13, "-"); - a_UUID.insert(18, "-"); - a_UUID.insert(23, "-"); - } - return true; } diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index 05f9c09a7..52afd71da 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -18,6 +18,9 @@ /** The maximum age for items to be kept in the cache. Any item older than this will be removed. */ const Int64 MAX_AGE = 7 * 24 * 60 * 60; // 7 days ago +/** The maximum number of names to send in a single query */ +const int MAX_PER_QUERY = 100; + @@ -29,6 +32,7 @@ const Int64 MAX_AGE = 7 * 24 * 60 * 60; // 7 days ago + /** This is the data of the root certs for Starfield Technologies, the CA that signed sessionserver.mojang.com's cert: Downloaded from http://certs.starfieldtech.com/repository/ */ static const AString & StarfieldCACert(void) @@ -161,9 +165,10 @@ AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_Player void cMojangAPI::AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID) { AString lcName(a_PlayerName); + AString UUID = MakeUUIDShort(a_UUID); Int64 Now = time(NULL); cCSLock Lock(m_CSNameToUUID); - m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(a_PlayerName, a_UUID, Now); + m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(a_PlayerName, UUID, Now); } @@ -234,8 +239,14 @@ AString cMojangAPI::MakeUUIDShort(const AString & a_UUID) case 36: { + // Remove the dashes from the string: AString res; - // TODO + res.reserve(32); + res.append(a_UUID, 0, 8); + res.append(a_UUID, 9, 4); + res.append(a_UUID, 14, 4); + res.append(a_UUID, 19, 4); + res.append(a_UUID, 24, 12); return res; } } @@ -256,7 +267,16 @@ AString cMojangAPI::MakeUUIDDashed(const AString & a_UUID) case 32: { AString res; - // TODO + res.reserve(36); + res.append(a_UUID, 0, 8); + res.push_back('-'); + res.append(a_UUID, 8, 4); + res.push_back('-'); + res.append(a_UUID, 12, 4); + res.push_back('-'); + res.append(a_UUID, 16, 4); + res.push_back('-'); + res.append(a_UUID, 20, 12); return res; } } @@ -362,11 +382,11 @@ void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames) while (!NamesToQuery.empty()) { - // Create the request body - a JSON containing up to 100 playernames: + // Create the request body - a JSON containing up to MAX_PER_QUERY playernames: Json::Value root; int Count = 0; AStringVector::iterator itr = NamesToQuery.begin(), end = NamesToQuery.end(); - for (; (itr != end) && (Count < 100); ++itr, ++Count) + for (; (itr != end) && (Count < MAX_PER_QUERY); ++itr, ++Count) { Json::Value req(*itr); root.append(req); @@ -377,7 +397,7 @@ void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames) // Create the HTTP request: AString Request; - Request += "POST " + m_NameToUUIDAddress + " HTTP/1.0\r\n"; + Request += "POST " + m_NameToUUIDAddress + " HTTP/1.0\r\n"; // We need to use HTTP 1.0 because we don't handle Chunked transfer encoding Request += "Host: " + m_NameToUUIDServer + "\r\n"; Request += "User-Agent: MCServer\r\n"; Request += "Connection: close\r\n"; @@ -430,7 +450,7 @@ void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames) { Json::Value & Val = root[idx]; AString JsonName = Val.get("name", "").asString(); - AString JsonUUID = Val.get("id", "").asString(); + AString JsonUUID = MakeUUIDShort(Val.get("id", "").asString()); if (JsonUUID.empty()) { continue; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index dadf82716..73aba167d 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -680,7 +680,7 @@ void cProtocol172::SendLoginSuccess(void) { cPacketizer Pkt(*this, 0x02); // Login success packet - Pkt.WriteString(m_Client->GetUUID()); + Pkt.WriteString(cMojangAPI::MakeUUIDDashed(m_Client->GetUUID())); Pkt.WriteString(m_Client->GetUsername()); } @@ -941,7 +941,7 @@ void cProtocol172::SendPlayerSpawn(const cPlayer & a_Player) // Called to spawn another player for the client cPacketizer Pkt(*this, 0x0c); // Spawn Player packet Pkt.WriteVarInt(a_Player.GetUniqueID()); - Pkt.WriteString(a_Player.GetClientHandle()->GetUUID()); + Pkt.WriteString(cMojangAPI::MakeUUIDDashed(a_Player.GetClientHandle()->GetUUID())); Pkt.WriteString(a_Player.GetName()); Pkt.WriteFPInt(a_Player.GetPosX()); Pkt.WriteFPInt(a_Player.GetPosY()); @@ -3014,7 +3014,7 @@ void cProtocol176::SendPlayerSpawn(const cPlayer & a_Player) // Called to spawn another player for the client cPacketizer Pkt(*this, 0x0c); // Spawn Player packet Pkt.WriteVarInt(a_Player.GetUniqueID()); - Pkt.WriteString(a_Player.GetClientHandle()->GetUUID()); + Pkt.WriteString(cMojangAPI::MakeUUIDDashed(a_Player.GetClientHandle()->GetUUID())); Pkt.WriteString(a_Player.GetName()); const Json::Value & Properties = m_Client->GetProperties(); From afef7a79d7a214eec66e427da589172a40a18fef Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 13:53:23 +0200 Subject: [PATCH 10/19] Debuggers: Updated for the new cMojangAPI changes. --- MCServer/Plugins/Debuggers/Debuggers.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index 80416acc7..575d696cb 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -287,7 +287,7 @@ function TestUUIDFromName() "nonexistent_player", } -- WARNING: Blocking operation! DO NOT USE IN TICK THREAD! - local UUIDs = cClientHandle:GetUUIDsFromPlayerNames(PlayerNames) + local UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames) -- Log the results: for _, name in ipairs(PlayerNames) do @@ -299,6 +299,15 @@ function TestUUIDFromName() end end + -- Test once more with the same players, valid-only. This should go directly from cache, so fast. + LOG("Testing again with the same valid players...") + local ValidPlayerNames = + { + "xoft", + "aloe_vera", + } + UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(ValidPlayerNames); + LOG("UUID-from-Name resolution test finished.") end From 1793644feff66b076980c669ede94d7b03eceafe Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 14:02:36 +0200 Subject: [PATCH 11/19] APIDump: Fixed UUID-related documentation. --- MCServer/Plugins/APIDump/APIDesc.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index e5114784e..692ca80ac 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -523,12 +523,12 @@ end Functions = { - GenerateOfflineUUID = { Params = "Username", Return = "string", Notes = "(STATIC) Generates an UUID based on the player name provided. This is used for the offline (non-auth) mode, when there's no UUID source. Each username generates a unique and constant UUID, so that when the player reconnects with the same name, their UUID is the same. Returns a 36-char UUID (with dashes)." }, + GenerateOfflineUUID = { Params = "Username", Return = "string", Notes = "(STATIC) Generates an UUID based on the player name provided. This is used for the offline (non-auth) mode, when there's no UUID source. Each username generates a unique and constant UUID, so that when the player reconnects with the same name, their UUID is the same. Returns a 32-char UUID (no dashes)." }, GetLocale = { Params = "", Return = "Locale", Notes = "Returns the locale string that the client sends as part of the protocol handshake. Can be used to provide localized strings." }, GetPing = { Params = "", Return = "number", Notes = "Returns the ping time, in ms" }, GetPlayer = { Params = "", Return = "{{cPlayer|cPlayer}}", Notes = "Returns the player object connected to this client. Note that this may be nil, for example if the player object is not yet spawned." }, GetUniqueID = { Params = "", Return = "number", Notes = "Returns the UniqueID of the client used to identify the client in the server" }, - GetUUID = { Params = "", Return = "string", Notes = "Returns the authentication-based UUID of the client. This UUID should be used to identify the player when persisting any player-related data." }, + GetUUID = { Params = "", Return = "string", Notes = "Returns the authentication-based UUID of the client. This UUID should be used to identify the player when persisting any player-related data. Returns a 32-char UUID (no dashes)" }, GetUsername = { Params = "", Return = "string", Notes = "Returns the username that the client has provided" }, GetViewDistance = { Params = "", Return = "number", Notes = "Returns the viewdistance (number of chunks loaded for the player in each direction)" }, HasPluginChannel = { Params = "ChannelName", Return = "bool", Notes = "Returns true if the client has registered to receive messages on the specified plugin channel." }, From 426773df17dbd7748b51021a27119240de5b1922 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 14:03:14 +0200 Subject: [PATCH 12/19] ManualBindings: Fixed alignment. --- src/Bindings/ManualBindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 026a6bad5..b1d302560 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -3141,9 +3141,9 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cClientHandle"); - tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); - tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); - tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); + tolua_constant(tolua_S, "MAX_VIEW_DISTANCE", cClientHandle::MAX_VIEW_DISTANCE); + tolua_constant(tolua_S, "MIN_VIEW_DISTANCE", cClientHandle::MIN_VIEW_DISTANCE); + tolua_function(tolua_S, "SendPluginMessage", tolua_cClientHandle_SendPluginMessage); tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cMojangAPI"); From 85d64d291a6d11df6689190268c1f4c6b1c02cdd Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 14:09:30 +0200 Subject: [PATCH 13/19] MojangAPI: Clarified the UUID conversion code. --- src/Protocol/MojangAPI.cpp | 15 +++++++++++++-- src/Protocol/MojangAPI.h | 6 ++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index 52afd71da..86ff134db 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -233,9 +233,14 @@ bool cMojangAPI::SecureRequest(const AString & a_ServerName, const AString & a_R AString cMojangAPI::MakeUUIDShort(const AString & a_UUID) { + // Note: we only check the string's length, not the actual content switch (a_UUID.size()) { - case 32: return a_UUID; + case 32: + { + // Already is a short UUID + return a_UUID; + } case 36: { @@ -260,12 +265,18 @@ AString cMojangAPI::MakeUUIDShort(const AString & a_UUID) AString cMojangAPI::MakeUUIDDashed(const AString & a_UUID) { + // Note: we only check the string's length, not the actual content switch (a_UUID.size()) { - case 36: return a_UUID; + case 36: + { + // Already is a dashed UUID + return a_UUID; + } case 32: { + // Insert dashes at the proper positions: AString res; res.reserve(36); res.append(a_UUID, 0, 8); diff --git a/src/Protocol/MojangAPI.h b/src/Protocol/MojangAPI.h index cc2902a19..c99f940ad 100644 --- a/src/Protocol/MojangAPI.h +++ b/src/Protocol/MojangAPI.h @@ -36,11 +36,13 @@ public: // tolua_begin /** Converts the given UUID to its short form (32 bytes, no dashes). - Logs a warning and returns empty string if not a UUID. */ + Logs a warning and returns empty string if not a UUID. + Note: only checks the string's length, not the actual content. */ static AString MakeUUIDShort(const AString & a_UUID); /** Converts the given UUID to its dashed form (36 bytes, 4 dashes). - Logs a warning and returns empty string if not a UUID. */ + Logs a warning and returns empty string if not a UUID. + Note: only checks the string's length, not the actual content. */ static AString MakeUUIDDashed(const AString & a_UUID); // tolua_end From a71c2da3f8ee329977902a36274d720908a77c48 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 17:07:57 +0200 Subject: [PATCH 14/19] APIDump: Added notes about cache to cMojangAPI. --- MCServer/Plugins/APIDump/APIDesc.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index 692ca80ac..2a8ae90f9 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -1619,7 +1619,13 @@ a_Player:OpenWindow(Window); Mojang uses two formats for UUIDs, short and dashed. MCServer works with short UUIDs internally, but will convert to dashed UUIDs where needed - in the protocol login for example. The MakeUUIDShort() and MakeUUIDDashed() functions are provided for plugins to use for conversion between the two - formats. + formats.

+

+ This class will cache values returned by the API service. The cache will hold the values for 7 days + by default, after that, they will no longer be available. This is in order to not let the server get + banned from using the API service, since they are rate-limited to 600 queries per 10 minutes. The + cache contents also gets updated whenever a player successfully joins, since that makes the server + contact the API service, too, and retrieve the relevant data.

]], Functions = { From 0336e12cee0a784b076b5f9423bce2b9f803d1c8 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 30 Jul 2014 17:09:41 +0200 Subject: [PATCH 15/19] MojangAPI: Renamed cache file to MojangAPI.sqlite. --- src/Protocol/MojangAPI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index 86ff134db..5fbc5b476 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -304,7 +304,7 @@ void cMojangAPI::LoadCachesFromDisk(void) try { // Open up the SQLite DB: - SQLite::Database db("NameToUUID.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + SQLite::Database db("MojangAPI.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)"); // Clean up old entries: @@ -341,7 +341,7 @@ void cMojangAPI::SaveCachesToDisk(void) try { // Open up the SQLite DB: - SQLite::Database db("NameToUUID.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + SQLite::Database db("MojangAPI.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)"); // Remove all entries: From 8b519bf6e20a987c9544ef11f0df6467831cc069 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Thu, 31 Jul 2014 10:02:50 +0200 Subject: [PATCH 16/19] MojangAPI: Added a UseCachedOnly param to GetUUIDsFromPlayerNames(). --- MCServer/Plugins/APIDump/APIDesc.lua | 2 +- MCServer/Plugins/Debuggers/Debuggers.lua | 32 +++++++++++++++++++++++- src/Bindings/ManualBindings.cpp | 19 ++++++++++---- src/Protocol/MojangAPI.cpp | 7 ++++-- src/Protocol/MojangAPI.h | 6 +++-- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index 2a8ae90f9..9d0e90999 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -1630,7 +1630,7 @@ a_Player:OpenWindow(Window); Functions = { AddPlayerNameToUUIDMapping = { Params = "PlayerName, UUID", Return = "", Notes = "Adds the specified PlayerName-to-UUID mapping into the cache, with current timestamp." }, - GetUUIDsFromPlayerNames = { Params = "PlayerNames", Return = "table", Notes = "Returns a table that contains the map, 'PlayerName' -> 'UUID', for all valid playernames in the input array-table. PlayerNames not recognized will not be set in the returned map. Queries the Mojang servers for the results. WARNING: Do NOT use this function while the server is running. Only use it when the server is starting up (inside the Initialize() method), otherwise you will lag the server severely." }, + GetUUIDsFromPlayerNames = { Params = "PlayerNames, [UseOnlyCached]", Return = "table", Notes = "Returns a table that contains the map, 'PlayerName' -> 'UUID', for all valid playernames in the input array-table. PlayerNames not recognized will not be set in the returned map. If UseOnlyCached is false (the default), queries the Mojang servers for the results that are not in the cache.
WARNING: Do NOT use this function with UseOnlyCached set to false while the server is running. Only use it when the server is starting up (inside the Initialize() method), otherwise you will lag the server severely." }, MakeUUIDDashed = { Params = "UUID", Return = "DashedUUID", Notes = "(STATIC) Converts the UUID to a dashed format (\"01234567-8901-2345-6789-012345678901\"). Accepts both dashed and short UUIDs. Logs a warning and returns an empty string if UUID format not recognized." }, MakeUUIDShort = { Params = "UUID", Return = "ShortUUID", Notes = "(STATIC) Converts the UUID to a short format (without dashes, \"01234567890123456789012345678901\"). Accepts both dashed and short UUIDs. Logs a warning and returns an empty string if UUID format not recognized." }, }, diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index 575d696cb..075cfa40c 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -307,8 +307,38 @@ function TestUUIDFromName() "aloe_vera", } UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(ValidPlayerNames); + + -- Log the results: + for _, name in ipairs(ValidPlayerNames) do + local UUID = UUIDs[name] + if (UUID == nil) then + LOG(" UUID(" .. name .. ") not found.") + else + LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"") + end + end + + -- Test yet again, cache-only: + LOG("Testing once more, cache only...") + local PlayerNames3 = + { + "xoft", + "aloe_vera", + "notch", -- Valid player name, but not cached (most likely :) + } + UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames3, true) - LOG("UUID-from-Name resolution test finished.") + -- Log the results: + for _, name in ipairs(PlayerNames3) do + local UUID = UUIDs[name] + if (UUID == nil) then + LOG(" UUID(" .. name .. ") not found.") + else + LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"") + end + end + + LOG("UUID-from-Name resolution tests finished.") end diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index b1d302560..038173c99 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -2163,7 +2163,7 @@ static int tolua_cMojangAPI_GetUUIDsFromPlayerNames(lua_State * L) if ( !S.CheckParamUserTable(1, "cMojangAPI") || !S.CheckParamTable(2) || - !S.CheckParamEnd(3) + !S.CheckParamEnd(4) ) { return 0; @@ -2177,19 +2177,27 @@ static int tolua_cMojangAPI_GetUUIDsFromPlayerNames(lua_State * L) { lua_rawgeti(L, 2, i); AString Name; - S.GetStackValue(3, Name); + S.GetStackValue(-1, Name); if (!Name.empty()) { PlayerNames.push_back(Name); } lua_pop(L, 1); } + + // If the UseOnlyCached param was given, read it; default to false + bool ShouldUseCacheOnly = false; + if (lua_gettop(L) == 3) + { + ShouldUseCacheOnly = (lua_toboolean(L, 3) != 0); + lua_pop(L, 1); + } // Push the output table onto the stack: - lua_newtable(L); // stack index 3 + lua_newtable(L); // Get the UUIDs: - AStringVector UUIDs = cRoot::Get()->GetMojangAPI().GetUUIDsFromPlayerNames(PlayerNames); + AStringVector UUIDs = cRoot::Get()->GetMojangAPI().GetUUIDsFromPlayerNames(PlayerNames, ShouldUseCacheOnly); if (UUIDs.size() != PlayerNames.size()) { // A hard error has occured while processing the request, no UUIDs were returned. Return an empty table: @@ -2197,7 +2205,8 @@ static int tolua_cMojangAPI_GetUUIDsFromPlayerNames(lua_State * L) } // Convert to output table, PlayerName -> UUID: - for (int i = 0; i < NumNames; i++) + size_t len = UUIDs.size(); + for (size_t i = 0; i < len; i++) { if (UUIDs[i].empty()) { diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index 5fbc5b476..71a5e53de 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -129,7 +129,7 @@ void cMojangAPI::Start(cIniFile & a_SettingsIni) -AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames) +AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames, bool a_UseOnlyCached) { // Convert all playernames to lowercase: AStringVector PlayerNames; @@ -140,7 +140,10 @@ AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_Player } // for itr - a_PlayerNames[] // Request the cache to populate any names not yet contained: - CacheNamesToUUIDs(PlayerNames); + if (!a_UseOnlyCached) + { + CacheNamesToUUIDs(PlayerNames); + } // Retrieve from cache: size_t idx = 0; diff --git a/src/Protocol/MojangAPI.h b/src/Protocol/MojangAPI.h index c99f940ad..ac8995bb5 100644 --- a/src/Protocol/MojangAPI.h +++ b/src/Protocol/MojangAPI.h @@ -50,8 +50,10 @@ public: /** Converts the player names into UUIDs. a_PlayerName[idx] will be converted to UUID and returned as idx-th value The UUID will be empty on error. - Blocking operation, do not use in world-tick thread! */ - AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName); + If a_UseOnlyCached is true, only the cached values are returned. + If a_UseOnlyCached is false, the names not found in the cache are looked up online, which is a blocking + operation, do not use this in world-tick thread! */ + AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName, bool a_UseOnlyCached = false); // tolua_begin From 59adf113f0ba6414edf061fc4ffbf2bb2b904883 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Thu, 31 Jul 2014 17:22:48 +0200 Subject: [PATCH 17/19] MojangAPI: Moved the settings to a separate ini section. --- src/Protocol/MojangAPI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index 71a5e53de..fa99d63fd 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -120,8 +120,8 @@ cMojangAPI::~cMojangAPI() void cMojangAPI::Start(cIniFile & a_SettingsIni) { - m_NameToUUIDServer = a_SettingsIni.GetValueSet("Authentication", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER); - m_NameToUUIDAddress = a_SettingsIni.GetValueSet("Authentication", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS); + m_NameToUUIDServer = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER); + m_NameToUUIDAddress = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS); LoadCachesFromDisk(); } From ecb86935f87ce2b9f1ae4671d8a8b722c798b8a2 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Thu, 31 Jul 2014 22:52:06 +0200 Subject: [PATCH 18/19] Fixed UUIDs handling in cPlayer. The loading expected dashed UUIDs, MCS uses short UUIDs throughout. --- src/Entities/Player.cpp | 13 ++++++++----- src/Entities/Player.h | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index fcc8eb9a0..477334948 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1700,8 +1700,10 @@ bool cPlayer::LoadFromDisk(void) // Load from the offline UUID file, if allowed: AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName()); + const char * OfflineUsage = " (unused)"; if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData()) { + OfflineUsage = ""; if (LoadFromFile(GetUUIDFileName(OfflineUUID))) { return true; @@ -1724,8 +1726,8 @@ bool cPlayer::LoadFromDisk(void) } // None of the files loaded successfully - LOG("Player data file not found for %s (%s, offline %s), will be reset to defaults.", - GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str() + LOG("Player data file not found for %s (%s, offline %s%s), will be reset to defaults.", + GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str(), OfflineUsage ); return false; } @@ -2212,12 +2214,13 @@ void cPlayer::Detach() AString cPlayer::GetUUIDFileName(const AString & a_UUID) { - ASSERT(a_UUID.size() == 36); + AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID); + ASSERT(UUID.length() == 36); AString res("players/"); - res.append(a_UUID, 0, 2); + res.append(UUID, 0, 2); res.push_back('/'); - res.append(a_UUID, 2, AString::npos); + res.append(UUID, 2, AString::npos); res.append(".json"); return res; } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 488884602..29bb4c39d 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -536,7 +536,7 @@ protected: */ bool m_bIsTeleporting; - /** The UUID of the player, as read from the ClientHandle. + /** The short UUID (no dashes) of the player, as read from the ClientHandle. If no ClientHandle is given, the UUID is initialized to empty. */ AString m_UUID; From 70fd7caf1f879b0588042d6542c27ef8f0909a43 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Thu, 31 Jul 2014 22:54:45 +0200 Subject: [PATCH 19/19] Removed trailing whitespace. --- src/Protocol/MojangAPI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp index fa99d63fd..45baa5a4f 100644 --- a/src/Protocol/MojangAPI.cpp +++ b/src/Protocol/MojangAPI.cpp @@ -245,7 +245,7 @@ AString cMojangAPI::MakeUUIDShort(const AString & a_UUID) return a_UUID; } - case 36: + case 36: { // Remove the dashes from the string: AString res; @@ -277,7 +277,7 @@ AString cMojangAPI::MakeUUIDDashed(const AString & a_UUID) return a_UUID; } - case 32: + case 32: { // Insert dashes at the proper positions: AString res;