/*
* 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 "BattlenetAccountMgr.h"
#include "AccountMgr.h"
#include "DatabaseEnv.h"
#include "SRP6.h"
#include "Util.h"
using GameAccountMgr = AccountMgr;
using BnetSRP6_OLD = Trinity::Crypto::SRP::BnetSRP6v1;
using BnetSRP6 = Trinity::Crypto::SRP::BnetSRP6v2;
enum class SrpVersion : int8
{
v1 = 1, // password length limit 16 characters, case-insensitive, uses SHA256 to generate verifier
v2 = 2 // password length limit 128 characters, case-sensitive, uses PBKDF2 with SHA512 to generate verifier
};
AccountOpResult Battlenet::AccountMgr::CreateBattlenetAccount(std::string email, std::string password, bool withGameAccount, std::string* gameAccountName)
{
if (utf8length(email) > MAX_BNET_EMAIL_STR)
return AccountOpResult::AOR_NAME_TOO_LONG;
if (utf8length(password) > MAX_BNET_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG;
Utf8ToUpperOnlyLatin(email);
if (GetId(email))
return AccountOpResult::AOR_NAME_ALREADY_EXIST;
std::string srpUsername = GetSrpUsername(email);
auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(srpUsername, password);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_ACCOUNT);
stmt->setString(0, email);
stmt->setInt8(1, AsUnderlyingType(SrpVersion::v2));
stmt->setBinary(2, salt);
stmt->setBinary(3, std::move(verifier));
LoginDatabase.DirectExecute(stmt);
uint32 newAccountId = GetId(email);
ASSERT(newAccountId);
if (withGameAccount)
{
*gameAccountName = std::to_string(newAccountId) + "#1";
std::string gameAccountPassword = password.substr(0, MAX_PASS_STR);
Utf8ToUpperOnlyLatin(gameAccountPassword);
GameAccountMgr::instance()->CreateAccount(*gameAccountName, gameAccountPassword, email, newAccountId, 1);
}
return AccountOpResult::AOR_OK;
}
AccountOpResult Battlenet::AccountMgr::ChangePassword(uint32 accountId, std::string newPassword)
{
std::string username;
if (!GetName(accountId, username))
return AccountOpResult::AOR_NAME_NOT_EXIST; // account doesn't exist
if (utf8length(newPassword) > MAX_BNET_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG;
std::string srpUsername = GetSrpUsername(username);
auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(srpUsername, newPassword);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_LOGON);
stmt->setInt8(0, AsUnderlyingType(SrpVersion::v2));
stmt->setBinary(1, salt);
stmt->setBinary(2, std::move(verifier));
stmt->setUInt32(3, accountId);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK;
}
bool Battlenet::AccountMgr::CheckPassword(uint32 accountId, std::string password)
{
std::string username;
if (!GetName(accountId, username))
return false;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHECK_PASSWORD);
stmt->setUInt32(0, accountId);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
Trinity::Crypto::SRP::Salt salt = (*result)[1].GetBinary();
Trinity::Crypto::SRP::Verifier verifier = (*result)[2].GetBinary();
switch (SrpVersion((*result)[0].GetInt8()))
{
case SrpVersion::v1:
Utf8ToUpperOnlyLatin(password);
return BnetSRP6_OLD(username, salt, verifier).CheckCredentials(username, password);
case SrpVersion::v2:
return BnetSRP6(username, salt, verifier).CheckCredentials(username, password);
default:
break;
}
}
return false;
}
AccountOpResult Battlenet::AccountMgr::LinkWithGameAccount(std::string_view email, std::string_view gameAccountName)
{
uint32 bnetAccountId = GetId(email);
if (!bnetAccountId)
return AccountOpResult::AOR_NAME_NOT_EXIST;
uint32 gameAccountId = GameAccountMgr::GetId(gameAccountName);
if (!gameAccountId)
return AccountOpResult::AOR_NAME_NOT_EXIST;
if (GetIdByGameAccount(gameAccountId))
return AccountOpResult::AOR_ACCOUNT_BAD_LINK;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LINK);
stmt->setUInt32(0, bnetAccountId);
stmt->setUInt8(1, GetMaxIndex(bnetAccountId) + 1);
stmt->setUInt32(2, gameAccountId);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK;
}
AccountOpResult Battlenet::AccountMgr::UnlinkGameAccount(std::string_view gameAccountName)
{
uint32 gameAccountId = GameAccountMgr::GetId(gameAccountName);
if (!gameAccountId)
return AccountOpResult::AOR_NAME_NOT_EXIST;
if (!GetIdByGameAccount(gameAccountId))
return AccountOpResult::AOR_ACCOUNT_BAD_LINK;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LINK);
stmt->setNull(0);
stmt->setNull(1);
stmt->setUInt32(2, gameAccountId);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK;
}
uint32 Battlenet::AccountMgr::GetId(std::string_view username)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_EMAIL);
stmt->setString(0, username);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
return (*result)[0].GetUInt32();
return 0;
}
bool Battlenet::AccountMgr::GetName(uint32 accountId, std::string& name)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_EMAIL_BY_ID);
stmt->setUInt32(0, accountId);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
name = (*result)[0].GetString();
return true;
}
return false;
}
uint32 Battlenet::AccountMgr::GetIdByGameAccount(uint32 gameAccountId)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT);
stmt->setUInt32(0, gameAccountId);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
return (*result)[0].GetUInt32();
return 0;
}
QueryCallback Battlenet::AccountMgr::GetIdByGameAccountAsync(uint32 gameAccountId)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT);
stmt->setUInt32(0, gameAccountId);
return LoginDatabase.AsyncQuery(stmt);
}
uint8 Battlenet::AccountMgr::GetMaxIndex(uint32 accountId)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_MAX_ACCOUNT_INDEX);
stmt->setUInt32(0, accountId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (result)
return (*result)[0].GetUInt8();
return 0;
}
std::string Battlenet::AccountMgr::GetSrpUsername(std::string name)
{
Utf8ToUpperOnlyLatin(name);
return ByteArrayToHexStr(Trinity::Crypto::SHA256::GetDigestOf(name));
}