2012-06-14 09:06:06 -04:00
# 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 ;
}
2012-06-16 13:02:25 -04:00
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 ( ) ) ;
2012-06-14 09:06:06 -04:00
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 ( ) ) ;
2012-06-16 13:02:25 -04:00
// 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
2012-06-14 09:06:06 -04:00
{
LOGINFO ( " Result was \" YES \" , so player is authenticated! " ) ;
return true ;
}
LOGINFO ( " Result was \" %s \" , so player is NOT authenticated! " , Result . c_str ( ) ) ;
return false ;
}