aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/bnetserver/Main.cpp2
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.cpp223
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.h10
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.cpp6
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);