Merge branch 'launcher-authoritive-auth'
This commit is contained in:
@@ -183,6 +183,7 @@ bool AuthSession::Update()
|
||||
return false;
|
||||
|
||||
_queryProcessor.ProcessReadyCallbacks();
|
||||
_transactionProcessor.ProcessReadyCallbacks();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -309,7 +310,9 @@ bool AuthSession::HandleLogonChallenge()
|
||||
// Get the account details from the account table
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
|
||||
stmt->SetData(0, GetRemoteIpAddress().to_string());
|
||||
stmt->SetData(1, login);
|
||||
stmt->SetData(1, GetRemoteIpAddress().to_string());
|
||||
stmt->SetData(2, _build);
|
||||
stmt->SetData(3, login);
|
||||
|
||||
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1)));
|
||||
return true;
|
||||
@@ -387,8 +390,21 @@ void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
|
||||
|
||||
uint8 securityFlags = 0;
|
||||
|
||||
// Check if a TOTP token is needed
|
||||
if (!fields[12].IsNull())
|
||||
_isDeveloperLogin = fields[15].Get<bool>();
|
||||
_launcherTicketGeneration.reset();
|
||||
|
||||
if (!_isDeveloperLogin && (fields[16].IsNull() || fields[17].IsNull() || fields[18].IsNull()))
|
||||
{
|
||||
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
|
||||
SendPacket(pkt);
|
||||
LOG_INFO("server.authserver", "'{}:{}' [AuthChallenge] launcher ticket required for account id {}",
|
||||
ipAddress, port, _accountInfo.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Launcher authentication completes MFA before ticket issuance. Only the
|
||||
// explicitly allowlisted developer flow may request the legacy TOTP UI.
|
||||
if (_isDeveloperLogin && !fields[12].IsNull())
|
||||
{
|
||||
securityFlags = 4;
|
||||
_totpSecret = fields[12].Get<Binary>();
|
||||
@@ -406,9 +422,19 @@ void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
|
||||
}
|
||||
}
|
||||
|
||||
_srp6.emplace(_accountInfo.Login,
|
||||
fields[13].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(),
|
||||
fields[14].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>());
|
||||
if (_isDeveloperLogin)
|
||||
{
|
||||
_srp6.emplace(_accountInfo.Login,
|
||||
fields[13].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(),
|
||||
fields[14].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>());
|
||||
}
|
||||
else
|
||||
{
|
||||
_launcherTicketGeneration = fields[16].Get<Binary, 16>();
|
||||
_srp6.emplace(_accountInfo.Login,
|
||||
fields[17].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(),
|
||||
fields[18].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>());
|
||||
}
|
||||
|
||||
// Fill the response packet with the result
|
||||
if (AuthHelper::IsAcceptedClientBuild(_build))
|
||||
@@ -515,45 +541,77 @@ bool AuthSession::HandleLogonProof()
|
||||
// No SQL injection (escaped user name) and IP address as received by socket
|
||||
|
||||
std::string address = sConfigMgr->GetOption<bool>("AllowLoggingIPAddressesInDatabase", true, true) ? GetRemoteIpAddress().to_string() : "0.0.0.0";
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
|
||||
stmt->SetData(0, _sessionKey);
|
||||
stmt->SetData(1, address);
|
||||
stmt->SetData(2, GetLocaleByName(_localizationName));
|
||||
stmt->SetData(3, _os);
|
||||
stmt->SetData(4, _accountInfo.Login);
|
||||
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithPreparedCallback([this, M2 = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey)](PreparedQueryResult const&)
|
||||
Acore::Crypto::SHA1::Digest serverProof = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey);
|
||||
|
||||
if (_isDeveloperLogin)
|
||||
{
|
||||
// Finish SRP6 and send the final result to the client
|
||||
ByteBuffer packet;
|
||||
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
|
||||
stmt->SetData(0, _sessionKey);
|
||||
stmt->SetData(1, address);
|
||||
stmt->SetData(2, GetLocaleByName(_localizationName));
|
||||
stmt->SetData(3, _os);
|
||||
stmt->SetData(4, _accountInfo.Login);
|
||||
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithPreparedCallback([this, serverProof](PreparedQueryResult const&)
|
||||
{
|
||||
sAuthLogonProof_S proof;
|
||||
proof.M2 = M2;
|
||||
proof.cmd = AUTH_LOGON_PROOF;
|
||||
proof.error = 0;
|
||||
proof.AccountFlags = _accountInfo.Flags;
|
||||
proof.SurveyId = 0;
|
||||
proof.LoginFlags = 0; // 0x1 = has account message
|
||||
SendLogonProofSuccess(serverProof);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(_launcherTicketGeneration);
|
||||
std::array<uint8, 16> claimNonce = Acore::Crypto::GetRandomBytes<16>();
|
||||
|
||||
packet.resize(sizeof(proof));
|
||||
std::memcpy(packet.contents(), &proof, sizeof(proof));
|
||||
}
|
||||
else
|
||||
LoginDatabaseTransaction transaction = LoginDatabase.BeginTransaction();
|
||||
|
||||
LoginDatabasePreparedStatement* consume = LoginDatabase.GetPreparedStatement(LOGIN_INS_LAUNCHER_TICKET_CONSUMPTION);
|
||||
consume->SetData(0, claimNonce);
|
||||
consume->SetData(1, _accountInfo.Id);
|
||||
consume->SetData(2, *_launcherTicketGeneration);
|
||||
transaction->Append(consume);
|
||||
|
||||
LoginDatabasePreparedStatement* remove = LoginDatabase.GetPreparedStatement(LOGIN_DEL_LAUNCHER_TICKET);
|
||||
remove->SetData(0, _accountInfo.Id);
|
||||
remove->SetData(1, *_launcherTicketGeneration);
|
||||
transaction->Append(remove);
|
||||
|
||||
LoginDatabasePreparedStatement* update = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAUNCHER_LOGONPROOF);
|
||||
update->SetData(0, _sessionKey);
|
||||
update->SetData(1, address);
|
||||
update->SetData(2, GetLocaleByName(_localizationName));
|
||||
update->SetData(3, _os);
|
||||
update->SetData(4, _accountInfo.Id);
|
||||
update->SetData(5, *_launcherTicketGeneration);
|
||||
update->SetData(6, claimNonce);
|
||||
transaction->Append(update);
|
||||
|
||||
std::array<uint8, 16> generation = *_launcherTicketGeneration;
|
||||
_transactionProcessor.AddCallback(LoginDatabase.AsyncCommitTransaction(transaction)).AfterComplete(
|
||||
[this, serverProof, generation, claimNonce](bool success)
|
||||
{
|
||||
sAuthLogonProof_S_Old proof;
|
||||
proof.M2 = M2;
|
||||
proof.cmd = AUTH_LOGON_PROOF;
|
||||
proof.error = 0;
|
||||
proof.unk2 = 0x00;
|
||||
if (!success)
|
||||
{
|
||||
SendLogonProofFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
packet.resize(sizeof(proof));
|
||||
std::memcpy(packet.contents(), &proof, sizeof(proof));
|
||||
}
|
||||
LoginDatabasePreparedStatement* claim = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LAUNCHER_TICKET_CLAIM);
|
||||
claim->SetData(0, _accountInfo.Id);
|
||||
claim->SetData(1, generation);
|
||||
claim->SetData(2, claimNonce);
|
||||
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(claim)
|
||||
.WithPreparedCallback([this, serverProof](PreparedQueryResult result)
|
||||
{
|
||||
if (!result)
|
||||
{
|
||||
SendLogonProofFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPacket(packet);
|
||||
_status = STATUS_AUTHED;
|
||||
}));
|
||||
SendLogonProofSuccess(serverProof);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -618,6 +676,47 @@ bool AuthSession::HandleLogonProof()
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuthSession::SendLogonProofFailure()
|
||||
{
|
||||
ByteBuffer packet;
|
||||
packet << uint8(AUTH_LOGON_PROOF);
|
||||
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
|
||||
packet << uint16(0);
|
||||
SendPacket(packet);
|
||||
}
|
||||
|
||||
void AuthSession::SendLogonProofSuccess(Acore::Crypto::SHA1::Digest const& serverProof)
|
||||
{
|
||||
ByteBuffer packet;
|
||||
if (_expversion & POST_BC_EXP_FLAG)
|
||||
{
|
||||
sAuthLogonProof_S proof;
|
||||
proof.M2 = serverProof;
|
||||
proof.cmd = AUTH_LOGON_PROOF;
|
||||
proof.error = 0;
|
||||
proof.AccountFlags = _accountInfo.Flags;
|
||||
proof.SurveyId = 0;
|
||||
proof.LoginFlags = 0;
|
||||
|
||||
packet.resize(sizeof(proof));
|
||||
std::memcpy(packet.contents(), &proof, sizeof(proof));
|
||||
}
|
||||
else
|
||||
{
|
||||
sAuthLogonProof_S_Old proof;
|
||||
proof.M2 = serverProof;
|
||||
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;
|
||||
}
|
||||
|
||||
bool AuthSession::HandleReconnectChallenge()
|
||||
{
|
||||
_status = STATUS_CLOSED;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "QueryResult.h"
|
||||
#include "SRP6.h"
|
||||
#include "Socket.h"
|
||||
#include "Transaction.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
@@ -89,10 +90,14 @@ private:
|
||||
void LogonChallengeCallback(PreparedQueryResult result);
|
||||
void ReconnectChallengeCallback(PreparedQueryResult result);
|
||||
void RealmListCallback(PreparedQueryResult result);
|
||||
void SendLogonProofFailure();
|
||||
void SendLogonProofSuccess(Acore::Crypto::SHA1::Digest const& serverProof);
|
||||
|
||||
bool VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect);
|
||||
|
||||
Optional<Acore::Crypto::SRP6> _srp6;
|
||||
Optional<std::array<uint8, 16>> _launcherTicketGeneration;
|
||||
bool _isDeveloperLogin = false;
|
||||
SessionKey _sessionKey = {};
|
||||
std::array<uint8, 16> _reconnectProof = {};
|
||||
|
||||
@@ -106,6 +111,7 @@ private:
|
||||
uint8 _expversion;
|
||||
|
||||
QueryCallbackProcessor _queryProcessor;
|
||||
AsyncCallbackProcessor<TransactionCallback> _transactionProcessor;
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
@@ -27,11 +27,14 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
"SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.Flags, a.failed_logins, "
|
||||
"ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, "
|
||||
"ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, "
|
||||
"aa.gmlevel, a.totp_secret, a.salt, a.verifier "
|
||||
"aa.gmlevel, a.totp_secret, a.salt, a.verifier, "
|
||||
"lda.account_id IS NOT NULL, lt.generation_id, lt.srp_salt, lt.srp_verifier "
|
||||
"FROM account a "
|
||||
"LEFT JOIN account_access aa ON a.id = aa.id "
|
||||
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
|
||||
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
|
||||
"LEFT JOIN launcher_dev_account lda ON lda.account_id = a.id AND lda.allowed_ip = ? AND lda.enabled = 1 "
|
||||
"LEFT JOIN launcher_ticket lt ON lt.account_id = a.id AND lt.expires_at > UTC_TIMESTAMP(6) AND lt.client_build = ? "
|
||||
"WHERE a.username = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE,
|
||||
"SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.Flags, a.failed_logins, "
|
||||
@@ -42,11 +45,11 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
"LEFT JOIN account_access aa ON a.id = aa.id "
|
||||
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
|
||||
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
|
||||
"WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_ASYNC);
|
||||
"WHERE a.username = ? AND a.session_key IS NOT NULL AND a.session_auth IN ('launcher', 'dev')", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, a.last_ip, a.locked, a.lock_country, a.expansion, a.Flags, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, "
|
||||
"aa.gmlevel, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) "
|
||||
"LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? "
|
||||
"AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
|
||||
"AND a.session_key IS NOT NULL AND a.session_auth IN ('launcher', 'dev') ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_IP_INFO, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
|
||||
@@ -61,7 +64,20 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'realmd', 'Failed login autoban', 1)", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LOGON, "UPDATE account SET salt = ?, verifier = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET session_key = ?, session_auth = 'dev', last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_INS_LAUNCHER_TICKET_CONSUMPTION,
|
||||
"INSERT INTO launcher_ticket_consumption (generation_id, account_id, claim_nonce, consumed_at) "
|
||||
"SELECT generation_id, account_id, ?, UTC_TIMESTAMP(6) FROM launcher_ticket "
|
||||
"WHERE account_id = ? AND generation_id = ? AND expires_at > UTC_TIMESTAMP(6)", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_DEL_LAUNCHER_TICKET,
|
||||
"DELETE FROM launcher_ticket WHERE account_id = ? AND generation_id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LAUNCHER_LOGONPROOF,
|
||||
"UPDATE account a JOIN launcher_ticket_consumption c ON c.account_id = a.id "
|
||||
"SET a.session_key = ?, a.session_auth = 'launcher', a.last_ip = ?, a.last_login = NOW(), "
|
||||
"a.locale = ?, a.failed_logins = 0, a.os = ? "
|
||||
"WHERE a.id = ? AND c.generation_id = ? AND c.claim_nonce = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_LAUNCHER_TICKET_CLAIM,
|
||||
"SELECT 1 FROM launcher_ticket_consumption WHERE account_id = ? AND generation_id = ? AND claim_nonce = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
|
||||
|
||||
@@ -41,6 +41,10 @@ enum LoginDatabaseStatements : uint32
|
||||
LOGIN_DEL_ACCOUNT_BANNED,
|
||||
LOGIN_UPD_LOGON,
|
||||
LOGIN_UPD_LOGONPROOF,
|
||||
LOGIN_INS_LAUNCHER_TICKET_CONSUMPTION,
|
||||
LOGIN_DEL_LAUNCHER_TICKET,
|
||||
LOGIN_UPD_LAUNCHER_LOGONPROOF,
|
||||
LOGIN_SEL_LAUNCHER_TICKET_CLAIM,
|
||||
LOGIN_SEL_LOGONCHALLENGE,
|
||||
LOGIN_SEL_RECONNECTCHALLENGE,
|
||||
LOGIN_UPD_FAILEDLOGINS,
|
||||
|
||||
Reference in New Issue
Block a user