2014-04-30 20:16:08 +02:00
/*
2016-01-01 00:34:25 +01:00
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
2014-04-30 20:16:08 +02:00
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AuthCodes.h"
2014-10-10 23:19:40 +02:00
#include "BitStream.h"
2014-10-12 20:58:25 +02:00
#include "PacketManager.h"
2014-10-10 23:19:40 +02:00
#include "SessionManager.h"
2014-05-06 23:43:29 +02:00
#include "Database/DatabaseEnv.h"
#include "HmacHash.h"
#include "Log.h"
#include "RealmList.h"
#include "SHA256.h"
2014-04-30 20:16:08 +02:00
#include <map>
2014-05-06 23:43:29 +02:00
2014-07-19 17:03:32 +02:00
Battlenet :: Session :: ModuleHandler const Battlenet :: Session :: ModuleHandlers [ MODULE_COUNT ] =
2014-05-06 23:43:29 +02:00
{
2014-07-19 17:03:32 +02:00
& Battlenet :: Session :: HandlePasswordModule ,
& Battlenet :: Session :: UnhandledModule ,
& Battlenet :: Session :: UnhandledModule ,
& Battlenet :: Session :: HandleSelectGameAccountModule ,
& Battlenet :: Session :: HandleRiskFingerprintModule ,
& Battlenet :: Session :: HandleResumeModule ,
2014-05-06 23:43:29 +02:00
};
2015-04-11 16:25:21 +02:00
void Battlenet :: AccountInfo :: LoadResult ( Field * fields )
{
// ba.id, ba.email, ba.locked, ba.lock_country, ba.last_ip, ba.failed_logins, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab WHERE email = ?
Id = fields [ 0 ]. GetUInt32 ();
Login = fields [ 1 ]. GetString ();
IsLockedToIP = fields [ 2 ]. GetBool ();
LockCountry = fields [ 3 ]. GetString ();
LastIP = fields [ 4 ]. GetString ();
FailedLogins = fields [ 5 ]. GetUInt32 ();
IsBanned = fields [ 6 ]. GetUInt64 () != 0 ;
IsPermanenetlyBanned = fields [ 7 ]. GetUInt64 () != 0 ;
}
void Battlenet :: GameAccountInfo :: LoadResult ( Field * fields )
{
// a.id, a.username, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, aa.gmlevel
Id = fields [ 0 ]. GetUInt32 ();
Name = fields [ 1 ]. GetString ();
IsBanned = fields [ 2 ]. GetUInt64 () != 0 ;
IsPermanenetlyBanned = fields [ 3 ]. GetUInt64 () != 0 ;
SecurityLevel = AccountTypes ( fields [ 4 ]. GetUInt8 ());
std :: size_t hashPos = Name . find ( '#' );
if ( hashPos != std :: string :: npos )
DisplayName = std :: string ( "WoW" ) + Name . substr ( hashPos + 1 );
else
DisplayName = Name ;
}
Battlenet :: Session :: Session ( tcp :: socket && socket ) : Socket ( std :: move ( socket )), _accountInfo ( new AccountInfo ()), _gameAccountInfo ( nullptr ), _locale (),
2015-04-11 18:58:13 +02:00
_os (), _build ( 0 ), _ipCountry (), I (), s (), v (), b (), B (), K (),
2014-12-24 13:48:52 +01:00
_reconnectProof (), _crypt (), _authed ( false ), _subscribedToRealmListUpdates ( false ), _toonOnline ( false )
2014-05-06 23:43:29 +02:00
{
static uint8 const N_Bytes [] =
{
0xAB , 0x24 , 0x43 , 0x63 , 0xA9 , 0xC2 , 0xA6 , 0xC3 , 0x3B , 0x37 , 0xE4 , 0x61 , 0x84 , 0x25 , 0x9F , 0x8B ,
0x3F , 0xCB , 0x8A , 0x85 , 0x27 , 0xFC , 0x3D , 0x87 , 0xBE , 0xA0 , 0x54 , 0xD2 , 0x38 , 0x5D , 0x12 , 0xB7 ,
0x61 , 0x44 , 0x2E , 0x83 , 0xFA , 0xC2 , 0x21 , 0xD9 , 0x10 , 0x9F , 0xC1 , 0x9F , 0xEA , 0x50 , 0xE3 , 0x09 ,
0xA6 , 0xE5 , 0x5E , 0x23 , 0xA7 , 0x77 , 0xEB , 0x00 , 0xC7 , 0xBA , 0xBF , 0xF8 , 0x55 , 0x8A , 0x0E , 0x80 ,
0x2B , 0x14 , 0x1A , 0xA2 , 0xD4 , 0x43 , 0xA9 , 0xD4 , 0xAF , 0xAD , 0xB5 , 0xE1 , 0xF5 , 0xAC , 0xA6 , 0x13 ,
0x1C , 0x69 , 0x78 , 0x64 , 0x0B , 0x7B , 0xAF , 0x9C , 0xC5 , 0x50 , 0x31 , 0x8A , 0x23 , 0x08 , 0x01 , 0xA1 ,
0xF5 , 0xFE , 0x31 , 0x32 , 0x7F , 0xE2 , 0x05 , 0x82 , 0xD6 , 0x0B , 0xED , 0x4D , 0x55 , 0x32 , 0x41 , 0x94 ,
0x29 , 0x6F , 0x55 , 0x7D , 0xE3 , 0x0F , 0x77 , 0x19 , 0xE5 , 0x6C , 0x30 , 0xEB , 0xDE , 0xF6 , 0xA7 , 0x86
};
N . SetBinary ( N_Bytes , sizeof ( N_Bytes ));
g . SetDword ( 2 );
SHA256Hash sha ;
sha . UpdateBigNumbers ( & N , & g , NULL );
sha . Finalize ();
k . SetBinary ( sha . GetDigest (), sha . GetLength ());
}
2014-07-19 17:03:32 +02:00
Battlenet :: Session ::~ Session ()
{
2015-04-11 18:58:13 +02:00
if ( _authed )
sSessionMgr . RemoveSession ( this );
2015-04-18 17:01:02 +02:00
delete _accountInfo ;
2014-07-19 17:03:32 +02:00
}
void Battlenet :: Session :: _SetVSFields ( std :: string const & pstr )
2014-05-06 23:43:29 +02:00
{
2014-07-19 17:03:32 +02:00
s . SetRand ( uint32 ( BufferSizes :: SRP_6_S ) * 8 );
2014-05-06 23:43:29 +02:00
BigNumber p ;
p . SetHexStr ( pstr . c_str ());
SHA256Hash sha ;
sha . UpdateBigNumbers ( & s , & p , NULL );
sha . Finalize ();
BigNumber x ;
x . SetBinary ( sha . GetDigest (), sha . GetLength ());
v = g . ModExp ( x , N );
2014-06-01 13:03:30 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_VS_FIELDS );
stmt -> setString ( 0 , v . AsHexStr ());
stmt -> setString ( 1 , s . AsHexStr ());
2015-04-11 16:25:21 +02:00
stmt -> setString ( 2 , _accountInfo -> Login );
2014-05-06 23:43:29 +02:00
2014-06-01 13:03:30 +02:00
LoginDatabase . Execute ( stmt );
2014-05-06 23:43:29 +02:00
}
2014-10-12 20:58:25 +02:00
void Battlenet :: Session :: LogUnhandledPacket ( PacketHeader const & header )
2014-10-05 16:21:27 +02:00
{
2014-10-12 20:58:25 +02:00
TC_LOG_DEBUG ( "session.packets" , "%s Received unhandled packet %s" , GetClientInfo (). c_str (), sPacketManager . GetClientPacketName ( header ));
2014-10-05 16:21:27 +02:00
}
2014-10-18 17:03:30 +02:00
void Battlenet :: Session :: HandleLogonRequest ( Authentication :: LogonRequest3 const & logonRequest )
2014-04-30 20:16:08 +02:00
{
2015-04-11 18:58:13 +02:00
if ( _queryCallback )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
2015-04-11 18:58:13 +02:00
logonResponse -> SetAuthResult ( AUTH_LOGON_TOO_FAST );
2014-10-10 23:48:01 +02:00
AsyncWrite ( logonResponse );
2015-04-11 18:58:13 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s attempted to log too quick after previous attempt!" , GetClientInfo (). c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
2015-10-06 00:30:47 +02:00
if ( logonRequest . Common . Program != "WoW" )
2014-05-02 02:55:10 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_INVALID_PROGRAM );
AsyncWrite ( logonResponse );
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s attempted to log in with game other than WoW (using %s)!" , GetClientInfo (). c_str (), logonRequest . Common . Program . c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-02 02:55:10 +02:00
}
2015-10-06 00:30:47 +02:00
if ( ! sComponentMgr -> HasPlatform ( logonRequest . Common . Platform ))
2014-05-02 02:55:10 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_INVALID_OS );
AsyncWrite ( logonResponse );
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s attempted to log in from an unsupported platform (using %s)!" , GetClientInfo (). c_str (), logonRequest . Common . Platform . c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-02 02:55:10 +02:00
}
2015-10-06 00:30:47 +02:00
if ( ! sComponentMgr -> HasPlatform ( logonRequest . Common . Locale ))
2014-05-02 02:55:10 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_UNSUPPORTED_LANGUAGE );
AsyncWrite ( logonResponse );
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s attempted to log in with unsupported locale (using %s)!" , GetClientInfo (). c_str (), logonRequest . Common . Locale . c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-02 02:55:10 +02:00
}
2015-10-06 00:30:47 +02:00
for ( Version :: Record const & component : logonRequest . Common . Versions )
2014-04-30 23:02:01 +02:00
{
2014-10-10 23:19:40 +02:00
if ( ! sComponentMgr -> HasComponent ( & component ))
2014-04-30 23:02:01 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
2015-10-06 00:30:47 +02:00
if ( ! sComponentMgr -> HasProgram ( component . ProgramId ))
2014-11-09 19:18:54 +01:00
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( AUTH_INVALID_PROGRAM );
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s is using unsupported component program %s!" , GetClientInfo (). c_str (), component . ProgramId . c_str ());
2014-11-09 19:18:54 +01:00
}
2015-10-06 00:30:47 +02:00
else if ( ! sComponentMgr -> HasPlatform ( component . Component ))
2014-11-09 19:18:54 +01:00
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( AUTH_INVALID_OS );
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s is using unsupported component platform %s!" , GetClientInfo (). c_str (), component . Component . c_str ());
2014-11-09 19:18:54 +01:00
}
2014-05-02 02:55:10 +02:00
else
2014-06-08 15:34:24 +02:00
{
2015-10-06 00:30:47 +02:00
if ( component . ProgramId != "WoW" || AuthHelper :: IsBuildSupportingBattlenet ( component . Version ))
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( AUTH_REGION_BAD_VERSION );
2014-06-08 15:34:24 +02:00
else
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( AUTH_USE_GRUNT_LOGON );
2014-11-09 19:18:54 +01:00
2015-10-06 00:30:47 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s is using unsupported component version %u!" , GetClientInfo (). c_str (), component . Version );
2014-06-08 15:34:24 +02:00
}
2014-04-30 23:02:01 +02:00
2014-10-10 23:48:01 +02:00
AsyncWrite ( logonResponse );
2014-10-05 16:21:27 +02:00
return ;
2014-04-30 23:02:01 +02:00
}
2014-05-06 23:43:29 +02:00
2015-10-06 00:30:47 +02:00
if ( component . Component == "base" )
_build = component . Version ;
2014-04-30 23:02:01 +02:00
}
2015-10-06 00:30:47 +02:00
std :: string login = logonRequest . Account ;
_locale = logonRequest . Common . Locale ;
_os = logonRequest . Common . Platform ;
2014-04-30 20:16:08 +02:00
2015-04-11 16:25:21 +02:00
Utf8ToUpperOnlyLatin ( login );
2015-04-11 18:58:13 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_SEL_BNET_ACCOUNT_INFO );
2015-04-11 16:25:21 +02:00
stmt -> setString ( 0 , login );
2014-05-13 22:02:51 +02:00
2015-04-11 18:58:13 +02:00
_queryCallback = std :: bind ( & Battlenet :: Session :: HandleLogonRequestCallback , this , std :: placeholders :: _1 );
_queryFuture = LoginDatabase . AsyncQuery ( stmt );
}
void Battlenet :: Session :: HandleLogonRequestCallback ( PreparedQueryResult result )
{
2014-05-06 23:43:29 +02:00
if ( ! result )
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_UNKNOWN_ACCOUNT );
AsyncWrite ( logonResponse );
2014-11-09 19:18:54 +01:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] %s is trying to log in from unknown account!" , GetClientInfo (). c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
Field * fields = result -> Fetch ();
2015-04-11 16:25:21 +02:00
_accountInfo -> LoadResult ( fields );
2014-05-06 23:43:29 +02:00
2015-04-11 18:58:13 +02:00
std :: string pStr = fields [ 8 ]. GetString ();
std :: string databaseV = fields [ 9 ]. GetString ();
std :: string databaseS = fields [ 10 ]. GetString ();
_gameAccounts . resize ( result -> GetRowCount ());
uint32 i = 0 ;
do
{
_gameAccounts [ i ++ ]. LoadResult ( result -> Fetch () + 11 );
} while ( result -> NextRow ());
std :: string ip_address = GetRemoteIpAddress (). to_string ();
2014-05-06 23:43:29 +02:00
// If the IP is 'locked', check that the player comes indeed from the correct IP address
2015-04-11 16:25:21 +02:00
if ( _accountInfo -> IsLockedToIP )
2014-05-06 23:43:29 +02:00
{
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] Account '%s' is locked to IP - '%s' is logging in from '%s'" , _accountInfo -> Login . c_str (), _accountInfo -> LastIP . c_str (), ip_address . c_str ());
2014-05-06 23:43:29 +02:00
2015-04-11 16:25:21 +02:00
if ( _accountInfo -> LastIP != ip_address )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_ACCOUNT_LOCKED );
AsyncWrite ( logonResponse );
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
}
else
{
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] Account '%s' is not locked to ip" , _accountInfo -> Login . c_str ());
if ( _accountInfo -> LockCountry . empty () || _accountInfo -> LockCountry == "00" )
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] Account '%s' is not locked to country" , _accountInfo -> Login . c_str ());
2015-04-11 18:58:13 +02:00
else if ( ! _accountInfo -> LockCountry . empty () && ! _ipCountry . empty ())
2014-05-06 23:43:29 +02:00
{
2015-04-11 18:58:13 +02:00
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] Account '%s' is locked to country: '%s' Player country is '%s'" , _accountInfo -> Login . c_str (), _accountInfo -> LockCountry . c_str (), _ipCountry . c_str ());
if ( _ipCountry != _accountInfo -> LockCountry )
2014-05-06 23:43:29 +02:00
{
2015-04-11 18:58:13 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_ACCOUNT_LOCKED );
AsyncWrite ( logonResponse );
return ;
2014-05-06 23:43:29 +02:00
}
}
}
// If the account is banned, reject the logon attempt
2015-04-11 16:25:21 +02:00
if ( _accountInfo -> IsBanned )
2014-05-06 23:43:29 +02:00
{
2015-04-11 16:25:21 +02:00
if ( _accountInfo -> IsPermanenetlyBanned )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( LOGIN_BANNED );
AsyncWrite ( logonResponse );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::LogonRequest] Banned account %s tried to login!" , ip_address . c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
else
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( LOGIN_SUSPENDED );
AsyncWrite ( logonResponse );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::LogonRequest] Temporarily banned account %s tried to login!" , ip_address . c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
}
SHA256Hash sha ;
2015-04-11 16:25:21 +02:00
sha . UpdateData ( _accountInfo -> Login );
2014-05-06 23:43:29 +02:00
sha . Finalize ();
I . SetBinary ( sha . GetDigest (), sha . GetLength ());
2014-10-10 23:19:40 +02:00
ModuleInfo * password = sModuleMgr -> CreateModule ( _os , "Password" );
ModuleInfo * thumbprint = sModuleMgr -> CreateModule ( _os , "Thumbprint" );
2014-05-06 23:43:29 +02:00
2014-07-19 17:03:32 +02:00
if ( databaseV . size () != size_t ( BufferSizes :: SRP_6_V ) * 2 || databaseS . size () != size_t ( BufferSizes :: SRP_6_S ) * 2 )
2014-05-06 23:43:29 +02:00
_SetVSFields ( pStr );
else
{
s . SetHexStr ( databaseS . c_str ());
v . SetHexStr ( databaseV . c_str ());
}
b . SetRand ( 128 * 8 );
B = (( v * k ) + g . ModExp ( b , N )) % N ;
2014-06-03 19:15:12 +02:00
BigNumber unk ;
unk . SetRand ( 128 * 8 );
2014-05-06 23:43:29 +02:00
BitStream passwordData ;
uint8 state = 0 ;
passwordData . WriteBytes ( & state , 1 );
passwordData . WriteBytes ( I . AsByteArray ( 32 ). get (), 32 );
passwordData . WriteBytes ( s . AsByteArray ( 32 ). get (), 32 );
passwordData . WriteBytes ( B . AsByteArray ( 128 ). get (), 128 );
2014-06-03 19:15:12 +02:00
passwordData . WriteBytes ( unk . AsByteArray ( 128 ). get (), 128 );
2014-05-06 23:43:29 +02:00
password -> DataSize = passwordData . GetSize ();
password -> Data = new uint8 [ password -> DataSize ];
memcpy ( password -> Data , passwordData . GetBuffer (), password -> DataSize );
_modulesWaitingForData . push ( MODULE_PASSWORD );
2014-10-10 23:48:01 +02:00
Authentication :: ProofRequest * proofRequest = new Authentication :: ProofRequest ();
proofRequest -> Modules . push_back ( password );
2014-05-06 23:43:29 +02:00
// if has authenticator, send Token module
2014-10-10 23:48:01 +02:00
proofRequest -> Modules . push_back ( thumbprint );
AsyncWrite ( proofRequest );
2014-04-30 20:16:08 +02:00
}
2014-10-10 23:48:01 +02:00
void Battlenet :: Session :: HandleResumeRequest ( Authentication :: ResumeRequest const & resumeRequest )
2014-06-01 13:56:36 +02:00
{
2015-04-11 18:58:13 +02:00
if ( _queryCallback )
{
Authentication :: ResumeResponse * logonResponse = new Authentication :: ResumeResponse ();
logonResponse -> SetAuthResult ( AUTH_LOGON_TOO_FAST );
AsyncWrite ( logonResponse );
TC_LOG_DEBUG ( "session" , "[Battlenet::ResumeRequest] %s attempted to log too quick after previous attempt!" , GetClientInfo (). c_str ());
return ;
}
2015-10-06 00:30:47 +02:00
std :: string login = resumeRequest . Account ;
_locale = resumeRequest . Common . Locale ;
_os = resumeRequest . Common . Platform ;
auto baseComponent = std :: find_if ( resumeRequest . Common . Versions . begin (), resumeRequest . Common . Versions . end (), []( Version :: Record const & c ) { return c . ProgramId == "base" ; });
if ( baseComponent != resumeRequest . Common . Versions . end ())
_build = baseComponent -> Version ;
2014-06-01 13:56:36 +02:00
2015-04-11 16:25:21 +02:00
Utf8ToUpperOnlyLatin ( login );
2014-06-01 13:56:36 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_SEL_BNET_RECONNECT_INFO );
2015-04-11 16:25:21 +02:00
stmt -> setString ( 0 , login );
2014-10-10 23:48:01 +02:00
stmt -> setString ( 1 , resumeRequest . GameAccountName );
2015-04-11 18:58:13 +02:00
_queryCallback = std :: bind ( & Battlenet :: Session :: HandleResumeRequestCallback , this , std :: placeholders :: _1 );
_queryFuture = LoginDatabase . AsyncQuery ( stmt );
}
void Battlenet :: Session :: HandleResumeRequestCallback ( PreparedQueryResult result )
{
2014-06-01 13:56:36 +02:00
if ( ! result )
{
2014-10-10 23:48:01 +02:00
Authentication :: ResumeResponse * resumeResponse = new Authentication :: ResumeResponse ();
resumeResponse -> SetAuthResult ( AUTH_UNKNOWN_ACCOUNT );
AsyncWrite ( resumeResponse );
2014-10-05 16:21:27 +02:00
return ;
2014-06-01 13:56:36 +02:00
}
Field * fields = result -> Fetch ();
2015-04-11 16:25:21 +02:00
_accountInfo -> LoadResult ( fields );
K . SetHexStr ( fields [ 8 ]. GetString (). c_str ());
2014-06-01 13:56:36 +02:00
2015-04-11 16:25:21 +02:00
_gameAccounts . resize ( 1 );
_gameAccountInfo = & _gameAccounts [ 0 ];
_gameAccountInfo -> LoadResult ( fields + 9 );
2014-06-01 13:56:36 +02:00
2014-10-10 23:19:40 +02:00
ModuleInfo * thumbprint = sModuleMgr -> CreateModule ( _os , "Thumbprint" );
ModuleInfo * resume = sModuleMgr -> CreateModule ( _os , "Resume" );
2014-06-01 13:56:36 +02:00
BitStream resumeData ;
uint8 state = 0 ;
_reconnectProof . SetRand ( 16 * 8 );
resumeData . WriteBytes ( & state , 1 );
resumeData . WriteBytes ( _reconnectProof . AsByteArray (). get (), 16 );
resume -> DataSize = resumeData . GetSize ();
resume -> Data = new uint8 [ resume -> DataSize ];
memcpy ( resume -> Data , resumeData . GetBuffer (), resume -> DataSize );
_modulesWaitingForData . push ( MODULE_RESUME );
2014-10-10 23:48:01 +02:00
Authentication :: ProofRequest * proofRequest = new Authentication :: ProofRequest ();
proofRequest -> Modules . push_back ( thumbprint );
proofRequest -> Modules . push_back ( resume );
AsyncWrite ( proofRequest );
2014-06-01 13:56:36 +02:00
}
2014-10-10 23:48:01 +02:00
void Battlenet :: Session :: HandleProofResponse ( Authentication :: ProofResponse const & proofResponse )
2014-04-30 20:16:08 +02:00
{
2015-10-06 00:30:47 +02:00
if ( _modulesWaitingForData . size () < proofResponse . Response . size ())
2014-05-06 23:43:29 +02:00
{
2014-10-04 15:45:33 +02:00
Authentication :: LogonResponse * complete = new Authentication :: LogonResponse ();
2014-07-19 17:03:32 +02:00
complete -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
AsyncWrite ( complete );
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
2014-04-30 20:16:08 +02:00
2014-05-06 23:43:29 +02:00
ServerPacket * response = nullptr ;
2015-10-06 00:30:47 +02:00
for ( size_t i = 0 ; i < proofResponse . Response . size (); ++ i )
2014-05-06 23:43:29 +02:00
{
2015-10-06 00:30:47 +02:00
if ( ! ( this ->* ( ModuleHandlers [ _modulesWaitingForData . front ()]))( proofResponse . Response [ i ], & response ))
2014-05-06 23:43:29 +02:00
break ;
_modulesWaitingForData . pop ();
}
if ( ! response )
{
2014-10-04 15:45:33 +02:00
response = new Authentication :: LogonResponse ();
static_cast < Authentication :: LogonResponse *> ( response ) -> SetAuthResult ( AUTH_INTERNAL_ERROR );
2014-05-06 23:43:29 +02:00
}
2014-07-19 17:03:32 +02:00
AsyncWrite ( response );
2014-04-30 20:16:08 +02:00
}
2014-10-05 16:21:27 +02:00
void Battlenet :: Session :: HandlePing ( Connection :: Ping const & /*ping*/ )
2014-04-30 20:16:08 +02:00
{
2014-10-04 15:45:33 +02:00
AsyncWrite ( new Connection :: Pong ());
2014-05-06 23:43:29 +02:00
}
2014-04-30 20:16:08 +02:00
2014-12-20 00:25:11 +01:00
void Battlenet :: Session :: HandleEnableEncryption ( Connection :: EnableEncryption & enableEncryption )
2014-05-06 23:43:29 +02:00
{
_crypt . Init ( & K );
2014-12-20 00:25:11 +01:00
_crypt . DecryptRecv ( enableEncryption . GetRemainingData (), enableEncryption . GetRemainingSize ());
2014-04-30 20:16:08 +02:00
}
2014-10-05 16:21:27 +02:00
void Battlenet :: Session :: HandleLogoutRequest ( Connection :: LogoutRequest const & /*logoutRequest*/ )
2014-06-01 13:56:36 +02:00
{
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_SESSION_KEY );
stmt -> setString ( 0 , "" );
2014-06-08 17:58:28 +02:00
stmt -> setBool ( 1 , false );
2015-04-11 16:25:21 +02:00
stmt -> setUInt32 ( 2 , _accountInfo -> Id );
2014-06-01 13:56:36 +02:00
LoginDatabase . Execute ( stmt );
}
2014-10-11 22:45:01 +02:00
void Battlenet :: Session :: HandleConnectionClosing ( Connection :: ConnectionClosing const & /*connectionClosing*/ )
2014-05-06 23:43:29 +02:00
{
2014-10-11 22:45:01 +02:00
}
2014-05-06 23:43:29 +02:00
2014-10-05 16:21:27 +02:00
void Battlenet :: Session :: HandleListSubscribeRequest ( WoWRealm :: ListSubscribeRequest const & /*listSubscribeRequest*/ )
2014-05-06 23:43:29 +02:00
{
2015-04-11 18:58:13 +02:00
if ( _subscribedToRealmListUpdates || _queryCallback )
return ;
2014-05-06 23:43:29 +02:00
2014-05-13 22:02:51 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_SEL_BNET_CHARACTER_COUNTS );
2015-04-11 16:25:21 +02:00
stmt -> setUInt32 ( 0 , _gameAccountInfo -> Id );
2014-05-13 22:02:51 +02:00
2015-04-11 18:58:13 +02:00
_queryCallback = std :: bind ( & Battlenet :: Session :: HandleListSubscribeRequestCallback , this , std :: placeholders :: _1 );
_queryFuture = LoginDatabase . AsyncQuery ( stmt );
}
void Battlenet :: Session :: HandleListSubscribeRequestCallback ( PreparedQueryResult result )
{
WoWRealm :: ListSubscribeResponse * listSubscribeResponse = new WoWRealm :: ListSubscribeResponse ();
if ( result )
2014-05-13 22:02:51 +02:00
{
do
{
2015-04-11 18:58:13 +02:00
Field * fields = result -> Fetch ();
2015-10-06 00:30:47 +02:00
listSubscribeResponse -> ToonCounts . emplace_back ( PrintableRealmHandle ( fields [ 2 ]. GetUInt8 (), fields [ 3 ]. GetUInt8 (), fields [ 1 ]. GetUInt32 ()), uint16 ( fields [ 0 ]. GetUInt8 ()));
2015-04-11 18:58:13 +02:00
} while ( result -> NextRow ());
2014-05-13 22:02:51 +02:00
}
2015-10-06 00:30:47 +02:00
AsyncWrite ( listSubscribeResponse );
2014-10-11 15:13:30 +02:00
for ( RealmList :: RealmMap :: value_type const & i : sRealmList -> GetRealms ())
2015-10-06 00:30:47 +02:00
AsyncWrite ( BuildListUpdate ( & i . second ));
2014-05-06 23:43:29 +02:00
2015-10-06 00:30:47 +02:00
AsyncWrite ( new WoWRealm :: ListComplete ());
2014-05-06 23:43:29 +02:00
2014-10-11 15:13:30 +02:00
_subscribedToRealmListUpdates = true ;
2014-05-06 23:43:29 +02:00
}
2014-10-11 15:13:30 +02:00
void Battlenet :: Session :: HandleListUnsubscribe ( WoWRealm :: ListUnsubscribe const & /*listUnsubscribe*/ )
2014-04-30 20:16:08 +02:00
{
2014-10-11 15:13:30 +02:00
_subscribedToRealmListUpdates = false ;
2014-05-06 23:43:29 +02:00
}
2014-10-10 23:48:01 +02:00
void Battlenet :: Session :: HandleJoinRequestV2 ( WoWRealm :: JoinRequestV2 const & joinRequest )
2014-04-30 20:16:08 +02:00
{
2014-10-10 23:48:01 +02:00
WoWRealm :: JoinResponseV2 * joinResponse = new WoWRealm :: JoinResponseV2 ();
2015-10-06 00:30:47 +02:00
Realm const * realm = sRealmList -> GetRealm ( joinRequest . Id );
if ( ! realm || realm -> Flags & ( REALM_FLAG_VERSION_MISMATCH | REALM_FLAG_OFFLINE ) || realm -> Build != _build )
2014-05-06 23:43:29 +02:00
{
2015-10-06 00:30:47 +02:00
joinResponse -> Type = WoWRealm :: JoinResponseV2 :: FAILURE ;
2014-10-10 23:48:01 +02:00
AsyncWrite ( joinResponse );
2014-10-05 16:21:27 +02:00
return ;
2014-05-06 23:43:29 +02:00
}
2015-10-06 00:30:47 +02:00
joinResponse -> Success . ServerSalt = rand32 ();
2014-05-06 23:43:29 +02:00
uint8 sessionKey [ 40 ];
2014-06-03 18:15:35 +02:00
HmacSha1 hmac ( K . GetNumBytes (), K . AsByteArray (). get ());
2014-05-06 23:43:29 +02:00
hmac . UpdateData (( uint8 * ) "WoW \0 " , 4 );
2015-10-06 00:30:47 +02:00
hmac . UpdateData (( uint8 * ) & joinRequest . ClientSalt , 4 );
hmac . UpdateData (( uint8 * ) & joinResponse -> Success . ServerSalt , 4 );
2014-05-06 23:43:29 +02:00
hmac . Finalize ();
memcpy ( sessionKey , hmac . GetDigest (), hmac . GetLength ());
2014-06-03 18:15:35 +02:00
HmacSha1 hmac2 ( K . GetNumBytes (), K . AsByteArray (). get ());
2014-05-06 23:43:29 +02:00
hmac2 . UpdateData (( uint8 * ) "WoW \0 " , 4 );
2015-10-06 00:30:47 +02:00
hmac2 . UpdateData (( uint8 * ) & joinResponse -> Success . ServerSalt , 4 );
hmac2 . UpdateData (( uint8 * ) & joinRequest . ClientSalt , 4 );
2014-05-06 23:43:29 +02:00
hmac2 . Finalize ();
memcpy ( sessionKey + hmac . GetLength (), hmac2 . GetDigest (), hmac2 . GetLength ());
LoginDatabase . DirectPExecute ( "UPDATE account SET sessionkey = '%s', last_ip = '%s', last_login = NOW(), locale = %u, failed_logins = 0, os = '%s' WHERE id = %u" ,
2015-04-11 16:25:21 +02:00
ByteArrayToHexStr ( sessionKey , 40 , true ). c_str (), GetRemoteIpAddress (). to_string (). c_str (), GetLocaleByName ( _locale ), _os . c_str (), _gameAccountInfo -> Id );
2014-05-06 23:43:29 +02:00
2015-10-06 00:30:47 +02:00
joinResponse -> Success . IPv4 . push_back ( realm -> GetAddressForClient ( GetRemoteIpAddress ()));
2014-05-06 23:43:29 +02:00
2014-10-10 23:48:01 +02:00
AsyncWrite ( joinResponse );
2014-04-30 20:16:08 +02:00
}
2014-10-13 17:22:24 +02:00
void Battlenet :: Session :: HandleGetStreamItemsRequest ( Cache :: GetStreamItemsRequest const & getStreamItemsRequest )
{
2015-10-06 00:30:47 +02:00
if ( getStreamItemsRequest . Stream . Type != Cache :: GetStreamItemsRequest :: StreamId :: DESCRIPTION )
return ;
if ( ModuleInfo * module = sModuleMgr -> CreateModule ( getStreamItemsRequest . Locale , getStreamItemsRequest . Stream . Description . ItemName ))
2014-10-13 17:22:24 +02:00
{
Cache :: GetStreamItemsResponse * getStreamItemsResponse = new Cache :: GetStreamItemsResponse ();
2015-10-06 00:30:47 +02:00
getStreamItemsResponse -> Token = getStreamItemsRequest . Token ;
getStreamItemsResponse -> Items . push_back ( module );
2014-10-13 17:22:24 +02:00
AsyncWrite ( getStreamItemsResponse );
}
}
2014-10-12 20:58:25 +02:00
inline std :: string PacketToStringHelper ( Battlenet :: ClientPacket const * packet )
{
if ( sLog -> ShouldLog ( "session.packets" , LOG_LEVEL_TRACE ))
return packet -> ToString ();
return sPacketManager . GetClientPacketName ( packet -> GetHeader ());
}
inline std :: string PacketToStringHelper ( Battlenet :: ServerPacket const * packet )
{
if ( sLog -> ShouldLog ( "session.packets" , LOG_LEVEL_TRACE ))
return packet -> ToString ();
return sPacketManager . GetServerPacketName ( packet -> GetHeader ());
2014-04-30 20:16:08 +02:00
}
2014-09-12 22:09:07 +02:00
void Battlenet :: Session :: ReadHandler ()
2014-04-30 20:16:08 +02:00
{
2014-10-05 16:21:27 +02:00
BitStream stream ( std :: move ( GetReadBuffer ()));
_crypt . DecryptRecv ( stream . GetBuffer (), stream . GetSize ());
2014-06-01 13:56:36 +02:00
2014-10-05 16:21:27 +02:00
while ( ! stream . IsRead ())
2014-07-27 21:50:01 +02:00
{
try
2014-07-19 17:03:32 +02:00
{
2014-07-27 21:50:01 +02:00
PacketHeader header ;
2015-10-06 00:30:47 +02:00
header . Command = stream . Read < uint32 > ( 6 );
2014-10-05 16:21:27 +02:00
if ( stream . Read < bool > ( 1 ))
header . Channel = stream . Read < int32 > ( 4 );
2014-07-19 17:03:32 +02:00
2015-10-06 00:30:47 +02:00
if ( header . Channel != AUTHENTICATION && ( header . Channel != CONNECTION || header . Command != Connection :: CMSG_PING ) && ! _authed )
2014-07-27 21:50:01 +02:00
{
2014-10-12 15:34:01 +02:00
TC_LOG_DEBUG ( "session.packets" , "%s Received not allowed %s. Client has not authed yet." , GetClientInfo (). c_str (), header . ToString (). c_str ());
2014-07-27 21:50:01 +02:00
CloseSocket ();
return ;
}
2014-07-19 17:03:32 +02:00
2014-10-12 20:58:25 +02:00
if ( ClientPacket * packet = sPacketManager . CreateClientPacket ( header , stream ))
2014-07-27 21:50:01 +02:00
{
2014-10-12 20:58:25 +02:00
if ( sPacketManager . IsHandled ( header ))
TC_LOG_DEBUG ( "session.packets" , "%s Received %s" , GetClientInfo (). c_str (), PacketToStringHelper ( packet ). c_str ());
2014-10-11 22:45:01 +02:00
2014-10-12 20:58:25 +02:00
packet -> CallHandler ( this );
2014-10-05 16:21:27 +02:00
delete packet ;
2014-04-30 20:16:08 +02:00
}
2014-10-12 20:58:25 +02:00
else if ( sPacketManager . GetClientPacketName ( header ))
{
LogUnhandledPacket ( header );
break ;
2014-04-30 20:16:08 +02:00
}
2014-07-27 21:50:01 +02:00
else
2014-04-30 20:16:08 +02:00
{
2014-10-12 15:34:01 +02:00
TC_LOG_DEBUG ( "session.packets" , "%s Received unknown %s" , GetClientInfo (). c_str (), header . ToString (). c_str ());
2014-07-27 21:50:01 +02:00
break ;
2014-04-30 20:16:08 +02:00
}
2014-07-27 21:50:01 +02:00
2014-10-05 16:21:27 +02:00
stream . AlignToNextByte ();
2014-07-27 21:50:01 +02:00
}
catch ( BitStreamPositionException const & e )
{
2014-10-12 15:34:01 +02:00
TC_LOG_ERROR ( "session.packets" , "%s Exception thrown during packet processing %s" , GetClientInfo (). c_str (), e . what ());
2014-07-27 21:50:01 +02:00
CloseSocket ();
return ;
2014-05-06 23:43:29 +02:00
}
2014-07-27 21:50:01 +02:00
}
2014-04-30 20:16:08 +02:00
2014-09-12 22:09:07 +02:00
GetReadBuffer (). Resize ( size_t ( BufferSizes :: Read ));
AsyncRead ();
2014-04-30 20:16:08 +02:00
}
2014-07-19 17:03:32 +02:00
void Battlenet :: Session :: Start ()
2014-04-30 20:16:08 +02:00
{
2015-04-11 18:58:13 +02:00
std :: string ip_address = GetRemoteIpAddress (). to_string ();
TC_LOG_TRACE ( "session" , "Accepted connection from %s" , ip_address . c_str ());
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_SEL_IP_INFO );
stmt -> setString ( 0 , ip_address );
stmt -> setUInt32 ( 1 , inet_addr ( ip_address . c_str ()));
_queryCallback = std :: bind ( & Battlenet :: Session :: CheckIpCallback , this , std :: placeholders :: _1 );
_queryFuture = LoginDatabase . AsyncQuery ( stmt );
}
void Battlenet :: Session :: CheckIpCallback ( PreparedQueryResult result )
{
if ( result )
{
bool banned = false ;
do
{
Field * fields = result -> Fetch ();
if ( fields [ 0 ]. GetUInt64 () != 0 )
banned = true ;
if ( ! fields [ 1 ]. GetString (). empty ())
_ipCountry = fields [ 1 ]. GetString ();
} while ( result -> NextRow ());
if ( banned )
{
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_INTERNAL_ERROR );
AsyncWrite ( logonResponse );
TC_LOG_DEBUG ( "session" , "[Battlenet::LogonRequest] Banned ip '%s:%d' tries to login!" , GetRemoteIpAddress (). to_string (). c_str (), GetRemotePort ());
return ;
}
}
2014-09-12 22:09:07 +02:00
AsyncRead ();
2014-04-30 20:16:08 +02:00
}
2014-04-30 23:02:01 +02:00
2015-04-11 18:58:13 +02:00
bool Battlenet :: Session :: Update ()
{
if ( ! BattlenetSocket :: Update ())
return false ;
if ( _queryFuture . valid () && _queryFuture . wait_for ( std :: chrono :: seconds ( 0 )) == std :: future_status :: ready )
{
2015-06-20 00:59:31 +02:00
auto callback = std :: move ( _queryCallback );
2015-10-24 15:39:32 +02:00
_queryCallback = nullptr ;
2015-06-20 00:59:31 +02:00
callback ( _queryFuture . get ());
2015-04-11 18:58:13 +02:00
}
return true ;
}
2014-07-19 17:03:32 +02:00
void Battlenet :: Session :: AsyncWrite ( ServerPacket * packet )
2014-04-30 23:02:01 +02:00
{
2014-08-12 00:44:26 +02:00
if ( ! IsOpen ())
{
delete packet ;
return ;
}
2014-10-12 20:58:25 +02:00
TC_LOG_DEBUG ( "session.packets" , "%s Sending %s" , GetClientInfo (). c_str (), PacketToStringHelper ( packet ). c_str ());
2014-07-19 17:03:32 +02:00
packet -> Write ();
2014-05-10 12:50:27 +02:00
2014-09-12 22:09:07 +02:00
MessageBuffer buffer ;
buffer . Write ( packet -> GetData (), packet -> GetSize ());
delete packet ;
2014-07-19 17:03:32 +02:00
2014-09-12 22:09:07 +02:00
std :: unique_lock < std :: mutex > guard ( _writeLock );
2014-07-19 17:03:32 +02:00
2014-09-12 22:09:07 +02:00
_crypt . EncryptSend ( buffer . GetReadPointer (), buffer . GetActiveSize ());
2014-07-27 21:50:01 +02:00
2014-09-12 22:09:07 +02:00
QueuePacket ( std :: move ( buffer ), guard );
2014-04-30 23:02:01 +02:00
}
2014-05-06 23:43:29 +02:00
inline void ReplaceResponse ( Battlenet :: ServerPacket ** oldResponse , Battlenet :: ServerPacket * newResponse )
{
if ( * oldResponse )
delete * oldResponse ;
* oldResponse = newResponse ;
}
2014-07-19 17:03:32 +02:00
bool Battlenet :: Session :: HandlePasswordModule ( BitStream * dataStream , ServerPacket ** response )
2014-05-06 23:43:29 +02:00
{
if ( dataStream -> GetSize () != 1 + 128 + 32 + 128 )
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , logonResponse );
2014-05-06 23:43:29 +02:00
return false ;
}
if ( dataStream -> Read < uint8 > ( 8 ) != 2 ) // State
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , logonResponse );
2014-05-06 23:43:29 +02:00
return false ;
}
BigNumber A , clientM1 , clientChallenge ;
A . SetBinary ( dataStream -> ReadBytes ( 128 ). get (), 128 );
clientM1 . SetBinary ( dataStream -> ReadBytes ( 32 ). get (), 32 );
clientChallenge . SetBinary ( dataStream -> ReadBytes ( 128 ). get (), 128 );
2014-11-06 18:49:19 +01:00
if ( A . IsZero ())
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , logonResponse );
2014-05-06 23:43:29 +02:00
return false ;
}
SHA256Hash sha ;
sha . UpdateBigNumbers ( & A , & B , NULL );
sha . Finalize ();
BigNumber u ;
u . SetBinary ( sha . GetDigest (), sha . GetLength ());
BigNumber S = (( A * v . ModExp ( u , N )) % N ). ModExp ( b , N );
uint8 S_bytes [ 128 ];
memcpy ( S_bytes , S . AsByteArray ( 128 ). get (), 128 );
uint8 part1 [ 64 ];
uint8 part2 [ 64 ];
for ( int i = 0 ; i < 64 ; ++ i )
{
part1 [ i ] = S_bytes [ i * 2 ];
part2 [ i ] = S_bytes [ i * 2 + 1 ];
}
SHA256Hash part1sha , part2sha ;
part1sha . UpdateData ( part1 , 64 );
part1sha . Finalize ();
part2sha . UpdateData ( part2 , 64 );
part2sha . Finalize ();
uint8 sessionKey [ SHA256_DIGEST_LENGTH * 2 ];
for ( int i = 0 ; i < SHA256_DIGEST_LENGTH ; ++ i )
{
sessionKey [ i * 2 ] = part1sha . GetDigest ()[ i ];
sessionKey [ i * 2 + 1 ] = part2sha . GetDigest ()[ i ];
}
K . SetBinary ( sessionKey , SHA256_DIGEST_LENGTH * 2 );
BigNumber M1 ;
uint8 hash [ SHA256_DIGEST_LENGTH ];
sha . Initialize ();
sha . UpdateBigNumbers ( & N , NULL );
sha . Finalize ();
memcpy ( hash , sha . GetDigest (), sha . GetLength ());
sha . Initialize ();
sha . UpdateBigNumbers ( & g , NULL );
sha . Finalize ();
for ( int i = 0 ; i < sha . GetLength (); ++ i )
hash [ i ] ^= sha . GetDigest ()[ i ];
SHA256Hash shaI ;
shaI . UpdateData ( ByteArrayToHexStr ( I . AsByteArray (). get (), 32 ));
shaI . Finalize ();
// Concat all variables for M1 hash
sha . Initialize ();
sha . UpdateData ( hash , SHA256_DIGEST_LENGTH );
sha . UpdateData ( shaI . GetDigest (), shaI . GetLength ());
sha . UpdateBigNumbers ( & s , & A , & B , & K , NULL );
sha . Finalize ();
M1 . SetBinary ( sha . GetDigest (), sha . GetLength ());
if ( memcmp ( M1 . AsByteArray (). get (), clientM1 . AsByteArray (). get (), 32 ))
{
2014-10-04 15:45:33 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_FAILED_LOGINS );
2015-04-11 16:25:21 +02:00
stmt -> setString ( 0 , _accountInfo -> Login );
2014-10-04 15:45:33 +02:00
LoginDatabase . Execute ( stmt );
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_UNKNOWN_ACCOUNT );
ReplaceResponse ( response , logonResponse );
2014-11-09 19:18:54 +01:00
TC_LOG_DEBUG ( "session" , "[Battlenet::Password] %s attempted to log in with invalid password!" , GetClientInfo (). c_str ());
2014-05-06 23:43:29 +02:00
return false ;
}
2015-04-11 16:25:21 +02:00
if ( _gameAccounts . empty ())
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( LOGIN_NO_GAME_ACCOUNT );
ReplaceResponse ( response , logonResponse );
2014-11-09 19:18:54 +01:00
TC_LOG_DEBUG ( "session" , "[Battlenet::Password] %s does not have any linked game accounts!" , GetClientInfo (). c_str ());
2014-05-06 23:43:29 +02:00
return false ;
}
BigNumber M ;
sha . Initialize ();
sha . UpdateBigNumbers ( & A , & M1 , & K , NULL );
sha . Finalize ();
M . SetBinary ( sha . GetDigest (), sha . GetLength ());
2014-06-03 19:15:12 +02:00
BigNumber serverProof ;
serverProof . SetRand ( 128 * 8 ); // just send garbage, server signature check is patched out in client
2014-05-06 23:43:29 +02:00
BitStream stream ;
2014-10-10 23:19:40 +02:00
ModuleInfo * password = sModuleMgr -> CreateModule ( _os , "Password" );
2014-05-06 23:43:29 +02:00
uint8 state = 3 ;
stream . WriteBytes ( & state , 1 );
stream . WriteBytes ( M . AsByteArray ( 32 ). get (), 32 );
2014-06-03 19:15:12 +02:00
stream . WriteBytes ( serverProof . AsByteArray ( 128 ). get (), 128 );
2014-05-06 23:43:29 +02:00
password -> DataSize = stream . GetSize ();
password -> Data = new uint8 [ password -> DataSize ];
memcpy ( password -> Data , stream . GetBuffer (), password -> DataSize );
2014-10-10 23:48:01 +02:00
Authentication :: ProofRequest * proofRequest = new Authentication :: ProofRequest ();
proofRequest -> Modules . push_back ( password );
2015-04-11 16:25:21 +02:00
if ( _gameAccounts . size () > 1 )
2014-05-06 23:43:29 +02:00
{
BitStream accounts ;
state = 0 ;
accounts . WriteBytes ( & state , 1 );
2015-04-11 16:25:21 +02:00
accounts . Write ( _gameAccounts . size (), 8 );
for ( GameAccountInfo const & gameAccount : _gameAccounts )
2014-05-06 23:43:29 +02:00
{
accounts . Write ( 2 , 8 );
2015-04-11 16:25:21 +02:00
accounts . WriteString ( gameAccount . DisplayName , 8 );
}
2014-05-06 23:43:29 +02:00
2014-10-10 23:19:40 +02:00
ModuleInfo * selectGameAccount = sModuleMgr -> CreateModule ( _os , "SelectGameAccount" );
2014-05-06 23:43:29 +02:00
selectGameAccount -> DataSize = accounts . GetSize ();
selectGameAccount -> Data = new uint8 [ selectGameAccount -> DataSize ];
memcpy ( selectGameAccount -> Data , accounts . GetBuffer (), selectGameAccount -> DataSize );
2014-10-10 23:48:01 +02:00
proofRequest -> Modules . push_back ( selectGameAccount );
2014-05-06 23:43:29 +02:00
_modulesWaitingForData . push ( MODULE_SELECT_GAME_ACCOUNT );
}
else
{
2015-04-11 16:25:21 +02:00
_gameAccountInfo = & _gameAccounts [ 0 ];
if ( _gameAccountInfo -> IsBanned )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
delete proofRequest ;
2014-05-06 23:43:29 +02:00
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
2015-04-11 16:25:21 +02:00
if ( _gameAccountInfo -> IsPermanenetlyBanned )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( LOGIN_BANNED );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::Password] Banned account %s tried to login!" , GetRemoteIpAddress (). to_string (). c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-05-06 23:43:29 +02:00
}
else
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( LOGIN_SUSPENDED );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::Password] Temporarily banned account %s tried to login!" , GetRemoteIpAddress (). to_string (). c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-05-06 23:43:29 +02:00
}
2014-10-10 23:48:01 +02:00
ReplaceResponse ( response , logonResponse );
2014-06-01 02:39:55 +02:00
return false ;
2014-05-06 23:43:29 +02:00
}
2014-10-10 23:48:01 +02:00
proofRequest -> Modules . push_back ( sModuleMgr -> CreateModule ( _os , "RiskFingerprint" ));
2014-05-06 23:43:29 +02:00
_modulesWaitingForData . push ( MODULE_RISK_FINGERPRINT );
}
2014-10-10 23:48:01 +02:00
ReplaceResponse ( response , proofRequest );
2014-05-06 23:43:29 +02:00
return true ;
}
2014-07-19 17:03:32 +02:00
bool Battlenet :: Session :: HandleSelectGameAccountModule ( BitStream * dataStream , ServerPacket ** response )
2014-05-06 23:43:29 +02:00
{
if ( dataStream -> Read < uint8 > ( 8 ) != 1 )
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , logonResponse );
2014-05-06 23:43:29 +02:00
return false ;
}
dataStream -> Read < uint8 > ( 8 );
std :: string account = dataStream -> ReadString ( 8 );
2014-08-22 20:09:18 +02:00
if ( account . empty ())
2014-06-08 14:54:37 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( LOGIN_NO_GAME_ACCOUNT );
ReplaceResponse ( response , logonResponse );
2014-06-08 14:54:37 +02:00
return false ;
}
2015-04-11 16:25:21 +02:00
for ( std :: size_t i = 0 ; i < _gameAccounts . size (); ++ i )
2014-08-22 20:09:18 +02:00
{
2015-04-11 16:25:21 +02:00
if ( _gameAccounts [ i ]. DisplayName == account )
{
_gameAccountInfo = & _gameAccounts [ i ];
break ;
}
2014-08-22 20:09:18 +02:00
}
2014-05-06 23:43:29 +02:00
2015-04-11 16:25:21 +02:00
if ( ! _gameAccountInfo )
2014-05-06 23:43:29 +02:00
{
2014-10-04 15:45:33 +02:00
Authentication :: LogonResponse * complete = new Authentication :: LogonResponse ();
2014-05-06 23:43:29 +02:00
complete -> SetAuthResult ( LOGIN_NO_GAME_ACCOUNT );
ReplaceResponse ( response , complete );
2014-11-09 19:18:54 +01:00
TC_LOG_DEBUG ( "session" , "[Battlenet::SelectGameAccount] %s attempted to log in with invalid game account name %s!" , GetClientInfo (). c_str (), account . c_str ());
2014-05-06 23:43:29 +02:00
return false ;
}
2015-04-11 16:25:21 +02:00
if ( _gameAccountInfo -> IsBanned )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
2015-04-11 16:25:21 +02:00
if ( _gameAccountInfo -> IsPermanenetlyBanned )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( LOGIN_BANNED );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::SelectGameAccount] Banned account %s tried to login!" , GetRemoteIpAddress (). to_string (). c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-05-06 23:43:29 +02:00
}
else
{
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( LOGIN_SUSPENDED );
2015-04-11 16:25:21 +02:00
TC_LOG_DEBUG ( "session" , "'%s:%d' [Battlenet::SelectGameAccount] Temporarily banned account %s tried to login!" , GetRemoteIpAddress (). to_string (). c_str (), GetRemotePort (), _accountInfo -> Login . c_str ());
2014-05-06 23:43:29 +02:00
}
2014-10-10 23:48:01 +02:00
ReplaceResponse ( response , logonResponse );
2014-06-01 02:39:55 +02:00
return false ;
2014-05-06 23:43:29 +02:00
}
2014-10-10 23:48:01 +02:00
Authentication :: ProofRequest * proofRequest = new Authentication :: ProofRequest ();
proofRequest -> Modules . push_back ( sModuleMgr -> CreateModule ( _os , "RiskFingerprint" ));
ReplaceResponse ( response , proofRequest );
2014-05-06 23:43:29 +02:00
_modulesWaitingForData . push ( MODULE_RISK_FINGERPRINT );
return true ;
}
2014-07-19 17:03:32 +02:00
bool Battlenet :: Session :: HandleRiskFingerprintModule ( BitStream * dataStream , ServerPacket ** response )
2014-05-06 23:43:29 +02:00
{
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
2015-04-11 16:25:21 +02:00
if ( dataStream -> Read < uint8 > ( 8 ) == 1 && _accountInfo && _gameAccountInfo )
2014-05-06 23:43:29 +02:00
{
2015-10-06 00:30:47 +02:00
logonResponse -> Result . Success . AccountId = _accountInfo -> Id ;
logonResponse -> Result . Success . GameAccountName = _gameAccountInfo -> Name ;
logonResponse -> Result . Success . GameAccountFlags = GAMEACCOUNT_FLAG_PROPASS ;
logonResponse -> Result . Success . LogonFailures = _accountInfo -> FailedLogins ;
2014-06-01 13:56:36 +02:00
SQLTransaction trans = LoginDatabase . BeginTransaction ();
2014-05-06 23:43:29 +02:00
2015-04-11 16:25:21 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_LAST_LOGIN_INFO );
2014-07-27 21:50:01 +02:00
stmt -> setString ( 0 , GetRemoteIpAddress (). to_string ());
2014-05-13 22:02:51 +02:00
stmt -> setUInt8 ( 1 , GetLocaleByName ( _locale ));
stmt -> setString ( 2 , _os );
2015-04-11 16:25:21 +02:00
stmt -> setUInt32 ( 3 , _accountInfo -> Id );
2014-06-01 13:56:36 +02:00
trans -> Append ( stmt );
stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_SESSION_KEY );
stmt -> setString ( 0 , K . AsHexStr ());
2014-06-08 17:58:28 +02:00
stmt -> setBool ( 1 , true );
2015-04-11 16:25:21 +02:00
stmt -> setUInt32 ( 2 , _accountInfo -> Id );
2014-06-01 13:56:36 +02:00
trans -> Append ( stmt );
LoginDatabase . CommitTransaction ( trans );
_authed = true ;
2014-10-10 23:19:40 +02:00
sSessionMgr . AddSession ( this );
2014-05-06 23:43:29 +02:00
}
else
2014-10-10 23:48:01 +02:00
logonResponse -> SetAuthResult ( AUTH_BAD_VERSION_HASH );
2014-05-06 23:43:29 +02:00
2014-10-10 23:48:01 +02:00
ReplaceResponse ( response , logonResponse );
2014-06-01 13:03:30 +02:00
return true ;
2014-05-06 23:43:29 +02:00
}
2014-07-19 17:03:32 +02:00
bool Battlenet :: Session :: HandleResumeModule ( BitStream * dataStream , ServerPacket ** response )
2014-06-01 13:56:36 +02:00
{
if ( dataStream -> Read < uint8 > ( 8 ) != 1 )
{
2014-10-10 23:48:01 +02:00
Authentication :: ResumeResponse * resumeResponse = new Authentication :: ResumeResponse ();
resumeResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , resumeResponse );
2014-06-01 13:56:36 +02:00
return false ;
}
static uint8 const ResumeClient = 0 ;
static uint8 const ResumeServer = 1 ;
2014-07-19 17:03:32 +02:00
std :: unique_ptr < uint8 [] > clientChallenge = dataStream -> ReadBytes ( 16 );
std :: unique_ptr < uint8 [] > clientProof = dataStream -> ReadBytes ( 32 );
2014-08-22 20:09:18 +02:00
std :: unique_ptr < uint8 [] > serverChallenge = _reconnectProof . AsByteArray ( 16 );
std :: unique_ptr < uint8 [] > sessionKey = K . AsByteArray ( 64 );
2014-06-01 13:56:36 +02:00
2014-06-03 18:15:35 +02:00
HmacSha256 clientPart ( 64 , sessionKey . get ());
2014-06-01 13:56:36 +02:00
clientPart . UpdateData ( & ResumeClient , 1 );
clientPart . UpdateData ( clientChallenge . get (), 16 );
clientPart . UpdateData ( serverChallenge . get (), 16 );
clientPart . Finalize ();
2014-06-03 18:15:35 +02:00
HmacSha256 serverPart ( 64 , sessionKey . get ());
2014-06-01 13:56:36 +02:00
serverPart . UpdateData ( & ResumeServer , 1 );
serverPart . UpdateData ( serverChallenge . get (), 16 );
serverPart . UpdateData ( clientChallenge . get (), 16 );
serverPart . Finalize ();
uint8 newSessionKey [ 64 ];
memcpy ( & newSessionKey [ 0 ], clientPart . GetDigest (), clientPart . GetLength ());
memcpy ( & newSessionKey [ 32 ], serverPart . GetDigest (), serverPart . GetLength ());
K . SetBinary ( newSessionKey , 64 );
2014-06-03 18:15:35 +02:00
HmacSha256 proof ( 64 , newSessionKey );
2014-06-01 13:56:36 +02:00
proof . UpdateData ( & ResumeClient , 1 );
proof . UpdateData ( clientChallenge . get (), 16 );
proof . UpdateData ( serverChallenge . get (), 16 );
proof . Finalize ();
if ( memcmp ( proof . GetDigest (), clientProof . get (), serverPart . GetLength ()))
{
2014-10-04 15:45:33 +02:00
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_FAILED_LOGINS );
2015-04-11 16:25:21 +02:00
stmt -> setString ( 0 , _accountInfo -> Login );
2014-10-04 15:45:33 +02:00
LoginDatabase . Execute ( stmt );
2014-11-09 19:18:54 +01:00
TC_LOG_DEBUG ( "session" , "[Battlenet::Resume] %s attempted to reconnect with invalid password!" , GetClientInfo (). c_str ());
2014-10-10 23:48:01 +02:00
Authentication :: ResumeResponse * resumeResponse = new Authentication :: ResumeResponse ();
resumeResponse -> SetAuthResult ( AUTH_UNKNOWN_ACCOUNT );
ReplaceResponse ( response , resumeResponse );
2014-06-01 13:56:36 +02:00
return false ;
}
PreparedStatement * stmt = LoginDatabase . GetPreparedStatement ( LOGIN_UPD_BNET_SESSION_KEY );
stmt -> setString ( 0 , K . AsHexStr ());
2014-06-08 17:58:28 +02:00
stmt -> setBool ( 1 , true );
2015-04-11 16:25:21 +02:00
stmt -> setUInt32 ( 2 , _accountInfo -> Id );
2014-06-01 13:56:36 +02:00
LoginDatabase . Execute ( stmt );
2014-06-03 18:15:35 +02:00
HmacSha256 serverProof ( 64 , newSessionKey );
2014-06-01 13:56:36 +02:00
serverProof . UpdateData ( & ResumeServer , 1 );
serverProof . UpdateData ( serverChallenge . get (), 16 );
serverProof . UpdateData ( clientChallenge . get (), 16 );
serverProof . Finalize ();
2014-10-10 23:19:40 +02:00
ModuleInfo * resume = sModuleMgr -> CreateModule ( _os , "Resume" );
2014-06-01 13:56:36 +02:00
BitStream resumeData ;
uint8 state = 2 ;
resumeData . WriteBytes ( & state , 1 );
resumeData . WriteBytes ( serverProof . GetDigest (), serverProof . GetLength ());
resume -> DataSize = resumeData . GetSize ();
resume -> Data = new uint8 [ resume -> DataSize ];
memcpy ( resume -> Data , resumeData . GetBuffer (), resume -> DataSize );
2014-10-10 23:48:01 +02:00
Authentication :: ResumeResponse * resumeResponse = new Authentication :: ResumeResponse ();
2015-10-06 00:30:47 +02:00
resumeResponse -> Result . Success . FinalRequest . push_back ( resume );
2014-10-10 23:48:01 +02:00
ReplaceResponse ( response , resumeResponse );
2014-06-01 13:56:36 +02:00
_authed = true ;
2014-10-10 23:19:40 +02:00
sSessionMgr . AddSession ( this );
2014-06-01 13:56:36 +02:00
return true ;
}
2014-07-19 17:03:32 +02:00
bool Battlenet :: Session :: UnhandledModule ( BitStream * /*dataStream*/ , ServerPacket ** response )
2014-05-06 23:43:29 +02:00
{
2014-10-12 15:34:01 +02:00
TC_LOG_ERROR ( "session.packets" , "Unhandled module." );
2014-10-10 23:48:01 +02:00
Authentication :: LogonResponse * logonResponse = new Authentication :: LogonResponse ();
logonResponse -> SetAuthResult ( AUTH_CORRUPTED_MODULE );
ReplaceResponse ( response , logonResponse );
2014-05-06 23:43:29 +02:00
return false ;
}
2014-10-11 15:13:30 +02:00
2015-10-06 00:30:47 +02:00
void Battlenet :: Session :: UpdateRealms ( std :: vector < Realm const *>& realms , std :: vector < RealmHandle >& deletedRealms )
2014-10-11 15:13:30 +02:00
{
for ( Realm const * realm : realms )
AsyncWrite ( BuildListUpdate ( realm ));
2015-10-06 00:30:47 +02:00
for ( RealmHandle & deleted : deletedRealms )
2014-10-11 15:13:30 +02:00
{
WoWRealm :: ListUpdate * listUpdate = new WoWRealm :: ListUpdate ();
2015-10-06 00:30:47 +02:00
listUpdate -> State . Type = WoWRealm :: ListUpdate :: StateType :: DELETED ;
2014-10-11 15:13:30 +02:00
listUpdate -> Id = deleted ;
AsyncWrite ( listUpdate );
}
}
Battlenet :: WoWRealm :: ListUpdate * Battlenet :: Session :: BuildListUpdate ( Realm const * realm ) const
{
2015-10-06 00:30:47 +02:00
uint32 flag = realm -> Flags ;
if ( realm -> Build != _build )
flag |= REALM_FLAG_VERSION_MISMATCH ;
2014-10-11 15:13:30 +02:00
WoWRealm :: ListUpdate * listUpdate = new WoWRealm :: ListUpdate ();
2015-10-06 00:30:47 +02:00
listUpdate -> State . Update . Category = realm -> Timezone ;
listUpdate -> State . Update . Population = realm -> PopulationLevel ;
listUpdate -> State . Update . StateFlags = ( realm -> AllowedSecurityLevel > _gameAccountInfo -> SecurityLevel ) ? 1 : 0 ;
listUpdate -> State . Update . Type = realm -> Type ;
listUpdate -> State . Update . Name = realm -> Name ;
2014-10-11 15:13:30 +02:00
2015-10-06 00:30:47 +02:00
if ( _gameAccountInfo -> SecurityLevel > SEC_PLAYER )
2014-10-11 15:13:30 +02:00
{
2015-10-06 00:30:47 +02:00
listUpdate -> State . Update . PrivilegedData = boost :: in_place ();
2014-10-11 15:13:30 +02:00
std :: ostringstream version ;
2015-10-06 00:30:47 +02:00
if ( RealmBuildInfo const * buildInfo = AuthHelper :: GetBuildInfo ( realm -> Build ))
version << buildInfo -> MajorVersion << '.' << buildInfo -> MinorVersion << '.' << buildInfo -> BugfixVersion << '.' << buildInfo -> Build ;
else
version << "x.x.x." << realm -> Build ;
2014-10-11 15:13:30 +02:00
2015-10-06 00:30:47 +02:00
listUpdate -> State . Update . PrivilegedData -> Version = version . str ();
listUpdate -> State . Update . PrivilegedData -> ConfigId = realm -> GetConfigId ();
listUpdate -> State . Update . PrivilegedData -> Address = realm -> GetAddressForClient ( GetRemoteIpAddress ());
2014-10-11 15:13:30 +02:00
}
2015-10-06 00:30:47 +02:00
listUpdate -> State . Update . InfoFlags = flag ;
2014-10-11 15:13:30 +02:00
listUpdate -> Id = realm -> Id ;
return listUpdate ;
}
2014-10-12 15:34:01 +02:00
std :: string Battlenet :: Session :: GetClientInfo () const
{
std :: ostringstream stream ;
stream << '[' << GetRemoteIpAddress () << ':' << GetRemotePort ();
2015-04-11 16:25:21 +02:00
if ( _accountInfo && ! _accountInfo -> Login . empty ())
stream << ", Account: " << _accountInfo -> Login ;
2014-10-12 15:34:01 +02:00
2015-04-11 16:25:21 +02:00
if ( _gameAccountInfo )
stream << ", Game account: " << _gameAccountInfo -> Name ;
2014-10-12 15:34:01 +02:00
stream << ']' ;
return stream . str ();
}