/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* 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 .
*/
#include "AuthSession.h"
#include "AES.h"
#include "AuthCodes.h"
#include "ByteBuffer.h"
#include "ClientBuildInfo.h"
#include "Config.h"
#include "CryptoGenerics.h"
#include "CryptoHash.h"
#include "CryptoRandom.h"
#include "DatabaseEnv.h"
#include "IPLocation.h"
#include "IoContext.h"
#include "Log.h"
#include "RealmList.h"
#include "SecretMgr.h"
#include "TOTP.h"
#include "Util.h"
#include
#include
using boost::asio::ip::tcp;
using boost::endian::little_uint16_t;
using boost::endian::little_uint32_t;
enum eAuthCmd : uint8
{
AUTH_LOGON_CHALLENGE = 0x00,
AUTH_LOGON_PROOF = 0x01,
AUTH_RECONNECT_CHALLENGE = 0x02,
AUTH_RECONNECT_PROOF = 0x03,
REALM_LIST = 0x10,
XFER_INITIATE = 0x30,
XFER_DATA = 0x31,
XFER_ACCEPT = 0x32,
XFER_RESUME = 0x33,
XFER_CANCEL = 0x34
};
#pragma pack(push, 1)
typedef struct AUTH_LOGON_CHALLENGE_C
{
uint8 cmd;
uint8 error;
little_uint16_t size;
little_uint32_t gamename;
uint8 version1;
uint8 version2;
uint8 version3;
little_uint16_t build;
little_uint32_t platform;
little_uint32_t os;
little_uint32_t country;
little_uint32_t timezone_bias;
little_uint32_t ip;
uint8 I_len;
char I[1];
} sAuthLogonChallenge_C;
static_assert(sizeof(sAuthLogonChallenge_C) == (1 + 1 + 2 + 4 + 1 + 1 + 1 + 2 + 4 + 4 + 4 + 4 + 4 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_C
{
uint8 cmd;
Trinity::Crypto::SRP6::EphemeralKey A;
Trinity::Crypto::SHA1::Digest clientM;
Trinity::Crypto::SHA1::Digest crc_hash;
uint8 number_of_keys;
uint8 securityFlags;
} sAuthLogonProof_C;
static_assert(sizeof(sAuthLogonProof_C) == (1 + 32 + 20 + 20 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_S
{
uint8 cmd;
uint8 error;
Trinity::Crypto::SHA1::Digest M2;
little_uint32_t AccountFlags;
little_uint32_t SurveyId;
little_uint16_t LoginFlags;
} sAuthLogonProof_S;
static_assert(sizeof(sAuthLogonProof_S) == (1 + 1 + 20 + 4 + 4 + 2));
typedef struct AUTH_LOGON_PROOF_S_OLD
{
uint8 cmd;
uint8 error;
Trinity::Crypto::SHA1::Digest M2;
little_uint32_t unk2;
} sAuthLogonProof_S_Old;
static_assert(sizeof(sAuthLogonProof_S_Old) == (1 + 1 + 20 + 4));
typedef struct AUTH_RECONNECT_PROOF_C
{
uint8 cmd;
uint8 R1[16];
Trinity::Crypto::SHA1::Digest R2, R3;
uint8 number_of_keys;
} sAuthReconnectProof_C;
static_assert(sizeof(sAuthReconnectProof_C) == (1 + 16 + 20 + 20 + 1));
#pragma pack(pop)
std::array VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } };
#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16)
#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4
#define REALM_LIST_PACKET_SIZE 5
struct AuthHandler
{
eAuthCmd cmd = { };
AuthStatus status = STATUS_CLOSED;
size_t packetSize = 0;
bool (*handler)(AuthSession*) = nullptr;
};
class AuthHandlerTable
{
public:
consteval AuthHandlerTable()
{
InitializeHandler(AUTH_LOGON_CHALLENGE, STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, [](AuthSession* session) { return session->HandleLogonChallenge(); });
InitializeHandler(AUTH_LOGON_PROOF, STATUS_LOGON_PROOF, sizeof(AUTH_LOGON_PROOF_C), [](AuthSession* session) { return session->HandleLogonProof(); });
InitializeHandler(AUTH_RECONNECT_CHALLENGE, STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, [](AuthSession* session) { return session->HandleReconnectChallenge(); });
InitializeHandler(AUTH_RECONNECT_PROOF, STATUS_RECONNECT_PROOF, sizeof(AUTH_RECONNECT_PROOF_C), [](AuthSession* session) { return session->HandleReconnectProof(); });
InitializeHandler(REALM_LIST, STATUS_AUTHED, REALM_LIST_PACKET_SIZE, [](AuthSession* session) { return session->HandleRealmList(); });
InitializeHandler(XFER_ACCEPT, STATUS_XFER, 1, [](AuthSession* session) { return session->HandleXferAccept(); });
InitializeHandler(XFER_RESUME, STATUS_XFER, 9, [](AuthSession* session) { return session->HandleXferResume(); });
InitializeHandler(XFER_CANCEL, STATUS_XFER, 1, [](AuthSession* session) { return session->HandleXferCancel(); });
}
constexpr AuthHandler const* operator[](eAuthCmd cmd) const
{
std::size_t index = GetOpcodeArrayIndex(cmd);
if (index >= _handlers.size())
return nullptr;
AuthHandler const& handler = _handlers[index];
if (handler.cmd != cmd)
return nullptr;
return &handler;
}
private:
// perfect hash function for all valid values of eAuthCmd
inline static constexpr std::size_t GetOpcodeArrayIndex(eAuthCmd c)
{
return (c & 0x7) + ((c & 0x10) >> 2) + ((c & 0x20) >> 5);
}
constexpr void InitializeHandler(eAuthCmd cmd, AuthStatus status, std::size_t packetSize, bool (*handler)(AuthSession*))
{
_handlers[GetOpcodeArrayIndex(cmd)] = { .cmd = cmd, .status = status, .packetSize = packetSize, .handler = handler, };
}
std::array _handlers;
} inline constexpr Handlers;
void AccountInfo::LoadResult(Field* fields)
{
// 0 1 2 3 4 5 6
//SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate,
// 7 8
// ab.unbandate = ab.bandate, aa.SecurityLevel (, more query-specific fields)
//FROM account a LEFT JOIN account_access aa ON a.id = aa.AccountID LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?
Id = fields[0].GetUInt32();
Login = fields[1].GetStringView();
IsLockedToIP = fields[2].GetBool();
LockCountry = fields[3].GetStringView();
LastIP = fields[4].GetStringView();
FailedLogins = fields[5].GetUInt32();
IsBanned = fields[6].GetUInt64() != 0;
IsPermanenetlyBanned = fields[7].GetUInt64() != 0;
SecurityLevel = AccountTypes(fields[8].GetUInt8());
// Use our own uppercasing of the account name instead of using UPPER() in mysql query
// This is how the account was created in the first place and changing it now would result in breaking
// login for all accounts having accented characters in their name
Utf8ToUpperOnlyLatin(Login);
}
AuthSession::AuthSession(tcp::socket&& socket) : Socket(std::move(socket)),
_timeout(*underlying_stream().get_executor().target()),
_status(STATUS_CHALLENGE), _locale(LOCALE_enUS), _os(0), _build(0), _expversion(0), _timezoneOffset(0min)
{
}
void AuthSession::Start()
{
std::string ip_address = GetRemoteIpAddress().to_string();
TC_LOG_TRACE("session", "Accepted connection from {}", ip_address);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
stmt->setString(0, ip_address);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1)));
}
bool AuthSession::Update()
{
if (!AuthSocket::Update())
return false;
_queryProcessor.ProcessReadyCallbacks();
return true;
}
void AuthSession::CheckIpCallback(PreparedQueryResult result)
{
if (result)
{
bool banned = false;
do
{
Field* fields = result->Fetch();
if (fields[0].GetUInt64() != 0)
banned = true;
} while (result->NextRow());
if (banned)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
TC_LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '{}:{}' tries to login!", GetRemoteIpAddress().to_string(), GetRemotePort());
return;
}
}
AsyncRead();
SetTimeout();
}
void AuthSession::ReadHandler()
{
MessageBuffer& packet = GetReadBuffer();
while (packet.GetActiveSize())
{
eAuthCmd cmd = eAuthCmd(packet.GetReadPointer()[0]);
AuthHandler const* itr = Handlers[cmd];
if (!itr || _status != itr->status)
{
CloseSocket();
return;
}
std::size_t size = itr->packetSize;
if (packet.GetActiveSize() < size)
break;
if (cmd == AUTH_LOGON_CHALLENGE || cmd == AUTH_RECONNECT_CHALLENGE)
{
sAuthLogonChallenge_C* challenge = reinterpret_cast(packet.GetReadPointer());
size += challenge->size;
if (size > MAX_ACCEPTED_CHALLENGE_SIZE)
{
CloseSocket();
return;
}
}
if (packet.GetActiveSize() < size)
break;
if (!itr->handler(this))
{
CloseSocket();
return;
}
packet.ReadCompleted(size);
SetTimeout();
}
AsyncRead();
}
void AuthSession::SendPacket(ByteBuffer& packet)
{
if (!IsOpen())
return;
if (!packet.empty())
{
MessageBuffer buffer(packet.size());
buffer.Write(packet.contents(), packet.size());
QueuePacket(std::move(buffer));
}
}
bool AuthSession::HandleLogonChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string_view login(challenge->I, challenge->I_len);
TC_LOG_DEBUG("server.authserver", "[AuthChallenge] '{}'", login);
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
_os = challenge->os;
_locale = GetLocaleByName(ClientBuild::ToCharArray(challenge->country).data());
_timezoneOffset = Minutes(challenge->timezone_bias);
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
stmt->setStringView(0, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
.WithPreparedCallback([this](PreparedQueryResult result) { LogonChallengeCallback(std::move(result)); }));
return true;
}
void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
std::string ipAddress = GetRemoteIpAddress().to_string();
uint16 port = GetRemotePort();
// If the IP is 'locked', check that the player comes indeed from the correct IP address
if (_accountInfo.IsLockedToIP)
{
TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to IP - '{}' is logging in from '{}'", _accountInfo.Login, _accountInfo.LastIP, ipAddress);
if (_accountInfo.LastIP != ipAddress)
{
pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
SendPacket(pkt);
return;
}
}
else
{
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress))
_ipCountry = location->CountryCode;
TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to ip", _accountInfo.Login);
if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00")
TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to country", _accountInfo.Login);
else if (!_ipCountry.empty())
{
TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to country: '{}' Player country is '{}'", _accountInfo.Login, _accountInfo.LockCountry, _ipCountry);
if (_ipCountry != _accountInfo.LockCountry)
{
pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK);
SendPacket(pkt);
return;
}
}
}
// If the account is banned, reject the logon attempt
if (_accountInfo.IsBanned)
{
if (_accountInfo.IsPermanenetlyBanned)
{
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
TC_LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
return;
}
else
{
pkt << uint8(WOW_FAIL_SUSPENDED);
SendPacket(pkt);
TC_LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Temporarily banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
return;
}
}
uint8 securityFlags = 0;
// Check if a TOTP token is needed
if (!fields[9].IsNull())
{
securityFlags = 4;
_totpSecret = fields[9].GetBinary();
if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY))
{
bool success = Trinity::Crypto::AEDecrypt(*_totpSecret, *secret);
if (!success)
{
pkt << uint8(WOW_FAIL_DB_BUSY);
TC_LOG_ERROR("server.authserver", "[AuthChallenge] Account '{}' has invalid ciphertext for TOTP token key stored", _accountInfo.Login);
SendPacket(pkt);
return;
}
}
}
_srp6.emplace(
_accountInfo.Login,
fields[10].GetBinary(),
fields[11].GetBinary()
);
// Fill the response packet with the result
if (AuthHelper::IsAcceptedClientBuild(_build))
{
pkt << uint8(WOW_SUCCESS);
pkt.append(_srp6->B);
pkt << uint8(1);
pkt.append(_srp6->g);
pkt << uint8(32);
pkt.append(_srp6->N);
pkt.append(_srp6->s);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
pkt << uint8(securityFlags); // security flags (0x0...0x04)
if (securityFlags & 0x01) // PIN input
{
pkt << uint32(0);
pkt << uint64(0) << uint64(0); // 16 bytes hash?
}
if (securityFlags & 0x02) // Matrix input
{
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint64(0);
}
if (securityFlags & 0x04) // Security token input
pkt << uint8(1);
TC_LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} is using '{}' locale ({})",
ipAddress, port, _accountInfo.Login, localeNames[_locale], uint32(_locale));
_status = STATUS_LOGON_PROOF;
}
else
pkt << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(pkt);
}
// Logon Proof command handler
bool AuthSession::HandleLogonProof()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleLogonProof");
_status = STATUS_CLOSED;
// Read the packet
sAuthLogonProof_C *logonProof = reinterpret_cast(GetReadBuffer().GetReadPointer());
// If the client has no valid version
if (_expversion == NO_VALID_EXP_FLAG)
{
// Check if we have the appropriate patch on the disk
TC_LOG_DEBUG("network", "Client with invalid version, patching is not implemented");
return false;
}
// Check if SRP6 results match (password is correct), else send an error
if (std::optional K = _srp6->VerifyChallengeResponse(logonProof->A, logonProof->clientM))
{
_sessionKey = *K;
// Check auth token
bool tokenSuccess = false;
bool sentToken = (logonProof->securityFlags & 0x04);
if (sentToken && _totpSecret)
{
uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C));
std::string token(reinterpret_cast(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size);
GetReadBuffer().ReadCompleted(sizeof(size) + size);
uint32 incomingToken = atoi(token.c_str());
tokenSuccess = Trinity::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken);
memset(_totpSecret->data(), 0, _totpSecret->size());
}
else if (!sentToken && !_totpSecret)
tokenSuccess = true;
if (!tokenSuccess)
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
return true;
}
if (!VerifyVersion(logonProof->A, logonProof->crc_hash, false))
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
TC_LOG_DEBUG("server.authserver", "'{}:{}' User '{}' successfully authenticated", GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
// Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
// No SQL injection (escaped user name) and IP address as received by socket
std::string address = sConfigMgr->GetBoolDefault("AllowLoggingIPAddressesInDatabase", true, true) ? GetRemoteIpAddress().to_string() : "127.0.0.1";
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
stmt->setBinary(0, _sessionKey);
stmt->setString(1, address);
stmt->setUInt32(2, _locale);
stmt->setStringView(3, ClientBuild::ToCharArray(_os).data());
stmt->setInt16(4, _timezoneOffset.count());
stmt->setString(5, _accountInfo.Login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
.WithPreparedCallback([this, M2 = Trinity::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey)](PreparedQueryResult const&)
{
// Finish SRP6 and send the final result to the client
ByteBuffer packet;
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
sAuthLogonProof_S proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.AccountFlags = GAMEACCOUNT_FLAG_PROPASS_LOCK;
proof.SurveyId = 0;
proof.LoginFlags = 0; // 0x1 = has account message
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
else
{
sAuthLogonProof_S_Old proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.unk2 = 0x00;
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
SendPacket(packet);
_status = STATUS_AUTHED;
}));
}
else
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
TC_LOG_INFO("server.authserver.hack", "'{}:{}' [AuthChallenge] account {} tried to login with invalid password!",
GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
uint32 MaxWrongPassCount = sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0);
// We can not include the failed account login hook. However, this is a workaround to still log this.
if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false))
{
LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING);
logstmt->setUInt32(0, _accountInfo.Id);
logstmt->setString(1, GetRemoteIpAddress().to_string());
logstmt->setString(2, "Login to WoW Failed - Incorrect Password");
LoginDatabase.Execute(logstmt);
}
if (MaxWrongPassCount > 0)
{
//Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS);
stmt->setString(0, _accountInfo.Login);
LoginDatabase.Execute(stmt);
if (++_accountInfo.FailedLogins >= MaxWrongPassCount)
{
uint32 WrongPassBanTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600);
bool WrongPassBanType = sConfigMgr->GetBoolDefault("WrongPass.BanType", false);
if (WrongPassBanType)
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED);
stmt->setUInt32(0, _accountInfo.Id);
stmt->setUInt32(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
TC_LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} got banned for '{}' seconds because it failed to authenticate '{}' times",
GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login, WrongPassBanTime, _accountInfo.FailedLogins);
}
else
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
stmt->setString(0, GetRemoteIpAddress().to_string());
stmt->setUInt32(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
TC_LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] IP got banned for '{}' seconds because account {} failed to authenticate '{}' times",
GetRemoteIpAddress().to_string(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login, _accountInfo.FailedLogins);
}
}
}
}
return true;
}
bool AuthSession::HandleReconnectChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string_view login(challenge->I, challenge->I_len);
TC_LOG_DEBUG("server.authserver", "[ReconnectChallenge] '{}'", login);
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
_os = challenge->os;
_locale = GetLocaleByName(ClientBuild::ToCharArray(challenge->country).data());
_timezoneOffset = Minutes(challenge->timezone_bias);
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE);
stmt->setStringView(0, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
.WithPreparedCallback([this](PreparedQueryResult result) { ReconnectChallengeCallback(std::move(result)); }));
return true;
}
void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_CHALLENGE);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
_sessionKey = fields[9].GetBinary();
Trinity::Crypto::GetRandomBytes(_reconnectProof);
_status = STATUS_RECONNECT_PROOF;
pkt << uint8(WOW_SUCCESS);
pkt.append(_reconnectProof);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
SendPacket(pkt);
}
bool AuthSession::HandleReconnectProof()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof");
_status = STATUS_CLOSED;
sAuthReconnectProof_C *reconnectProof = reinterpret_cast(GetReadBuffer().GetReadPointer());
if (_accountInfo.Login.empty())
return false;
Trinity::Crypto::SHA1 sha;
sha.UpdateData(_accountInfo.Login);
sha.UpdateData(reconnectProof->R1, 16);
sha.UpdateData(_reconnectProof);
sha.UpdateData(_sessionKey);
sha.Finalize();
if (sha.GetDigest() == reconnectProof->R2)
{
if (!VerifyVersion(reconnectProof->R1, reconnectProof->R3, true))
{
ByteBuffer packet;
packet << uint8(AUTH_RECONNECT_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
// Sending response
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_PROOF);
pkt << uint8(WOW_SUCCESS);
pkt << uint16(0); // LoginFlags, 1 has account message
SendPacket(pkt);
_status = STATUS_AUTHED;
return true;
}
else
{
TC_LOG_ERROR("server.authserver.hack", "'{}:{}' [ERROR] user {} tried to login, but session is invalid.", GetRemoteIpAddress().to_string(),
GetRemotePort(), _accountInfo.Login);
return false;
}
}
bool AuthSession::HandleRealmList()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleRealmList");
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS);
stmt->setUInt32(0, _accountInfo.Id);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1)));
_status = STATUS_WAITING_FOR_REALM_LIST;
return true;
}
void AuthSession::RealmListCallback(PreparedQueryResult result)
{
std::map characterCounts;
if (result)
{
do
{
Field* fields = result->Fetch();
characterCounts[fields[0].GetUInt32()] = fields[1].GetUInt8();
} while (result->NextRow());
}
// Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
ByteBuffer pkt;
size_t RealmListSize = 0;
for (RealmList::RealmMap::value_type const& i : sRealmList->GetRealms())
{
Realm const& realm = i.second;
// don't work with realms which not compatible with the client
bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build));
// No SQL injection. id of realm is controlled by the database.
uint32 flag = realm.Flags;
ClientBuild::Info const* buildInfo = ClientBuild::GetBuildInfo(realm.Build);
if (!okBuild)
{
if (!buildInfo)
continue;
flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for
}
if (!buildInfo)
flag &= ~REALM_FLAG_SPECIFYBUILD;
std::string name = realm.Name;
if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
Trinity::StringFormatTo(std::back_inserter(name), " ({}.{}.{})", buildInfo->MajorVersion, buildInfo->MinorVersion, buildInfo->BugfixVersion);
uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0;
pkt << uint8(realm.Type); // realm type
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
pkt << uint8(lock); // if 1, then realm locked
pkt << uint8(flag); // RealmFlags
pkt << name;
pkt << boost::lexical_cast(realm.GetAddressForClient(GetRemoteIpAddress()));
pkt << float(realm.PopulationLevel);
pkt << uint8(characterCounts[realm.Id.Realm]);
pkt << uint8(realm.Timezone); // realm category
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
pkt << uint8(realm.Id.Realm);
else
pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients
if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
{
pkt << uint8(buildInfo->MajorVersion);
pkt << uint8(buildInfo->MinorVersion);
pkt << uint8(buildInfo->BugfixVersion);
pkt << uint16(buildInfo->Build);
}
++RealmListSize;
}
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
pkt << uint8(0x10);
pkt << uint8(0x00);
}
else // 1.12.1 and 1.12.2 clients
{
pkt << uint8(0x00);
pkt << uint8(0x02);
}
// make a ByteBuffer which stores the RealmList's size
ByteBuffer RealmListSizeBuffer;
RealmListSizeBuffer << uint32(0);
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
RealmListSizeBuffer << uint16(RealmListSize);
else
RealmListSizeBuffer << uint32(RealmListSize);
ByteBuffer hdr;
hdr << uint8(REALM_LIST);
hdr << uint16(pkt.size() + RealmListSizeBuffer.size());
hdr.append(RealmListSizeBuffer); // append RealmList's size buffer
hdr.append(pkt); // append realms in the realmlist
SendPacket(hdr);
_status = STATUS_AUTHED;
}
bool AuthSession::HandleXferAccept()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleXferAccept");
// empty handler meant to close the connection if received
return false;
}
bool AuthSession::HandleXferResume()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleXferResume");
// empty handler meant to close the connection if received
return false;
}
bool AuthSession::HandleXferCancel()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleXferCancel");
// empty handler meant to close the connection if received
return false;
}
bool AuthSession::VerifyVersion(std::span a, Trinity::Crypto::SHA1::Digest const& versionProof, bool isReconnect)
{
if (!sConfigMgr->GetBoolDefault("StrictVersionCheck", false))
return true;
Trinity::Crypto::SHA1::Digest zeros = { };
Trinity::Crypto::SHA1::Digest const* versionHash = nullptr;
if (!isReconnect)
{
ClientBuild::Info const* buildInfo = ClientBuild::GetBuildInfo(_build);
if (!buildInfo)
return false;
auto platformItr = std::ranges::find(buildInfo->ExecutableHashes, _os, &ClientBuild::ExecutableHash::Platform);
if (platformItr == buildInfo->ExecutableHashes.end())
return true; // not filled serverside
versionHash = &platformItr->Hash;
}
else
versionHash = &zeros;
Trinity::Crypto::SHA1 version;
version.UpdateData(a);
version.UpdateData(*versionHash);
version.Finalize();
return versionProof == version.GetDigest();
}
void AuthSession::SetTimeout()
{
_timeout.cancel();
switch (_status)
{
case STATUS_AUTHED:
case STATUS_WAITING_FOR_REALM_LIST:
_timeout.expires_after(1min);
break;
case STATUS_XFER:
return;
default:
_timeout.expires_after(10s);
break;
}
_timeout.async_wait([selfRef = weak_from_this()](boost::system::error_code const& error)
{
std::shared_ptr self = selfRef.lock();
if (!self)
return;
if (error == boost::asio::error::operation_aborted)
return;
TC_LOG_DEBUG("server.authserver", "{}:{} session timed out.", self->GetRemoteIpAddress().to_string(), self->GetRemotePort());
self->CloseSocket();
});
}