summaryrefslogblamecommitdiffstats
path: root/source/cAuthenticator.cpp
blob: 268514940d6c5beaefbd7cae9209ddb04549b07d (plain) (tree)


































































































































































                                                                                                                                            






                                                                                                    

































































































                                                                                                                                                                                                                    



                                                                                                                    










                                                                                      

#include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules

#include "cAuthenticator.h"
#include "cBlockingTCPLink.h"
#include "cRoot.h"
#include "cServer.h"

#include "../iniFile/iniFile.h"

#include <sstream>





#define DEFAULT_AUTH_SERVER "session.minecraft.net"
#define DEFAULT_AUTH_ADDRESS "/game/checkserver.jsp?user=%USERNAME%&serverId=%SERVERID%"
#define MAX_REDIRECTS 10





cAuthenticator::cAuthenticator(void) :
	super("cAuthenticator"),
	m_Server(DEFAULT_AUTH_SERVER),
	m_Address(DEFAULT_AUTH_ADDRESS),
	m_ShouldAuthenticate(true)
{
	ReadINI();
}





cAuthenticator::~cAuthenticator()
{
	Stop();
}





/// Read custom values from INI
void cAuthenticator::ReadINI(void)
{
	cIniFile IniFile("settings.ini");
	if (!IniFile.ReadFile())
	{
		return;
	}
	
	m_Server  = IniFile.GetValue("Authentication", "Server");
	m_Address = IniFile.GetValue("Authentication", "Address");
	m_ShouldAuthenticate = IniFile.GetValueB("Authentication", "Authenticate", true);
	bool bSave = false;
	
	if (m_Server.length() == 0)
	{
		m_Server = DEFAULT_AUTH_SERVER;
		IniFile.SetValue("Authentication", "Server", m_Server);
		bSave = true;
	}
	if (m_Address.length() == 0)
	{
		m_Address = DEFAULT_AUTH_ADDRESS;
		IniFile.SetValue("Authentication", "Address", m_Address);
		bSave = true;
	}

	if (bSave)
	{
		IniFile.SetValueB("Authentication", "Authenticate", m_ShouldAuthenticate);
		IniFile.WriteFile();
	}
}





/// Queues a request for authenticating a user. If the auth fails, the user is kicked
void cAuthenticator::Authenticate(int a_ClientID, const AString & a_UserName, const AString & a_ServerHash)
{
	if (!m_ShouldAuthenticate)
	{
		cRoot::Get()->AuthenticateUser(a_ClientID);
		return;
	}

	cCSLock Lock(m_CS);
	m_Queue.push_back(cUser(a_ClientID, a_UserName, a_ServerHash));
	m_QueueNonempty.Set();
}





void cAuthenticator::Stop(void)
{
	m_ShouldTerminate = true;
	m_QueueNonempty.Set();
	Wait();
}





void cAuthenticator::Execute(void)
{
	for (;;)
	{
		cCSLock Lock(m_CS);
		while (!m_ShouldTerminate && (m_Queue.size() == 0))
		{
			cCSUnlock Unlock(Lock);
			m_QueueNonempty.Wait();
		}
		if (m_ShouldTerminate)
		{
			return;
		}
		ASSERT(!m_Queue.empty());
		
		int ClientID = m_Queue.front().mClientID;
		AString UserName = m_Queue.front().mName;
		AString ActualAddress = m_Address;
		ReplaceString(ActualAddress, "%USERNAME%", UserName);
		ReplaceString(ActualAddress, "%SERVERID%", cRoot::Get()->GetServer()->GetServerID());
		m_Queue.pop_front();
		Lock.Unlock();

		if (!AuthFromAddress(m_Server, ActualAddress, UserName))
		{
			cRoot::Get()->KickUser(ClientID, "Failed to authenticate account!");
		}
		else
		{
			cRoot::Get()->AuthenticateUser(ClientID);
		}
	}  // for (-ever)
}





