diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/bnetserver/Main.cpp | 2 | ||||
-rw-r--r-- | src/server/bnetserver/REST/LoginRESTService.cpp | 223 | ||||
-rw-r--r-- | src/server/bnetserver/REST/LoginRESTService.h | 10 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/LoginDatabase.cpp | 6 |
4 files changed, 158 insertions, 83 deletions
diff --git a/src/server/bnetserver/Main.cpp b/src/server/bnetserver/Main.cpp index 5957ae980aa..ba25132f707 100644 --- a/src/server/bnetserver/Main.cpp +++ b/src/server/bnetserver/Main.cpp @@ -163,7 +163,7 @@ int main(int argc, char** argv) return 1; } - if (!sLoginService.Start(*ioService)) + if (!sLoginService.Start(ioService.get())) { TC_LOG_ERROR("server.bnetserver", "Failed to initialize login service"); return 1; diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp index a94ee27666e..c1ad26bde8f 100644 --- a/src/server/bnetserver/REST/LoginRESTService.cpp +++ b/src/server/bnetserver/REST/LoginRESTService.cpp @@ -18,7 +18,9 @@ #include "LoginRESTService.h" #include "Configuration/Config.h" #include "DatabaseEnv.h" +#include "Errors.h" #include "ProtobufJSON.h" +#include "Optional.h" #include "Realm.h" #include "SessionManager.h" #include "SHA1.h" @@ -31,6 +33,37 @@ int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; } +class AsyncLoginRequest +{ +public: + AsyncLoginRequest(std::shared_ptr<soap> client) + : _client(std::move(client)) { } + + AsyncLoginRequest(AsyncLoginRequest const&) = delete; + AsyncLoginRequest& operator=(AsyncLoginRequest const&) = delete; + AsyncLoginRequest(AsyncLoginRequest&&) = default; + AsyncLoginRequest& operator=(AsyncLoginRequest&&) = default; + + bool InvokeIfReady() + { + ASSERT(_callback.is_initialized()); + return _callback->InvokeIfReady() == QueryCallback::Completed; + } + + soap* GetClient() const { return _client.get(); } + void SetCallback(QueryCallback val) { _callback = std::move(val); } + std::unique_ptr<Battlenet::Session::AccountInfo>& GetResult() { return _result; } + void SetResult(std::unique_ptr<Battlenet::Session::AccountInfo> val) { _result = std::move(val); } + +private: + std::shared_ptr<soap> _client; + Optional<QueryCallback> _callback; + std::unique_ptr<Battlenet::Session::AccountInfo> _result; +}; + +/* Codes 600 to 999 are user definable */ +#define SOAP_CUSTOM_STATUS_ASYNC 600 + int32 handle_get_plugin(soap* soapClient) { return sLoginService.HandleGet(soapClient); @@ -41,8 +74,9 @@ int32 handle_post_plugin(soap* soapClient) return sLoginService.HandlePost(soapClient); } -bool LoginRESTService::Start(boost::asio::io_service& ioService) +bool LoginRESTService::Start(boost::asio::io_service* ioService) { + _ioService = ioService; _bindIP = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0"); _port = sConfigMgr->GetIntDefault("LoginREST.Port", 8081); if (_port < 0 || _port > 0xFFFF) @@ -52,7 +86,7 @@ bool LoginRESTService::Start(boost::asio::io_service& ioService) } boost::system::error_code ec; - boost::asio::ip::tcp::resolver resolver(ioService); + boost::asio::ip::tcp::resolver resolver(*ioService); boost::asio::ip::tcp::resolver::iterator end; std::string configuredAddress = sConfigMgr->GetStringDefault("LoginREST.ExternalAddress", "127.0.0.1"); @@ -97,7 +131,7 @@ bool LoginRESTService::Start(boost::asio::io_service& ioService) input->set_type("submit"); input->set_label("Log In"); - _loginTicketCleanupTimer = new boost::asio::deadline_timer(ioService); + _loginTicketCleanupTimer = new boost::asio::deadline_timer(*ioService); _loginTicketCleanupTimer->expires_from_now(boost::posix_time::seconds(10)); _loginTicketCleanupTimer->async_wait(std::bind(&LoginRESTService::CleanupLoginTickets, this, std::placeholders::_1)); @@ -172,10 +206,13 @@ void LoginRESTService::Run() TC_LOG_DEBUG("server.rest", "Accepted connection from IP=%s", address.to_string().c_str()); - std::thread([soapClient] + _ioService->post([soapClient]() { - soap_serve(soapClient.get()); - }).detach(); + soapClient->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count + soap_begin(soapClient.get()); + if (soap_begin_recv(soapClient.get()) != SOAP_CUSTOM_STATUS_ASYNC) + soap_closesock(soapClient.get()); + }); } // and release the context handle here - soap does not own it so it should not free it on exit @@ -214,7 +251,6 @@ int32 LoginRESTService::HandlePost(soap* soapClient) soap_http_body(soapClient, &buf, &len); Battlenet::JSON::Login::LoginForm loginForm; - Battlenet::JSON::Login::LoginResult loginResult; if (!JSON::Deserialize(buf, &loginForm)) { if (soap_register_plugin_arg(soapClient, &ResponseCodePlugin::Init, nullptr) != SOAP_OK) @@ -225,6 +261,7 @@ int32 LoginRESTService::HandlePost(soap* soapClient) responseCode->ErrorCode = 400; + Battlenet::JSON::Login::LoginResult loginResult; loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN); loginResult.set_error_code("UNABLE_TO_DECODE"); loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later."); @@ -248,99 +285,127 @@ int32 LoginRESTService::HandlePost(soap* soapClient) PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_INFO); stmt->setString(0, login); - if (PreparedQueryResult result = LoginDatabase.Query(stmt)) + std::string sentPasswordHash = CalculateShaPassHash(login, password); + + std::shared_ptr<AsyncLoginRequest> request = std::make_shared<AsyncLoginRequest>(*reinterpret_cast<std::shared_ptr<soap>*>(soapClient->user)); + request->SetCallback(LoginDatabase.AsyncQuery(stmt) + .WithChainingPreparedCallback([request, login, sentPasswordHash](QueryCallback& callback, PreparedQueryResult result) { - std::string pass_hash = result->Fetch()[13].GetString(); + if (result) + { + std::string pass_hash = result->Fetch()[13].GetString(); - std::unique_ptr<Battlenet::Session::AccountInfo> accountInfo = Trinity::make_unique<Battlenet::Session::AccountInfo>(); - accountInfo->LoadResult(result); + request->SetResult(Trinity::make_unique<Battlenet::Session::AccountInfo>()); + request->GetResult()->LoadResult(result); - if (CalculateShaPassHash(login, std::move(password)) == pass_hash) - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID); - stmt->setUInt32(0, accountInfo->Id); - if (PreparedQueryResult characterCountsResult = LoginDatabase.Query(stmt)) + if (sentPasswordHash == pass_hash) + { + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID); + stmt->setUInt32(0, request->GetResult()->Id); + callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); + return; + } + else if (!request->GetResult()->IsBanned) { - do + std::string ip_address = boost::asio::ip::address_v4(request->GetClient()->ip).to_string(); + uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0)); + + if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false)) + TC_LOG_DEBUG("server.rest", "[%s, Account %s, Id %u] Attempted to connect with wrong password!", ip_address.c_str(), login.c_str(), request->GetResult()->Id); + + if (maxWrongPassword) { - Field* fields = characterCountsResult->Fetch(); - accountInfo->GameAccounts[fields[0].GetUInt32()] - .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8(); + SQLTransaction trans = LoginDatabase.BeginTransaction(); + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_FAILED_LOGINS); + stmt->setUInt32(0, request->GetResult()->Id); + trans->Append(stmt); + + ++request->GetResult()->FailedLogins; + + TC_LOG_DEBUG("server.rest", "MaxWrongPass : %u, failed_login : %u", maxWrongPassword, request->GetResult()->Id); + + if (request->GetResult()->FailedLogins >= maxWrongPassword) + { + BanMode banType = BanMode(sConfigMgr->GetIntDefault("WrongPass.BanType", uint16(BanMode::BAN_IP))); + int32 banTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600); + + if (banType == BanMode::BAN_ACCOUNT) + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_ACCOUNT_AUTO_BANNED); + stmt->setUInt32(0, request->GetResult()->Id); + } + else + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); + stmt->setString(0, ip_address); + } + + stmt->setUInt32(1, banTime); + trans->Append(stmt); + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_RESET_FAILED_LOGINS); + stmt->setUInt32(0, request->GetResult()->Id); + trans->Append(stmt); + } - } while (characterCountsResult->NextRow()); + LoginDatabase.CommitTransaction(trans); + } } + } + Battlenet::JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(Battlenet::JSON::Login::DONE); + sLoginService.SendResponse(request->GetClient(), loginResult); + }) + .WithChainingPreparedCallback([request](QueryCallback& callback, PreparedQueryResult characterCountsResult) + { + if (characterCountsResult) + { + do + { + Field* fields = characterCountsResult->Fetch(); + request->GetResult()->GameAccounts[fields[0].GetUInt32()] + .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8(); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS); - stmt->setUInt32(0, accountInfo->Id); - if (PreparedQueryResult lastPlayerCharactersResult = LoginDatabase.Query(stmt)) + } while (characterCountsResult->NextRow()); + } + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS); + stmt->setUInt32(0, request->GetResult()->Id); + callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); + }) + .WithPreparedCallback([request](PreparedQueryResult lastPlayerCharactersResult) + { + if (lastPlayerCharactersResult) + { + do { Field* fields = lastPlayerCharactersResult->Fetch(); Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() }; - Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()] + Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = request->GetResult()->GameAccounts[fields[0].GetUInt32()] .LastPlayedCharacters[realmId.GetSubRegionAddress()]; lastPlayedCharacter.RealmId = realmId; lastPlayedCharacter.CharacterName = fields[4].GetString(); lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64(); lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32(); - } - - BigNumber ticket; - ticket.SetRand(20 * 8); - - loginResult.set_login_ticket("TC-" + ByteArrayToHexStr(ticket.AsByteArray(20).get(), 20)); - AddLoginTicket(loginResult.login_ticket(), std::move(accountInfo)); + } while (lastPlayerCharactersResult->NextRow()); } - else if (!accountInfo->IsBanned) - { - uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0)); - - if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false)) - TC_LOG_DEBUG("server.rest", "[%s, Account %s, Id %u] Attempted to connect with wrong password!", ip_address.c_str(), login.c_str(), accountInfo->Id); - - if (maxWrongPassword) - { - SQLTransaction trans = LoginDatabase.BeginTransaction(); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_FAILED_LOGINS); - stmt->setUInt32(0, accountInfo->Id); - trans->Append(stmt); - - ++accountInfo->FailedLogins; - - TC_LOG_DEBUG("server.rest", "MaxWrongPass : %u, failed_login : %u", maxWrongPassword, accountInfo->Id); - - if (accountInfo->FailedLogins >= maxWrongPassword) - { - BanMode banType = BanMode(sConfigMgr->GetIntDefault("WrongPass.BanType", uint16(BanMode::BAN_IP))); - int32 banTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600); - if (banType == BanMode::BAN_ACCOUNT) - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_ACCOUNT_AUTO_BANNED); - stmt->setUInt32(0, accountInfo->Id); - } - else - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); - stmt->setString(0, ip_address); - } + BigNumber ticket; + ticket.SetRand(20 * 8); - stmt->setUInt32(1, banTime); - trans->Append(stmt); + Battlenet::JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(Battlenet::JSON::Login::DONE); + loginResult.set_login_ticket("TC-" + ByteArrayToHexStr(ticket.AsByteArray(20).get(), 20)); + sLoginService.SendResponse(request->GetClient(), loginResult); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_RESET_FAILED_LOGINS); - stmt->setUInt32(0, accountInfo->Id); - trans->Append(stmt); - } + sLoginService.AddLoginTicket(loginResult.login_ticket(), std::move(request->GetResult())); + })); - LoginDatabase.CommitTransaction(trans); - } - } - } + _ioService->post(std::bind(&LoginRESTService::HandleAsyncRequest, this, std::move(request))); - loginResult.set_authentication_state(Battlenet::JSON::Login::DONE); - return SendResponse(soapClient, loginResult); + return SOAP_CUSTOM_STATUS_ASYNC; } int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message const& response) @@ -352,6 +417,12 @@ int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message return soap_end_send(soapClient); } +void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncLoginRequest> request) +{ + if (!request->InvokeIfReady()) + _ioService->post(std::bind(&LoginRESTService::HandleAsyncRequest, this, std::move(request))); +} + std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password) { SHA256Hash email; diff --git a/src/server/bnetserver/REST/LoginRESTService.h b/src/server/bnetserver/REST/LoginRESTService.h index 6616812c30d..ff9729e3333 100644 --- a/src/server/bnetserver/REST/LoginRESTService.h +++ b/src/server/bnetserver/REST/LoginRESTService.h @@ -18,8 +18,8 @@ #ifndef LoginRESTService_h__ #define LoginRESTService_h__ -#include "Session.h" #include "Define.h" +#include "Session.h" #include "Login.pb.h" #include <boost/asio/io_service.hpp> #include <boost/asio/ip/tcp.hpp> @@ -29,6 +29,7 @@ #include <mutex> #include <thread> +class AsyncLoginRequest; struct soap; struct soap_plugin; @@ -41,11 +42,11 @@ enum class BanMode class LoginRESTService { public: - LoginRESTService() : _stopped(false), _port(0), _loginTicketCleanupTimer(nullptr) { } + LoginRESTService() : _ioService(nullptr), _stopped(false), _port(0), _loginTicketCleanupTimer(nullptr) { } static LoginRESTService& Instance(); - bool Start(boost::asio::io_service& ioService); + bool Start(boost::asio::io_service* ioService); void Stop(); boost::asio::ip::tcp::endpoint const& GetAddressForClient(boost::asio::ip::address const& address) const; @@ -63,6 +64,8 @@ private: int32 SendResponse(soap* soapClient, google::protobuf::Message const& response); + void HandleAsyncRequest(std::shared_ptr<AsyncLoginRequest> request); + std::string CalculateShaPassHash(std::string const& name, std::string const& password); void AddLoginTicket(std::string const& id, std::unique_ptr<Battlenet::Session::AccountInfo> accountInfo); @@ -99,6 +102,7 @@ private: char const* ContentType; }; + boost::asio::io_service* _ioService; std::thread _thread; std::atomic<bool> _stopped; Battlenet::JSON::Login::FormInputs _formInputs; diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index fe2d913538d..7c49b1846fd 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -121,12 +121,12 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_BNET_ACCOUNT_INFO, "SELECT " BnetAccountInfo ", " BnetGameAccountInfo ", ba.sha_pass_hash" " 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.id AND aa.RealmID = -1 WHERE ba.email = ? ORDER BY a.id", CONNECTION_SYNCH); + " LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID = -1 WHERE ba.email = ? 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_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_SYNCH); - 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_SYNCH); + 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); PrepareStatement(LOGIN_DEL_BNET_LAST_PLAYER_CHARACTERS, "DELETE FROM account_last_played_character WHERE accountId = ? AND region = ? AND battlegroup = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_BNET_LAST_PLAYER_CHARACTERS, "INSERT INTO account_last_played_character (accountId, region, battlegroup, realmId, characterName, characterGUID, lastPlayedTime) VALUES (?,?,?,?,?,?,?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`sha_pass_hash`) VALUES (?, ?)", CONNECTION_SYNCH); |