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;