Cuberite
A lightweight, fast and extensible game server for Minecraft
Authenticator.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
4 #include "Authenticator.h"
5 #include "MojangAPI.h"
6 #include "../Root.h"
7 #include "../Server.h"
8 #include "../ClientHandle.h"
9 #include "../UUID.h"
10 
11 #include "../IniFile.h"
12 #include "json/json.h"
13 
14 #include "../mbedTLS++/BlockingSslClientSocket.h"
15 
16 
17 
18 
19 
20 #define DEFAULT_AUTH_SERVER "sessionserver.mojang.com"
21 #define DEFAULT_AUTH_ADDRESS "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%"
22 
23 
24 
25 
26 
28  super("cAuthenticator"),
29  m_Server(DEFAULT_AUTH_SERVER),
30  m_Address(DEFAULT_AUTH_ADDRESS),
31  m_ShouldAuthenticate(true)
32 {
33 }
34 
35 
36 
37 
38 
40 {
41  Stop();
42 }
43 
44 
45 
46 
47 
49 {
50  m_Server = a_Settings.GetValueSet ("Authentication", "Server", DEFAULT_AUTH_SERVER);
51  m_Address = a_Settings.GetValueSet ("Authentication", "Address", DEFAULT_AUTH_ADDRESS);
52  m_ShouldAuthenticate = a_Settings.GetValueSetB("Authentication", "Authenticate", true);
53 }
54 
55 
56 
57 
58 
59 void cAuthenticator::Authenticate(int a_ClientID, const AString & a_UserName, const AString & a_ServerHash)
60 {
62  {
63  Json::Value Value;
64  cRoot::Get()->AuthenticateUser(a_ClientID, a_UserName, cClientHandle::GenerateOfflineUUID(a_UserName), Value);
65  return;
66  }
67 
68  cCSLock LOCK(m_CS);
69  m_Queue.push_back(cUser(a_ClientID, a_UserName, a_ServerHash));
71 }
72 
73 
74 
75 
76 
78 {
79  ReadSettings(a_Settings);
80  super::Start();
81 }
82 
83 
84 
85 
86 
88 {
89  m_ShouldTerminate = true;
91  super::Stop();
92 }
93 
94 
95 
96 
97 
99 {
100  for (;;)
101  {
102  cCSLock Lock(m_CS);
103  while (!m_ShouldTerminate && (m_Queue.size() == 0))
104  {
105  cCSUnlock Unlock(Lock);
107  }
108  if (m_ShouldTerminate)
109  {
110  return;
111  }
112  ASSERT(!m_Queue.empty());
113 
114  cAuthenticator::cUser & User = m_Queue.front();
115  int ClientID = User.m_ClientID;
116  AString UserName = User.m_Name;
117  AString ServerID = User.m_ServerID;
118  m_Queue.pop_front();
119  Lock.Unlock();
120 
121  AString NewUserName = UserName;
122  cUUID UUID;
123  Json::Value Properties;
124  if (AuthWithYggdrasil(NewUserName, ServerID, UUID, Properties))
125  {
126  LOGINFO("User %s authenticated with UUID %s", NewUserName.c_str(), UUID.ToShortString().c_str());
127  cRoot::Get()->AuthenticateUser(ClientID, NewUserName, UUID, Properties);
128  }
129  else
130  {
131  cRoot::Get()->KickUser(ClientID, "Failed to authenticate account!");
132  }
133  } // for (-ever)
134 }
135 
136 
137 
138 
139 
140 bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_ServerId, cUUID & a_UUID, Json::Value & a_Properties)
141 {
142  LOGD("Trying to authenticate user %s", a_UserName.c_str());
143 
144  // Create the GET request:
145  AString ActualAddress = m_Address;
146  ReplaceString(ActualAddress, "%USERNAME%", a_UserName);
147  ReplaceString(ActualAddress, "%SERVERID%", a_ServerId);
148 
149  AString Request;
150  Request += "GET " + ActualAddress + " HTTP/1.0\r\n";
151  Request += "Host: " + m_Server + "\r\n";
152  Request += "User-Agent: Cuberite\r\n";
153  Request += "Connection: close\r\n";
154  Request += "\r\n";
155 
156  AString Response;
157  if (!cMojangAPI::SecureRequest(m_Server, Request, Response))
158  {
159  return false;
160  }
161 
162  // Check the HTTP status line:
163  const AString Prefix("HTTP/1.1 200 OK");
164  AString HexDump;
165  if (Response.compare(0, Prefix.size(), Prefix))
166  {
167  LOGINFO("User %s failed to auth, bad HTTP status line received", a_UserName.c_str());
168  LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
169  return false;
170  }
171 
172  // Erase the HTTP headers from the response:
173  size_t idxHeadersEnd = Response.find("\r\n\r\n");
174  if (idxHeadersEnd == AString::npos)
175  {
176  LOGINFO("User %s failed to authenticate, bad HTTP response header received", a_UserName.c_str());
177  LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
178  return false;
179  }
180  Response.erase(0, idxHeadersEnd + 4);
181 
182  // Parse the Json response:
183  if (Response.empty())
184  {
185  return false;
186  }
187  Json::Value root;
188  Json::Reader reader;
189  if (!reader.parse(Response, root, false))
190  {
191  LOGWARNING("cAuthenticator: Cannot parse received data (authentication) to JSON!");
192  return false;
193  }
194  a_UserName = root.get("name", "Unknown").asString();
195  a_Properties = root["properties"];
196  if (!a_UUID.FromString(root.get("id", "").asString()))
197  {
198  LOGWARNING("cAuthenticator: Recieved invalid UUID format");
199  return false;
200  }
201 
202  // Store the player's profile in the MojangAPI caches:
203  cRoot::Get()->GetMojangAPI().AddPlayerProfile(a_UserName, a_UUID, a_Properties);
204 
205  return true;
206 }
207 
208 
209 
210 
211 
212 /* In case we want to export this function to the plugin API later - don't forget to add the relevant INI configuration lines for DEFAULT_PROPERTIES_ADDRESS
213 
214 #define DEFAULT_PROPERTIES_ADDRESS "/session/minecraft/profile/%UUID%"
215 
216 // Gets the properties, such as skin, of a player based on their UUID via Mojang's API
217 bool GetPlayerProperties(const AString & a_UUID, Json::Value & a_Properties);
218 
219 bool cAuthenticator::GetPlayerProperties(const AString & a_UUID, Json::Value & a_Properties)
220 {
221  LOGD("Trying to get properties for user %s", a_UUID.c_str());
222 
223  // Create the GET request:
224  AString ActualAddress = m_PropertiesAddress;
225  ReplaceString(ActualAddress, "%UUID%", a_UUID);
226 
227  AString Request;
228  Request += "GET " + ActualAddress + " HTTP/1.0\r\n";
229  Request += "Host: " + m_Server + "\r\n";
230  Request += "User-Agent: Cuberite\r\n";
231  Request += "Connection: close\r\n";
232  Request += "\r\n";
233 
234  AString Response;
235  if (!ConnectSecurelyToAddress(StarfieldCACert(), m_Server, Request, Response))
236  {
237  return false;
238  }
239 
240  // Check the HTTP status line:
241  const AString Prefix("HTTP/1.1 200 OK");
242  AString HexDump;
243  if (Response.compare(0, Prefix.size(), Prefix))
244  {
245  LOGINFO("Failed to get properties for user %s, bad HTTP status line received", a_UUID.c_str());
246  LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
247  return false;
248  }
249 
250  // Erase the HTTP headers from the response:
251  size_t idxHeadersEnd = Response.find("\r\n\r\n");
252  if (idxHeadersEnd == AString::npos)
253  {
254  LOGINFO("Failed to get properties for user %s, bad HTTP response header received", a_UUID.c_str());
255  LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
256  return false;
257  }
258  Response.erase(0, idxHeadersEnd + 4);
259 
260  // Parse the Json response:
261  if (Response.empty())
262  {
263  return false;
264  }
265 
266  Json::Value root;
267  Json::Reader reader;
268  if (!reader.parse(Response, root, false))
269  {
270  LOGWARNING("cAuthenticator: Cannot parse received properties data to JSON!");
271  return false;
272  }
273 
274  a_Properties = root["properties"];
275  return true;
276 }
277 */
278 
279 
280 
281 
void Set(void)
Sets the event - releases one thread that has been waiting in Wait().
Definition: Event.cpp:53
std::atomic< bool > m_ShouldTerminate
The overriden Execute() method should check this value periodically and terminate if this is true...
Definition: IsThread.h:32
void ReadSettings(cSettingsRepositoryInterface &a_Settings)
(Re-)read server and address from INI:
AString ToShortString() const
Converts the UUID to a short form string (i.e without dashes).
Definition: UUID.cpp:133
bool FromString(const AString &a_StringUUID)
Tries to interpret the string as a short or long form UUID and assign from it.
Definition: UUID.cpp:102
bool m_ShouldAuthenticate
Definition: Authenticator.h:84
cUserList m_Queue
Definition: Authenticator.h:71
#define DEFAULT_AUTH_SERVER
void ReplaceString(AString &iHayStack, const AString &iNeedle, const AString &iReplaceWith)
Replaces each occurence of iNeedle in iHayStack with iReplaceWith.
virtual ~cAuthenticator() override
void AddPlayerProfile(const AString &a_PlayerName, const cUUID &a_UUID, const Json::Value &a_Properties)
Called by the Authenticator to add a profile that it has received from authenticating a user...
Definition: MojangAPI.cpp:433
void AuthenticateUser(int a_ClientID, const AString &a_Name, const cUUID &a_UUID, const Json::Value &a_Properties)
Called by cAuthenticator to auth the specified user.
Definition: Root.cpp:747
Definition: UUID.h:10
static bool SecureRequest(const AString &a_ServerName, const AString &a_Request, AString &a_Response)
Connects to the specified server using SSL, sends the given request and receives the response...
Definition: MojangAPI.cpp:455
void Wait(void)
Waits until the event has been set.
Definition: Event.cpp:24
void KickUser(int a_ClientID, const AString &a_Reason)
Kicks the user, no matter in what world they are.
Definition: Root.cpp:738
void Authenticate(int a_ClientID, const AString &a_UserName, const AString &a_ServerHash)
Queues a request for authenticating a user.
bool AuthWithYggdrasil(AString &a_UserName, const AString &a_ServerId, cUUID &a_UUID, Json::Value &a_Properties)
Returns true if the user authenticated okay, false on error Returns the case-corrected username...
cMojangAPI & GetMojangAPI(void)
Definition: Root.h:116
void LOGINFO(const char *a_Format, fmt::ArgList a_ArgList)
Definition: Logger.cpp:165
Temporary RAII unlock for a cCSLock.
#define DEFAULT_AUTH_ADDRESS
#define ASSERT(x)
Definition: Globals.h:335
void LOGWARNING(const char *a_Format, fmt::ArgList a_ArgList)
Definition: Logger.cpp:174
#define LOGD(...)
Definition: LoggerSimple.h:40
void Unlock(void)
virtual AString GetValueSet(const AString &keyname, const AString &valuename, const AString &defValue="")=0
Gets the value; if not found, write the default to the repository.
void Stop(void)
Signals the thread to terminate and waits until it&#39;s finished.
Definition: IsThread.cpp:110
void Stop(void)
Stops the authenticator thread.
cEvent m_QueueNonempty
Definition: Authenticator.h:72
virtual void Execute(void) override
cIsThread override:
void GenerateOfflineUUID(void)
Generates an UUID based on the username stored for this client, and stores it in the m_UUID member...
std::string AString
Definition: StringUtils.h:13
virtual bool GetValueSetB(const AString &keyname, const AString &valuename, const bool defValue=false)=0
AString m_Address
The URL to use for auth, without server part.
Definition: Authenticator.h:81
static cRoot * Get()
Definition: Root.h:51
AString & CreateHexDump(AString &a_Out, const void *a_Data, size_t a_Size, size_t a_BytesPerLine)
format binary data this way: 00001234: 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 1234567890abcd...
cCriticalSection m_CS
Definition: Authenticator.h:70
RAII for cCriticalSection - locks the CS on creation, unlocks on destruction.
bool Start(void)
Starts the thread; returns without waiting for the actual start.
Definition: IsThread.cpp:80
AString m_Server
The server that is to be contacted for auth / UUID conversions.
Definition: Authenticator.h:75