bool cAuthenticator::AuthFromAddress(const AString & a_Server, const AString & a_Address, const AString & a_UserName, int a_Level /* = 1 */)
{
	// Returns true if the user authenticated okay, false on error; iLevel is the recursion deptht (bails out if too deep)

	cBlockingTCPLink Link;
	if (!Link.Connect(a_Server.c_str(), 80))
	{
		LOGERROR("cAuthenticator: cannot connect to auth server \"%s\", kicking user \"%s\"", a_Server.c_str(), a_Server.c_str());
		return false;
	}
	
	Link.SendMessage( AString( "GET " + a_Address + " HTTP/1.1\r\n" ).c_str());
	Link.SendMessage( AString( "User-Agent: MCServer\r\n" ).c_str());
	// FIXME: AString( "Host: %s\r\n", a_Server.c_str() ).c_str() doesn't work. Causes segfault.
	Link.SendMessage( AString( "Host: session.minecraft.net\r\n" ).c_str());
	Link.SendMessage( AString( "Accept: */*\r\n" ).c_str());
	Link.SendMessage( AString( "Connection: keep-alive\r\n" ).c_str());
	Link.SendMessage( AString( "\r\n" ).c_str());
	AString DataRecvd;
	Link.ReceiveData(DataRecvd);
	Link.CloseSocket();

	std::stringstream ss(DataRecvd);

	// Parse the data received:
	std::string temp;
	ss >> temp;
	bool bRedirect = false;
	bool bOK = false;
	if ((temp.compare("HTTP/1.1") == 0) || (temp.compare("HTTP/1.0") == 0))
	{
		int code;
		ss >> code;
		if (code == 302)
		{
			// redirect blabla
			LOGINFO("Need to redirect!");
			if (a_Level > MAX_REDIRECTS)
			{
				LOGERROR("cAuthenticator: received too many levels of redirection from auth server \"%s\" for user \"%s\", bailing out and kicking the user", a_Server.c_str(), a_UserName.c_str());
				return false;
			}
			bRedirect = true;
		}
		else if (code == 200)
		{
			LOGINFO("Got 200 OK :D");
			bOK = true;
		}
	}
	else
	{
		LOGERROR("cAuthenticator: cannot parse auth reply from server \"%s\" for user \"%s\", kicking the user.", a_Server.c_str(), a_UserName.c_str());
		return false;
	}

	if( bRedirect )
	{
		AString Location;
		// Search for "Location:"
		bool bFoundLocation = false;
		while( !bFoundLocation && ss.good() )
		{
			char c = 0;
			while( c != '\n' )
			{
				ss.get( c );
			}
			AString Name;
			ss >> Name;
			if (Name.compare("Location:") == 0)
			{
				bFoundLocation = true;
				ss >> Location;
			}
		}
		if (!bFoundLocation)
		{
			LOGERROR("cAuthenticator: received invalid redirection from auth server \"%s\" for user \"%s\", kicking user.", a_Server.c_str(), a_UserName.c_str());
			return false;
		}

		Location = Location.substr(strlen("http://"), std::string::npos); // Strip http://
		std::string Server = Location.substr( 0, Location.find( "/" ) ); // Only leave server address
		Location = Location.substr( Server.length(), std::string::npos);
		return AuthFromAddress(Server, Location, a_UserName, a_Level + 1);
	}

	if (!bOK)
	{
		LOGERROR("cAuthenticator: received an error from auth server \"%s\" for user \"%s\", kicking user.", a_Server.c_str(), a_UserName.c_str());
		return false;
	}

	// Header says OK, so receive the rest.
	// Go past header, double \n means end of headers
	char c = 0;
	while (ss.good())
	{
		while (c != '\n')
		{
			ss.get(c);
		}
		ss.get(c);
		if( c == '\n' || c == '\r' || ss.peek() == '\r' || ss.peek() == '\n' )
			break;
	}
	if (!ss.good())
	{
		LOGERROR("cAuthenticator: error while parsing response body from auth server \"%s\" for user \"%s\", kicking user.", a_Server.c_str(), a_UserName.c_str());
		return false;
	}

	std::string Result;
	ss >> Result;
	LOGINFO("Got result: %s", Result.c_str());
	// if (Result.compare("YES") == 0)
	// TODO: I'm assuming the only response containing 3 letters is "YES", but proper reading of the return code
	//       would be preferred.
	if (Result.compare("3") == 0) // FIXME: Quick and dirty hack to support auth
	{
		LOGINFO("Result was \"YES\", so player is authenticated!");
		return true;
	}
	LOGINFO("Result was \"%s\", so player is NOT authenticated!", Result.c_str());
	return false;
}