2010-08-11 15:23:10 +02:00
|
|
|
// ***************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Reality - The Matrix Online Server Emulator
|
2009-08-18 16:39:50 +00:00
|
|
|
// Copyright (C) 2006-2010 Rajko Stojadinovic
|
2010-08-11 15:23:10 +02:00
|
|
|
// http://mxoemu.info
|
2009-08-18 16:39:50 +00:00
|
|
|
//
|
2010-08-11 15:23:10 +02:00
|
|
|
// ---------------------------------------------------------------------------
|
2009-08-18 16:39:50 +00:00
|
|
|
//
|
2010-08-11 15:23:10 +02:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Affero General Public License as
|
|
|
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
// License, or (at your option) any later version.
|
2009-08-18 16:39:50 +00:00
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2010-08-11 15:23:10 +02:00
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2009-08-18 16:39:50 +00:00
|
|
|
//
|
2010-08-11 15:23:10 +02:00
|
|
|
// ---------------------------------------------------------------------------
|
2009-08-18 16:39:50 +00:00
|
|
|
//
|
2010-08-11 15:23:10 +02:00
|
|
|
// ***************************************************************************
|
2009-08-18 16:39:50 +00:00
|
|
|
|
2009-08-11 12:17:38 +00:00
|
|
|
#include "AuthSocket.h"
|
|
|
|
|
#include "Common.h"
|
|
|
|
|
#include "Util.h"
|
|
|
|
|
#include "ByteBuffer.h"
|
|
|
|
|
#include "AuthServer.h"
|
|
|
|
|
#include "Log.h"
|
|
|
|
|
#include "Timer.h"
|
|
|
|
|
#include "TCPVariableLengthPacket.h"
|
2009-08-16 14:29:06 +00:00
|
|
|
#include "Database/DatabaseEnv.h"
|
2010-02-08 14:51:16 +00:00
|
|
|
#include "SignedDataStruct.h"
|
2009-08-11 12:17:38 +00:00
|
|
|
|
|
|
|
|
AuthSocket::AuthSocket(ISocketHandler& h) : TCPVarLenSocket(h)
|
|
|
|
|
{
|
2010-02-08 14:51:16 +00:00
|
|
|
matrixVersion = 0;
|
2009-08-11 12:17:38 +00:00
|
|
|
packetNum = 0;
|
|
|
|
|
memset(finalChallenge,0,sizeof(finalChallenge));
|
|
|
|
|
memset(challenge,0,sizeof(challenge));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AuthSocket::~AuthSocket()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum AuthOpcode
|
|
|
|
|
{
|
|
|
|
|
AS_GetPublicKeyRequest = 0x06,
|
|
|
|
|
AS_GetPublicKeyReply = 0x07,
|
|
|
|
|
AS_AuthRequest = 0x08,
|
|
|
|
|
AS_AuthChallenge = 0x09,
|
|
|
|
|
AS_AuthChallengeResponse = 0x0A,
|
|
|
|
|
AS_AuthReply = 0x0B,
|
|
|
|
|
AS_CreateCharacterRequest = 0x0C,
|
|
|
|
|
AS_CreateCharacterReply = 0x0D,
|
|
|
|
|
AS_DeleteCharacterRequest = 0x0E,
|
|
|
|
|
AS_DeleteCharacterReply = 0x0F,
|
|
|
|
|
AS_LockAccountRequest = 0x10,
|
|
|
|
|
AS_LockAccountReply = 0x11,
|
|
|
|
|
AS_UnlockAccountRequest = 0x12,
|
|
|
|
|
AS_UnlockAccountReply = 0x13,
|
|
|
|
|
AS_PSAuthenticateRequest = 0x14,
|
|
|
|
|
AS_WorldIdAndStatus = 0x15,
|
|
|
|
|
AS_PSAuthenticateReply = 0x16,
|
|
|
|
|
AS_PSCreateCharacterRequest = 0x17,
|
|
|
|
|
AS_PSCreateCharacterReply = 0x18,
|
|
|
|
|
AS_PSGetWorldListRequest = 0x19,
|
|
|
|
|
AS_PSGetWorldListReply = 0x1A,
|
|
|
|
|
AS_PSGetWorldPopulationsRequest = 0x1B,
|
|
|
|
|
AS_PSGetWorldPopulationsReply = 0x1C,
|
|
|
|
|
AS_GetAccountInfoRequest = 0x1D,
|
|
|
|
|
AS_GetAccountInfoReply = 0x1E,
|
|
|
|
|
AS_ProxyConnectRequest = 0x1F,
|
|
|
|
|
AS_ProxyConnectReply = 0x20,
|
|
|
|
|
AS_WorldShuttingDown = 0x21,
|
|
|
|
|
AS_PSResetAccountInUseRequest = 0x22,
|
|
|
|
|
AS_SetTransSessionPenaltyRequest = 0x23,
|
|
|
|
|
AS_PSSetTransSessionPenaltyRequest = 0x24,
|
|
|
|
|
AS_PSSetTransSessionPenaltyReply = 0x25,
|
|
|
|
|
AS_SetWorldStatusRequest = 0x26,
|
|
|
|
|
AS_SetWorldStatusReply = 0x27,
|
|
|
|
|
AS_PSSetWorldStatusRequest = 0x28,
|
|
|
|
|
AS_PSSetWorldStatusReply = 0x29,
|
|
|
|
|
AS_UnlockAllAccountsRequest = 0x2A,
|
|
|
|
|
AS_LockRecoveringAccountRequest = 0x2B,
|
|
|
|
|
AS_LockRecoveringAccountReply = 0x2C,
|
|
|
|
|
AS_RefreshCertificateRequest = 0x2D,
|
|
|
|
|
AS_RefreshCertificateReply = 0x2E,
|
|
|
|
|
AS_PSCertRefreshRequest = 0x2F,
|
|
|
|
|
AS_PSCertRefreshReply = 0x30,
|
|
|
|
|
AS_PSSetWorldVersionRequest = 0x31,
|
|
|
|
|
AS_PSSetWorldVersionReply = 0x32,
|
|
|
|
|
AS_IAmProxyLeader = 0x33,
|
|
|
|
|
AS_RouteToAuthServer = 0x34,
|
|
|
|
|
AS_GetWorldListRequest = 0x35,
|
|
|
|
|
AS_GetWorldListReply = 0x36
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void AuthSocket::ProcessData( const byte *buf,size_t len )
|
|
|
|
|
{
|
|
|
|
|
ByteBuffer packetContents(buf,len);
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Auth Receieved |%1%|") % Bin2Hex(packetContents));
|
2009-08-11 12:17:38 +00:00
|
|
|
|
|
|
|
|
byte packetOpcode;
|
|
|
|
|
packetContents >> packetOpcode;
|
|
|
|
|
AuthOpcode opcode = AuthOpcode(packetOpcode);
|
|
|
|
|
|
|
|
|
|
switch (opcode)
|
|
|
|
|
{
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AS_GetPublicKeyRequest:
|
|
|
|
|
{
|
2010-02-08 14:51:16 +00:00
|
|
|
HandleGetPublicKeyRequest(packetContents);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AS_AuthRequest:
|
|
|
|
|
{
|
|
|
|
|
HandleAuthRequest(packetContents);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AS_AuthChallengeResponse:
|
|
|
|
|
{
|
|
|
|
|
HandleAuthChallengeResponse(packetContents);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AuthSocket::VerifyPassword( const string& plaintextPass, const string& passwordSalt, const string& passwordHash )
|
|
|
|
|
{
|
|
|
|
|
if (sAuth.HashPassword(passwordSalt,plaintextPass) == passwordHash)
|
|
|
|
|
return true;
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
void AuthSocket::HandleGetPublicKeyRequest( ByteBuffer &packet )
|
|
|
|
|
{
|
|
|
|
|
packet >> matrixVersion;
|
|
|
|
|
string clientVersionStr = ClientVersionString(matrixVersion);
|
|
|
|
|
if (clientVersionStr != "7.5668" )
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
WARNING_LOG(format("Auth client connected with unknown version %1%") % clientVersionStr );
|
2010-02-08 14:51:16 +00:00
|
|
|
}
|
|
|
|
|
uint32 rsaVersion;
|
|
|
|
|
packet >> rsaVersion;
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
TCPVariableLengthPacket awesome;
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
if (rsaVersion == 4)
|
|
|
|
|
{
|
|
|
|
|
awesome
|
|
|
|
|
<< byte(AS_GetPublicKeyReply)
|
|
|
|
|
<< uint32(0)
|
|
|
|
|
<< uint32(getTime()) //time
|
|
|
|
|
<< uint32(0x04) //rsa method
|
|
|
|
|
<< byte(0)
|
|
|
|
|
<< uint32(0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
awesome
|
|
|
|
|
<< byte(AS_GetPublicKeyReply)
|
|
|
|
|
<< uint32(0)
|
|
|
|
|
<< uint32(getTime()) //time
|
|
|
|
|
<< uint32(0x04) //rsa method
|
|
|
|
|
<< uint8(0x12)
|
|
|
|
|
<< uint8(0x00)
|
|
|
|
|
<< uint8(0x11) //public exponent
|
|
|
|
|
<< uint8(0x94)
|
|
|
|
|
<< uint8(0x00);
|
|
|
|
|
|
|
|
|
|
awesome.append(sAuth.GetPubKeyData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SendPacket(awesome);
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Sending AS_GetPublicKeyReply: |%1%|") % Bin2Hex(awesome));
|
2010-02-08 14:51:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AuthSocket::HandleAuthRequest( ByteBuffer &packet )
|
|
|
|
|
{
|
|
|
|
|
#pragma pack(push,1)
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
uint32 rsaType;
|
|
|
|
|
uint32 unknownz1;
|
|
|
|
|
char unknownz2[31];
|
|
|
|
|
uint16 blobLen;
|
|
|
|
|
}requestHdr;
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
requestHdr requestHeader;
|
|
|
|
|
memset(&requestHeader,0,sizeof(requestHeader));
|
|
|
|
|
packet.read((uint8 *)&requestHeader,sizeof(requestHeader));
|
|
|
|
|
|
|
|
|
|
vector<byte> encryptedBlob;
|
|
|
|
|
encryptedBlob.resize(requestHeader.blobLen);
|
|
|
|
|
packet.read(&encryptedBlob[0],encryptedBlob.size());
|
|
|
|
|
|
|
|
|
|
string decryptedBlob;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
decryptedBlob = sAuth.Decrypt(string((const char*)&encryptedBlob[0],encryptedBlob.size()));
|
|
|
|
|
}
|
2010-08-10 19:11:15 +02:00
|
|
|
catch (CryptoPP::InvalidCiphertext)
|
2010-02-08 14:51:16 +00:00
|
|
|
{
|
|
|
|
|
ERROR_LOG("Invalid RSA ciphertext, client used bad pubkey.dat, disconnecting.");
|
|
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Got encrypted Blob: |%1%|") % Bin2Hex(decryptedBlob));
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
ByteBuffer rsaBlobBuffer(decryptedBlob.substr(sizeof(byte)));
|
|
|
|
|
|
|
|
|
|
//should error out if not 4
|
|
|
|
|
uint32 rsaBlobMethod;
|
|
|
|
|
rsaBlobBuffer >> rsaBlobMethod;
|
|
|
|
|
if (rsaBlobMethod != 4)
|
|
|
|
|
{
|
|
|
|
|
WARNING_LOG("rsaMethod in rsaBlob not 4!");
|
|
|
|
|
}
|
|
|
|
|
uint16 someShort;
|
|
|
|
|
rsaBlobBuffer >> someShort;
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("someShort is %1%") % someShort);
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
byte twofishKey[16];
|
|
|
|
|
rsaBlobBuffer.read(twofishKey,sizeof(twofishKey));
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Auth TF key: |%s|") % Bin2Hex(twofishKey,sizeof(twofishKey)));
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
//instantiate ciphers
|
2010-02-16 13:16:38 +00:00
|
|
|
m_tfEngine.Initialize(twofishKey,sizeof(twofishKey));
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
//should check this time to be the same as the one we sent it in previous packet
|
|
|
|
|
uint32 theTime;
|
|
|
|
|
rsaBlobBuffer >> theTime;
|
|
|
|
|
time_t expandedTime = theTime;
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("time is: %1%") % ctime(&expandedTime));
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
uint16 usernameLen;
|
|
|
|
|
rsaBlobBuffer >> usernameLen;
|
|
|
|
|
vector<char> usernameVect;
|
|
|
|
|
usernameVect.resize(usernameLen);
|
|
|
|
|
rsaBlobBuffer.read((uint8 *)&usernameVect[0],usernameVect.size());
|
|
|
|
|
string theUsername(&usernameVect[0],usernameVect.size()-1);
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Username: |%1%|") % theUsername);
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
//copy to variable
|
|
|
|
|
m_username = theUsername;
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
//scope for db ptr
|
2010-02-08 14:51:16 +00:00
|
|
|
{
|
2010-02-27 23:36:10 +01:00
|
|
|
scoped_ptr<QueryResult> result(sDatabase.Query(format("SELECT `userId`, `username`, `passwordSalt`, `passwordHash`, `publicExponent`, `publicModulus`, `privateExponent`, `timeCreated` FROM `users` WHERE `username` = '%1%' LIMIT 1") % sDatabase.EscapeString(m_username) ) );
|
2010-02-14 11:47:57 +00:00
|
|
|
if (result == NULL)
|
2009-08-11 12:17:38 +00:00
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
INFO_LOG(format("Username %1% doesn't exist, disconnecting.") % m_username );
|
2010-02-08 14:51:16 +00:00
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
Field *field = result->Fetch();
|
|
|
|
|
m_userId = field[0].GetUInt32();
|
|
|
|
|
m_username = field[1].GetString();
|
|
|
|
|
m_passwordSalt = field[2].GetString();
|
|
|
|
|
m_passwordHash = field[3].GetString();
|
|
|
|
|
m_publicExponent = field[4].GetUInt16();
|
|
|
|
|
|
|
|
|
|
const char *pubModulusStr = field[5].GetString();
|
|
|
|
|
if (pubModulusStr != NULL)
|
|
|
|
|
m_publicModulus = string(pubModulusStr,96);
|
|
|
|
|
else
|
|
|
|
|
m_publicModulus.clear();
|
|
|
|
|
|
|
|
|
|
const char *privExponentStr = field[6].GetString();
|
|
|
|
|
if (privExponentStr != NULL)
|
|
|
|
|
m_privateExponent = string(field[6].GetString(),96);
|
|
|
|
|
else
|
|
|
|
|
m_privateExponent.clear();
|
|
|
|
|
|
|
|
|
|
m_timeCreated = field[7].GetUInt32();
|
|
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
|
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//now, for the reply
|
|
|
|
|
//reply consists entirely of challenge, which is used to make twofish IV and encrypt challenge response
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//create challenge
|
|
|
|
|
byte decryptedChallenge[16];
|
|
|
|
|
CryptoPP::AutoSeededRandomPool randPool;
|
|
|
|
|
randPool.GenerateBlock(decryptedChallenge,sizeof(decryptedChallenge));
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//to create challenge, we encrypt the random bytes
|
|
|
|
|
//encrypt our random bytes using twofish and 0 IV
|
2010-02-16 13:16:38 +00:00
|
|
|
m_tfEngine.SetEncryptionIV(); // just in case
|
|
|
|
|
ByteBuffer ourChallenge = m_tfEngine.Encrypt(decryptedChallenge,sizeof(decryptedChallenge),false);
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("TF Encrypted Challenge (as sent) |%1%|") % Bin2Hex(ourChallenge));
|
2010-02-08 14:51:16 +00:00
|
|
|
//copy to variable
|
2010-02-16 13:16:38 +00:00
|
|
|
memcpy(challenge,ourChallenge.contents(),sizeof(challenge));
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Decrypted Challenge (feed to md5) |%1%|") % Bin2Hex(decryptedChallenge,sizeof(decryptedChallenge)) );
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//do md5 transform to decrypted challenge to make final iv
|
|
|
|
|
byte MD5Digest[16];
|
|
|
|
|
CryptoPP::Weak::MD5 md5Transformer;
|
|
|
|
|
md5Transformer.Update(decryptedChallenge,sizeof(decryptedChallenge));
|
|
|
|
|
md5Transformer.Final(MD5Digest);
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Processed Challenge: |%1%|") % Bin2Hex(MD5Digest,sizeof(MD5Digest)) );
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//store this challenge
|
|
|
|
|
memcpy(finalChallenge,MD5Digest,sizeof(finalChallenge));
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//make reply
|
|
|
|
|
TCPVariableLengthPacket reply;
|
|
|
|
|
reply << byte(AS_AuthChallenge);
|
|
|
|
|
//send encrypted tf bytes, client will decrypt then md5 to generate IV
|
2010-02-16 13:16:38 +00:00
|
|
|
reply.append(ourChallenge.contents(),ourChallenge.size());
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
SendPacket(reply);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Sending AS_AuthChallenge: |%1%|") % Bin2Hex(reply) );
|
2010-02-08 14:51:16 +00:00
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
void AuthSocket::HandleAuthChallengeResponse( ByteBuffer &packet )
|
|
|
|
|
{
|
|
|
|
|
uint16 someShort;
|
|
|
|
|
packet >> someShort;
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("someShort is: %d") % someShort);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 cipherTextLen;
|
|
|
|
|
packet >> cipherTextLen;
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
vector<byte> cipherText;
|
|
|
|
|
cipherText.resize(cipherTextLen);
|
|
|
|
|
packet.read(&cipherText[0],cipherText.size());
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//now we have the ciphertext, lets decrypt
|
|
|
|
|
//challenge response from client is encrypted with IV set to 0
|
2010-02-16 13:16:38 +00:00
|
|
|
m_tfEngine.SetDecryptionIV();
|
|
|
|
|
ByteBuffer chRspPlain = m_tfEngine.Decrypt(&cipherText[0],cipherText.size(),false);
|
|
|
|
|
DEBUG_LOG(format("Challenge response decrypted: |%1%|") % Bin2Hex(chRspPlain) );
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
size_t packetSize = 0;
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint8 someByte;
|
|
|
|
|
chRspPlain >> someByte;
|
|
|
|
|
packetSize += sizeof(someByte);
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
byte processedChallenge[16];
|
|
|
|
|
chRspPlain.read(processedChallenge,sizeof(processedChallenge));
|
|
|
|
|
packetSize += sizeof(processedChallenge);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
if (memcmp(processedChallenge,finalChallenge,sizeof(processedChallenge)))
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
WARNING_LOG(format("Processed challenge mismatch: got |%1%|, expected |%2%|, disconnecting.")
|
|
|
|
|
% Bin2Hex(processedChallenge,sizeof(processedChallenge))
|
|
|
|
|
% Bin2Hex(finalChallenge,sizeof(finalChallenge)) );
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 unknown1;
|
|
|
|
|
chRspPlain >> unknown1;
|
|
|
|
|
packetSize += sizeof(unknown1);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 unknown2;
|
|
|
|
|
chRspPlain >> unknown2;
|
|
|
|
|
packetSize += sizeof(unknown2);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 unknown3;
|
|
|
|
|
chRspPlain >> unknown3;
|
|
|
|
|
packetSize += sizeof(unknown3);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 passwordLen;
|
|
|
|
|
chRspPlain >> passwordLen;
|
|
|
|
|
packetSize += sizeof(passwordLen);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
vector<char> password;
|
|
|
|
|
password.resize(passwordLen);
|
|
|
|
|
chRspPlain.read((byte*)&password[0],password.size());
|
|
|
|
|
packetSize += password.size();
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 soePassLen;
|
|
|
|
|
chRspPlain >> soePassLen;
|
|
|
|
|
packetSize += sizeof(soePassLen);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
vector<char> soePass;
|
|
|
|
|
soePass.resize(soePassLen);
|
|
|
|
|
chRspPlain.read((byte*)&soePass[0],soePass.size());
|
|
|
|
|
packetSize += soePass.size();
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 paddingLen;
|
|
|
|
|
chRspPlain >> paddingLen;
|
|
|
|
|
packetSize += sizeof(paddingLen);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
vector<byte> padding;
|
|
|
|
|
padding.resize(paddingLen);
|
|
|
|
|
chRspPlain.read(&padding[0],paddingLen);
|
|
|
|
|
packetSize += padding.size();
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
if (packetSize != chRspPlain.size())
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
WARNING_LOG(format("Challenge response blob size mismatch !, expected %1%, received %2%, disconnecting.") %packetSize % chRspPlain.size());
|
2010-02-08 14:51:16 +00:00
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Parsed contents: 1(const 23): |%1%|, 2(size): |%2%|, 3(size): |%3%|, User Password: |%4%|, SOE Password: |%5%|") % unknown1 % unknown2 % unknown3 % &password[0] % &soePass[0]);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
if (VerifyPassword(string(&password[0]),m_passwordSalt,m_passwordHash) == false)
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
WARNING_LOG(format("User %1% supplied an invalid password, disconnecting.") % m_username );
|
2010-02-08 14:51:16 +00:00
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_publicExponent != 17 || m_publicModulus.size() != 96 || m_privateExponent.size() != 96)
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
INFO_LOG(format("Invalid RSA keys for user %1%, regenerating.") % m_username );
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
//generate RSA keypair for client
|
|
|
|
|
CryptoPP::AutoSeededRandomPool randPool;
|
|
|
|
|
CryptoPP::InvertibleRSAFunction params;
|
|
|
|
|
params.GenerateRandomWithKeySize( randPool, 768 );
|
|
|
|
|
CryptoPP::RSA::PublicKey userPubKey(params);
|
|
|
|
|
CryptoPP::RSA::PrivateKey userPrivKey(params);
|
|
|
|
|
m_publicExponent = uint16(userPubKey.GetPublicExponent().ConvertToLong());
|
|
|
|
|
byte tempBuf[96];
|
|
|
|
|
userPubKey.GetModulus().Encode(tempBuf,sizeof(tempBuf));
|
|
|
|
|
m_publicModulus = string((const char*)tempBuf,sizeof(tempBuf));
|
|
|
|
|
m_privateExponent.clear();
|
|
|
|
|
CryptoPP::StringSink privateExponentSink(m_privateExponent);
|
|
|
|
|
userPrivKey.GetPrivateExponent().Encode(privateExponentSink,userPrivKey.GetPrivateExponent().MinEncodedSize());
|
|
|
|
|
|
|
|
|
|
if (m_publicExponent == 17 && m_publicModulus.size() == 96 && m_privateExponent.size() == 96)
|
2009-08-11 12:17:38 +00:00
|
|
|
{
|
2009-08-16 14:29:06 +00:00
|
|
|
break;
|
2009-08-11 12:17:38 +00:00
|
|
|
}
|
2010-02-08 14:51:16 +00:00
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//update db info
|
2010-02-13 14:32:04 +00:00
|
|
|
sDatabase.Execute(format("UPDATE `users` SET `publicExponent` = %1%, `publicModulus` = %2%, `privateExponent` = %3% WHERE `userId` = %4%")
|
|
|
|
|
% m_publicExponent
|
|
|
|
|
% Bin2Hex(m_publicModulus,BIN2HEX_ZEROES)
|
|
|
|
|
% Bin2Hex(m_privateExponent,BIN2HEX_ZEROES)
|
|
|
|
|
% m_userId );
|
2010-02-08 14:51:16 +00:00
|
|
|
}
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
signedDataStruct signedData;
|
|
|
|
|
memset(&signedData,0,sizeof(signedData));
|
|
|
|
|
|
|
|
|
|
signedData.unknownByte = 1;
|
|
|
|
|
signedData.userId1 = m_userId;
|
|
|
|
|
strncpy(signedData.userName,m_username.c_str(),sizeof(signedData.userName)-1);
|
|
|
|
|
signedData.unknownShort = 256;
|
|
|
|
|
signedData.expiryTime = getTime() + 60 * 10; //10 minutes ahead, just like on real server
|
|
|
|
|
signedData.publicExponent = swap16(m_publicExponent); //almost forgot the swap16 here :D
|
|
|
|
|
memcpy(signedData.modulus,m_publicModulus.data(),sizeof(signedData.modulus));
|
|
|
|
|
signedData.timeCreated = m_timeCreated;
|
|
|
|
|
|
|
|
|
|
//sign the data that needs to be signed
|
|
|
|
|
//its not that actual part which is signed, but the md5 of it, and sign makes a md5 of that, i think wb is just fucking with us
|
|
|
|
|
CryptoPP::Weak::MD5 md5Object;
|
|
|
|
|
md5Object.Update((const byte*)&signedData,sizeof(signedData));
|
|
|
|
|
byte signMePlease[16];
|
|
|
|
|
md5Object.Final(signMePlease);
|
|
|
|
|
ByteBuffer signature = sAuth.SignWith1024Bit(signMePlease,sizeof(signMePlease));
|
|
|
|
|
|
|
|
|
|
//the encrypted data is the private exponent of user's RSA key
|
2010-02-16 13:16:38 +00:00
|
|
|
//to encrypt 96 byte exponent, use auth_key as key and challenge as IV
|
|
|
|
|
m_tfEngine.SetEncryptionIV(challenge,sizeof(challenge));
|
|
|
|
|
ByteBuffer encryptedPrivateExponent=m_tfEngine.Encrypt((const byte*)m_privateExponent.data(),m_privateExponent.size(),false);
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
if (encryptedPrivateExponent.size() != 96)
|
|
|
|
|
{
|
2010-02-13 14:32:04 +00:00
|
|
|
ERROR_LOG(format("Encrypted private exponent ended up being something other than 96 bytes (%1% bytes), disconnecting.")
|
|
|
|
|
% encryptedPrivateExponent.size());
|
2010-02-08 14:51:16 +00:00
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//send reply
|
|
|
|
|
TCPVariableLengthPacket worldPacket;
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
#pragma pack(push,1)
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
uint8 opcode; //AS_AuthReply
|
|
|
|
|
byte unknown1[10]; //10 zero bytes
|
|
|
|
|
uint16 offsetAuthData; //offset into packet where worldList data ends, and authData starts
|
|
|
|
|
uint16 offsetEncryptedData; //offset into packet where authData ends, and encrypted pkey starts
|
|
|
|
|
uint32 unknown2; //always 1F 00 00 00, aka 0x1F 31d
|
|
|
|
|
uint16 offsetCharData; //offset into packet where this header ends, and charData starts (should be always 0x21, sizeof(AuthReplyHeader))
|
|
|
|
|
uint32 unknown3; //always 6E D1 00 00, aka 53614d
|
|
|
|
|
uint32 offsetServerData; //offset into packet where charData ends, and serverData begins
|
|
|
|
|
uint32 offsetUsername; //offset into packet where authData ends, and userName at the end begins
|
|
|
|
|
} AuthReplyHeader;
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
AuthReplyHeader packetHeader;
|
|
|
|
|
memset(&packetHeader,0,sizeof(packetHeader));
|
|
|
|
|
packetHeader.opcode = AS_AuthReply;
|
|
|
|
|
packetHeader.unknown2 = 0x1F;
|
|
|
|
|
packetHeader.unknown3 = 0x0000D16E;
|
|
|
|
|
packetHeader.offsetCharData = sizeof(packetHeader);
|
|
|
|
|
|
|
|
|
|
worldPacket.append((const byte*)&packetHeader,sizeof(packetHeader)); //we will have to get back and overwrite this later with the proper values
|
|
|
|
|
|
|
|
|
|
uint16 numCharacters;
|
|
|
|
|
|
2010-02-13 14:32:04 +00:00
|
|
|
scoped_ptr<QueryResult> result(sDatabase.Query(format("SELECT `charId`, `worldId`, `status`, `handle` FROM `characters` WHERE `userId` = %1%") % m_userId));
|
2010-02-14 11:47:57 +00:00
|
|
|
if(result == NULL)
|
2010-02-08 14:51:16 +00:00
|
|
|
numCharacters = 0;
|
|
|
|
|
else
|
|
|
|
|
numCharacters = result->GetRowCount();
|
|
|
|
|
|
|
|
|
|
//number of characters user has
|
|
|
|
|
worldPacket << uint16(numCharacters);
|
|
|
|
|
|
|
|
|
|
if (numCharacters > 0)
|
|
|
|
|
{
|
|
|
|
|
#pragma pack(push,1)
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
uint8 unknown1; //always 0
|
|
|
|
|
uint16 handleStrOffset; //offset from start of this charDataItem to string
|
|
|
|
|
uint64 charId; //64bit globally unique character id
|
|
|
|
|
uint8 status; //ok,in transit,banned
|
|
|
|
|
uint16 worldId; //world the char is in
|
|
|
|
|
} CharacterData;
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
ByteBuffer characterDatas;
|
|
|
|
|
ByteBuffer characterStrings;
|
|
|
|
|
|
|
|
|
|
for (uint i=0;i<numCharacters;i++)
|
|
|
|
|
{
|
|
|
|
|
Field *field = result->Fetch();
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
CharacterData currCharacter;
|
|
|
|
|
currCharacter.unknown1 = 0;
|
|
|
|
|
currCharacter.charId = field[0].GetUInt64();
|
|
|
|
|
currCharacter.worldId = field[1].GetUInt16();
|
|
|
|
|
currCharacter.status = field[2].GetUInt8();
|
|
|
|
|
//how many bytes untill we get to character strings start + offset into character strings
|
|
|
|
|
currCharacter.handleStrOffset = (numCharacters - i)*sizeof(CharacterData) + characterStrings.wpos();
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
string guidHex = Bin2Hex((const byte*)&currCharacter.charId,sizeof(currCharacter.charId));
|
2010-08-23 17:14:15 +02:00
|
|
|
DEBUG_LOG(format("Character GUID: %1%") % guidHex );
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//add the character to character datas
|
|
|
|
|
characterDatas.append((const byte*)&currCharacter,sizeof(currCharacter));
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
string characterString = field[3].GetString();
|
2010-08-10 19:11:15 +02:00
|
|
|
characterStrings.writeString(characterString);
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//fetch next row
|
|
|
|
|
if (result->NextRow() == false)
|
|
|
|
|
break;
|
|
|
|
|
}
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//add both chars and strings to worldpacket
|
|
|
|
|
worldPacket.append(characterDatas);
|
|
|
|
|
worldPacket.append(characterStrings);
|
|
|
|
|
}
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//character data has ended, server data starts at this pos
|
|
|
|
|
packetHeader.offsetServerData = worldPacket.wpos();
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//fetch server list data
|
2010-08-23 17:14:15 +02:00
|
|
|
result.reset(sDatabase.Query("SELECT `worldId`, `name`, `type`, `status`, `numPlayers` FROM `worlds`"));
|
|
|
|
|
if(result == NULL || result->GetRowCount() < 1)
|
2010-02-08 14:51:16 +00:00
|
|
|
{
|
|
|
|
|
ERROR_LOG("No worlds in db, disconnecting.");
|
|
|
|
|
SetCloseAndDelete(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
uint16 numWorlds = result->GetRowCount();
|
|
|
|
|
worldPacket << uint16(numWorlds);
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
Field *field = result->Fetch();
|
2009-08-11 12:17:38 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
#pragma pack(push,1)
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
uint8 unknown1; //always 0
|
|
|
|
|
uint16 worldId;
|
|
|
|
|
char worldName[20];
|
|
|
|
|
uint8 status; //down,open,etc
|
|
|
|
|
uint8 type; //pvp or non pvp
|
|
|
|
|
uint32 clientVersion; //always D9 21 07 00 (7.xxxx)
|
|
|
|
|
uint16 unknown4; //always 1
|
|
|
|
|
uint8 load; //31,32,33
|
|
|
|
|
} WorldData;
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
WorldData currWorld;
|
|
|
|
|
memset(&currWorld,0,sizeof(currWorld));
|
|
|
|
|
|
|
|
|
|
currWorld.unknown1 = 0;
|
|
|
|
|
currWorld.worldId = field[0].GetUInt16();
|
|
|
|
|
string worldNameStr = field[1].GetString();
|
|
|
|
|
strncpy(currWorld.worldName,worldNameStr.c_str(),worldNameStr.length());
|
|
|
|
|
currWorld.type = field[2].GetUInt8();
|
|
|
|
|
currWorld.status = field[3].GetUInt8();
|
|
|
|
|
currWorld.clientVersion = matrixVersion;
|
|
|
|
|
currWorld.unknown4 = 1;
|
2010-08-23 17:14:15 +02:00
|
|
|
|
|
|
|
|
//determine load
|
|
|
|
|
{
|
|
|
|
|
uint32 numPlayers = field[4].GetUInt32();
|
|
|
|
|
if (numPlayers < 50)
|
|
|
|
|
currWorld.load = 0x31; //low load
|
|
|
|
|
else if (numPlayers < 100)
|
|
|
|
|
currWorld.load = 0x32; //medium load
|
|
|
|
|
else
|
|
|
|
|
currWorld.load = 0x33; //high load
|
|
|
|
|
}
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
worldPacket.append((const byte*)&currWorld,sizeof(currWorld));
|
2009-08-11 12:17:38 +00:00
|
|
|
}
|
2010-02-08 14:51:16 +00:00
|
|
|
while(result->NextRow());
|
2009-08-16 14:29:06 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
//worldlist data ended, now starts auth data
|
|
|
|
|
packetHeader.offsetAuthData = worldPacket.wpos();
|
2009-08-16 22:45:56 +00:00
|
|
|
|
2010-02-08 14:51:16 +00:00
|
|
|
worldPacket << uint16(swap16(0x3601)); //indicates start of auth data
|
|
|
|
|
//then the signature of the signed data
|
|
|
|
|
worldPacket.append(signature);
|
|
|
|
|
//then the signed data itself
|
|
|
|
|
worldPacket.append((const byte*)&signedData,sizeof(signedData));
|
|
|
|
|
|
|
|
|
|
//auth data ended, now starts key data
|
|
|
|
|
packetHeader.offsetEncryptedData = worldPacket.wpos();
|
|
|
|
|
|
|
|
|
|
//then the size of the encrypted blob
|
|
|
|
|
worldPacket << uint16(encryptedPrivateExponent.size());
|
|
|
|
|
//then the encrypted blob itself
|
2010-02-16 13:16:38 +00:00
|
|
|
worldPacket.append(encryptedPrivateExponent.contents(),encryptedPrivateExponent.size());
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
//key data ended, now starts username data
|
|
|
|
|
packetHeader.offsetUsername = worldPacket.wpos();
|
|
|
|
|
|
2010-08-10 19:11:15 +02:00
|
|
|
//then the username string as mxo string
|
|
|
|
|
worldPacket.writeString(m_username);
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
//we need to rewrite the header back to the front now
|
|
|
|
|
worldPacket.put(0,(const byte*)&packetHeader,sizeof(packetHeader));
|
|
|
|
|
|
|
|
|
|
//for debugging, lets dump the packet we just created
|
2010-02-13 14:32:04 +00:00
|
|
|
DEBUG_LOG(format("Sending AS_AuthReply: %1%") % Bin2Hex(worldPacket) );
|
2010-02-08 14:51:16 +00:00
|
|
|
|
|
|
|
|
SendPacket(worldPacket);
|
2009-08-16 14:29:06 +00:00
|
|
|
}
|