35#include <rapidjson/document.h>
41 Field* fields = result->Fetch();
51 static constexpr uint32 GameAccountFieldsOffset = 8;
55 GameAccounts[result->Fetch()[GameAccountFieldsOffset].GetUInt32()].LoadResult(result->Fetch() + GameAccountFieldsOffset);
57 }
while (result->NextRow());
66 IsPermanenetlyBanned = fields[3].
GetUInt32() != 0;
67 IsBanned = IsPermanenetlyBanned || UnbanDate > time(
nullptr);
70 std::size_t hashPos =
Name.find(
'#');
71 if (hashPos != std::string::npos)
72 DisplayName = std::string(
"WoW") +
Name.substr(hashPos + 1);
88 underlying_stream().async_handshake(boost::asio::ssl::stream_base::server,
89 [sess = shared_from_this()](boost::system::error_code
const& error) { sess->HandshakeHandler(error); });
94 std::string ip_address = GetRemoteIpAddress().to_string();
95 TC_LOG_TRACE(
"session",
"{} Accepted connection", GetClientInfo());
104 .WithPreparedCallback([sess = shared_from_this()](
PreparedQueryResult result) { sess->CheckIpCallback(std::move(result)); }));
114 Field* fields = result->Fetch();
115 if (fields[0].GetUInt64() != 0)
118 }
while (result->NextRow());
122 TC_LOG_DEBUG(
"session",
"{} tries to log in using banned IP!", GetClientInfo());
136 _queryProcessor.ProcessReadyCallbacks();
146 QueuePacket(std::move(*packet));
154 header.
set_size(response->ByteSize());
156 uint16 headerSize = header.ByteSize();
160 packet.
Write(&headerSize,
sizeof(headerSize));
166 response->SerializeToArray(ptr, response->GetCachedSize());
178 uint16 headerSize = header.ByteSize();
182 packet.
Write(&headerSize,
sizeof(headerSize));
196 header.
set_size(request->ByteSize());
199 uint16 headerSize = header.ByteSize();
203 packet.
Write(&headerSize,
sizeof(headerSize));
209 request->SerializeToArray(ptr, request->GetCachedSize());
216 if (logonRequest->program() !=
"WoW")
218 TC_LOG_DEBUG(
"session",
"[Battlenet::LogonRequest] {} attempted to log in with game other than WoW (using {})!", GetClientInfo(), logonRequest->program());
222 if (logonRequest->platform() !=
"Win" && logonRequest->platform() !=
"Wn64" && logonRequest->platform() !=
"Mc64")
224 TC_LOG_DEBUG(
"session",
"[Battlenet::LogonRequest] {} attempted to log in from an unsupported platform (using {})!", GetClientInfo(), logonRequest->platform());
230 TC_LOG_DEBUG(
"session",
"[Battlenet::LogonRequest] {} attempted to log in with unsupported locale (using {})!", GetClientInfo(), logonRequest->locale());
234 _locale = logonRequest->locale();
235 _os = logonRequest->platform();
236 _build = logonRequest->application_version();
238 _timezoneOffset = [&]
240 if (!logonRequest->has_device_id())
243 rapidjson::Document doc;
244 doc.Parse(logonRequest->device_id());
245 if (doc.HasParseError())
248 auto itr = doc.FindMember(
"UTCO");
249 if (itr == doc.MemberEnd())
252 if (!itr->value.IsUint())
258 if (logonRequest->has_cached_web_credentials())
259 return VerifyWebCredentials(logonRequest->cached_web_credentials(), continuation);
270 if (verifyWebCredentialsRequest->has_web_credentials())
271 return VerifyWebCredentials(verifyWebCredentialsRequest->web_credentials(), continuation);
281 if (request->program() != 0x576F57)
283 auto asPrintable = [](
char c) {
return std::isprint(c) ? c :
' '; };
285 TC_LOG_DEBUG(
"session",
"[Battlenet::HandleGenerateWebCredentials] {} attempted to generate web cretentials with game other than WoW (using {}{}{}{})!",
286 GetClientInfo(), asPrintable((request->program() >> 24) & 0xFF), asPrintable((request->program() >> 16) & 0xFF),
287 asPrintable((request->program() >> 8) & 0xFF), asPrintable(request->program() & 0xFF));
294 _queryProcessor.AddCallback(
LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([
this, asyncContinuation = std::move(continuation)](
PreparedQueryResult result)
297 Battlenet::Services::Authentication asyncContinuationService(this);
298 authentication::v1::GenerateWebCredentialsResponse response;
299 response.set_web_credentials((*result)[0].GetCString());
300 asyncContinuation(&asyncContinuationService, ERROR_OK, &response);
308 if (webCredentials.empty())
314 std::function<void(
ServiceBase*,
uint32, ::google::protobuf::Message
const*)> asyncContinuation = std::move(continuation);
315 std::shared_ptr<AccountInfo> accountInfo = std::make_shared<AccountInfo>();
318 Battlenet::Services::Authentication asyncContinuationService(this);
322 asyncContinuation(&asyncContinuationService, ERROR_DENIED, &response);
326 accountInfo->LoadResult(result);
328 if (accountInfo->LoginTicketExpiry < time(
nullptr))
330 asyncContinuation(&asyncContinuationService,
ERROR_TIMED_OUT, &response);
340 if (characterCountsResult)
344 Field* fields = characterCountsResult->Fetch();
345 accountInfo->GameAccounts[fields[0].
GetUInt32()]
348 }
while (characterCountsResult->NextRow());
355 .WithPreparedCallback([
this, accountInfo, asyncContinuation](
PreparedQueryResult lastPlayerCharactersResult)
357 if (lastPlayerCharactersResult)
361 Field* fields = lastPlayerCharactersResult->Fetch();
364 .LastPlayedCharacters[realmId.GetSubRegionAddress()];
366 lastPlayedCharacter.
RealmId = realmId;
371 }
while (lastPlayerCharactersResult->NextRow());
374 _accountInfo = accountInfo;
378 std::string ip_address = GetRemoteIpAddress().to_string();
381 if (_accountInfo->IsLockedToIP)
383 TC_LOG_DEBUG(
"session",
"[Session::HandleVerifyWebCredentials] Account '{}' is locked to IP - '{}' is logging in from '{}'",
384 _accountInfo->Login, _accountInfo->LastIP, ip_address);
386 if (_accountInfo->LastIP != ip_address)
395 _ipCountry = location->CountryCode;
397 TC_LOG_DEBUG(
"session",
"[Session::HandleVerifyWebCredentials] Account '{}' is not locked to ip", _accountInfo->Login);
398 if (_accountInfo->LockCountry.empty() || _accountInfo->LockCountry ==
"00")
399 TC_LOG_DEBUG(
"session",
"[Session::HandleVerifyWebCredentials] Account '{}' is not locked to country", _accountInfo->Login);
400 else if (!_accountInfo->LockCountry.empty() && !_ipCountry.empty())
402 TC_LOG_DEBUG(
"session",
"[Session::HandleVerifyWebCredentials] Account '{}' is locked to country: '{}' Player country is '{}'",
403 _accountInfo->Login, _accountInfo->LockCountry, _ipCountry);
405 if (_ipCountry != _accountInfo->LockCountry)
414 if (_accountInfo->IsBanned)
416 if (_accountInfo->IsPermanenetlyBanned)
418 TC_LOG_DEBUG(
"session",
"{} [Session::HandleVerifyWebCredentials] Banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
424 TC_LOG_DEBUG(
"session",
"{} [Session::HandleVerifyWebCredentials] Temporarily banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
430 authentication::v1::LogonResult logonResult;
431 logonResult.set_error_code(0);
432 logonResult.mutable_account_id()->set_low(_accountInfo->Id);
433 logonResult.mutable_account_id()->set_high(
UI64LIT(0x100000000000000));
434 for (
auto const& [
id, gameAccountInfo] : accountInfo->GameAccounts)
436 EntityId* gameAccountId = logonResult.add_game_account_id();
437 gameAccountId->
set_low(gameAccountInfo.Id);
441 if (!_ipCountry.empty())
442 logonResult.set_geoip_country(_ipCountry);
444 std::array<uint8, 64> k = Trinity::Crypto::GetRandomBytes<64>();
445 logonResult.set_session_key(k.data(), 64);
449 asyncContinuation(&asyncContinuationService,
ERROR_OK, &response);
450 Service<authentication::v1::AuthenticationListener>(
this).OnLogonComplete(&logonResult);
461 if (request->options().field_privacy_info())
463 response->mutable_state()->mutable_privacy_info()->set_is_using_rid(
false);
464 response->mutable_state()->mutable_privacy_info()->set_is_visible_for_view_friends(
false);
465 response->mutable_state()->mutable_privacy_info()->set_is_hidden_from_friend_finder(
true);
467 response->mutable_tags()->set_privacy_info_tag(0xD7CA834D);
478 if (request->options().field_game_level_info())
480 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
481 if (itr != _accountInfo->GameAccounts.end())
483 response->mutable_state()->mutable_game_level_info()->set_name(itr->second.DisplayName);
484 response->mutable_state()->mutable_game_level_info()->set_program(5730135);
487 response->mutable_tags()->set_game_level_info_tag(0x5C46D483);
490 if (request->options().field_game_status())
492 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
493 if (itr != _accountInfo->GameAccounts.end())
495 response->mutable_state()->mutable_game_status()->set_is_suspended(itr->second.IsBanned);
496 response->mutable_state()->mutable_game_status()->set_is_banned(itr->second.IsPermanenetlyBanned);
497 response->mutable_state()->mutable_game_status()->set_suspension_expires(
uint64(itr->second.UnbanDate) * 1000000);
500 response->mutable_state()->mutable_game_status()->set_program(5730135);
501 response->mutable_tags()->set_game_status_tag(0x98B75F99);
521 std::unordered_map<std::string, Variant const*>
params;
522 auto removeSuffix = [](std::string
const& string) -> std::string
524 size_t pos =
string.rfind(
'_');
525 if (pos != std::string::npos)
526 return string.substr(0, pos);
531 for (
int32 i = 0; i < request->attribute_size(); ++i)
533 Attribute const& attr = request->attribute(i);
534 if (strstr(attr.
name().c_str(),
"Command_") == attr.
name().c_str())
545 TC_LOG_ERROR(
"session.rpc",
"{} sent ClientRequest with no command.", GetClientInfo());
549 auto itr = ClientRequestHandlers.find(removeSuffix(command->
name()));
550 if (itr == ClientRequestHandlers.end())
552 TC_LOG_ERROR(
"session.rpc",
"{} sent ClientRequest with unknown command {}.", GetClientInfo(), removeSuffix(command->
name()));
556 return (this->*itr->second)(
params, response);
561 auto itr =
params.find(paramName);
562 return itr !=
params.end() ? itr->second :
nullptr;
570 std::size_t jsonStart = identity->blob_value().find(
':');
571 if (jsonStart != std::string::npos &&
::JSON::Deserialize(identity->blob_value().substr(jsonStart + 1), &data))
573 auto itr = _accountInfo->GameAccounts.find(data.
gameaccountid());
574 if (itr != _accountInfo->GameAccounts.end())
575 _gameAccountInfo = &itr->second;
579 if (!_gameAccountInfo)
582 if (_gameAccountInfo->IsPermanenetlyBanned)
584 else if (_gameAccountInfo->IsBanned)
587 bool clientInfoOk =
false;
591 std::size_t jsonStart = clientInfo->blob_value().find(
':');
592 if (jsonStart != std::string::npos &&
::JSON::Deserialize(clientInfo->blob_value().substr(jsonStart + 1), &data))
594 if (_clientSecret.size() == data.
info().
secret().size())
597 memcpy(_clientSecret.data(), data.
info().
secret().data(), _clientSecret.size());
606 stmt->
setString(0, GetRemoteIpAddress().to_string());
613 Attribute* attribute = response->add_attribute();
614 attribute->
set_name(
"Param_RealmListTicket");
624 auto lastPlayerChar = _gameAccountInfo->LastPlayedCharacters.find(subRegion->string_value());
625 if (lastPlayerChar != _gameAccountInfo->LastPlayedCharacters.end())
627 std::vector<uint8> compressed =
sRealmList->GetRealmEntryJSON(lastPlayerChar->second.RealmId, _build);
629 if (compressed.empty())
632 Attribute* attribute = response->add_attribute();
633 attribute->
set_name(
"Param_RealmEntry");
636 attribute = response->add_attribute();
637 attribute->
set_name(
"Param_CharacterName");
640 attribute = response->add_attribute();
641 attribute->
set_name(
"Param_CharacterGUID");
644 attribute = response->add_attribute();
645 attribute->
set_name(
"Param_LastPlayedTime");
657 if (!_gameAccountInfo)
660 std::string subRegionId;
662 subRegionId = subRegion->string_value();
664 std::vector<uint8> compressed =
sRealmList->GetRealmList(_build, subRegionId);
666 if (compressed.empty())
669 Attribute* attribute = response->add_attribute();
670 attribute->
set_name(
"Param_RealmList");
674 for (
auto const& characterCount : _gameAccountInfo->CharacterCounts)
678 countEntry->
set_count(characterCount.second);
681 std::string json =
"JSONRealmCharacterCountList:" +
::JSON::Serialize(realmCharacterCounts);
683 uLongf compressedLength = compressBound(json.length());
684 compressed.resize(4 + compressedLength);
685 *
reinterpret_cast<uint32*
>(compressed.data()) = json.length() + 1;
687 if (compress(compressed.data() + 4, &compressedLength,
reinterpret_cast<uint8 const*
>(json.c_str()), json.length() + 1) != Z_OK)
690 attribute = response->add_attribute();
691 attribute->
set_name(
"Param_CharacterCountList");
699 return sRealmList->JoinRealm(realmAddress->uint_value(), _build, GetRemoteIpAddress(), _clientSecret,
GetLocaleByName(_locale),
700 _os, _timezoneOffset, _gameAccountInfo->Name, response);
710 if (request->attribute_key().find(
"Command_RealmListRequest_v1") == 0)
723 TC_LOG_ERROR(
"session",
"{} SSL Handshake failed {}", GetClientInfo(), error.message());
731template<
bool(Battlenet::Session::*processMethod)(), MessageBuffer Battlenet::Session::*outputBuffer>
753 if (!(session->*processMethod)())
770 if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderLengthHandler, &Battlenet::Session::_headerLengthBuffer>(
this, packet))
773 if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderHandler, &Battlenet::Session::_headerBuffer>(
this, packet))
776 if (!PartialProcessPacket<&Battlenet::Session::ReadDataHandler, &Battlenet::Session::_packetBuffer>(
this, packet))
779 _headerLengthBuffer.Reset();
780 _headerBuffer.Reset();
788 uint16 len = *
reinterpret_cast<uint16*
>(_headerLengthBuffer.GetReadPointer());
790 _headerBuffer.Resize(len);
797 if (!header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize()))
800 _packetBuffer.Resize(header.
size());
807 bool parseSuccess = header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize());
816 auto itr = _responseCallbacks.find(header.
token());
817 if (itr != _responseCallbacks.end())
819 itr->second(std::move(_packetBuffer));
820 _responseCallbacks.erase(header.
token());
823 _packetBuffer.Reset();
831 std::ostringstream stream;
832 stream <<
'[' << GetRemoteIpAddress() <<
':' << GetRemotePort();
833 if (_accountInfo && !_accountInfo->Login.empty())
834 stream <<
", Account: " << _accountInfo->Login;
836 if (_gameAccountInfo)
837 stream <<
", Game account: " << _gameAccountInfo->Name;
@ ERROR_GAME_ACCOUNT_BANNED
@ ERROR_RISK_ACCOUNT_LOCKED
@ ERROR_RPC_NOT_IMPLEMENTED
@ ERROR_RPC_MALFORMED_REQUEST
@ ERROR_WOW_SERVICES_INVALID_JOIN_TICKET
@ ERROR_UTIL_SERVER_INVALID_IDENTITY_ARGS
@ ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE
@ ERROR_USER_SERVER_BAD_WOW_ACCOUNT
@ ERROR_GAME_ACCOUNT_SUSPENDED
@ ERROR_WOW_SERVICES_DENIED_REALM_LIST_TICKET
@ ERROR_UTIL_SERVER_UNKNOWN_REALM
void EndianConvertReverse(T &)
LocaleConstant GetLocaleByName(std::string_view name)
constexpr bool IsValidLocale(LocaleConstant locale)
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
std::unordered_set< uint32 > params[2]
#define TC_LOG_DEBUG(filterType__,...)
#define TC_LOG_TRACE(filterType__,...)
#define TC_LOG_ERROR(filterType__,...)
@ LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID
@ LOGIN_UPD_BNET_LAST_LOGIN_INFO
@ LOGIN_SEL_BNET_EXISTING_AUTHENTICATION_BY_ID
@ LOGIN_DEL_EXPIRED_IP_BANS
@ LOGIN_SEL_BNET_ACCOUNT_INFO
@ LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS
if(posix_memalign(&__mallocedMemory, __align, __size)) return NULL
#define sServiceDispatcher
bool PartialProcessPacket(Battlenet::Session *session, MessageBuffer &inputBuffer)
static Variant const * GetParam(std::unordered_map< std::string, Variant const * > const ¶ms, char const *paramName)
uint32 VerifyWebCredentials(std::string const &webCredentials, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Session(boost::asio::ip::tcp::socket &&socket)
uint32 JoinRealm(std::unordered_map< std::string, Variant const * > const ¶ms, game_utilities::v1::ClientResponse *response)
std::string GetClientInfo() const
uint32 HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const *request, std::function< void(ServiceBase *, uint32, google::protobuf::Message const *)> &continuation)
static std::unordered_map< std::string, ClientRequestHandler > const ClientRequestHandlers
void SendResponse(uint32 token, pb::Message const *response)
uint32 GetRealmList(std::unordered_map< std::string, Variant const * > const ¶ms, game_utilities::v1::ClientResponse *response)
uint32 HandleProcessClientRequest(game_utilities::v1::ClientRequest const *request, game_utilities::v1::ClientResponse *response)
uint32 HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const *verifyWebCredentialsRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
void CheckIpCallback(PreparedQueryResult result)
void HandshakeHandler(boost::system::error_code const &error)
uint32 GetLastCharPlayed(std::unordered_map< std::string, Variant const * > const ¶ms, game_utilities::v1::ClientResponse *response)
uint32 HandleGetGameAccountState(account::v1::GetGameAccountStateRequest const *request, account::v1::GetGameAccountStateResponse *response)
void ReadHandler() override
bool ReadHeaderLengthHandler()
void AsyncWrite(MessageBuffer *packet)
uint32 HandleGetAllValuesForAttribute(game_utilities::v1::GetAllValuesForAttributeRequest const *request, game_utilities::v1::GetAllValuesForAttributeResponse *response)
std::array< uint8, 32 > _clientSecret
GameAccountInfo * _gameAccountInfo
uint32 HandleLogon(authentication::v1::LogonRequest const *logonRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
void SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const *request, std::function< void(MessageBuffer)> callback)
MessageBuffer _headerLengthBuffer
uint32 HandleGetAccountState(account::v1::GetAccountStateRequest const *request, account::v1::GetAccountStateResponse *response)
uint32 GetRealmListTicket(std::unordered_map< std::string, Variant const * > const ¶ms, game_utilities::v1::ClientResponse *response)
std::shared_ptr< AccountInfo > _accountInfo
Class used to access individual fields of database query result.
std::string GetString() const
void set_wowrealmaddress(::google::protobuf::uint32 value)
void set_count(::google::protobuf::uint32 value)
inline ::JSON::RealmList::RealmCharacterCountEntry * add_counts()
inline ::google::protobuf::uint32 gameaccountid() const
void Resize(size_type bytes)
size_type GetRemainingSpace() const
void ReadCompleted(size_type bytes)
void WriteCompleted(size_type bytes)
size_type GetActiveSize() const
uint8 * GetWritePointer()
void Write(void const *data, std::size_t size)
void setUInt8(const uint8 index, const uint8 value)
void setUInt32(const uint8 index, const uint32 value)
void setString(const uint8 index, const std::string &value)
void SetNextQuery(QueryCallback &&next)
const ::bgs::protocol::Variant & value() const
const ::std::string & name() const
inline ::bgs::protocol::Variant * mutable_value()
void set_name(const ::std::string &value)
void set_high(::google::protobuf::uint64 value)
void set_low(::google::protobuf::uint64 value)
void set_int_value(::google::protobuf::int64 value)
void set_blob_value(const ::std::string &value)
void set_string_value(const ::std::string &value)
void set_payload_type(const ::std::string &value)
void set_payload(const ::std::string &value)
TC_SHARED_API bool Deserialize(std::string const &json, google::protobuf::Message *message)
TC_SHARED_API std::string Serialize(google::protobuf::Message const &message)
Minutes GetOffsetByHash(uint32 hash)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
void Update(VignetteData &vignette, WorldObject const *owner)
bool IsPermanenetlyBanned
std::unordered_map< uint32, GameAccountInfo > GameAccounts
void LoadResult(PreparedQueryResult result)
void LoadResult(Field const *fields)
std::string CharacterName
Battlenet::RealmHandle RealmId