TrinityCore
Session.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Session.h"
20#include "ByteConverter.h"
21#include "CryptoRandom.h"
22#include "DatabaseEnv.h"
23#include "Errors.h"
24#include "Hash.h"
25#include "IPLocation.h"
26#include "LoginRESTService.h"
27#include "MapUtils.h"
28#include "ProtobufJSON.h"
29#include "QueryCallback.h"
30#include "RealmList.h"
31#include "RealmList.pb.h"
32#include "ServiceDispatcher.h"
33#include "SslContext.h"
34#include "Timezone.h"
35#include <rapidjson/document.h>
36#include <zlib.h>
37
39{
40 // ba.id, ba.email, ba.locked, ba.lock_country, ba.last_ip, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab WHERE email = ?
41 Field* fields = result->Fetch();
42 Id = fields[0].GetUInt32();
43 Login = fields[1].GetString();
44 IsLockedToIP = fields[2].GetBool();
45 LockCountry = fields[3].GetString();
46 LastIP = fields[4].GetString();
47 LoginTicketExpiry = fields[5].GetUInt32();
48 IsBanned = fields[6].GetUInt64() != 0;
49 IsPermanenetlyBanned = fields[7].GetUInt64() != 0;
50
51 static constexpr uint32 GameAccountFieldsOffset = 8;
52
53 do
54 {
55 GameAccounts[result->Fetch()[GameAccountFieldsOffset].GetUInt32()].LoadResult(result->Fetch() + GameAccountFieldsOffset);
56
57 } while (result->NextRow());
58}
59
61{
62 // a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel
63 Id = fields[0].GetUInt32();
64 Name = fields[1].GetString();
65 UnbanDate = fields[2].GetUInt32();
66 IsPermanenetlyBanned = fields[3].GetUInt32() != 0;
67 IsBanned = IsPermanenetlyBanned || UnbanDate > time(nullptr);
68 SecurityLevel = AccountTypes(fields[4].GetUInt8());
69
70 std::size_t hashPos = Name.find('#');
71 if (hashPos != std::string::npos)
72 DisplayName = std::string("WoW") + Name.substr(hashPos + 1);
73 else
74 DisplayName = Name;
75}
76
77Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket), SslContext::instance()),
80{
82}
83
85
87{
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); });
90}
91
93{
94 std::string ip_address = GetRemoteIpAddress().to_string();
95 TC_LOG_TRACE("session", "{} Accepted connection", GetClientInfo());
96
97 // Verify that this IP is not in the ip_banned table
98 LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
99
101 stmt->setString(0, ip_address);
102
103 _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
104 .WithPreparedCallback([sess = shared_from_this()](PreparedQueryResult result) { sess->CheckIpCallback(std::move(result)); }));
105}
106
108{
109 if (result)
110 {
111 bool banned = false;
112 do
113 {
114 Field* fields = result->Fetch();
115 if (fields[0].GetUInt64() != 0)
116 banned = true;
117
118 } while (result->NextRow());
119
120 if (banned)
121 {
122 TC_LOG_DEBUG("session", "{} tries to log in using banned IP!", GetClientInfo());
123 CloseSocket();
124 return;
125 }
126 }
127
128 AsyncHandshake();
129}
130
132{
134 return false;
135
136 _queryProcessor.ProcessReadyCallbacks();
137
138 return true;
139}
140
142{
143 if (!IsOpen())
144 return;
145
146 QueuePacket(std::move(*packet));
147}
148
149void Battlenet::Session::SendResponse(uint32 token, pb::Message const* response)
150{
151 Header header;
152 header.set_token(token);
153 header.set_service_id(0xFE);
154 header.set_size(response->ByteSize());
155
156 uint16 headerSize = header.ByteSize();
157 EndianConvertReverse(headerSize);
158
159 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + response->GetCachedSize());
160 packet.Write(&headerSize, sizeof(headerSize));
161 uint8* ptr = packet.GetWritePointer();
162 packet.WriteCompleted(header.GetCachedSize());
163 header.SerializePartialToArray(ptr, header.GetCachedSize());
164 ptr = packet.GetWritePointer();
165 packet.WriteCompleted(response->GetCachedSize());
166 response->SerializeToArray(ptr, response->GetCachedSize());
167
168 AsyncWrite(&packet);
169}
170
172{
173 Header header;
174 header.set_token(token);
175 header.set_status(status);
176 header.set_service_id(0xFE);
177
178 uint16 headerSize = header.ByteSize();
179 EndianConvertReverse(headerSize);
180
181 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize());
182 packet.Write(&headerSize, sizeof(headerSize));
183 uint8* ptr = packet.GetWritePointer();
184 packet.WriteCompleted(header.GetCachedSize());
185 header.SerializeToArray(ptr, header.GetCachedSize());
186
187 AsyncWrite(&packet);
188}
189
190void Battlenet::Session::SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const* request)
191{
192 Header header;
193 header.set_service_id(0);
194 header.set_service_hash(serviceHash);
195 header.set_method_id(methodId);
196 header.set_size(request->ByteSize());
197 header.set_token(_requestToken++);
198
199 uint16 headerSize = header.ByteSize();
200 EndianConvertReverse(headerSize);
201
202 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + request->GetCachedSize());
203 packet.Write(&headerSize, sizeof(headerSize));
204 uint8* ptr = packet.GetWritePointer();
205 packet.WriteCompleted(header.GetCachedSize());
206 header.SerializeToArray(ptr, header.GetCachedSize());
207 ptr = packet.GetWritePointer();
208 packet.WriteCompleted(request->GetCachedSize());
209 request->SerializeToArray(ptr, request->GetCachedSize());
210
211 AsyncWrite(&packet);
212}
213
214uint32 Battlenet::Session::HandleLogon(authentication::v1::LogonRequest const* logonRequest, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
215{
216 if (logonRequest->program() != "WoW")
217 {
218 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with game other than WoW (using {})!", GetClientInfo(), logonRequest->program());
219 return ERROR_BAD_PROGRAM;
220 }
221
222 if (logonRequest->platform() != "Win" && logonRequest->platform() != "Wn64" && logonRequest->platform() != "Mc64")
223 {
224 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in from an unsupported platform (using {})!", GetClientInfo(), logonRequest->platform());
225 return ERROR_BAD_PLATFORM;
226 }
227
228 if (!IsValidLocale(GetLocaleByName(logonRequest->locale())))
229 {
230 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with unsupported locale (using {})!", GetClientInfo(), logonRequest->locale());
231 return ERROR_BAD_LOCALE;
232 }
233
234 _locale = logonRequest->locale();
235 _os = logonRequest->platform();
236 _build = logonRequest->application_version();
237
238 _timezoneOffset = [&]
239 {
240 if (!logonRequest->has_device_id())
241 return 0min;
242
243 rapidjson::Document doc;
244 doc.Parse(logonRequest->device_id());
245 if (doc.HasParseError())
246 return 0min;
247
248 auto itr = doc.FindMember("UTCO");
249 if (itr == doc.MemberEnd())
250 return 0min;
251
252 if (!itr->value.IsUint())
253 return 0min;
254
255 return Trinity::Timezone::GetOffsetByHash(itr->value.GetUint());
256 }();
257
258 if (logonRequest->has_cached_web_credentials())
259 return VerifyWebCredentials(logonRequest->cached_web_credentials(), continuation);
260
262 externalChallenge.set_payload_type("web_auth_url");
263 externalChallenge.set_payload(Trinity::StringFormat("https://{}:{}/bnetserver/login/", sLoginService.GetHostnameForClient(GetRemoteIpAddress()), sLoginService.GetPort()));
264 Service<challenge::v1::ChallengeListener>(this).OnExternalChallenge(&externalChallenge);
265 return ERROR_OK;
266}
267
268uint32 Battlenet::Session::HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const* verifyWebCredentialsRequest, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
269{
270 if (verifyWebCredentialsRequest->has_web_credentials())
271 return VerifyWebCredentials(verifyWebCredentialsRequest->web_credentials(), continuation);
272
273 return ERROR_DENIED;
274}
275
276uint32 Battlenet::Session::HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const* request, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
277{
278 if (!_authed)
279 return ERROR_DENIED;
280
281 if (request->program() != 0x576F57)
282 {
283 auto asPrintable = [](char c) { return std::isprint(c) ? c : ' '; };
284
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));
288 return ERROR_BAD_PROGRAM;
289 }
290
292 stmt->setUInt32(0, _accountInfo->Id);
293
294 _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([this, asyncContinuation = std::move(continuation)](PreparedQueryResult result)
295 {
296 // just send existing credentials back (not the best but it works for now with them being stored in db)
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);
301 }));
302
303 return ERROR_OK;
304}
305
306uint32 Battlenet::Session::VerifyWebCredentials(std::string const& webCredentials, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
307{
308 if (webCredentials.empty())
309 return ERROR_DENIED;
310
312 stmt->setString(0, webCredentials);
313
314 std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)> asyncContinuation = std::move(continuation);
315 std::shared_ptr<AccountInfo> accountInfo = std::make_shared<AccountInfo>();
316 _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithChainingPreparedCallback([this, accountInfo, asyncContinuation](QueryCallback& callback, PreparedQueryResult result)
317 {
318 Battlenet::Services::Authentication asyncContinuationService(this);
319 NoData response;
320 if (!result)
321 {
322 asyncContinuation(&asyncContinuationService, ERROR_DENIED, &response);
323 return;
324 }
325
326 accountInfo->LoadResult(result);
327
328 if (accountInfo->LoginTicketExpiry < time(nullptr))
329 {
330 asyncContinuation(&asyncContinuationService, ERROR_TIMED_OUT, &response);
331 return;
332 }
333
335 stmt->setUInt32(0, accountInfo->Id);
336 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
337 })
338 .WithChainingPreparedCallback([accountInfo](QueryCallback& callback, PreparedQueryResult characterCountsResult)
339 {
340 if (characterCountsResult)
341 {
342 do
343 {
344 Field* fields = characterCountsResult->Fetch();
345 accountInfo->GameAccounts[fields[0].GetUInt32()]
346 .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8();
347
348 } while (characterCountsResult->NextRow());
349 }
350
352 stmt->setUInt32(0, accountInfo->Id);
353 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
354 })
355 .WithPreparedCallback([this, accountInfo, asyncContinuation](PreparedQueryResult lastPlayerCharactersResult)
356 {
357 if (lastPlayerCharactersResult)
358 {
359 do
360 {
361 Field* fields = lastPlayerCharactersResult->Fetch();
362 Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() };
363 Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()]
364 .LastPlayedCharacters[realmId.GetSubRegionAddress()];
365
366 lastPlayedCharacter.RealmId = realmId;
367 lastPlayedCharacter.CharacterName = fields[4].GetString();
368 lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64();
369 lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32();
370
371 } while (lastPlayerCharactersResult->NextRow());
372 }
373
374 _accountInfo = accountInfo;
375 Battlenet::Services::Authentication asyncContinuationService(this);
376 NoData response;
377
378 std::string ip_address = GetRemoteIpAddress().to_string();
379
380 // If the IP is 'locked', check that the player comes indeed from the correct IP address
381 if (_accountInfo->IsLockedToIP)
382 {
383 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to IP - '{}' is logging in from '{}'",
384 _accountInfo->Login, _accountInfo->LastIP, ip_address);
385
386 if (_accountInfo->LastIP != ip_address)
387 {
388 asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response);
389 return;
390 }
391 }
392 else
393 {
394 if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ip_address))
395 _ipCountry = location->CountryCode;
396
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())
401 {
402 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to country: '{}' Player country is '{}'",
403 _accountInfo->Login, _accountInfo->LockCountry, _ipCountry);
404
405 if (_ipCountry != _accountInfo->LockCountry)
406 {
407 asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response);
408 return;
409 }
410 }
411 }
412
413 // If the account is banned, reject the logon attempt
414 if (_accountInfo->IsBanned)
415 {
416 if (_accountInfo->IsPermanenetlyBanned)
417 {
418 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
419 asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_BANNED, &response);
420 return;
421 }
422 else
423 {
424 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Temporarily banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
425 asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_SUSPENDED, &response);
426 return;
427 }
428 }
429
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)
435 {
436 EntityId* gameAccountId = logonResult.add_game_account_id();
437 gameAccountId->set_low(gameAccountInfo.Id);
438 gameAccountId->set_high(UI64LIT(0x200000200576F57));
439 }
440
441 if (!_ipCountry.empty())
442 logonResult.set_geoip_country(_ipCountry);
443
444 std::array<uint8, 64> k = Trinity::Crypto::GetRandomBytes<64>();
445 logonResult.set_session_key(k.data(), 64);
446
447 _authed = true;
448
449 asyncContinuation(&asyncContinuationService, ERROR_OK, &response);
450 Service<authentication::v1::AuthenticationListener>(this).OnLogonComplete(&logonResult);
451 }));
452
453 return ERROR_OK;
454}
455
457{
458 if (!_authed)
459 return ERROR_DENIED;
460
461 if (request->options().field_privacy_info())
462 {
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);
466
467 response->mutable_tags()->set_privacy_info_tag(0xD7CA834D);
468 }
469
470 return ERROR_OK;
471}
472
474{
475 if (!_authed)
476 return ERROR_DENIED;
477
478 if (request->options().field_game_level_info())
479 {
480 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
481 if (itr != _accountInfo->GameAccounts.end())
482 {
483 response->mutable_state()->mutable_game_level_info()->set_name(itr->second.DisplayName);
484 response->mutable_state()->mutable_game_level_info()->set_program(5730135); // WoW
485 }
486
487 response->mutable_tags()->set_game_level_info_tag(0x5C46D483);
488 }
489
490 if (request->options().field_game_status())
491 {
492 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
493 if (itr != _accountInfo->GameAccounts.end())
494 {
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);
498 }
499
500 response->mutable_state()->mutable_game_status()->set_program(5730135); // WoW
501 response->mutable_tags()->set_game_status_tag(0x98B75F99);
502 }
503
504 return ERROR_OK;
505}
506
507std::unordered_map<std::string, Battlenet::Session::ClientRequestHandler> const Battlenet::Session::ClientRequestHandlers =
508{
509 { "Command_RealmListTicketRequest_v1", &Battlenet::Session::GetRealmListTicket },
510 { "Command_LastCharPlayedRequest_v1", &Battlenet::Session::GetLastCharPlayed },
511 { "Command_RealmListRequest_v1", &Battlenet::Session::GetRealmList },
512 { "Command_RealmJoinRequest_v1", &Battlenet::Session::JoinRealm },
513};
514
516{
517 if (!_authed)
518 return ERROR_DENIED;
519
520 Attribute const* command = nullptr;
521 std::unordered_map<std::string, Variant const*> params;
522 auto removeSuffix = [](std::string const& string) -> std::string
523 {
524 size_t pos = string.rfind('_');
525 if (pos != std::string::npos)
526 return string.substr(0, pos);
527
528 return string;
529 };
530
531 for (int32 i = 0; i < request->attribute_size(); ++i)
532 {
533 Attribute const& attr = request->attribute(i);
534 if (strstr(attr.name().c_str(), "Command_") == attr.name().c_str())
535 {
536 command = &attr;
537 params[removeSuffix(attr.name())] = &attr.value();
538 }
539 else
540 params[attr.name()] = &attr.value();
541 }
542
543 if (!command)
544 {
545 TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with no command.", GetClientInfo());
547 }
548
549 auto itr = ClientRequestHandlers.find(removeSuffix(command->name()));
550 if (itr == ClientRequestHandlers.end())
551 {
552 TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with unknown command {}.", GetClientInfo(), removeSuffix(command->name()));
554 }
555
556 return (this->*itr->second)(params, response);
557}
558
559static Variant const* GetParam(std::unordered_map<std::string, Variant const*> const& params, char const* paramName)
560{
561 auto itr = params.find(paramName);
562 return itr != params.end() ? itr->second : nullptr;
563}
564
565uint32 Battlenet::Session::GetRealmListTicket(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
566{
567 if (Variant const* identity = GetParam(params, "Param_Identity"))
568 {
570 std::size_t jsonStart = identity->blob_value().find(':');
571 if (jsonStart != std::string::npos && ::JSON::Deserialize(identity->blob_value().substr(jsonStart + 1), &data))
572 {
573 auto itr = _accountInfo->GameAccounts.find(data.gameaccountid());
574 if (itr != _accountInfo->GameAccounts.end())
575 _gameAccountInfo = &itr->second;
576 }
577 }
578
579 if (!_gameAccountInfo)
581
582 if (_gameAccountInfo->IsPermanenetlyBanned)
584 else if (_gameAccountInfo->IsBanned)
586
587 bool clientInfoOk = false;
588 if (Variant const* clientInfo = GetParam(params, "Param_ClientInfo"))
589 {
591 std::size_t jsonStart = clientInfo->blob_value().find(':');
592 if (jsonStart != std::string::npos && ::JSON::Deserialize(clientInfo->blob_value().substr(jsonStart + 1), &data))
593 {
594 if (_clientSecret.size() == data.info().secret().size())
595 {
596 clientInfoOk = true;
597 memcpy(_clientSecret.data(), data.info().secret().data(), _clientSecret.size());
598 }
599 }
600 }
601
602 if (!clientInfoOk)
604
606 stmt->setString(0, GetRemoteIpAddress().to_string());
607 stmt->setUInt8(1, GetLocaleByName(_locale));
608 stmt->setString(2, _os);
609 stmt->setUInt32(3, _accountInfo->Id);
610
611 LoginDatabase.Execute(stmt);
612
613 Attribute* attribute = response->add_attribute();
614 attribute->set_name("Param_RealmListTicket");
615 attribute->mutable_value()->set_blob_value("AuthRealmListTicket");
616
617 return ERROR_OK;
618}
619
620uint32 Battlenet::Session::GetLastCharPlayed(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
621{
622 if (Variant const* subRegion = GetParam(params, "Command_LastCharPlayedRequest_v1"))
623 {
624 auto lastPlayerChar = _gameAccountInfo->LastPlayedCharacters.find(subRegion->string_value());
625 if (lastPlayerChar != _gameAccountInfo->LastPlayedCharacters.end())
626 {
627 std::vector<uint8> compressed = sRealmList->GetRealmEntryJSON(lastPlayerChar->second.RealmId, _build);
628
629 if (compressed.empty())
631
632 Attribute* attribute = response->add_attribute();
633 attribute->set_name("Param_RealmEntry");
634 attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size());
635
636 attribute = response->add_attribute();
637 attribute->set_name("Param_CharacterName");
638 attribute->mutable_value()->set_string_value(lastPlayerChar->second.CharacterName);
639
640 attribute = response->add_attribute();
641 attribute->set_name("Param_CharacterGUID");
642 attribute->mutable_value()->set_blob_value(&lastPlayerChar->second.CharacterGUID, sizeof(lastPlayerChar->second.CharacterGUID));
643
644 attribute = response->add_attribute();
645 attribute->set_name("Param_LastPlayedTime");
646 attribute->mutable_value()->set_int_value(int32(lastPlayerChar->second.LastPlayedTime));
647 }
648
649 return ERROR_OK;
650 }
651
653}
654
655uint32 Battlenet::Session::GetRealmList(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
656{
657 if (!_gameAccountInfo)
659
660 std::string subRegionId;
661 if (Variant const* subRegion = GetParam(params, "Command_RealmListRequest_v1"))
662 subRegionId = subRegion->string_value();
663
664 std::vector<uint8> compressed = sRealmList->GetRealmList(_build, subRegionId);
665
666 if (compressed.empty())
668
669 Attribute* attribute = response->add_attribute();
670 attribute->set_name("Param_RealmList");
671 attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size());
672
674 for (auto const& characterCount : _gameAccountInfo->CharacterCounts)
675 {
676 ::JSON::RealmList::RealmCharacterCountEntry* countEntry = realmCharacterCounts.add_counts();
677 countEntry->set_wowrealmaddress(characterCount.first);
678 countEntry->set_count(characterCount.second);
679 }
680
681 std::string json = "JSONRealmCharacterCountList:" + ::JSON::Serialize(realmCharacterCounts);
682
683 uLongf compressedLength = compressBound(json.length());
684 compressed.resize(4 + compressedLength);
685 *reinterpret_cast<uint32*>(compressed.data()) = json.length() + 1;
686
687 if (compress(compressed.data() + 4, &compressedLength, reinterpret_cast<uint8 const*>(json.c_str()), json.length() + 1) != Z_OK)
689
690 attribute = response->add_attribute();
691 attribute->set_name("Param_CharacterCountList");
692 attribute->mutable_value()->set_blob_value(compressed.data(), compressedLength + 4);
693 return ERROR_OK;
694}
695
696uint32 Battlenet::Session::JoinRealm(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
697{
698 if (Variant const* realmAddress = GetParam(params, "Param_RealmAddress"))
699 return sRealmList->JoinRealm(realmAddress->uint_value(), _build, GetRemoteIpAddress(), _clientSecret, GetLocaleByName(_locale),
700 _os, _timezoneOffset, _gameAccountInfo->Name, response);
701
703}
704
706{
707 if (!_authed)
708 return ERROR_DENIED;
709
710 if (request->attribute_key().find("Command_RealmListRequest_v1") == 0)
711 {
712 sRealmList->WriteSubRegions(response);
713 return ERROR_OK;
714 }
715
717}
718
719void Battlenet::Session::HandshakeHandler(boost::system::error_code const& error)
720{
721 if (error)
722 {
723 TC_LOG_ERROR("session", "{} SSL Handshake failed {}", GetClientInfo(), error.message());
724 CloseSocket();
725 return;
726 }
727
728 AsyncRead();
729}
730
731template<bool(Battlenet::Session::*processMethod)(), MessageBuffer Battlenet::Session::*outputBuffer>
732inline bool PartialProcessPacket(Battlenet::Session* session, MessageBuffer& inputBuffer)
733{
734 MessageBuffer& buffer = session->*outputBuffer;
735
736 // We have full read header, now check the data payload
737 if (buffer.GetRemainingSpace() > 0)
738 {
739 // need more data in the payload
740 std::size_t readDataSize = std::min(inputBuffer.GetActiveSize(), buffer.GetRemainingSpace());
741 buffer.Write(inputBuffer.GetReadPointer(), readDataSize);
742 inputBuffer.ReadCompleted(readDataSize);
743 }
744
745 if (buffer.GetRemainingSpace() > 0)
746 {
747 // Couldn't receive the whole data this time.
748 ASSERT(inputBuffer.GetActiveSize() == 0);
749 return false;
750 }
751
752 // just received fresh new payload
753 if (!(session->*processMethod)())
754 {
755 session->CloseSocket();
756 return false;
757 }
758
759 return true;
760}
761
763{
764 if (!IsOpen())
765 return;
766
767 MessageBuffer& packet = GetReadBuffer();
768 while (packet.GetActiveSize() > 0)
769 {
770 if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderLengthHandler, &Battlenet::Session::_headerLengthBuffer>(this, packet))
771 break;
772
773 if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderHandler, &Battlenet::Session::_headerBuffer>(this, packet))
774 break;
775
776 if (!PartialProcessPacket<&Battlenet::Session::ReadDataHandler, &Battlenet::Session::_packetBuffer>(this, packet))
777 break;
778
779 _headerLengthBuffer.Reset();
780 _headerBuffer.Reset();
781 }
782
783 AsyncRead();
784}
785
787{
788 uint16 len = *reinterpret_cast<uint16*>(_headerLengthBuffer.GetReadPointer());
790 _headerBuffer.Resize(len);
791 return true;
792}
793
795{
796 Header header;
797 if (!header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize()))
798 return false;
799
800 _packetBuffer.Resize(header.size());
801 return true;
802}
803
805{
806 Header header;
807 bool parseSuccess = header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize());
808 ASSERT(parseSuccess);
809
810 if (header.service_id() != 0xFE)
811 {
812 sServiceDispatcher.Dispatch(this, header.service_hash(), header.token(), header.method_id(), std::move(_packetBuffer));
813 }
814 else
815 {
816 auto itr = _responseCallbacks.find(header.token());
817 if (itr != _responseCallbacks.end())
818 {
819 itr->second(std::move(_packetBuffer));
820 _responseCallbacks.erase(header.token());
821 }
822 else
823 _packetBuffer.Reset();
824 }
825
826 return true;
827}
828
830{
831 std::ostringstream stream;
832 stream << '[' << GetRemoteIpAddress() << ':' << GetRemotePort();
833 if (_accountInfo && !_accountInfo->Login.empty())
834 stream << ", Account: " << _accountInfo->Login;
835
836 if (_gameAccountInfo)
837 stream << ", Game account: " << _gameAccountInfo->Name;
838
839 stream << ']';
840
841 return stream.str();
842}
@ 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_BAD_PROGRAM
@ ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE
@ ERROR_USER_SERVER_BAD_WOW_ACCOUNT
@ ERROR_GAME_ACCOUNT_SUSPENDED
@ ERROR_BAD_LOCALE
@ ERROR_WOW_SERVICES_DENIED_REALM_LIST_TICKET
@ ERROR_DENIED
@ ERROR_BAD_PLATFORM
@ ERROR_UTIL_SERVER_UNKNOWN_REALM
@ ERROR_TIMED_OUT
void EndianConvertReverse(T &)
Definition: ByteConverter.h:49
LocaleConstant GetLocaleByName(std::string_view name)
Definition: Common.cpp:36
constexpr bool IsValidLocale(LocaleConstant locale)
Definition: Common.h:95
AccountTypes
Definition: Common.h:39
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
Definition: DatabaseEnv.cpp:22
uint8_t uint8
Definition: Define.h:144
int32_t int32
Definition: Define.h:138
uint64_t uint64
Definition: Define.h:141
#define UI64LIT(N)
Definition: Define.h:127
uint16_t uint16
Definition: Define.h:143
uint32_t uint32
Definition: Define.h:142
std::unordered_set< uint32 > params[2]
Definition: DisableMgr.cpp:50
#define ASSERT
Definition: Errors.h:68
#define sIPLocation
Definition: IPLocation.h:51
#define TC_LOG_DEBUG(filterType__,...)
Definition: Log.h:156
#define TC_LOG_TRACE(filterType__,...)
Definition: Log.h:153
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
@ 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
Definition: LoginDatabase.h:32
@ LOGIN_SEL_BNET_ACCOUNT_INFO
@ LOGIN_SEL_IP_INFO
Definition: LoginDatabase.h:34
@ LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS
#define sLoginService
#define sRealmList
Definition: RealmList.h:96
if(posix_memalign(&__mallocedMemory, __align, __size)) return NULL
#define sServiceDispatcher
bool PartialProcessPacket(Battlenet::Session *session, MessageBuffer &inputBuffer)
Definition: Session.cpp:732
static Variant const * GetParam(std::unordered_map< std::string, Variant const * > const &params, char const *paramName)
Definition: Session.cpp:559
uint32 VerifyWebCredentials(std::string const &webCredentials, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition: Session.cpp:306
Session(boost::asio::ip::tcp::socket &&socket)
Definition: Session.cpp:77
uint32 JoinRealm(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition: Session.cpp:696
std::string GetClientInfo() const
Definition: Session.cpp:829
void AsyncHandshake()
Definition: Session.cpp:86
uint32 HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const *request, std::function< void(ServiceBase *, uint32, google::protobuf::Message const *)> &continuation)
Definition: Session.cpp:276
static std::unordered_map< std::string, ClientRequestHandler > const ClientRequestHandlers
Definition: Session.h:159
void SendResponse(uint32 token, pb::Message const *response)
Definition: Session.cpp:149
uint32 GetRealmList(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition: Session.cpp:655
uint32 HandleProcessClientRequest(game_utilities::v1::ClientRequest const *request, game_utilities::v1::ClientResponse *response)
Definition: Session.cpp:515
uint32 HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const *verifyWebCredentialsRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition: Session.cpp:268
void CheckIpCallback(PreparedQueryResult result)
Definition: Session.cpp:107
std::string _ipCountry
Definition: Session.h:178
bool ReadDataHandler()
Definition: Session.cpp:804
void Start() override
Definition: Session.cpp:92
void HandshakeHandler(boost::system::error_code const &error)
Definition: Session.cpp:719
bool Update() override
Definition: Session.cpp:131
uint32 GetLastCharPlayed(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition: Session.cpp:620
uint32 HandleGetGameAccountState(account::v1::GetGameAccountStateRequest const *request, account::v1::GetGameAccountStateResponse *response)
Definition: Session.cpp:473
void ReadHandler() override
Definition: Session.cpp:762
bool ReadHeaderLengthHandler()
Definition: Session.cpp:786
void AsyncWrite(MessageBuffer *packet)
Definition: Session.cpp:141
uint32 HandleGetAllValuesForAttribute(game_utilities::v1::GetAllValuesForAttributeRequest const *request, game_utilities::v1::GetAllValuesForAttributeResponse *response)
Definition: Session.cpp:705
std::array< uint8, 32 > _clientSecret
Definition: Session.h:180
GameAccountInfo * _gameAccountInfo
Definition: Session.h:171
uint32 HandleLogon(authentication::v1::LogonRequest const *logonRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition: Session.cpp:214
void SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const *request, std::function< void(MessageBuffer)> callback)
Definition: Session.h:124
uint32 _requestToken
Definition: Session.h:187
std::string _locale
Definition: Session.h:173
std::string _os
Definition: Session.h:174
Minutes _timezoneOffset
Definition: Session.h:176
MessageBuffer _headerLengthBuffer
Definition: Session.h:166
uint32 HandleGetAccountState(account::v1::GetAccountStateRequest const *request, account::v1::GetAccountStateResponse *response)
Definition: Session.cpp:456
bool ReadHeaderHandler()
Definition: Session.cpp:794
uint32 GetRealmListTicket(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition: Session.cpp:565
std::shared_ptr< AccountInfo > _accountInfo
Definition: Session.h:170
Class used to access individual fields of database query result.
Definition: Field.h:90
uint8 GetUInt8() const
Definition: Field.cpp:30
std::string GetString() const
Definition: Field.cpp:118
uint64 GetUInt64() const
Definition: Field.cpp:78
bool GetBool() const
Definition: Field.h:98
uint32 GetUInt32() const
Definition: Field.cpp:62
const ::std::string & secret() const
void set_wowrealmaddress(::google::protobuf::uint32 value)
void set_count(::google::protobuf::uint32 value)
inline ::JSON::RealmList::RealmCharacterCountEntry * add_counts()
const ::JSON::RealmList::ClientInformation & info() const
inline ::google::protobuf::uint32 gameaccountid() const
void Resize(size_type bytes)
Definition: MessageBuffer.h:52
size_type GetRemainingSpace() const
Definition: MessageBuffer.h:69
void ReadCompleted(size_type bytes)
Definition: MessageBuffer.h:63
void WriteCompleted(size_type bytes)
Definition: MessageBuffer.h:65
uint8 * GetReadPointer()
Definition: MessageBuffer.h:59
size_type GetActiveSize() const
Definition: MessageBuffer.h:67
uint8 * GetWritePointer()
Definition: MessageBuffer.h:61
void Write(void const *data, std::size_t size)
Definition: MessageBuffer.h:93
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)
void CloseSocket()
Definition: Socket.h:152
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)
inline ::google::protobuf::uint32 method_id() const
void set_method_id(::google::protobuf::uint32 value)
int GetCachedSize() const
Definition: rpc_types.pb.h:622
inline ::google::protobuf::uint32 service_id() const
void set_token(::google::protobuf::uint32 value)
void set_service_id(::google::protobuf::uint32 value)
void set_status(::google::protobuf::uint32 value)
inline ::google::protobuf::uint32 size() const
inline ::google::protobuf::uint32 service_hash() const
void set_service_hash(::google::protobuf::uint32 value)
inline ::google::protobuf::uint32 token() const
void set_size(::google::protobuf::uint32 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)
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)
Definition: Timezone.cpp:124
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
void Update(VignetteData &vignette, WorldObject const *owner)
Definition: Vignette.cpp:90
STL namespace.
std::unordered_map< uint32, GameAccountInfo > GameAccounts
Definition: Session.h:109
void LoadResult(PreparedQueryResult result)
Definition: Session.cpp:38
void LoadResult(Field const *fields)
Definition: Session.cpp:60