/*
* 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 "AccountMgr.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "CryptoHash.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "Realm.h"
#include "ScriptMgr.h"
#include "SRP6.h"
#include "Util.h"
#include "World.h"
#include "WorldSession.h"
using AccountSRP6 = Trinity::Crypto::SRP::GruntSRP6;
AccountMgr::AccountMgr() { }
AccountMgr::~AccountMgr()
{
ClearRBAC();
}
AccountMgr* AccountMgr::instance()
{
static AccountMgr instance;
return &instance;
}
AccountOpResult AccountMgr::CreateAccount(std::string username, std::string password, std::string email /*= ""*/, uint32 bnetAccountId /*= 0*/, uint8 bnetIndex /*= 0*/)
{
if (utf8length(username) > MAX_ACCOUNT_STR)
return AccountOpResult::AOR_NAME_TOO_LONG; // username's too long
if (utf8length(password) > MAX_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG; // password's too long
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(password);
Utf8ToUpperOnlyLatin(email);
if (GetId(username))
return AccountOpResult::AOR_NAME_ALREADY_EXIST; // username does already exist
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT);
stmt->setString(0, username);
auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(username, password);
stmt->setBinary(1, salt);
stmt->setBinary(2, verifier);
stmt->setString(3, email);
stmt->setString(4, email);
if (bnetAccountId && bnetIndex)
{
stmt->setUInt32(5, bnetAccountId);
stmt->setUInt8(6, bnetIndex);
}
else
{
stmt->setNull(5);
stmt->setNull(6);
}
LoginDatabase.DirectExecute(stmt); // Enforce saving, otherwise AddGroup can fail
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_REALM_CHARACTERS_INIT);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK; // everything's fine
}
AccountOpResult AccountMgr::DeleteAccount(uint32 accountId)
{
// Check if accounts exists
LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_ID);
loginStmt->setUInt32(0, accountId);
PreparedQueryResult result = LoginDatabase.Query(loginStmt);
if (!result)
return AccountOpResult::AOR_NAME_NOT_EXIST;
// Obtain accounts characters
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID);
stmt->setUInt32(0, accountId);
result = CharacterDatabase.Query(stmt);
if (result)
{
do
{
ObjectGuid guid = ObjectGuid::Create((*result)[0].GetUInt64());
// Kick if player is online
if (Player* p = ObjectAccessor::FindConnectedPlayer(guid))
{
WorldSession* s = p->GetSession();
s->KickPlayer("AccountMgr::DeleteAccount Deleting the account"); // mark session to remove at next session list update
s->LogoutPlayer(false); // logout player without waiting next session list update
}
Player::DeleteFromDB(guid, accountId, false); // no need to update realm characters
} while (result->NextRow());
}
// table realm specific but common for all characters of account for realm
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_TUTORIALS);
stmt->setUInt32(0, accountId);
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_DATA);
stmt->setUInt32(0, accountId);
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_BAN);
stmt->setUInt32(0, accountId);
CharacterDatabase.Execute(stmt);
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT);
loginStmt->setUInt32(0, accountId);
trans->Append(loginStmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_ACCESS);
loginStmt->setUInt32(0, accountId);
trans->Append(loginStmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_REALM_CHARACTERS);
loginStmt->setUInt32(0, accountId);
trans->Append(loginStmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_BANNED);
loginStmt->setUInt32(0, accountId);
trans->Append(loginStmt);
loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_MUTED);
loginStmt->setUInt32(0, accountId);
trans->Append(loginStmt);
LoginDatabase.CommitTransaction(trans);
return AccountOpResult::AOR_OK;
}
AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword)
{
// Check if accounts exists
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_ID);
stmt->setUInt32(0, accountId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (!result)
return AccountOpResult::AOR_NAME_NOT_EXIST;
if (utf8length(newUsername) > MAX_ACCOUNT_STR)
return AccountOpResult::AOR_NAME_TOO_LONG;
if (utf8length(newPassword) > MAX_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG;
Utf8ToUpperOnlyLatin(newUsername);
Utf8ToUpperOnlyLatin(newPassword);
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_USERNAME);
stmt->setString(0, newUsername);
stmt->setUInt32(1, accountId);
LoginDatabase.Execute(stmt);
auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(newUsername, newPassword);
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
stmt->setBinary(0, salt);
stmt->setBinary(1, verifier);
stmt->setUInt32(2, accountId);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK;
}
AccountOpResult AccountMgr::ChangePassword(uint32 accountId, std::string newPassword)
{
std::string username;
if (!GetName(accountId, username))
{
sScriptMgr->OnFailedPasswordChange(accountId);
return AccountOpResult::AOR_NAME_NOT_EXIST; // account doesn't exist
}
if (utf8length(newPassword) > MAX_PASS_STR)
{
sScriptMgr->OnFailedPasswordChange(accountId);
return AccountOpResult::AOR_PASS_TOO_LONG;
}
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(newPassword);
auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(username, newPassword);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
stmt->setBinary(0, salt);
stmt->setBinary(1, verifier);
stmt->setUInt32(2, accountId);
LoginDatabase.Execute(stmt);
sScriptMgr->OnPasswordChange(accountId);
return AccountOpResult::AOR_OK;
}
AccountOpResult AccountMgr::ChangeEmail(uint32 accountId, std::string newEmail)
{
std::string username;
if (!GetName(accountId, username))
{
sScriptMgr->OnFailedEmailChange(accountId);
return AccountOpResult::AOR_NAME_NOT_EXIST; // account doesn't exist
}
if (utf8length(newEmail) > MAX_EMAIL_STR)
{
sScriptMgr->OnFailedEmailChange(accountId);
return AccountOpResult::AOR_EMAIL_TOO_LONG;
}
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(newEmail);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_EMAIL);
stmt->setString(0, newEmail);
stmt->setUInt32(1, accountId);
LoginDatabase.Execute(stmt);
sScriptMgr->OnEmailChange(accountId);
return AccountOpResult::AOR_OK;
}
AccountOpResult AccountMgr::ChangeRegEmail(uint32 accountId, std::string newEmail)
{
std::string username;
if (!GetName(accountId, username))
{
sScriptMgr->OnFailedEmailChange(accountId);
return AccountOpResult::AOR_NAME_NOT_EXIST; // account doesn't exist
}
if (utf8length(newEmail) > MAX_EMAIL_STR)
{
sScriptMgr->OnFailedEmailChange(accountId);
return AccountOpResult::AOR_EMAIL_TOO_LONG;
}
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(newEmail);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_REG_EMAIL);
stmt->setString(0, newEmail);
stmt->setUInt32(1, accountId);
LoginDatabase.Execute(stmt);
sScriptMgr->OnEmailChange(accountId);
return AccountOpResult::AOR_OK;
}
uint32 AccountMgr::GetId(std::string_view username)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME);
stmt->setStringView(0, username);
PreparedQueryResult result = LoginDatabase.Query(stmt);
return (result) ? (*result)[0].GetUInt32() : 0;
}
uint32 AccountMgr::GetSecurity(uint32 accountId, int32 realmId)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_GMLEVEL_BY_REALMID);
stmt->setUInt32(0, accountId);
stmt->setInt32(1, realmId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
return (result) ? (*result)[0].GetUInt8() : uint32(SEC_PLAYER);
}
QueryCallback AccountMgr::GetSecurityAsync(uint32 accountId, int32 realmId, std::function callback)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_GMLEVEL_BY_REALMID);
stmt->setUInt32(0, accountId);
stmt->setInt32(1, realmId);
return LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([callback = std::move(callback)](PreparedQueryResult result)
{
callback(result ? uint32((*result)[0].GetUInt8()) : uint32(SEC_PLAYER));
});
}
bool AccountMgr::GetName(uint32 accountId, std::string& name)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_USERNAME_BY_ID);
stmt->setUInt32(0, accountId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (result)
{
name = (*result)[0].GetString();
return true;
}
return false;
}
bool AccountMgr::GetEmail(uint32 accountId, std::string& email)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_EMAIL_BY_ID);
stmt->setUInt32(0, accountId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (result)
{
email = (*result)[0].GetString();
return true;
}
return false;
}
bool AccountMgr::CheckPassword(std::string username, std::string password)
{
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(password);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME);
stmt->setString(0, username);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
Trinity::Crypto::SRP::Salt salt = (*result)[0].GetBinary();
Trinity::Crypto::SRP::Verifier verifier = (*result)[1].GetBinary();
if (AccountSRP6(username, salt, verifier).CheckCredentials(username, password))
return true;
}
return false;
}
bool AccountMgr::CheckPassword(uint32 accountId, std::string password)
{
std::string username;
if (!GetName(accountId, username))
return false;
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(password);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD);
stmt->setUInt32(0, accountId);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
Trinity::Crypto::SRP::Salt salt = (*result)[0].GetBinary();
Trinity::Crypto::SRP::Verifier verifier = (*result)[1].GetBinary();
if (AccountSRP6(username, salt, verifier).CheckCredentials(username, password))
return true;
}
return false;
}
bool AccountMgr::CheckEmail(uint32 accountId, std::string newEmail)
{
std::string oldEmail;
// We simply return false for a non-existing email
if (!GetEmail(accountId, oldEmail))
return false;
Utf8ToUpperOnlyLatin(oldEmail);
Utf8ToUpperOnlyLatin(newEmail);
if (strcmp(oldEmail.c_str(), newEmail.c_str()) == 0)
return true;
return false;
}
uint32 AccountMgr::GetCharactersCount(uint32 accountId)
{
// check character count
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS);
stmt->setUInt32(0, accountId);
PreparedQueryResult result = CharacterDatabase.Query(stmt);
return (result) ? (*result)[0].GetUInt64() : 0;
}
bool AccountMgr::IsBannedAccount(std::string const& name)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME);
stmt->setString(0, name);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (!result)
return false;
return true;
}
bool AccountMgr::IsPlayerAccount(uint32 gmlevel)
{
return gmlevel == SEC_PLAYER;
}
bool AccountMgr::IsAdminAccount(uint32 gmlevel)
{
return gmlevel >= SEC_ADMINISTRATOR && gmlevel <= SEC_CONSOLE;
}
bool AccountMgr::IsConsoleAccount(uint32 gmlevel)
{
return gmlevel == SEC_CONSOLE;
}
void AccountMgr::LoadRBAC()
{
ClearRBAC();
TC_LOG_DEBUG("rbac", "AccountMgr::LoadRBAC");
uint32 oldMSTime = getMSTime();
uint32 count1 = 0;
uint32 count2 = 0;
uint32 count3 = 0;
TC_LOG_DEBUG("rbac", "AccountMgr::LoadRBAC: Loading permissions");
QueryResult result = LoginDatabase.Query("SELECT id, name FROM rbac_permissions");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 account permission definitions. DB table `rbac_permissions` is empty.");
return;
}
do
{
Field* field = result->Fetch();
uint32 id = field[0].GetUInt32();
_permissions[id] = new rbac::RBACPermission(id, field[1].GetString());
++count1;
}
while (result->NextRow());
TC_LOG_DEBUG("rbac", "AccountMgr::LoadRBAC: Loading linked permissions");
result = LoginDatabase.Query("SELECT id, linkedId FROM rbac_linked_permissions ORDER BY id ASC");
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 linked permissions. DB table `rbac_linked_permissions` is empty.");
return;
}
uint32 permissionId = 0;
rbac::RBACPermission* permission = nullptr;
do
{
Field* field = result->Fetch();
uint32 newId = field[0].GetUInt32();
if (permissionId != newId)
{
permissionId = newId;
permission = _permissions[newId];
}
uint32 linkedPermissionId = field[1].GetUInt32();
if (linkedPermissionId == permissionId)
{
TC_LOG_ERROR("sql.sql", "RBAC Permission {} has itself as linked permission. Ignored", permissionId);
continue;
}
permission->AddLinkedPermission(linkedPermissionId);
++count2;
}
while (result->NextRow());
TC_LOG_DEBUG("rbac", "AccountMgr::LoadRBAC: Loading default permissions");
result = LoginDatabase.PQuery("SELECT secId, permissionId FROM rbac_default_permissions WHERE (realmId = {} OR realmId = -1) ORDER BY secId ASC", realm.Id.Realm);
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 default permission definitions. DB table `rbac_default_permissions` is empty.");
return;
}
uint8 secId = 255;
rbac::RBACPermissionContainer* permissions = nullptr;
do
{
Field* field = result->Fetch();
uint32 newId = field[0].GetUInt32();
if (secId != newId || permissions == nullptr)
{
secId = newId;
permissions = &_defaultPermissions[secId];
}
permissions->insert(field[1].GetUInt32());
++count3;
}
while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} permission definitions, {} linked permissions and {} default permissions in {} ms", count1, count2, count3, GetMSTimeDiffToNow(oldMSTime));
}
void AccountMgr::UpdateAccountAccess(rbac::RBACData* rbac, uint32 accountId, uint8 securityLevel, int32 realmId)
{
if (rbac && securityLevel != rbac->GetSecurityLevel())
rbac->SetSecurityLevel(securityLevel);
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
// Delete old security level from DB
if (realmId == -1)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_ACCESS);
stmt->setUInt32(0, accountId);
trans->Append(stmt);
}
else
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_ACCOUNT_ACCESS_BY_REALM);
stmt->setUInt32(0, accountId);
stmt->setUInt32(1, realmId);
trans->Append(stmt);
}
// Add new security level
if (securityLevel)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_ACCESS);
stmt->setUInt32(0, accountId);
stmt->setUInt8(1, securityLevel);
stmt->setInt32(2, realmId);
trans->Append(stmt);
}
LoginDatabase.CommitTransaction(trans);
}
rbac::RBACPermission const* AccountMgr::GetRBACPermission(uint32 permissionId) const
{
TC_LOG_TRACE("rbac", "AccountMgr::GetRBACPermission: {}", permissionId);
rbac::RBACPermissionsContainer::const_iterator it = _permissions.find(permissionId);
if (it != _permissions.end())
return it->second;
return nullptr;
}
bool AccountMgr::HasPermission(uint32 accountId, uint32 permissionId, uint32 realmId)
{
if (!accountId)
{
TC_LOG_ERROR("rbac", "AccountMgr::HasPermission: Wrong accountId 0");
return false;
}
rbac::RBACData rbac(accountId, "", realmId, GetSecurity(accountId, realmId));
rbac.LoadFromDB();
bool hasPermission = rbac.HasPermission(permissionId);
TC_LOG_DEBUG("rbac", "AccountMgr::HasPermission [AccountId: {}, PermissionId: {}, realmId: {}]: {}",
accountId, permissionId, realmId, hasPermission);
return hasPermission;
}
void AccountMgr::ClearRBAC()
{
for (std::pair& permission : _permissions)
delete permission.second;
_permissions.clear();
_defaultPermissions.clear();
}
rbac::RBACPermissionContainer const& AccountMgr::GetRBACDefaultPermissions(uint8 secLevel)
{
TC_LOG_TRACE("rbac", "AccountMgr::GetRBACDefaultPermissions: secLevel {} - size: {}", secLevel, uint32(_defaultPermissions[secLevel].size()));
return _defaultPermissions[secLevel];
}