Core/Authserver: Re-organize the accounts table (PR #25135)
- no longer use sha_pass_hash for anything else core-side (.account, SOAP, RA) - salt/verifier/session_key are now binary - old s/v/sha_pass_hash fields kept around for backwards compatibility - sha_pass_hash is still updated (for now), s/v are not - sha_pass_hash is only read if s/v have been manually changed - SRP6 b now uses the full 32 bytes of randomness (instead of randomly only using 19) (cherry picked from commit 3164b58c7d170810b69378950c0891e5f5b8678b)
This commit is contained in:
@@ -25,10 +25,12 @@ DROP TABLE IF EXISTS `account`;
|
||||
CREATE TABLE `account` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Identifier',
|
||||
`username` varchar(32) NOT NULL DEFAULT '',
|
||||
`salt` BINARY(32),
|
||||
`verifier` BINARY(32),
|
||||
`session_key` BINARY(40) DEFAULT NULL,
|
||||
`sha_pass_hash` varchar(40) NOT NULL DEFAULT '',
|
||||
`sessionkey` varchar(128) NOT NULL DEFAULT '',
|
||||
`v` varchar(64) NOT NULL DEFAULT '',
|
||||
`s` varchar(64) NOT NULL DEFAULT '',
|
||||
`v` varchar(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead',
|
||||
`s` varchar(64) NOT NULL DEFAULT 'dummy value, use `salt` instead',
|
||||
`token_key` varchar(100) NOT NULL DEFAULT '',
|
||||
`email` varchar(255) NOT NULL DEFAULT '',
|
||||
`reg_mail` varchar(255) NOT NULL DEFAULT '',
|
||||
@@ -2363,7 +2365,8 @@ INSERT INTO `updates` VALUES
|
||||
('2020_07_03_00_auth.sql','ED7175E51F248ADC5EF60E7CEA9627CC3191ED4C','RELEASED','2020-07-03 20:09:39',0),
|
||||
('2020_07_23_00_auth.sql','5F47E1CEECA9F837C85C2DAC7ECD47AED321F502','RELEASED','2020-07-23 19:54:42',0),
|
||||
('2020_07_24_00_auth.sql','06598162E9C84DDF8AA1F83D0410D056C3F7F69E','RELEASED','2020-07-24 00:44:34',0),
|
||||
('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0);
|
||||
('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0),
|
||||
('2020_08_02_00_auth.sql','B0290F6558C59262D9DDD8071060A8803DD56930','ARCHIVED','2020-08-02 00:00:00',0);
|
||||
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-- AUTH CLEANUP PHASE 3
|
||||
-- update `account` structure
|
||||
-- sha_pass_hash/s/v kept around for now, for backwards compatibility
|
||||
ALTER TABLE `account`
|
||||
DROP COLUMN `sessionkey`,
|
||||
ADD COLUMN `salt` BINARY(32) AFTER `username`,
|
||||
ADD COLUMN `verifier` BINARY(32) AFTER `salt`,
|
||||
ADD COLUMN `session_key` BINARY(40) AFTER `verifier`,
|
||||
MODIFY COLUMN `s` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `salt` instead',
|
||||
MODIFY COLUMN `v` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead';
|
||||
|
||||
UPDATE `account` SET `salt`=REVERSE(UNHEX(`s`)), `s`=DEFAULT WHERE LENGTH(`s`)=64;
|
||||
UPDATE `account` SET `verifier`=REVERSE(UNHEX(`v`)), `v`=DEFAULT WHERE LENGTH(`v`)=64;
|
||||
@@ -24,7 +24,12 @@
|
||||
using SHA1 = Trinity::Crypto::SHA1;
|
||||
using SRP6 = Trinity::Crypto::SRP6;
|
||||
|
||||
/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
|
||||
/*static*/ std::array<uint8, 1> const SRP6::g = []()
|
||||
{
|
||||
std::array<uint8, 1> g_temp;
|
||||
g_temp[0] = 7;
|
||||
return g_temp;
|
||||
}();
|
||||
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
|
||||
/*static*/ BigNumber const SRP6::_g(SRP6::g);
|
||||
/*static*/ BigNumber const SRP6::_N(N);
|
||||
@@ -54,7 +59,7 @@ using SRP6 = Trinity::Crypto::SRP6;
|
||||
// merge this into CalculateVerifier once the sha_pass hack finally gets nuked from orbit
|
||||
/*static*/ SRP6::Verifier SRP6::CalculateVerifierFromHash(SHA1::Digest const& hash, SRP6::Salt const& salt)
|
||||
{
|
||||
return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>(false);
|
||||
return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>();
|
||||
}
|
||||
|
||||
/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
|
||||
@@ -88,7 +93,7 @@ using SRP6 = Trinity::Crypto::SRP6;
|
||||
}
|
||||
|
||||
SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
|
||||
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<19>()), _v(verifier, false), s(salt), B(_B(_b, _v)) {}
|
||||
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {}
|
||||
|
||||
Optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
|
||||
{
|
||||
|
||||
@@ -154,6 +154,8 @@ int main(int argc, char** argv)
|
||||
if (!StartDB())
|
||||
return 1;
|
||||
|
||||
sSessionMgr.FixLegacyAuthHashes();
|
||||
|
||||
// Load IP Location Database
|
||||
sIPLocation->Load();
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
*/
|
||||
|
||||
#include "SessionManager.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "SRP6.h"
|
||||
#include "Util.h"
|
||||
|
||||
bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount)
|
||||
{
|
||||
@@ -27,6 +30,48 @@ bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext
|
||||
return true;
|
||||
}
|
||||
|
||||
void Battlenet::SessionManager::FixLegacyAuthHashes()
|
||||
{
|
||||
TC_LOG_INFO("server.bnetserver", "Updating password hashes...");
|
||||
uint32 const start = getMSTime();
|
||||
// the auth update query nulls salt/verifier if they cannot be converted
|
||||
// if they are non-null but s/v have been cleared, that means a legacy tool touched our auth DB (otherwise, the core might've done it itself, it used to use those hacks too)
|
||||
QueryResult result = LoginDatabase.Query("SELECT id, sha_pass_hash, IF((salt IS null) AND (verifier IS null), 0, 1) AS shouldWarn FROM account WHERE s != DEFAULT(s) OR v != DEFAULT(v) OR salt IS NULL OR verifier IS NULL");
|
||||
if (!result)
|
||||
{
|
||||
TC_LOG_INFO("server.bnetserver", ">> No password hashes to update - this took us %u ms to realize", GetMSTimeDiffToNow(start));
|
||||
return;
|
||||
}
|
||||
|
||||
bool hadWarning = false;
|
||||
uint32 c = 0;
|
||||
LoginDatabaseTransaction tx = LoginDatabase.BeginTransaction();
|
||||
do
|
||||
{
|
||||
uint32 const id = (*result)[0].GetUInt32();
|
||||
std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationDataFromHash_DEPRECATED_DONOTUSE(
|
||||
HexStrToByteArray<Trinity::Crypto::SHA1::DIGEST_LENGTH>((*result)[1].GetString())
|
||||
);
|
||||
|
||||
if ((*result)[2].GetInt64() && !hadWarning)
|
||||
{
|
||||
hadWarning = true;
|
||||
TC_LOG_WARN("server.bnetserver", "(!) You appear to be using an outdated external account management tool.\n(!!) This is INSECURE, has been deprecated, and will cease to function entirely in the near future.\n(!) Update your external tool.\n(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.");
|
||||
}
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
|
||||
stmt->setBinary(0, registrationData.first);
|
||||
stmt->setBinary(1, registrationData.second);
|
||||
stmt->setUInt32(2, id);
|
||||
tx->Append(stmt);
|
||||
|
||||
++c;
|
||||
} while (result->NextRow());
|
||||
LoginDatabase.CommitTransaction(tx);
|
||||
|
||||
TC_LOG_INFO("server.bnetserver", ">> %u password hashes updated in %u ms", c, GetMSTimeDiffToNow(start));
|
||||
}
|
||||
|
||||
NetworkThread<Battlenet::Session>* Battlenet::SessionManager::CreateThreads() const
|
||||
{
|
||||
return new NetworkThread<Session>[GetNetworkThreadCount()];
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace Battlenet
|
||||
|
||||
bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override;
|
||||
|
||||
void FixLegacyAuthHashes();
|
||||
|
||||
protected:
|
||||
NetworkThread<Session>* CreateThreads() const override;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "Field.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "MySQLHacks.h"
|
||||
|
||||
@@ -247,6 +248,12 @@ std::vector<uint8> Field::GetBinary() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void Field::GetBinarySizeChecked(uint8* buf, size_t length) const
|
||||
{
|
||||
ASSERT(data.value && (data.length == length), "Expected %zu-byte binary blob, got %sdata (%u bytes) instead", length, data.value ? "" : "no ", data.length);
|
||||
memcpy(buf, data.value, length);
|
||||
}
|
||||
|
||||
void Field::SetByteValue(char const* newValue, uint32 length)
|
||||
{
|
||||
// This value stores raw bytes that have to be explicitly cast later
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "Define.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
enum class DatabaseFieldTypes : uint8
|
||||
@@ -104,6 +105,14 @@ class TC_DATABASE_API Field
|
||||
char const* GetCString() const;
|
||||
std::string GetString() const;
|
||||
std::vector<uint8> GetBinary() const;
|
||||
template <size_t S>
|
||||
std::array<uint8, S> GetBinary() const
|
||||
{
|
||||
std::array<uint8, S> buf;
|
||||
GetBinarySizeChecked(buf.data(), S);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
bool IsNull() const
|
||||
{
|
||||
@@ -129,6 +138,8 @@ class TC_DATABASE_API Field
|
||||
QueryResultFieldMetadata const* meta;
|
||||
void LogWrongType(char const* getter) const;
|
||||
void SetMetadata(QueryResultFieldMetadata const* fieldMeta);
|
||||
|
||||
void GetBinarySizeChecked(uint8* buf, size_t size) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,16 +33,17 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET sessionkey = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, sessionkey FROM account WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_SV, "UPDATE account SET s = ?, v = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET session_key = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, session_key FROM account WHERE id = ? AND LENGTH(session_key) = 40", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LOGON, "UPDATE account SET salt = ?, verifier = ?, s = DEFAULT, v = DEFAULT WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_LOGON_LEGACY, "UPDATE account SET sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, "
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, "
|
||||
"bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id "
|
||||
"FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id "
|
||||
"LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 "
|
||||
"WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
|
||||
"WHERE a.username = ? AND LENGTH(a.session_key) = 64 ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
|
||||
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);
|
||||
@@ -55,14 +56,13 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_INS_REALM_CHARACTERS, "INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, sha_pass_hash, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, NOW(), ?, ?)", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, ?, NOW(), ?, ?)", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid = account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK_COUNTRY, "UPDATE account SET lock_country = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_INS_LOG, "INSERT INTO logs (time, realm, type, level, string) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET v = 0, s = 0, username = ?, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_PASSWORD, "UPDATE account SET v = 0, s = 0, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET username = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_EMAIL, "UPDATE account SET email = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_REG_EMAIL, "UPDATE account SET reg_mail = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
@@ -79,8 +79,8 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL, "SELECT SecurityLevel FROM account_access WHERE AccountID = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_GET_GMLEVEL_BY_REALMID, "SELECT SecurityLevel FROM account_access WHERE AccountID = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_GET_USERNAME_BY_ID, "SELECT username FROM account WHERE id = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT 1 FROM account WHERE id = ? AND sha_pass_hash = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT 1 FROM account WHERE username = ? AND sha_pass_hash = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT salt, verifier FROM account WHERE id = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT salt, verifier FROM account WHERE username = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_PINFO, "SELECT a.username, aa.SecurityLevel, a.email, a.reg_mail, a.last_ip, DATE_FORMAT(a.last_login, '%Y-%m-%d %T'), a.mutetime, a.mutereason, a.muteby, a.failed_logins, a.locked, a.OS FROM account a LEFT JOIN account_access aa ON (a.id = aa.AccountID AND (aa.RealmID = ? OR aa.RealmID = -1)) WHERE a.id = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM account_banned WHERE id = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_GM_ACCOUNTS, "SELECT a.username, aa.SecurityLevel FROM account a, account_access aa WHERE a.id=aa.AccountID AND aa.SecurityLevel >= ? AND (aa.realmid = -1 OR aa.realmid = ?)", CONNECTION_SYNCH);
|
||||
@@ -123,7 +123,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
|
||||
" FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account"
|
||||
" LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC);
|
||||
|
||||
@@ -38,7 +38,8 @@ enum LoginDatabaseStatements : uint32
|
||||
LOGIN_DEL_ACCOUNT_BANNED,
|
||||
LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION,
|
||||
LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION,
|
||||
LOGIN_UPD_SV,
|
||||
LOGIN_UPD_LOGON,
|
||||
LOGIN_UPD_LOGON_LEGACY,
|
||||
LOGIN_SEL_ACCOUNT_ID_BY_NAME,
|
||||
LOGIN_SEL_ACCOUNT_LIST_BY_NAME,
|
||||
LOGIN_SEL_ACCOUNT_INFO_BY_NAME,
|
||||
@@ -62,7 +63,6 @@ enum LoginDatabaseStatements : uint32
|
||||
LOGIN_UPD_ACCOUNT_LOCK_COUNTRY,
|
||||
LOGIN_INS_LOG,
|
||||
LOGIN_UPD_USERNAME,
|
||||
LOGIN_UPD_PASSWORD,
|
||||
LOGIN_UPD_EMAIL,
|
||||
LOGIN_UPD_REG_EMAIL,
|
||||
LOGIN_UPD_MUTE_TIME,
|
||||
|
||||
@@ -96,6 +96,12 @@ class TC_DATABASE_API PreparedStatementBase
|
||||
void setDouble(const uint8 index, const double value);
|
||||
void setString(const uint8 index, const std::string& value);
|
||||
void setBinary(const uint8 index, const std::vector<uint8>& value);
|
||||
template <size_t Size>
|
||||
void setBinary(const uint8 index, std::array<uint8, Size> const& value)
|
||||
{
|
||||
std::vector<uint8> vec(value.begin(), value.end());
|
||||
setBinary(index, vec);
|
||||
}
|
||||
void setNull(const uint8 index);
|
||||
|
||||
uint32 GetIndex() const { return m_index; }
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "Player.h"
|
||||
#include "Realm.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SRP6.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include "WorldSession.h"
|
||||
@@ -59,18 +60,21 @@ AccountOpResult AccountMgr::CreateAccount(std::string username, std::string pass
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT);
|
||||
|
||||
stmt->setString(0, username);
|
||||
stmt->setString(1, CalculateShaPassHash(username, password));
|
||||
stmt->setString(2, email);
|
||||
std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(username, password);
|
||||
stmt->setBinary(1, registrationData.first);
|
||||
stmt->setBinary(2, registrationData.second);
|
||||
stmt->setString(3, email);
|
||||
stmt->setString(4, email);
|
||||
|
||||
if (bnetAccountId && bnetIndex)
|
||||
{
|
||||
stmt->setUInt32(4, bnetAccountId);
|
||||
stmt->setUInt8(5, bnetIndex);
|
||||
stmt->setUInt32(5, bnetAccountId);
|
||||
stmt->setUInt8(6, bnetIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->setNull(4);
|
||||
stmt->setNull(5);
|
||||
stmt->setNull(6);
|
||||
}
|
||||
|
||||
LoginDatabase.DirectExecute(stmt); // Enforce saving, otherwise AddGroup can fail
|
||||
@@ -156,6 +160,13 @@ AccountOpResult AccountMgr::DeleteAccount(uint32 accountId)
|
||||
return AccountOpResult::AOR_OK;
|
||||
}
|
||||
|
||||
// Do not use this. Use the appropriate methods on Trinity::Crypto::SRP6 to do whatever you are trying to do.
|
||||
// See issue #25157.
|
||||
static std::string CalculateShaPassHash_DEPRECATED_DONOTUSE(std::string const& name, std::string const& password)
|
||||
{
|
||||
return ByteArrayToHexStr(Trinity::Crypto::SHA1::GetDigestOf(name, ":", password));
|
||||
}
|
||||
|
||||
AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword)
|
||||
{
|
||||
// Check if accounts exists
|
||||
@@ -176,13 +187,24 @@ AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUser
|
||||
Utf8ToUpperOnlyLatin(newPassword);
|
||||
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_USERNAME);
|
||||
|
||||
stmt->setString(0, newUsername);
|
||||
stmt->setString(1, CalculateShaPassHash(newUsername, newPassword));
|
||||
stmt->setUInt32(2, accountId);
|
||||
|
||||
stmt->setUInt32(1, accountId);
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(newUsername, newPassword);
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
|
||||
stmt->setBinary(0, registrationData.first);
|
||||
stmt->setBinary(1, registrationData.second);
|
||||
stmt->setUInt32(2, accountId);
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
{
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON_LEGACY);
|
||||
stmt->setString(0, CalculateShaPassHash_DEPRECATED_DONOTUSE(newUsername, newPassword));
|
||||
stmt->setUInt32(1, accountId);
|
||||
LoginDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
return AccountOpResult::AOR_OK;
|
||||
}
|
||||
|
||||
@@ -204,22 +226,21 @@ AccountOpResult AccountMgr::ChangePassword(uint32 accountId, std::string newPass
|
||||
|
||||
Utf8ToUpperOnlyLatin(username);
|
||||
Utf8ToUpperOnlyLatin(newPassword);
|
||||
std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(username, newPassword);
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_PASSWORD);
|
||||
|
||||
stmt->setString(0, CalculateShaPassHash(username, newPassword));
|
||||
stmt->setUInt32(1, accountId);
|
||||
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_SV);
|
||||
|
||||
stmt->setString(0, "");
|
||||
stmt->setString(1, "");
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
|
||||
stmt->setBinary(0, registrationData.first);
|
||||
stmt->setBinary(1, registrationData.second);
|
||||
stmt->setUInt32(2, accountId);
|
||||
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
{
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON_LEGACY);
|
||||
stmt->setString(0, CalculateShaPassHash_DEPRECATED_DONOTUSE(username, newPassword));
|
||||
stmt->setUInt32(1, accountId);
|
||||
LoginDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
sScriptMgr->OnPasswordChange(accountId);
|
||||
return AccountOpResult::AOR_OK;
|
||||
}
|
||||
@@ -354,10 +375,16 @@ bool AccountMgr::CheckPassword(uint32 accountId, std::string password)
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD);
|
||||
stmt->setUInt32(0, accountId);
|
||||
stmt->setString(1, CalculateShaPassHash(username, password));
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
return (result) ? true : false;
|
||||
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
|
||||
{
|
||||
Trinity::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP6::SALT_LENGTH>();
|
||||
Trinity::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<Trinity::Crypto::SRP6::VERIFIER_LENGTH>();
|
||||
if (Trinity::Crypto::SRP6::CheckLogin(username, password, salt, verifier))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AccountMgr::CheckEmail(uint32 accountId, std::string newEmail)
|
||||
@@ -387,11 +414,6 @@ uint32 AccountMgr::GetCharactersCount(uint32 accountId)
|
||||
return (result) ? (*result)[0].GetUInt64() : 0;
|
||||
}
|
||||
|
||||
std::string AccountMgr::CalculateShaPassHash(std::string const& name, std::string const& password)
|
||||
{
|
||||
return ByteArrayToHexStr(Trinity::Crypto::SHA1::GetDigestOf(name, ":", password));
|
||||
}
|
||||
|
||||
bool AccountMgr::IsBannedAccount(std::string const& name)
|
||||
{
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME);
|
||||
|
||||
@@ -74,7 +74,6 @@ class TC_GAME_API AccountMgr
|
||||
static bool GetEmail(uint32 accountId, std::string& email);
|
||||
static uint32 GetCharactersCount(uint32 accountId);
|
||||
|
||||
static std::string CalculateShaPassHash(std::string const& name, std::string const& password);
|
||||
static bool IsBannedAccount(std::string const& name);
|
||||
static bool IsPlayerAccount(uint32 gmlevel);
|
||||
static bool IsAdminAccount(uint32 gmlevel);
|
||||
|
||||
@@ -618,15 +618,15 @@ struct AccountInfo
|
||||
|
||||
explicit AccountInfo(Field* fields)
|
||||
{
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
// SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel,
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
// SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel,
|
||||
// 12 13 14
|
||||
// bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id
|
||||
// FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?)
|
||||
// LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id LEFT JOIN account r ON a.id = r.recruiter
|
||||
// WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1
|
||||
// WHERE a.username = ? AND LENGTH(a.session_key) = 40 ORDER BY aa.RealmID DESC LIMIT 1
|
||||
Game.Id = fields[0].GetUInt32();
|
||||
HexStrToByteArray(fields[1].GetString(), Game.KeyData.data());
|
||||
Game.KeyData = fields[1].GetBinary<64>();
|
||||
BattleNet.LastIP = fields[2].GetString();
|
||||
BattleNet.IsLockedToIP = fields[3].GetBool();
|
||||
BattleNet.LockCountry = fields[4].GetString();
|
||||
@@ -734,7 +734,7 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<WorldPackets::Auth::
|
||||
// This also allows to check for possible "hack" attempts on account
|
||||
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION);
|
||||
stmt->setString(0, ByteArrayToHexStr(_sessionKey));
|
||||
stmt->setBinary(0, _sessionKey);
|
||||
stmt->setUInt32(1, account.Game.Id);
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
@@ -896,7 +896,7 @@ void WorldSocket::HandleAuthContinuedSessionCallback(std::shared_ptr<WorldPacket
|
||||
uint32 accountId = uint32(key.Fields.AccountId);
|
||||
Field* fields = result->Fetch();
|
||||
std::string login = fields[0].GetString();
|
||||
HexStrToByteArray(fields[1].GetString(), _sessionKey.data());
|
||||
_sessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>();
|
||||
|
||||
Trinity::Crypto::HMAC_SHA256 hmac(_sessionKey);
|
||||
hmac.UpdateData(reinterpret_cast<uint8 const*>(&authSession->Key), sizeof(authSession->Key));
|
||||
|
||||
@@ -411,7 +411,7 @@ uint32 RealmList::JoinRealm(uint32 realmAddress, uint32 build, boost::asio::ip::
|
||||
memcpy(&keyData[32], serverSecret.data(), 32);
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO);
|
||||
stmt->setString(0, ByteArrayToHexStr(keyData));
|
||||
stmt->setBinary(0, keyData);
|
||||
stmt->setString(1, clientAddress.to_string());
|
||||
stmt->setUInt8(2, locale);
|
||||
stmt->setString(3, os);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Config.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "SRP6.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include <boost/asio/buffer.hpp>
|
||||
@@ -159,22 +160,21 @@ bool RASession::CheckPassword(const std::string& user, const std::string& pass)
|
||||
std::string safe_pass = pass;
|
||||
Utf8ToUpperOnlyLatin(safe_pass);
|
||||
|
||||
std::string hash = AccountMgr::CalculateShaPassHash(safe_user, safe_pass);
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME);
|
||||
|
||||
stmt->setString(0, safe_user);
|
||||
stmt->setString(1, hash);
|
||||
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
|
||||
{
|
||||
TC_LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str());
|
||||
return false;
|
||||
Trinity::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP6::SALT_LENGTH>();
|
||||
Trinity::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<Trinity::Crypto::SRP6::VERIFIER_LENGTH>();
|
||||
|
||||
if (Trinity::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier))
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
TC_LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RASession::ProcessCommand(std::string& command)
|
||||
|
||||
Reference in New Issue
Block a user